- name
- metrics-chart
- description
- Render Axiom metrics query results (application/vnd.metrics.v3+json) as line charts. Zero-dependency Unicode/ASCII by default; upgrades to inline PNG/SVG/sixel via gnuplot when present. Use when you have a metrics v3 query response and want to see the series as a chart in the terminal or transcript.
# metrics-chart
Turns a metrics query response into a **multi-series line chart**.
Input is exactly the body the Axiom metrics query service returns for
`application/vnd.metrics.v3+json` — defined in the metrics query service
(`service/query`, `JsonV3QueryResponse` in `src/server.rs`). It is a
regularly-sampled, multi-series time series; this skill draws it.
## TL;DR
```bash
# From a file, stdout picks the best renderer for your terminal:
python3 scripts/metrics_chart.py response.json
# From a pipe (e.g. straight off the query API):
curl ... -H 'Accept: application/vnd.metrics.v3+json' | python3 scripts/metrics_chart.py
# Force a zero-dependency ASCII chart (always works, anywhere):
python3 scripts/metrics_chart.py --format ascii response.json
```
> **Aggregate first.** Charts are only readable with a *handful* of series.
> The cleanest result comes from a query that already groups/aggregates to a few
> lines (e.g. `group by status` rather than per-`pod`). This skill's series
> reduction (below) is a safety net for high-cardinality results, **not** a
> substitute for aggregating in the query.
## Input format (vnd.metrics.v3+json)
```json
{
"metadata": {
"group_keys": ["code", "method"],
"warnings": [],
"unit": "ms",
"custom_unit": "millisec"
},
"series": [
{
"metric": "http_requests",
"tags": { "code": 200, "method": "GET", "path": "/" },
"start": 1750753164,
"resolution": 60,
"data": [420.0, 42.0, 30.0, 9.0, 15.0, 18.0]
}
]
}
```
| Field | Meaning |
| --------------------- | ----------------------------------------------------------------------- |
| `metadata.group_keys` | Tags the query grouped on → used to build legend labels. |
| `metadata.warnings` | Shown as `⚠` captions under the chart. |
| `metadata.unit` | Canonical y-axis unit (`ms`, `bytes`, …). |
| `metadata.custom_unit`| Human y-axis label; preferred over `unit` when present. |
| `series[].start` | Unix **seconds** (UTC) of `data[0]`. |
| `series[].resolution` | **Seconds** per point; `x[i] = start + resolution*i` (uniform spacing). |
| `series[].data` | Y values; **`null` = gap** (the line breaks, it is not drawn as 0). |
The parser also accepts v2/v2a (same shape with an extra per-series `summary`,
ignored) and the v0/v1 bare-array form, so you can feed older responses too.
## Behaviour
- **Labels** come from `group_keys` values joined with ` | ` → `200 | GET`
(not `code=200,method=GET`). Without `group_keys`, falls back to `k=v` tags.
- **Local time** axis by default; `--tz UTC` or `--tz Europe/Berlin` to override.
- **Gaps**: `null` points break the line.
- **Series reduction** (unless `--all`), in order:
1. **Collapse overlapping** series whose lines essentially coincide (within
`--eps`, default 2% of the y-range); the most prominent is kept.
2. **Top-N** by peak value (`--top`, default 8).
3. The legend prints **how many were dropped**, e.g.
`showing 8 of 30 series (5 overlapping, 17 low/hidden)`, plus the
aggregate-your-query tip.
## Output formats
`--format auto` (default) picks the best renderer for the context:
- an **inline image** when `gnuplot` is installed and the terminal supports
images (Kitty/Ghostty, iTerm2);
- a **PNG file plus a "display this file" instruction** when output is
piped/captured (e.g. an AI agent harness) and `gnuplot` is installed — the
agent then displays the file;
- **ASCII** in a plain terminal, or whenever `gnuplot` is unavailable.
Force a specific backend with `--format`:
| `--format` | Needs | Output |
| ---------- | --------- | ------------------------------------------------------------- |
| `auto` | — | inline image, else a PNG file + display instruction when piped, else ASCII |
| `ascii` | nothing | Unicode braille line chart; renders in any terminal/transcript/CI log. |
| `png` | gnuplot | PNG; displayed inline (Kitty/iTerm2) or written to `--output`/temp. |
| `svg` | gnuplot | SVG written to `--output` or a temp file (path printed). |
| `sixel` | gnuplot | sixel stream to stdout (terminals with sixel support). |
If an image format is requested but `gnuplot` is missing, it prints a note and
falls back to ASCII — it never hard-fails.
## Using from an AI agent / harness
Terminal harnesses (e.g. pi) collapse tool output and don't repaint inline
ANSI, so ASCII charts come out monochrome and folded. **Prefer images:** render
a file and let the agent display it. With `--format auto` (the default) this is
automatic whenever output is piped and `gnuplot` is installed — the skill
writes a PNG and prints:
```
metrics-chart: wrote image to /tmp/metrics-chart-XXXX.png
→ Display this file to the user with your image tool (e.g. read it); do not paste its contents.
```
The agent should then **display that file** (its image/`read` tool renders it
inline, in colour, un-folded). To control the path, pass `--output chart.png`.
Do not paste the PNG bytes or the ASCII into chat.
## Options
| Flag | Default | Meaning |
| ------------------- | ------- | -------------------------------------------------- |
| `--format` | `auto` | `auto`/`ascii`/`png`/`svg`/`sixel`. |
| `--tz NAME` | local | IANA tz for the x-axis; `UTC` for UTC. |
| `--top N` | `8` | Max series after collapse; must be >= 1. |
| `--eps F` | `0.02` | Overlap threshold as a fraction of the y-range. |
| `--all` | off | Draw every series (disable collapse + top-N). |
| `--width` / `--height` | auto | Chart size (cells for ASCII, pixels for images). |
| `--title TEXT` | — | Chart title. |
| `--output PATH` | — | Write png/svg to PATH instead of a temp file. |
| `--color` / `--no-color` | auto | Force ANSI colour on/off (default: on when a TTY). |
## Dependencies
- **Default path: none.** Pure Python 3.9+ standard library. The ASCII renderer
always works.
- **Optional: `gnuplot`** (one system binary) for PNG/SVG/sixel. Install only if
you want high-fidelity images.
## Why not graphviz?
`graphviz` (`dot`/`neato`) lays out node-edge graphs; it has no numeric/time
axis and cannot plot `data[i]` over time. This payload is a numeric time series
with no graph structure, so graphviz does not apply. `gnuplot` is the correct
image backend. (matplotlib is avoided on purpose: it is a heavy multi-package
pip install.)
## Tests
```bash
cd scripts && python3 -m unittest test_metrics_chart -v
```
The parser tests are pinned to the golden bytes from the metrics query
service's `service/query/src/server/tests.rs`.
Ver en GitHub