From 88103657fb230bb41217a06aa5602ae23e7acb8b Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Wed, 17 Sep 2025 21:33:45 +0300 Subject: =?UTF-8?q?feat(stats,tmux):=20global=20=CE=A3@window=20stats=20ac?= =?UTF-8?q?ross=20processes=20with=20flocked=20cache;=20width=20mitigation?= =?UTF-8?q?=20(narrow/maxlen);=20configurable=20[stats]=20window=5Fminutes?= =?UTF-8?q?;=20robust=20coverage=20parsing;=20docs=20update\n\n-=20Add=20i?= =?UTF-8?q?nternal/stats=20with=20windowed=20event=20cache=20+=20flock=20+?= =?UTF-8?q?=20atomic=20writes\n-=20Wire=20stats=20into=20LSP/CLI/Tmux=20Ac?= =?UTF-8?q?tion;=20tmux=20shows=20=CE=A3@window=20with=20per-model=20tail\?= =?UTF-8?q?n-=20HEXAI=5FTMUX=5FSTATUS=5FNARROW=20and=20HEXAI=5FTMUX=5FSTAT?= =?UTF-8?q?US=5FMAXLEN=20for=20width=20control\n-=20Add=20[stats]=20window?= =?UTF-8?q?=5Fminutes=20to=20config=20and=20apply=20on=20startup\n-=20Impr?= =?UTF-8?q?ove=20Magefile=20coverage=20handling;=20add=20tests=20to=20lift?= =?UTF-8?q?=20coverage=20>85%\n-=20Update=20docs/tmux.md=20and=20config=20?= =?UTF-8?q?example?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/hexaicli/run.go | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) (limited to 'internal/hexaicli/run.go') diff --git a/internal/hexaicli/run.go b/internal/hexaicli/run.go index 984bc85..9909f4f 100644 --- a/internal/hexaicli/run.go +++ b/internal/hexaicli/run.go @@ -17,6 +17,7 @@ import ( "codeberg.org/snonux/hexai/internal/llm" "codeberg.org/snonux/hexai/internal/llmutils" "codeberg.org/snonux/hexai/internal/logging" + "codeberg.org/snonux/hexai/internal/stats" "codeberg.org/snonux/hexai/internal/tmux" ) @@ -26,6 +27,9 @@ func Run(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io. // Load configuration with a logger so file-based config is respected. logger := log.New(stderr, "hexai ", log.LstdFlags|log.Lmsgprefix) cfg := appconfig.Load(logger) + if cfg.StatsWindowMinutes > 0 { + stats.SetWindow(time.Duration(cfg.StatsWindowMinutes) * time.Minute) + } client, err := newClientFromApp(cfg) if err != nil { fmt.Fprintf(stderr, logging.AnsiBase+"hexai: LLM disabled: %v"+logging.AnsiReset+"\n", err) @@ -146,20 +150,28 @@ func runChat(ctx context.Context, client llm.Client, msgs []llm.Message, input s fmt.Fprint(out, output) } dur := time.Since(start) - // Compute simple stats for tmux heartbeat + // Contribute to global stats and update tmux status sent := 0 for _, m := range msgs { sent += len(m.Content) } recv := len(output) - mins := dur.Minutes() - if mins <= 0 { - mins = 0.001 - } - rpm := float64(1) / mins - fmt.Fprintf(errw, "\n"+logging.AnsiBase+"done provider=%s model=%s time=%s in_bytes=%d out_bytes=%d"+logging.AnsiReset+"\n", - client.Name(), client.DefaultModel(), dur.Round(time.Millisecond), sent, recv) - _ = tmux.SetStatus(tmux.FormatLLMStatsStatusColored(client.Name(), client.DefaultModel(), 1, rpm, int64(sent), int64(recv))) + _ = stats.Update(ctx, client.Name(), client.DefaultModel(), sent, recv) + snap, _ := stats.TakeSnapshot() + minsWin := snap.Window.Minutes() + if minsWin <= 0 { + minsWin = 0.001 + } + scopeReqs := int64(0) + if pe, ok := snap.Providers[client.Name()]; ok { + if mc, ok2 := pe.Models[client.DefaultModel()]; ok2 { + scopeReqs = mc.Reqs + } + } + scopeRPM := float64(scopeReqs) / minsWin + fmt.Fprintf(errw, "\n"+logging.AnsiBase+"done provider=%s model=%s time=%s in_bytes=%d out_bytes=%d | global Σ reqs=%d rpm=%.2f"+logging.AnsiReset+"\n", + client.Name(), client.DefaultModel(), dur.Round(time.Millisecond), sent, recv, snap.Global.Reqs, snap.RPM) + _ = tmux.SetStatus(tmux.FormatGlobalStatusColored(snap.Global.Reqs, snap.RPM, snap.Global.Sent, snap.Global.Recv, client.Name(), client.DefaultModel(), scopeRPM, scopeReqs, snap.Window)) return nil } -- cgit v1.2.3