A bytes axis read "20.0BB" - compact's billion with a B after it - and a quiet node's CPU axis read 0% all the way up. Sizes are now binary like every other on these pages, a percent under ten keeps a decimal, and a window of an hour is "the last hour" rather than "1h0m0s". Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
93 lines
2.6 KiB
Go
93 lines
2.6 KiB
Go
package web
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
"time"
|
|
|
|
"gitea.mynodes.xyz/ilker/llmgw/internal/promq"
|
|
)
|
|
|
|
// History reads the cluster's past out of a metrics store.
|
|
type History interface {
|
|
RangeQuery(ctx context.Context, expr, nameBy string, window, step time.Duration) ([]promq.Series, error)
|
|
}
|
|
|
|
// FleetCharts are the operational panels: what the cluster is doing, rather
|
|
// than what it has been billed for.
|
|
//
|
|
// The usage charts already on this page come from llmgw's own store and answer
|
|
// a different question — who spent what. These answer whether the thing is
|
|
// healthy, and they need history llmgw does not keep.
|
|
type FleetCharts struct {
|
|
Saturation TimeSeries
|
|
Queue TimeSeries
|
|
KV TimeSeries
|
|
Ready TimeSeries
|
|
Window string
|
|
// Err is set when the metrics store could not be read. Shown as a line of
|
|
// text where the charts would be: an empty panel reads as a healthy cluster
|
|
// with nothing happening, which is the opposite of not knowing.
|
|
Err string
|
|
}
|
|
|
|
// fleetPanel is one chart and the query behind it.
|
|
var fleetPanels = []struct {
|
|
name string
|
|
expr string
|
|
nameBy string
|
|
unit string
|
|
into func(*FleetCharts, TimeSeries)
|
|
}{
|
|
{
|
|
"saturation", "llmgw_pool_saturation", "model", "",
|
|
func(f *FleetCharts, t TimeSeries) { f.Saturation = t },
|
|
},
|
|
{
|
|
"queue depth", "llmgw_endpoint_waiting", "replica", "",
|
|
func(f *FleetCharts, t TimeSeries) { f.Queue = t },
|
|
},
|
|
{
|
|
"kv cache", "llmgw_endpoint_kv_cache_usage", "replica", "%",
|
|
func(f *FleetCharts, t TimeSeries) { f.KV = t },
|
|
},
|
|
{
|
|
"replicas ready", "llmgw_model_replicas_ready", "model", "",
|
|
func(f *FleetCharts, t TimeSeries) { f.Ready = t },
|
|
},
|
|
}
|
|
|
|
// LoadFleetCharts queries each panel and renders it.
|
|
//
|
|
// One failure fails them all, deliberately: four panels where three are drawn
|
|
// and one is blank invites reading the blank one as "nothing is happening
|
|
// there". Either the metrics store answered or it did not.
|
|
func LoadFleetCharts(ctx context.Context, h History, window time.Duration,
|
|
log *slog.Logger,
|
|
) *FleetCharts {
|
|
if h == nil {
|
|
return nil
|
|
}
|
|
if window <= 0 {
|
|
window = time.Hour
|
|
}
|
|
// Sixty points across the window. More is detail nobody can see at this
|
|
// size and a bigger response to parse on every page load.
|
|
step := window / 60
|
|
|
|
out := &FleetCharts{Window: windowWord(window)}
|
|
for _, p := range fleetPanels {
|
|
series, err := h.RangeQuery(ctx, p.expr, p.nameBy, window, step)
|
|
if err != nil {
|
|
if log != nil {
|
|
log.Warn("could not read history for the dashboard",
|
|
"panel", p.name, "err", err)
|
|
}
|
|
out.Err = err.Error()
|
|
return out
|
|
}
|
|
p.into(out, LineChart(series, p.unit, 4))
|
|
}
|
|
return out
|
|
}
|