Files
llmgw/internal/web/dashcharts.go
T
Ilker ManapandClaude Opus 5.5 9ae526302b Label chart axes in binary sizes and small percents, and windows in words
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>
2026-09-28 23:30:01 +02:00

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
}