RootTrace

Dashboards and the metric explorer

Every other metric view in RootTrace answers the question it was built for. The explorer answers the ones nobody anticipated, and a dashboard is a saved set of those answers.

The explorer

Pick a source, a metric, an aggregation, and optionally something to group by. There is no query language, deliberately: the controls map one-to-one onto the API's parameters, a UI builds queries from controls anyway, and a language would be a parser plus a lifetime of compatibility for the same result.

Sources

SourceWhat it holdsDimensions
apmCustom application metrics from the SDKsservice, deployment, environment, and any tag
transactionsLatency and throughput per transactionservice, deployment, environment, type, transaction
checksEverything the collector checkscheck_type, host, resource, environment

Aggregations

avg, sum, min, max, last, count, rate.

avg is weighted by sample count: sum/count across the window, not the mean of the per-minute means, which would weight a minute with one request the same as a minute with ten thousand. rate is events per second over the bucket.

Percentiles are refused, not approximated

Metrics are stored as per-minute rollups, so individual values are gone by the time a query runs. Any p95 computed from them would be invented, and an invented p95 is worse than no p95 because it ends up quoted in an SLA review. Asking for one returns a 422 that says so.

Where exact percentiles are possible (request latency, from the histograms the APM wrappers ship), they live on the APM pages and in latency SLOs.

Intervals

Chosen automatically to keep a series under 1,500 points. An explicit interval is honoured only if it fits; a minute grid over a year is slower to compute and no more informative than the hour grid it gets promoted to.

Filters

Bare query parameters named after the source's own dimensions:

GET /api/metrics/query?source=checks&name=disk&agg=max&group_by=host&host=web-01
GET /api/metrics/query?source=apm&name=queue_depth&group_by=tag:region&tag.region=eu

Anything not a dimension of that source is refused rather than passed through. GET /api/metrics/dimensions/{dimension}/values lists what a dimension actually takes, so filters offer real choices.

Dashboards

A widget saves a query, not data. Rendering re-runs it through the explorer, so a dashboard is always current and there is no second storage path to drift.

Building one

Run a query in the explorer, then Add to dashboard. That is the whole flow: the query already exists at that point, and rebuilding it in a widget form would be the same work twice. The dialog asks for a title and a destination, and will create the first dashboard inline.

What gets saved is the query behind the chart on screen, not the current state of the dropdowns: those can be changed after Run without the chart being re-run.

Layout

A 12-column grid with per-widget x, y, width, height. Below 720px it collapses to one column, because a 3-of-12 chart on a phone is eighty pixels of nothing.

A widget added from the explorer is placed below everything already there. The explorer has no idea what the dashboard looks like, so placement is the server's job.

Widgets are validated when saved

A widget naming a dimension its source does not have is rejected at save time, not at render time. A dashboard that saves cleanly and then shows an error on the one widget nobody scrolled to is worse than a save that refuses.

Adding and removing is an append, not a rewrite

POST /api/dashboards/{id}/widgets appends one widget and DELETE /api/dashboards/{id}/widgets/{widget_id} removes one. Rewriting the whole widget list would mean two people adding a widget at the same time each send the list they read, and the second write silently drops the first one's widget.

Limits: 40 widgets per dashboard, 200 dashboards per workspace.

API

EndpointPurpose
GET /api/metrics/catalogSources, their metrics, dimensions, and available aggregations
GET /api/metrics/dimensions/{dimension}/valuesThe values a dimension takes
GET /api/metrics/queryOne time series per group
GET/POST /api/dashboardsList and create
GET/PATCH/DELETE /api/dashboards/{id}Read (with each widget's render call), edit, remove
POST/DELETE /api/dashboards/{id}/widgets[/{widget_id}]Append and remove one widget

Reading is open to any member; creating and editing needs engineer.