Grafana dashboards¶
Dashboard sets¶
The repository contains two dashboard sets:
examples/grafana/dashboards/: the original 11 dashboards.examples/grafana/dashboards-v2/: an advanced bundle with expanded AI navigation and dedicated input/cache + cost/savings visibility.
For v2 screenshots and feature highlights, see Grafana dashboards v2.
Legacy set (11 dashboards)¶
The repository ships eleven ready-to-provision dashboards in
examples/grafana/dashboards/,
generated by examples/grafana/generate.py and validated in CI against a real
Prometheus and Grafana with synthetic data (see Validation).
| File | Dashboard | Answers |
|---|---|---|
01-overview.json |
Overview | How many tokens, requests and dollars in the selected range; savings; exporter status. |
02-tokens.json |
Tokens | Tokens by direction and model, share over time, top models, cache hit and reasoning share. |
03-time-patterns.json |
Time patterns | Tokens, requests and cost per bucket (hour, day, week, month); hour of day; weekday; period vs previous period. |
04-reported-cost.json |
Reported cost | Reported spend, month to date, monthly projection, budget, cost per request and per 1M tokens. |
05-equivalent-cost.json |
Equivalent cost & savings | What local and free models would cost on a pay-per-token API, savings, price coverage, what-if prices. |
06-reference-models.json |
Reference model comparison | What all your usage would cost if it had run on a reference model (for example Claude Sonnet or GPT-5). |
07-efficiency.json |
Efficiency | Tokens per request, output per request, output/prompt ratio, $ per 1M tokens. |
08-reliability.json |
Reliability | Errors, aborts, truncations, stop reasons, usage and cost missing. |
09-model-drilldown.json |
Model drilldown | Everything about one provider/model pair. |
10-local-vs-llamacpp.json |
Local models vs llama.cpp | OMP local usage next to the llama.cpp server metrics (speed, slots, cache), side by side. |
11-exporter-health.json |
Exporter health | Import cycle, invalid records, files, memory, CPU, price catalog and unresolved prices. |
All dashboards share the tag omp-usage, uids prefixed with omp-usage- and
links to each other.
Provisioning¶
- Copy or mount either
examples/grafana/dashboards/(legacy) orexamples/grafana/dashboards-v2/(advanced) into the Grafana container, for example at/var/lib/grafana/dashboards/omp-usage. - Add the dashboard provider
examples/grafana/provisioning/dashboards/omp-usage.ymlto/etc/grafana/provisioning/dashboards/. It loads the files into the folder "OMP usage". - If you do not provision the datasource already, use
examples/grafana/provisioning/datasources/prometheus.yml. The dashboards default to the datasource uidprometheus; any other Prometheus datasource can be selected with the Data source variable.
Docker Compose (Dokploy works the same way):
services:
grafana:
image: grafana/grafana:13.2.3
volumes:
- ./omp-usage/examples/grafana/dashboards:/var/lib/grafana/dashboards/omp-usage:ro
- ./omp-usage/examples/grafana/provisioning/dashboards/omp-usage.yml:/etc/grafana/provisioning/dashboards/omp-usage.yml:ro
To load v2 instead of legacy, change the first volume source to
./omp-usage/examples/grafana/dashboards-v2.
Manual import also works: Dashboards → New → Import → upload a JSON file, then select your Prometheus datasource.
Datasource scrape interval
Set the datasource "Scrape interval" (jsonData.timeInterval) to the
scrape_interval of the omp_usage job (30 s in the example). Grafana
uses it for $__rate_interval and as the minimum bucket.
Variables¶
| Variable | Used in | Meaning |
|---|---|---|
| Data source | all | Prometheus datasource (default uid prometheus). |
| Job | all | Prometheus job of the exporter (default omp_usage). Multi-select for several machines. |
| Provider, Model | most | Filter; Model depends on Provider. Single value in Model drilldown. |
| Bucket | most | Bar width for "per bucket" panels: 1h, 6h, 1d, 7d, 30d. Totals in legends are exact. |
| Compare | Time patterns | Offset of the "previous period" series (1d, 7d, 30d). |
| Budget | Reported cost | Monthly budget in USD, used by the gauge and the projection. |
| What-if models, In / Out / Cache R / Cache W (USD/1M) | Equivalent | Try prices without changing the pricing file. |
| Reference | References | Reference models from references in the pricing file; one repeated panel per model. |
| Local provider, llama.cpp job | Local | OMP provider of the local server (default llama.cpp) and its Prometheus job (default llm). |
How the numbers are computed¶
Every "selected range" number is increase(counter[$__range]), so it follows
the Grafana time picker. Every "per bucket" panel uses
increase(counter[$__interval]) with the panel interval set to $bucket, so
each bar is the exact amount in that bucket.
# Tokens per model in the selected range
sum by (provider, model) (increase(omp_llm_tokens_total{job=~"$job"}[$__range]))
# Tokens per model per bucket (panel interval = $bucket)
sum by (provider, model) (increase(omp_llm_tokens_total{job=~"$job"}[$__interval]))
# Cache hit ratio: cache_read over all prompt tokens
sum(increase(omp_llm_tokens_total{direction="cache_read"}[$__range]))
/
sum(increase(omp_llm_tokens_total{direction=~"input|cache_read|cache_write"}[$__range]))
increase() attributes tokens to the scrape that saw them, not to the original
event time, and history imported from old JSONL files lands at import time.
See metrics.
Equivalent cost¶
With --pricing-file the exporter publishes prices per
provider/model/direction. The dashboards multiply tokens by price at query
time:
sum by (provider, model) (
increase(omp_llm_tokens_total{job=~"$job"}[$__range])
* on (provider, model, direction) group_left
max by (provider, model, direction) (omp_llm_price_usd_per_million_tokens{job=~"$job"})
) / 1e6
- Reported cost is what OMP reported (
omp_llm_reported_cost_usd_total). It is never replaced or mixed with prices. - Equivalent cost is tokens × price: "what this would cost on a
pay-per-token API". Directions without a price (for example
cache_writewhen the model has no cache write price) are not counted. They are not treated as zero. - Effective cost is the reported cost when it is above zero, otherwise the equivalent cost. Local and free models stop showing $0.
- Saved is, per priced model, equivalent cost minus reported cost, summed. Unpriced models are left out, so the number is never inflated by unknown usage. The panel "Priced share of tokens" shows how much of the usage has a price.
- Prices are the current gauge values applied to the whole range. If a price changes, past usage is shown at the new price.
To price a local model like an OpenRouter model, map it in the pricing file (see pricing):
{
"models": [
{ "provider": "llama.cpp", "model": "qwen3.6-35b-a3b", "openrouter": "qwen/qwen3.6-35b-a3b" }
]
}
:free OpenRouter models are priced at their paid variant automatically;
openrouter/free stays at 0 because its underlying model is unknown.
What-if prices¶
The Equivalent cost dashboard has text variables for input, output, cache read and cache write prices (USD per 1M tokens) and a model selector. The what-if panels apply those prices to the selected models only. Nothing is stored; use them to test a price before adding it to the pricing file.
Reference models¶
references in the pricing file (for example anthropic/claude-sonnet-4.5,
openai/gpt-5) are exported as
omp_llm_reference_price_usd_per_million_tokens{reference_model,direction}.
The References dashboard prices all selected usage at each reference
model:
sum by (reference_model) (
sum by (direction) (increase(omp_llm_tokens_total{job=~"$job"}[$__range]))
* on (direction) group_right
max by (reference_model, direction) (omp_llm_reference_price_usd_per_million_tokens{job=~"$job"})
) / 1e6
Time patterns¶
"Tokens by hour of day" and "by weekday" query hourly buckets and group them with Grafana transformations (Format time → Group by), so they use the dashboard time zone. Use a range of at least a week for a meaningful profile.
Local models and llama.cpp¶
The llama.cpp server metrics (llamacpp:*, job llm) have no model label and
include requests that did not come from OMP. The same local generation can be
counted in both omp_llm_tokens_total{provider="llama.cpp"} and
llamacpp:tokens_predicted_total. The Local dashboard puts them next to each
other and computes ratios (for example "OMP share of llama.cpp generation"),
but never adds them together. Change the Local provider variable if your
local OMP provider has another name.
Recording rules and alerts¶
examples/prometheus/
has a scrape config, optional recording rules, alerts and promtool unit
tests. See Prometheus. The dashboards do not need the
recording rules.
Changing the dashboards¶
Edit examples/grafana/generate.py, not the JSON:
python3 examples/grafana/generate.py # regenerate
python3 examples/grafana/generate.py --check # CI: fails if JSON is out of date
Dashboards edited in the Grafana UI can be exported and kept in your own
repository; provisioning sets allowUiUpdates: true but a reprovision
overwrites UI changes.
Validation¶
examples/grafana/validate/ checks every query of every panel against a real
Prometheus and Grafana:
python3 examples/grafana/validate/synth.py --days 21 > data.om # 21 days of synthetic usage
promtool tsdb create-blocks-from openmetrics data.om tsdb
prometheus --storage.tsdb.path=tsdb ... # plus Grafana with the provisioning files
python3 examples/grafana/validate/check_panels.py --to <last sample epoch>
The check fails on any query error and on any panel without data. CI runs the
same steps (.github/workflows/dashboards.yml).