From fec66af1c86fa682eb8a04904f2737f18bc1edaa Mon Sep 17 00:00:00 2001 From: iris Date: Sun, 1 Nov 2026 13:21:09 +0100 Subject: [PATCH] agents board: a busiest-agents table, joined by agent Five instant table-format queries, one per column, joined on the agent label, sorted by turns. Checked in a real Grafana 13 against a mock Prometheus. --- docs/swarm/services.md | 2 +- .../swarm-grafana/dashboards/agents.json | 209 ++++++++++++++++++ nix/module-eval/grafana.nix | 25 +++ 3 files changed, 235 insertions(+), 1 deletion(-) diff --git a/docs/swarm/services.md b/docs/swarm/services.md index 750c6ffe..9f9b3df7 100644 --- a/docs/swarm/services.md +++ b/docs/swarm/services.md @@ -200,7 +200,7 @@ Grafana start — see [`credentials.md`](credentials.md). `nix/host-modules/swarm-grafana/dashboards/`; edits made in the UI aren't kept. -- `agents`: per-agent resource usage and Claude Code activity. +- `agents`: per-agent resource usage and Claude Code activity. The **activity** tab ends with a **Busiest agents** table: turns, input, output and cache-read tokens, and cost for each agent in the range, busiest first. - `authelia`, `bao`, `queue`: the identity provider, the secret store and the NATS queue. - `claude-usage`: Claude use by model, effort, token type and source. Filter by hive, service (agents or the review classifier) and agent. The service filter separates the classifier's API-key spend from the agents. - `egress`: what agents send through the egress proxy, in three tabs. **overview** has the totals (requests, agents active, refused, credential challenges, data transferred), requests over time by status, and the busiest agents and destinations. **refused** lists the hosts the proxy refused for each agent. **would-deny** lists what agents in `log` mode reached that restricted mode would refuse. The host lists are tables, one row per host (and agent), with a count column you can sort. Filter by agent. diff --git a/nix/host-modules/swarm-grafana/dashboards/agents.json b/nix/host-modules/swarm-grafana/dashboards/agents.json index e4aae4d3..53631f4a 100644 --- a/nix/host-modules/swarm-grafana/dashboards/agents.json +++ b/nix/host-modules/swarm-grafana/dashboards/agents.json @@ -2353,6 +2353,202 @@ } } } + }, + "panel-67": { + "kind": "Panel", + "spec": { + "id": 67, + "title": "Busiest agents", + "description": "Turns, tokens and cost per agent in the selected range, busiest first.", + "links": [], + "data": { + "kind": "QueryGroup", + "spec": { + "queries": [ + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "@datasourceUid@" + }, + "spec": { + "expr": "sum by (agent) (increase({__name__=\"hyperhive.agent.turn.count\", hive=~\"$hive\", agent=~\"$agent\"}[$__range]))", + "instant": true, + "format": "table" + } + }, + "refId": "A", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "@datasourceUid@" + }, + "spec": { + "expr": "sum by (agent) (increase({__name__=\"claude_code.token.usage\", hive=~\"$hive\", agent=~\"$agent\", type=\"input\"}[$__range]))", + "instant": true, + "format": "table" + } + }, + "refId": "B", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "@datasourceUid@" + }, + "spec": { + "expr": "sum by (agent) (increase({__name__=\"claude_code.token.usage\", hive=~\"$hive\", agent=~\"$agent\", type=\"output\"}[$__range]))", + "instant": true, + "format": "table" + } + }, + "refId": "C", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "@datasourceUid@" + }, + "spec": { + "expr": "sum by (agent) (increase({__name__=\"claude_code.token.usage\", hive=~\"$hive\", agent=~\"$agent\", type=\"cacheRead\"}[$__range]))", + "instant": true, + "format": "table" + } + }, + "refId": "D", + "hidden": false + } + }, + { + "kind": "PanelQuery", + "spec": { + "query": { + "kind": "DataQuery", + "group": "prometheus", + "version": "v0", + "datasource": { + "name": "@datasourceUid@" + }, + "spec": { + "expr": "sum by (agent) (increase({__name__=\"claude_code.cost.usage\", hive=~\"$hive\", agent=~\"$agent\"}[$__range]))", + "instant": true, + "format": "table" + } + }, + "refId": "E", + "hidden": false + } + } + ], + "queryOptions": {}, + "transformations": [ + { + "kind": "joinByField", + "spec": { + "id": "joinByField", + "options": { + "byField": "agent", + "mode": "outer" + } + } + }, + { + "kind": "organize", + "spec": { + "id": "organize", + "options": { + "excludeByName": { + "Time": true + }, + "renameByName": { + "agent": "Agent", + "Value #A": "Turns", + "Value #B": "Input tokens", + "Value #C": "Output tokens", + "Value #D": "Cache read tokens", + "Value #E": "Cost" + }, + "indexByName": { + "agent": 0, + "Value #A": 1, + "Value #B": 2, + "Value #C": 3, + "Value #D": 4, + "Value #E": 5 + } + } + } + } + ] + } + }, + "vizConfig": { + "kind": "VizConfig", + "group": "table", + "version": "", + "spec": { + "options": { + "showHeader": true, + "sortBy": [ + { + "displayName": "Turns", + "desc": true + } + ] + }, + "fieldConfig": { + "defaults": { + "unit": "short", + "decimals": 0 + }, + "overrides": [ + { + "matcher": { + "id": "byName", + "options": "Cost" + }, + "properties": [ + { + "id": "unit", + "value": "currencyUSD" + }, + { + "id": "decimals", + "value": 2 + } + ] + } + ] + } + } + } + } } }, "layout": { @@ -2444,6 +2640,19 @@ "name": "panel-60" } } + }, + { + "kind": "GridLayoutItem", + "spec": { + "x": 0, + "y": 23, + "width": 24, + "height": 8, + "element": { + "kind": "ElementReference", + "name": "panel-67" + } + } } ] } diff --git a/nix/module-eval/grafana.nix b/nix/module-eval/grafana.nix index b4f95f9f..3c947a12 100644 --- a/nix/module-eval/grafana.nix +++ b/nix/module-eval/grafana.nix @@ -453,6 +453,31 @@ let ] && lib.all (m: lib.hasInfix "\"${m}\"" recorded) queried; } + { + # The agents board's busiest-agents table is five instant queries, one per + # column, in table format. Joining them by agent is what makes it one row per + # agent; without the join a table panel shows a frame per query. + name = "the agents dashboard's busiest-agents table joins its queries by agent"; + ok = + let + board = builtins.fromJSON (builtins.readFile ../host-modules/swarm-grafana/dashboards/agents.json); + tables = lib.filter (e: e.spec.vizConfig.group == "table") (lib.attrValues board.spec.elements); + table = lib.head tables; + xf = table.spec.data.spec.transformations; + in + lib.length tables == 1 + && table.spec.title == "Busiest agents" + && lib.length table.spec.data.spec.queries == 5 + && lib.all ( + q: q.spec.query.spec.format == "table" && q.spec.query.spec.instant + ) table.spec.data.spec.queries + && + map (t: t.spec.id) xf == [ + "joinByField" + "organize" + ] + && (lib.head xf).spec.options.byField == "agent"; + } { # The egress board ships, and its queries read the two squid log lines as # squid writes them: the would_deny line's field layout, and the default