A Grafana dashboard for monitoring Claude Code CLI usage on Prometheus-compatible backends. Consumes Claude Code's OpenTelemetry metrics (emitted via OTLP) and queries them with PromQL. Compatible with Prometheus, VictoriaMetrics, Mimir, and Thanos.
grafana.com: dashboard 25255 Inspired by dashboard 25052 by 1w2w3y, which targets Azure Application Insights via KQL. This is a parallel implementation for the Prometheus stack, with every panel re-queried in PromQL against Anthropic's published OpenTelemetry metric names.
Top-of-dashboard KPIs and leaderboards.
Cost and token usage trends, broken down by model.
Per-hour activity, cost decomposition, and cache hit ratio.
Overview: KPIs for sessions, users, total cost, total tokens, commits, pull requests, lines added/removed, active time, tokens by type, and tool decisions.
Leaderboards: top users by cost and tokens, top sessions by cost, top repos by cost (off by default, see Optional: per-repo cost attribution), cost by model, edit decisions by language, and sessions by terminal.
Cost & Tokens: cost over time (overall and by model) and token usage over time (by type and by model).
Activity & Productivity: active time per hour, lines of code per hour, and tool decisions over time.
Cost Breakdown: cost by query source, cost by effort, and cache hit ratio.
The dashboard uses three header variables for filtering: organization, user, and model. Default time range is the last 7 days.
- Grafana 11+
- A Prometheus-compatible data source: Prometheus, VictoriaMetrics, Mimir, or Thanos
- Claude Code with OpenTelemetry telemetry enabled, with metrics routed into your Prometheus-compatible backend
This dashboard expects the standard "OTel Collector in the middle" pipeline:
Claude Code → OTLP → OTel Collector → /metrics → Prometheus → dashboard
If you already run an OTel Collector and Prometheus, you mostly need to (1) tell Claude Code where to send OTLP, (2) add a Prometheus exporter to your Collector, and (3) add a scrape job to Prometheus.
Set these in your shell environment (or in ~/.claude/settings.json under env):
# Enable telemetry
export CLAUDE_CODE_ENABLE_TELEMETRY=1
# Where to send OTLP. Use the HTTP receiver port (4318) for easier
# debugging; gRPC (4317) works equally well.
export OTEL_EXPORTER_OTLP_ENDPOINT="http://your-collector:4318"
# Recommended: pin temporality to cumulative. Prometheus-family backends
# require cumulative counters. The OpenTelemetry SDK currently defaults to
# cumulative, but defaults can drift between SDK versions, so set it
# explicitly to avoid silent breakage on upgrades.
export OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=cumulativeFor full details on Claude Code's telemetry options, see Anthropic's monitoring documentation.
The same env block works in two centralized locations that take precedence over each user's ~/.claude/settings.json, so you don't have to touch every developer's machine.
If your org has the Claude.ai admin console (Teams / Enterprise plans), use Managed settings → Claude Code. Settings sync to every client at startup and hourly after that, and users can't override them.
Otherwise, drop a settings.json at the OS managed-settings path and push it via MDM, Ansible, or whatever config tool you already use:
macOS: ~/Library/Application Support/Claude Code/settings.json
Linux: ~/.config/Claude Code/settings.json
Windows: %APPDATA%\Claude Code\settings.json
See Claude Code settings precedence for the full chain.
By default Claude Code's OTLP stream has no repo identifier, so the Top Repos by Cost panel will be empty. To populate it, set the git.repo OpenTelemetry resource attribute on the shell that launches Claude. A wrapper that derives the repo name from the current working directory works well:
claude() {
local repo
repo="$(git -C "$PWD" rev-parse --show-toplevel 2>/dev/null)"
if [ -n "$repo" ]; then
OTEL_RESOURCE_ATTRIBUTES="${OTEL_RESOURCE_ATTRIBUTES:+$OTEL_RESOURCE_ATTRIBUTES,}git.repo=${repo##*/}" \
command claude "$@"
else
command claude "$@"
fi
}This sets git.repo to the basename of the repo root. If you have multiple repos sharing a basename, derive a more specific value (for example from git remote get-url origin) instead.
The Collector's resource_to_telemetry_conversion: enabled: true (already in the example config) promotes git.repo to the Prometheus label git_repo automatically. No additional Collector or Prometheus configuration is needed.
A minimal Collector configuration that accepts OTLP from Claude Code and exposes a Prometheus /metrics endpoint is provided in otel-collector-config.yaml.
If you already run a Collector with other pipelines (traces, logs), add the otlp receiver and prometheus exporter to it; you don't need a separate Collector.
Add a scrape job pointing at the Collector's Prometheus exporter port (default :9464). Example in prometheus-scrape.yaml.
VictoriaMetrics, Grafana Mimir, and Thanos all accept the same scrape job configuration in their scraper components (vmagent, distributor with --web.enable-otlp-receiver, etc.).
Two options:
Via grafana.com ID (once the dashboard is listed there): In Grafana, go to Dashboards → New → Import and paste the dashboard ID.
Via JSON file:
Download dashboard.json from this repo. In Grafana, go to Dashboards → New → Import → Upload JSON file and select it. When prompted, choose your Prometheus-compatible data source.
The dashboard queries the following metric names emitted by Claude Code:
claude_code_session_count_totalclaude_code_token_usage_tokens_totalclaude_code_cost_usage_USD_totalclaude_code_active_time_seconds_totalclaude_code_lines_of_code_count_totalclaude_code_commit_count_totalclaude_code_pull_request_count_totalclaude_code_code_edit_tool_decision_total
Filter labels used: organization_id, user_email, model, session_id, terminal_type, type (token type), language (file language), decision, query_source, effort.
No data anywhere. Verify the full pipeline by checking each hop:
- Is Claude Code emitting? Run any Claude Code command, then
curl http://your-collector:9464/metrics | grep claude_code_. You should see Claude Code's metric series listed. - Is Prometheus scraping? In Prometheus, go to Status → Targets and confirm the
claude-code-metrics(or whatever you named it) job isUP. - Are queries finding data? In Grafana Explore, select your Prometheus data source and query
claude_code_session_count_total. Values should appear.
Some panels show data but Sessions by Terminal (or other panels using count aggregations) is empty. Check that your Collector's prometheus exporter has resource_to_telemetry_conversion: enabled: true set (see the example config). Without it, some attribute-derived labels may not be exposed.
Counters look wrong (negative rates, jumps to zero). Likely a temporality mismatch, with Claude Code emitting delta metrics into a cumulative-expecting backend. Set OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE=cumulative in Claude Code's environment.
Pull Requests = 0 even though I've made PRs. Claude Code only emits the PR counter when Claude Code itself opens the PR (e.g., via the gh CLI inside a Claude Code session). PRs you open manually outside Claude Code don't count.
Cost numbers don't match my Anthropic billing dashboard. Claude Code's cost metric is computed client-side from token counts and published model prices, so it's an estimate. It will be close to billing but not identical, particularly across pricing changes or for cached tokens that get billed differently than the local estimate assumes.
Issues and pull requests welcome. If you've extended the dashboard with panels that work well, particularly anything that adds custom labels via Collector processors, please open an issue describing the setup. Useful patterns may make their way into the canonical version.
For dashboard JSON edits: please make changes in Grafana's UI, export, and submit the resulting JSON. Hand-editing the JSON file directly tends to introduce subtle structural issues that aren't visible until import.
Original dashboard concept and panel set: grafana.com dashboard 25052 by 1w2w3y, targeting Azure Application Insights.
This Prometheus port: @rockdarko.


