Metrics

Charting a metric with an aggregate select, the metric catalog, and the aggregation-versus-encoding rules that decide whether a chart comes back empty.

Metrics

Pick Metrics in the sidebar, or write a query with from metrics. Unlike logs and traces, metrics has no plain row view: you either list what metrics exist, or chart one.

Listing what you have

select * from metrics and select <columns> from metrics are not projections here; they are the catalog question, "what metrics does this org have", the same answer up0 metrics ls gives on the terminal (see From the terminal). The catalog has its own fixed shape (metric_name, metric_type, series_count) and its own fixed ordering (busiest first), so a where or order by next to it is rejected rather than silently ignored:

select * from metrics where service_name = 'checkout'
→ Listing metrics doesn't support a where clause — labels only apply once
  you're charting a metric, e.g. select avg('http.server.duration') from
  metrics where attr:'region' = 'eu-west'.

Charting one metric

Name a function and a metric to switch from the catalog to a chart:

select avg('example.queue.depth') from metrics
select quantile('example.http.duration', '0.95') from metrics
select avg('example.queue.depth') from metrics group by 'route'

A charted gauge metric: avg(up0_ingest_test_...) over metrics, one series, one data point

The chart's legend switches between a compact list and a per-series summary table. group by splits the chart into one series per distinct value of the named label, up to 10 label keys at once.

Filtering by label

Metrics filter by label, never by a metric-record column: where accepts only attr:'<label>' = '<value>' predicates (and !=), ANDed together. There is no or form for a label filter, and no plain-column predicate at all:

select avg('example.queue.depth') from metrics where attr:'region' = 'eu-west'
select avg('example.queue.depth') from metrics
  where attr:'region' = 'eu-west' and attr:'env' != 'staging'

The aggregation has to match the encoding

metric_type, visible in the catalog and echoed back on every charted series, is not decoration. It decides which aggregations can return anything at all:

metric_typeWorks with
gauge, countersum, avg, min, max, count, quantile
histogram, exponential_histogram, summaryquantile only

sum/avg/min/max/count operate on a raw scalar value, which only gauges and counters have. A histogram data point is already an aggregate (bucket counts, not a number), so avg('some.histogram.metric') returns null in every bucket. That reads as "this metric is broken" when it actually means "wrong aggregation for this encoding".

For anything histogram-shaped, use quantile, which resolves the storage shape server-side and works across all four encodings. You do not need to check the catalog first:

select quantile('example.http.duration', '0.95') from metrics

'0.95', quoted, is a fraction in [0, 1], not a percentile.

Two behaviours worth knowing before you read the chart

Counters are not rate-corrected. A counter's value is its raw cumulative total, so charting one shows growth, not throughput. metric_type on the series tells you which case you are in.

An empty bucket is null, not 0, except for sum and count, where zero is a real answer. A null means no data was recorded in that bucket; rendering it as 0 would assert a measurement that never happened.

For summary metrics, only the quantiles the instrumentation actually recorded come back. Asking quantile for a fraction it never computed returns null rather than a value interpolated between two others. upzero will not invent a number the client never reported.

Bucket width

Bucket width is derived from the time range, targeting roughly 50 buckets across whatever you selected. You control the range, never the width directly.

Why logs and traces cannot be aggregated (yet)

select avg(...) from logs is a parse error, not a compile error. See Query language for why the refusal happens as early as it does, and what "yet" means here.

On this page