Skip to content

The dashboard

The hosted app is at app.metergraph.dev. A local or BYOC deployment serves the same interface at whatever host you configured.

This page is a reference for what each screen shows and how each figure is derived. If a number looks wrong, the derivation is almost always the explanation.

A left sidebar, collapsible. The collapsed state is the only piece of interface state the app persists, in localStorage under mg.nav.collapsed.

ItemURLShown when
Get started#get-startedOnly until your first trace has been processed. The URL stays reachable forever
Performance#performanceAlways. This is also the landing page
Analysis#analysisThe deployment has the analysis_runs capability
Traces#tracesAlways
Alerts#alertsAlways. Carries a red badge with the open-alert count
Batch API#batch-apiManaged deployments only
Settings#settingsAlways
Keys#api-keysAlways
Admin#adminOnly for a platform administrator
DocsexternalAlways

Routing is by URL fragment, so every screen is linkable and the browser back button works. An unrecognized fragment lands on Performance. #calls is rewritten to #traces.

Below the navigation, the account row shows your email, your workspace and your role, and holds Sign out.

On a free workspace the sidebar carries a usage meter: the label Free plan, a percentage, a bar, and {calls counted} / {allowance} calls. The bar turns amber at 80 percent and red once the allowance is spent, at which point a Book a call link appears. See Limits and allowances.

BannerAppears when
Capture pausedThe monthly allowance is spent. Ingest is returning 402
First traceA first trace was processed while the tab was open. Links to Traces
Viewing asA platform administrator is in a read-only impersonation session
Preview noticeYou are on a preview deployment, not the production app

Most pages carry an environment multi-select. It reads the environments seen in the current window and starts with every environment checked. The button label is All environments, No environments selected, or N selected.

Two behaviors are worth knowing.

It does not persist. There is no URL parameter, no localStorage entry and no shared store. Reloading the page resets it to all environments. Changing the window keeps your selection where the names still exist.

All-selected is not the same as filtering to every name. When every environment is checked the app sends no environment parameter at all, which is what keeps calls that carry no environment label visible. Selecting every name individually would drop them. This was the behavior of an earlier version and is now deliberately avoided.

#performance, and the landing page. Activity, spend, latency, errors and replay quality for one window, drillable into a single bucket, a single workload, and the individual traces underneath.

ControlOptionsDefault
WindowToday (24 hours, hourly buckets), Last 7 days (six-hour buckets), Last 30 days (daily buckets)Today
EnvironmentMulti-selectAll
Group byTotal, Model, WorkloadTotal
MetricCalls, Errors, LatencyCalls
Workloads / ModelsMulti-select subset of the ranked seriesEmpty, meaning everything

Clicking a bar on the timeline scopes the whole page to that bucket. A dismissible pill names the selected bucket. None of these controls persist across a reload.

TileWindow valueWhat it measures
SpendSum of cost_usd over every call in the windowA lower bound. A call the catalog could not price contributes zero
Callscount(*) over the same rowsEvery captured call, errored ones included
Error rateErrored calls divided by all callsA call counts as errored when its error flag is true, or its status is error, failed, failure, cancelled or canceled

Under each tile is a comparison against the immediately preceding window of the same length. A change under one percent reads flat and draws no arrow.

Select a bucket and all three switch to that bucket, with the window figure moved into the note beneath. The Calls note becomes a multiple of typical, where “typical” is the median of the window’s non-zero buckets, not its mean. It reads when there is no non-zero baseline.

A pill appears in the header only when something is wrong.

TextMeaning
N batch(es) rejected. Check your API key and payload formatBatches were refused outright
N batch(es) missing from this window. We are identifying the causeUnclassified gap
N batch(es) missing from this window. We are restoring them after an upstream outageKnown upstream incident, nothing for you to do
No traces received in this windowThe workspace has sent traces before, but not here
no traces received yetNothing has ever arrived
No traces received in the last 12 hoursDelivery may have stopped

When a last-received time is known the pill also names how long ago the last trace arrived. Healthy ingestion draws no pill at all.

Call volume, errors and p95 latency on one chart. The trailing bucket is always partial by construction, so it is drawn as In progress rather than dropped. It keeps its real counts and stays in the window totals.

Two rules the chart enforces rather than fudging:

  • Latency percentiles come from Postgres percentile_cont over raw latency_ms, per bucket. They are never averaged.
  • A percentile cannot be recombined from parts, so selecting a subset while showing Latency draws no p95 and says so, rather than approximating one.

Breaking down by model or workload draws at most four named series plus a folded Other band. Calls and errors sum across a fold. Average latency is weighted by call count. Percentiles are dropped.

One row per workload over the window, or over the selected bucket.

ColumnWhat it holds
WorkloadThe route name. A route the SDK never labeled reads Unlabeled template · {first 8 hex} and its full template: key is in the tooltip
Window shapeA sparkline of that workload’s calls and errors per bucket
SpendSum of cost_usd, with cost per call beneath it
CallsCall count, with a multiple of typical beneath it when a bucket is selected. Red at 2× or more
Latencyp95, with p50 beneath. Red when p95 exceeds 1.5× the window p95
ErrorsError count, red when this workload’s error rate exceeds 1.5× the window rate. The info button names the leading error_type, or unclassified
ModelThe workload’s top model, with +N when it uses several. The provider prefix is the second line
QualityGrade dots and a letter from the most recent analysis that replayed this workload. Reads not inspected otherwise

The quality grade comes from replay evidence, not from production traffic. A/B count as held. See Analysis for what the grades mean.

The ten newest traces in the window, or twenty-five inside a selected bucket. Columns are the same as the Traces page. All traces → opens the full list.

ColumnWhat it holds
ModelThe raw model identifier as your code sent it
SpendSum of cost_usd for that model
$/answerSpend divided by call count
CallsCall count
Tokensinput_tokens + output_tokens, summed

When some calls carry no price the table says so directly:

A lower bound. N calls carry no price.

That count is the difference between all calls in the window and those whose cost_status is priced or reported. To find out which models are behind it, see Model catalog and pricing.

A ranked row of chips, one per error_type, with counts. A null type reads unclassified. The section is absent when nothing failed.

StateTrigger
Onboarding scaffold, with Connect your appNothing has ever been received. Each section shows the question it will answer once data lands
Quiet windowZero calls in this window, but the workspace has sent traces before. The tiles read and the page suggests a wider window
No environments selectedYou cleared the environment filter

A quiet window is not the same as a workspace that never delivered, and the page distinguishes them.

#traces. The individual traces over a window of their own, each opening in place into its span waterfall.

The window options are Today, Last 7 days, Last 30 days and Last 90 days, defaulting to 7 days. Ninety days is the ceiling because that is the default metadata retention. The list returns at most 100 traces.

This page carries no summary tiles. It is deliberately a list.

ColumnWhat it holds
TraceThe trace name, else its first route, else LLM trace. A Failed chip when the trace errored. Second line: the first 12 characters of the trace ID, and whether content is captured, truncated or not captured
StartedLocal time, to the second
DurationWall-clock span of the trace
SpansNumber of spans
TokensMeasured tokens across the trace
CostSum of cost_usd. A zero renders as , because the list cannot distinguish a free trace from an unpriced one
ModelThe trace’s first model, with +N when it used several

Expanding a row draws a waterfall of its spans, ordered by nesting with offsets in milliseconds from the earliest span. Selecting a span opens an inspector with, in order: Model, Provider, Workload, Function, Cost (reading Unpriced when there is none), Tokens, Cache (only when non-zero), Reasoning tokens (only when non-zero), Latency, First token, Streamed, Status, Endpoint, Request id.

Below that, tool names as chips, then the captured Request and Response, pretty-printed and copyable. Where content was not captured they read Request not captured and Response not captured. Where the server clipped a field the inspector says Truncated at the 100 KB field limit.

#analysis, also reachable at #optimize. The most recent fixed-profile analysis of your production traffic: what it replayed, what held, what it would save, and what to ship.

Available only where the deployment has the analysis_runs capability.

Start analysis opens a confirmation dialog that states every parameter and lets you change none of them. The profile is fixed on purpose: the window (latest complete UTC days), the evaluation sample size, how many workloads are covered (top N by spend), the objective, the opportunity types, and the candidate models.

A run is blocked with a reason when there is not enough trace data, or when the catalog has no price for a production model the run would need.

While a run is active, Running now shows four stages: Queued, Exporting traffic, Analyzing, Importing report. You can leave the page. The previous analysis stays visible until the new one finishes.

A failed run appears under Recent attempt with a plain-language cause.

TileValueDerivation
Replays that held{held}/{total}, with a percentage beneathGrades A and B, over all four grades, summed across every graded finding. Reads No replays yet when no finding carries grade counts
Avg quality changeA signed percentageThe mean of each finding’s quality ratio against its baseline, minus one. Findings with no measured ratio are excluded, never counted as zero
SavingsWhole dollars per monthThe analysis report’s own projected monthly saving. It is deliberately not the sum of the rows below, and it is a projection from observed traffic rather than a live catalog computation
ShippedA countRecommendations whose delivery reached “applied”
ColumnWhat it holds
ChangeThe recommendation title, its kind (Model swap, Search-enabled stack swap, Prompt context reduction, Result caching, Batch-first), and a Shipped or Superseded chip
WorkloadThe route it applies to
EvidenceGrade dots and a case count, or the literal Not replayed
Qualityheld, or a signed percentage against the baseline
Latencyheld, N× faster, or N× slower
SavesWhole dollars per month for this recommendation alone

Superseded means a later analysis retested the same workload.

View evidence opens the recommendation, where a model swap shows the full candidate comparison, the pipeline’s stated reason, and the actual replayed question with both models’ verbatim answers side by side.

Workloads that were analyzed and produced nothing worth shipping, with the reason. This section existing is the point: silence would be indistinguishable from a failed run.

The page says which of these happened rather than showing an empty table.

OutcomeWhat it means
Your current models are already the cheaper choiceEvery candidate was analyzed and none beat what you run today at equal or better quality
This analysis could not evaluate every candidateSome workloads did not complete. Run it again
Not enough traffic in any single workloadNo workload reached the minimum trace count. The page names the largest one and the threshold
No analysis yetNothing has been run

One vocabulary, used by the Performance quality column, the analysis dots, the model comparison legend and the eval definition.

GradeMeaningCounts as held?
AEquivalent outcomeYes
BSame outcome with only non-impacting differencesYes
CImpacting difference that could change a downstream decisionNo
FUnusable result or execution failureNo

Reachable from a recommendation. One row per candidate model against your baseline, with Replays held, Quality against baseline, Cost as a ratio plus the absolute figure, Latency as a ratio plus milliseconds, and the Safe-switch rate as a percentage. A chart view plots safe-switch rate against cost on a log axis, with your current cost marked at 1×.

#alerts. Detector alerts with the evidence each detector measured. Detectors run every five minutes, which the header states.

No summary tiles. The only count is the sidebar badge, which is the number of open alerts.

ColumnWhat it holds
AlertThe alert title, with {environment} · {detector} beneath
StatusTwo chips: severity (Critical, Warning, Info) and state (Open, Acknowledged, Resolved)
EvidenceWhat the detector measured, and against what baseline. See below
WhenLast seen {ago} while live, Resolved {ago} once closed
Acknowledge, for an open alert, unless your role is viewer

Evidence, per detector:

DetectorMeasureDetail
Cost driftN.NN× cost per callRecent median against baseline median, per call
Latency driftN.NN× slowerRecent median against baseline median, plus the share of calls above the old p95
Unusual or runaway spend${amount} in {span}How many calls fell outside the environment’s cost distribution, and the projected daily figure
Anything elsenoneThe detector’s own explanation

An environment filter appears only when at least one alert carries an environment. Alerts with no environment are always shown, whatever the filter.

Empty state: No alerts. Cost drift, latency drift and unusual spend are watched on every route, every 5 minutes.

See Set up alerts for what each detector looks for and where alerts are delivered.

#batch-api. Managed deployments only.

Metergraph runs one synthetic, single-request probe through each provider’s Batch API on a fixed schedule and measures how long it actually takes. This is not your traffic, and it is aggregated across every tenant, so it is operational data rather than a measurement of your workloads or a service-level guarantee.

The single control is the direct-fallback deadline: 3m, 5m, 10m, 15m or 20m, defaulting to 10 minutes. A direct-fallback deadline is the point at which a caller stops waiting on the batch and sends a normal request instead. It is the same number you would pass as deadline_seconds to batch_first().

Per provider:

ColumnWhat it holds
ModelThe probed model
CompletedNumber of probes that finished
FailedNumber that did not
p50, p90, p95Completion time percentiles

An All models row pools the provider’s probes.

The headline figure is Expected relative cost at this deadline, one of:

  • N% cheaper than always using the direct API, expected
  • N% more than always using the direct API, expected. Duplicate cost on a missed deadline
  • About the same as always using the direct API, expected
  • Unavailable: the catalog has no batch price for a contributing model

Beneath it, the share of completed probes that finished inside the deadline. A completion-time histogram follows, with a low-sample warning under five probes.

Empty state: No completed or failed probes yet. Probes run on a four-hour schedule per provider, check back soon.

#settings.

Organization. Rename the workspace. Owners only, and read-only on a BYOC deployment, where the footer reads “Workspace settings are managed by your operator.” The permanent workspace slug is shown beneath and cannot be changed.

People. Invite by email with a role of Member or Viewer, defaulting to Member. The table shows each person’s name, email, role, status and join or invite date, with Resend for a pending invitation. Roles are owner, member and viewer.

RoleCan do
ownerEverything, including renaming the workspace and inviting people
memberEverything operational, including creating and disabling ingest keys
viewerRead. Cannot acknowledge an alert or manage keys

Run Metergraph locally. The Docker Compose setup, with a copyable command block. See Run it locally.

Archive. Pages that left the navigation during the interface rebuild and stay routable until each capability re-homes: Overview, Functions, Routes, Models and Datasets.

See Manage your workspace.

#api-keys, labeled Keys in the sidebar. The header chip reads {n} active.

Create a key by naming it. The token is shown exactly once, in a highlighted block with a copy button and a Dismiss, I’ve stored it button. Only its SHA-256 digest is stored, so a lost key must be replaced rather than recovered.

ColumnWhat it holds
KeyThe label you gave it, with its masked hint and creation date beneath
Statusactive or disabled
Disable, which is immediate and irreversible

Every dashboard-created key carries exactly the ingest scope. The read and agent:read scopes are operator-CLI only, which is why the MCP server needs a key the dashboard cannot mint. See Use the MCP server.

Below the table, the same Wrap your client snippets as Get started, with tabs for Python, TypeScript and Agent. The snippet substitutes your real token while a key reveal is open.

Viewers cannot create or disable keys and see “Ask a workspace member to create an ingest key.”

#get-started, titled See your first trace. It leaves the sidebar once a trace has been processed, and stays reachable at its URL.

Three steps, polled every five seconds while the tab is visible until a trace arrives.

  1. Create a key. Collapses to a done row once one exists.
  2. Wrap your client. The language-tabbed snippet.
  3. First trace. The status of ingestion:
StateWhat it says
No key yetWaiting on an ingest key
Key, nothing receivedWaiting for your first trace, with a live indicator
Received, still processingGot your data. Processing
Received, processing failedReceived, but processing failed. Check your key, or see the docs
Received, delayed upstreamReceived. Processing delayed by an upstream outage
DoneFirst trace processed

When the first trace lands, step three is replaced by a card naming the trace, its model, its token count and its cost, with a link into Traces. A trace that arrived but errored says so plainly rather than declaring success.

See Send your first trace.

#admin. Platform administrators only, and not present on a BYOC deployment. Product health, workspace activation and audited read-only access. It shows other people’s workspaces, so it is documented here only so you know what it is.

Impersonation is read-only, includes customer-visible prompt and response content, and is recorded in an audit log until the administrator exits or signs out. Every tab sharing the browser session switches with it, and a banner says so on every page.

These left the navigation and remain routable.

PageURLWhat it shows
Overview#overviewTrace counts, span counts, error rate and ingestion health, plus provider, function and model breakdowns
Functions#functionsCost by call site, with a search box
Models#modelsSpend by provider and by model
Routes#routesCost attributed by route
Datasets#datasetsCoverage dataset construction. See Build a dataset

It does not persist filters. Every window, environment, grouping, metric, subset and deadline resets on reload. The URL fragment carries only the route and any report identifiers, so a link you send someone opens the page, not your view of it.

It does not recompute cost. Every currency figure on Performance and Traces is a sum of the per-call cost_usd written at ingest. The one figure computed against the catalog at read time is Batch API’s expected relative cost, which is why it is the one place that can say the catalog has no batch price for a model.