CI / test (push) Has been cancelled
Usage could be read as a total — per key, per user, per model, per group — and the cluster's own charts covered the fleet's hardware. What nobody could see was when a group used it. "Research doubled its traffic three weeks ago" is not a question a column of totals can be asked, and it is the question somebody asks before a quota conversation. /admin/usage draws requests, tokens and spend over a chosen window, grouped by any level of the tenancy tree or by person. Person is not a level of that tree — one person holds keys in several projects — so the grouping is its own type rather than store.Scope with a fourth value nothing else could use. Three things learned from the charts already here. Buckets are gap-filled per group, because a line drawn straight across an absence claims traffic that did not happen, and because the chart sums its tail by timestamp and can only do that if every series shares a clock. Series are ranked by total rather than by peak — a counter's question is "who used the most", and ranking by peak would colour the team that made one burst over the team that ran steadily all week. And the spend chart is replaced by a sentence when no model has a price, rather than drawn flat along the bottom. The bucket query groups by ordinal and was planned against the cluster's PostgreSQL before being believed, with a test alongside the others that run there: three reports have shipped working on SQLite and failing on it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
81 lines
3.3 KiB
HTML
81 lines
3.3 KiB
HTML
{{define "admin_usage"}}<!doctype html>
|
|
<html lang="en"><head>
|
|
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
|
<title>Usage · llmgw</title><link rel="stylesheet" href="/_static/base.css">
|
|
</head><body><div class="wrap">
|
|
<header>
|
|
<h1>Usage over time</h1>
|
|
<p class="sub">Who is using the cluster, and when</p>
|
|
<nav><a href="/admin">cluster</a> <a href="/admin/users">accounts</a>
|
|
<a href="/admin/quotas">quotas</a> <a href="/profile">your keys</a></nav>
|
|
</header>
|
|
|
|
{{if .Error}}<p class="error">{{.Error}}</p>{{end}}
|
|
|
|
{{/* One row of controls above the charts, which is where a reader looks for
|
|
them. Links rather than a script: the choice is in the URL, so a view worth
|
|
coming back to can be bookmarked or pasted to somebody else. */}}
|
|
<div class="filters">
|
|
<span class="filter-label">Group by</span>
|
|
{{range .Groups}}<a class="chip{{if .Selected}} on{{end}}"
|
|
href="/admin/usage?by={{.Key}}&window={{$.Window}}">{{.Label}}</a>{{end}}
|
|
<span class="filter-label">Window</span>
|
|
{{range .Windows}}<a class="chip{{if .Selected}} on{{end}}"
|
|
href="/admin/usage?by={{$.By}}&window={{.Key}}">{{.Label}}</a>{{end}}
|
|
</div>
|
|
|
|
<div class="chart-row">
|
|
{{template "tschart" dict "t" .Requests "title" "Requests"
|
|
"note" "Requests served in each slice, counted where the key belongs now rather than where it was at the time."}}
|
|
{{template "tschart" dict "t" .Tokens "title" "Tokens"
|
|
"note" "Prompt and completion together, which is the figure a person recognises as what they consumed."}}
|
|
</div>
|
|
|
|
<div class="chart-row">
|
|
{{if .Priced}}
|
|
{{template "tschart" dict "t" .Spend "title" "Spend"
|
|
"note" "In whole units, the same ones a budget is written in."}}
|
|
{{else}}
|
|
{{/* Said rather than drawn. A spend chart flat along the bottom reads as a
|
|
quiet month, and this is not that. */}}
|
|
<div class="chart-wrap">
|
|
<h3 class="chart-title">Spend</h3>
|
|
<p class="empty">No model has a price, so every request costs zero and this
|
|
chart would be a flat line however busy the cluster is. Set prices in the
|
|
<code>pricing</code> block of the server configuration.</p>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
|
|
{{if .Rows}}
|
|
<table>
|
|
<tr><th>Group</th><th>Requests</th><th>Tokens</th><th>Spend</th><th>Errors</th></tr>
|
|
{{range .Rows}}
|
|
<tr>
|
|
<td>{{.Name}}</td>
|
|
<td>{{compact .Requests}}</td>
|
|
<td>{{compact .Tokens}}</td>
|
|
<td>{{.Spend}}</td>
|
|
<td>{{if .Errors}}{{.Errors}}{{else}}—{{end}}</td>
|
|
</tr>
|
|
{{end}}
|
|
</table>
|
|
{{else}}
|
|
<p class="empty">Nothing recorded in this window.</p>
|
|
{{end}}
|
|
|
|
{{/* The charts fold their tail; the table does not. Both are here because they
|
|
answer different questions, and the one that is easy to get wrong is
|
|
thinking the four coloured lines are all of it. */}}
|
|
<p class="note">The charts draw the four busiest groups by total and sum the
|
|
rest into “other”, so they always add up to the whole cluster. The table below
|
|
is every group, in the same order.</p>
|
|
|
|
<p class="note">A person is not a level of the quota tree — one person holds
|
|
keys in several projects — so grouping by person crosses the tree rather than
|
|
naming a level of it. Quotas are written on the other three, on the
|
|
<a href="/admin/quotas">quotas page</a>.</p>
|
|
|
|
<footer><span>llmgw {{.Version}}</span><span>{{.Now}}</span></footer>
|
|
</div></body></html>{{end}}
|