mirror of
https://github.com/mostlygeek/llama-swap.git
synced 2026-06-09 06:46:34 +02:00
7e3e94a08a
Add a comprehensive performance monitoring system that collects CPU, memory, swap, load average, network IO, and GPU stats. Provides both a REST API for the UI and a Prometheus /metrics endpoint. Backend changes: - New internal/perf package with configurable interval-based stats collection - GPU monitoring via LACT (Unix socket) and nvidia-smi fallback on Linux - Ring buffer (internal/ring) for time-series stat storage - Prometheus /metrics endpoint with all system and GPU metrics - Moved LogMonitor to internal/logmon package - New PerformanceConfig for hot-reloadable monitoring settings - REST /api/performance endpoint replacing SSE streaming UI changes: - New Performance page with real-time charts for CPU, memory, GPU, and network - Reusable PerformanceChart component - LLAMA_SWAP_URL environment variable support - Improved capture dialog display Other: - Example Grafana dashboard for Prometheus metrics - monitor-test standalone binary - Config schema and example updates fixes #596
122 lines
3.0 KiB
Go
122 lines
3.0 KiB
Go
package proxy
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/mostlygeek/llama-swap/internal/logmon"
|
|
)
|
|
|
|
func (pm *ProxyManager) sendLogsHandlers(c *gin.Context) {
|
|
accept := c.GetHeader("Accept")
|
|
if strings.Contains(accept, "text/html") {
|
|
c.Redirect(http.StatusFound, "/ui/")
|
|
} else {
|
|
c.Header("Content-Type", "text/plain")
|
|
history := pm.muxLogger.GetHistory()
|
|
_, err := c.Writer.Write(history)
|
|
if err != nil {
|
|
c.AbortWithError(http.StatusInternalServerError, err)
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (pm *ProxyManager) streamLogsHandler(c *gin.Context) {
|
|
c.Header("Content-Type", "text/plain")
|
|
c.Header("Transfer-Encoding", "chunked")
|
|
c.Header("X-Content-Type-Options", "nosniff")
|
|
// prevent nginx from buffering streamed logs
|
|
c.Header("X-Accel-Buffering", "no")
|
|
|
|
logMonitorId := strings.TrimPrefix(c.Param("logMonitorID"), "/")
|
|
|
|
// Handle case where query string might be included in the parameter
|
|
// (can happen with catch-all routes on some versions/setups)
|
|
if idx := strings.Index(logMonitorId, "?"); idx != -1 {
|
|
logMonitorId = logMonitorId[:idx]
|
|
}
|
|
|
|
logger, err := pm.getLogger(logMonitorId)
|
|
if err != nil {
|
|
c.String(http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
|
|
flusher, ok := c.Writer.(http.Flusher)
|
|
if !ok {
|
|
c.AbortWithError(http.StatusInternalServerError, fmt.Errorf("streaming unsupported"))
|
|
return
|
|
}
|
|
|
|
_, skipHistory := c.GetQuery("no-history")
|
|
// Send history first if not skipped
|
|
|
|
if !skipHistory {
|
|
history := logger.GetHistory()
|
|
if len(history) != 0 {
|
|
c.Writer.Write(history)
|
|
flusher.Flush()
|
|
}
|
|
}
|
|
|
|
sendChan := make(chan []byte, 10)
|
|
ctx, cancel := context.WithCancel(c.Request.Context())
|
|
defer logger.OnLogData(func(data []byte) {
|
|
select {
|
|
case sendChan <- data:
|
|
case <-ctx.Done():
|
|
return
|
|
default:
|
|
}
|
|
})()
|
|
|
|
for {
|
|
select {
|
|
case <-c.Request.Context().Done():
|
|
cancel()
|
|
return
|
|
case <-pm.shutdownCtx.Done():
|
|
cancel()
|
|
return
|
|
case data := <-sendChan:
|
|
c.Writer.Write(data)
|
|
flusher.Flush()
|
|
}
|
|
}
|
|
}
|
|
|
|
// getLogger searches for the appropriate logger based on the logMonitorId
|
|
func (pm *ProxyManager) getLogger(logMonitorId string) (*logmon.Monitor, error) {
|
|
switch logMonitorId {
|
|
case "":
|
|
// maintain the default
|
|
return pm.muxLogger, nil
|
|
case "proxy":
|
|
return pm.proxyLogger, nil
|
|
case "upstream":
|
|
return pm.upstreamLogger, nil
|
|
default:
|
|
// search for a models specific logger using findModelInPath
|
|
// to handle model names with slashes (e.g., "author/model")
|
|
if _, name, _, found := pm.findModelInPath("/" + logMonitorId); found {
|
|
for _, group := range pm.processGroups {
|
|
if process, found := group.GetMember(name); found {
|
|
return process.Logger(), nil
|
|
}
|
|
}
|
|
// also check the matrix when processGroups doesn't contain the model
|
|
if pm.matrix != nil {
|
|
if process, found := pm.matrix.GetProcess(name); found {
|
|
return process.Logger(), nil
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("invalid logger. Use 'proxy', 'upstream' or a model's ID")
|
|
}
|
|
}
|