4aae61297f
Analytics parity phases 2–7 (docs/plans/analytics-parity/), built on
138e706 and ported onto v2.3.1:
- Cost by AI tool (?tool= on cost/analytics/stats/workflows, by_tool,
Codex fast/priority and flex tiers, agenthub_cost_usd_total{tool,model})
- Date ranges (from/to) on cost/analytics/workflows; Analytics range select
with Today / This week (Monday) / This month (1st) / 7d / 30d / 90d / All /
Custom; workflow filters carried into workflow-stats.js (read worker)
- Budgets and cost alerts as alert rule types (alert_rules rebuilt without
its rule_type CHECK), GET /api/budgets/status, rule preview, spend pace,
status-bar budget chip; rules stay editable in place
- Query Explorer (/query, allowlisted sort, CSV/JSON export) and scheduled
reports (report_definitions/report_runs, Settings → Reports)
- Session Replay tab and immutable snapshots (session_snapshots, /snapshot/:token)
- Remote Codex sync, push ingest with a hashed token, privacy redaction and
ignored folders (Settings → Data), Cursor sessions (no cost)
- Read-only MCP server, statusline, Grafana/Prometheus files
Also: resizable sidebar, pet run/wave animations, the workspace home keeps a
half-typed prompt, and the pre-commit hook runs only the related tests for a
small commit.
85 lines
3.4 KiB
JavaScript
85 lines
3.4 KiB
JavaScript
/**
|
|
* @file Static checks for the Prometheus/Grafana bundle (`monitoring/`,
|
|
* `docker-compose.monitoring.yml`): every YAML and dashboard JSON parses, the
|
|
* compose file mounts paths that exist, and every `agenthub_*` metric a board
|
|
* or recording rule queries is one `server/routes/metrics.js` exports — except
|
|
* `agenthub_cost_usd_total`, which those panels mark as required from another
|
|
* phase. Docker is never run.
|
|
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
|
*/
|
|
|
|
const { it } = require("node:test");
|
|
const assert = require("node:assert/strict");
|
|
const fs = require("node:fs");
|
|
const path = require("node:path");
|
|
const yaml = require("js-yaml");
|
|
|
|
const ROOT = path.resolve(__dirname, "..", "..");
|
|
const MON = path.join(ROOT, "monitoring");
|
|
const readYaml = (p) => yaml.load(fs.readFileSync(path.join(ROOT, p), "utf8"));
|
|
const BOARDS = fs
|
|
.readdirSync(path.join(MON, "grafana", "dashboards"))
|
|
.map((f) => path.join(MON, "grafana", "dashboards", f));
|
|
|
|
const exported = new Set(
|
|
fs
|
|
.readFileSync(path.join(ROOT, "server", "routes", "metrics.js"), "utf8")
|
|
.match(/"agenthub_[a-z_]+"/g)
|
|
.map((s) => s.slice(1, -1))
|
|
);
|
|
const OPTIONAL = new Set(["agenthub_cost_usd_total"]);
|
|
const metricsIn = (expr) => expr.match(/\bagenthub_[a-z_]+\b/g) || [];
|
|
|
|
it("compose, Prometheus and provisioning YAML parse and point at real files", () => {
|
|
const compose = readYaml("docker-compose.monitoring.yml");
|
|
assert.deepEqual(Object.keys(compose.services).sort(), ["grafana", "prometheus"]);
|
|
for (const svc of Object.values(compose.services)) {
|
|
for (const v of svc.volumes) {
|
|
const src = v.split(":")[0];
|
|
if (src.startsWith("./")) assert.ok(fs.existsSync(path.join(ROOT, src)), src);
|
|
}
|
|
}
|
|
const prom = readYaml("monitoring/prometheus/prometheus.yml");
|
|
assert.equal(prom.scrape_configs[0].metrics_path, "/api/metrics");
|
|
for (const f of prom.rule_files) assert.ok(fs.existsSync(path.join(MON, "prometheus", f)), f);
|
|
readYaml("monitoring/grafana/provisioning/datasources/datasource.yml");
|
|
readYaml("monitoring/grafana/provisioning/dashboards/provider.yml");
|
|
});
|
|
|
|
it("recording rules query only exported metrics", () => {
|
|
const rules = readYaml("monitoring/prometheus/agenthub-rules.yml");
|
|
const all = rules.groups.flatMap((g) => g.rules);
|
|
assert.ok(all.length > 0);
|
|
for (const r of all) {
|
|
assert.match(r.record, /^agenthub:/);
|
|
for (const m of metricsIn(r.expr))
|
|
assert.ok(exported.has(m) || OPTIONAL.has(m), `${r.record}: ${m}`);
|
|
}
|
|
});
|
|
|
|
it("three boards: valid JSON, unique uid and panel ids, exported metrics only", () => {
|
|
assert.equal(BOARDS.length, 3);
|
|
const uids = new Set();
|
|
let costPanels = 0;
|
|
for (const file of BOARDS) {
|
|
const b = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
assert.ok(b.uid && !uids.has(b.uid), file);
|
|
uids.add(b.uid);
|
|
const ids = b.panels.map((p) => p.id);
|
|
assert.equal(new Set(ids).size, ids.length, `${file}: duplicate panel id`);
|
|
for (const p of b.panels) {
|
|
assert.equal(p.datasource.uid, "${ds}");
|
|
for (const t of p.targets) {
|
|
for (const m of metricsIn(t.expr)) {
|
|
assert.ok(exported.has(m) || OPTIONAL.has(m), `${b.uid} / ${p.title}: ${m}`);
|
|
if (OPTIONAL.has(m)) {
|
|
assert.match(p.description || "", /Requires agenthub_cost_usd_total/, p.title);
|
|
costPanels++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
assert.ok(costPanels >= 4, "cost panels present");
|
|
});
|