Migrate Metric Expressions β timeseries
Metrics in Gen3: timeseries
In Gen2, you used metric expressions like builtin:host.cpu.usage:avg:splitBy("dt.entity.host"). In Gen3, you use the timeseries command.
Basic Patterns
// Single metric
timeseries avg(dt.host.cpu.usage), by:{dt.entity.host}
// Multiple metrics together
timeseries {
cpu = avg(dt.host.cpu.usage),
memory = avg(dt.host.memory.usage),
disk = avg(dt.host.disk.used.percent)
}, by:{dt.entity.host}
// With time range
timeseries avg(dt.host.cpu.usage), by:{dt.entity.host}, from:now()-2h
π Try it: Open a Notebook β add DQL section β paste the multi-metric query above. You'll get a chart with CPU, memory, and disk on the same timeline.
Key Metric Names
Gen2 Metric Key Gen3 Metric Key
ββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββββ
builtin:host.cpu.usage dt.host.cpu.usage
builtin:host.mem.usage dt.host.memory.usage
builtin:host.disk.usedPct dt.host.disk.used.percent
builtin:service.response.time dt.service.request.response_time
builtin:service.requestCount.total dt.service.request.count
builtin:service.errors.total.count dt.service.request.failure_count
β οΈ dt.service.request.response_time is in microseconds, not milliseconds. Divide by 1000 for ms.
Aggregation Functions
avg() β average
sum() β total
min() / max() β extremes
percentile(metric, 95) β P95 (great for response time)
count(metric) β distinct metric series per time slot
Post-Processing Timeseries Results
timeseries cpu = avg(dt.host.cpu.usage), by:{dt.entity.host}
| fieldsAdd avg_cpu = arrayAvg(cpu), max_cpu = arrayMax(cpu)
| filter avg_cpu > 50
π‘ Timeseries results are arrays. Use arrayAvg(), arrayMax(), arraySum() to aggregate them into single values for filtering or sorting.
Metric Discovery
// Discover up to 20 host CPU metric keys
metrics from:now()-15m, to:now()
| filter startsWith(metric.key, "dt.host.cpu.")
| fields metric.key
| dedup metric.key
| sort metric.key asc
| limit 20
metrics discovers keys and dimensions, not measurement values. Discovery is limited to the last ten days and 100,000 scanned series; narrow the filter and inspect query warnings. This capped result is not a complete inventory. Discovery uses coarse time windows, so use timeseries to check values within your requested interval.
π Try it: In a Notebook, start typing timeseries avg(dt.host. to explore autocomplete suggestions. Confirm the chosen metric and dimensions in your environment; suggestions are not proof of recent data.
Advanced Timeseries Parameters
The timeseries command has powerful parameters beyond the basics:
Parameter What It Does Example
βββββββββββ ββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββ
filter:{} Filter entities INSIDE the command filter:{dt.entity.host == "HOST-123"}
shift: Compare with a past period shift:-7d (overlay last week)
default: Fill empty time slots default:0 (may still return no rows)
nonempty: Produce an empty series if no data matches nonempty:true
scalar: Collapse array to single value scalar:true (for tables)
union: Keep groups present in any aggregation union:true
interval: Time bucket size interval:1h, interval:5m
By default, multiple aggregations retain only groups present in every aggregation (intersection). union:true retains groups present in any aggregation; it does not add entities missing from all metrics. default:0 fills empty time slots, but alone may still return no rows. Combine it with nonempty:true when you deliberately want a zero-filled series even if no data matches.
Missing telemetry is not automatically a measured zero. Choose zero-filling based on what the metric means, not just to remove chart gaps. See the official metric command reference.
// Week-over-week comparison with shift
timeseries current = avg(dt.host.cpu.usage),
by:{dt.smartscape.host},
from:now()-15m, to:now(), interval:1m
| append [
timeseries previous = avg(dt.host.cpu.usage),
by:{dt.smartscape.host},
from:now()-15m, to:now(), interval:1m, shift:-7d
]
// Scalar for table output (single value per entity)
timeseries usage = avg(dt.host.cpu.usage, scalar:true), by:{dt.entity.host}
| sort usage desc
| limit 5
π‘ filter:{} inside timeseries is more performant than | filter after the pipe β it filters at the data source level. Use it for entity filtering.
The comparison queries the last 15 minutes and the corresponding period seven days earlier. The shifted observations are displayed on the current time axis. Missing historical data remains missingβit is not zero.
π§ Migration Step: Convert Your Metric Expressions
For each Gen2 metric expression in your dashboards and alerts, convert using this pattern:
Gen2 Metric Expression Gen3 DQL Timeseries
ββββββββββββββββββββββββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββββββ
builtin:host.cpu.usage:avg timeseries avg(dt.host.cpu.usage)
builtin:host.cpu.usage:avg:splitBy("dt.entity.host") timeseries avg(dt.host.cpu.usage), by:{dt.entity.host}
builtin:service.response.time:percentile(95) timeseries percentile(dt.service.request.response_time, 95)
builtin:host.mem.usage:max:splitBy():sort(value, timeseries m=max(dt.host.memory.usage, scalar:true),
descending):limit(5) by:{dt.entity.host} | sort m desc | limit 5
(metricA - metricB) / metricA * 100 Aggregate A and B, then calculate with fieldsAdd
π Migration shortcut: Use autocomplete and the discovery query above to find candidate metrics, then verify their meaning, dimensions, and data. Check mappings against the official Grail metric reference; byte and percentage metrics are not interchangeable.