Grail metrics are queried with DQL, while classic metrics are queried using metric selectors. This guide shows how to convert classic metric selector expressions to DQL so you can smoothly and efficiently transition to Grail.
To make converting queries easier, Data Explorer and Dashboards Classic can now help you transform your existing metric selector queries into DQL.
Because there isn't an exact, one-to-one mapping between Classic metric selectors and DQL, some queries will require manual adjustments during the conversion. The troubleshooting section on this page can help you in these scenarios.
In this section, you'll learn how to use the conversion tool in Dynatrace to produce DQL from existing Classic metric selectors.
To automatically convert the metric selector to a DQL query in Notebooks or Dashboards
Go to Data Explorer to create and run your query.

The metric selector for this query in the advanced mode reads as:
builtin:host.cpu.usage:splitBy("dt.entity.host"):sort(value(auto,descending)):limit(20)
Select Open with… in the upper-right corner of the Data Explorer Result section.

Follow the on-screen instructions to:
When the destination document is displayed, verify the resulting query.

In our example, the resulting query is
timeseries usage = avg(dt.host.cpu.usage), by: { dt.entity.host }| sort arrayAvg(usage) desc| fieldsAdd entityName(dt.entity.host)| limit 20
If you have questions, see the troubleshooting section.
If your metric selector is not automatically convertable, in this section you'll learn how to manually convert your metric selector to DQL.
filter and splitBy, can be included in this initial command.timeseries command can be added by additional commands as illustrated below.A list of Classic metrics and their equivalents in Grail can be found in the Built-in Metrics on Grail. Bear in mind that not all Classic metrics have Grail equivalents migration guides, and some metrics are not yet supported on Grail.
You should use Grail metrics when querying DQL. A list of Classic metrics and their equivalents in Grail can be found in Built-in Metrics on Grail.
Some metrics, such as those prefixed with builtin:apps, are not supported on Grail and cannot be queried with DQL. The full list is available on Built-in Metrics on Grail.
Not all Classic metric selectors have equivalent DQL. If you're not sure whether your Classic metric is supported on Grail, look it up on Built-in Metrics on Grail.
In the process of transitioning from classic queries to Grail in Dynatrace, you might notice some differences in your query results. This is expected, as not all classic metrics have direct equivalents in Grail. However, if you encounter differences where you expect the queries to match, the following scenarios might help explain why.
It's possible that none of the following scenarios describe your query if you converted your Metrics Classic selector yourself. Try creating the DQL using the transpiler and comparing the results.
Scenario
Guide
The automatically converted DQL query returns no data on Grail.
Not all classic metrics have equivalents in Grail, and some metrics are not yet supported on Grail. For a list classic metrics and their equivalents in Grail, see Built-in Metrics on Grail.
The charts look similar but the numbers are slightly different.
To reduce complexity, several metrics have been refactored and have no direct equivalents in Grail. The runtime metrics migration guide will help you migrate these queries to Grail.
The percentile function returns a different result than in Metrics Classic.
Grail uses a more accurate algorithm to calculate percentiles. Learn more in DQL Metric commands.
The count function returns a different result than in Metrics Classic.
DQL uses the count function to calculate metric cardinality. You may need to use sum(..., rollup:total). To learn more, see the count aggregation section.
The chart looks similar, but the numbers are on a different scale.
Scale factor changes may indicate a bug in the metric calculation. In this case, please report the issue to the Dynatrace support team.
In DQL, there are equivalent aggregations for avg, sum, min, max, count, and percentile.
builtin:host.cpu.usage:splitby():avg
For example, the Classic metric selector query above can be converted to DQL as follows:
timeseries avg(dt.host.cpu.usage)
Specifying a time aggregation is possible with both the Metrics Classic metric selector and DQL.
builtin:host.cpu.usage:max:splitby():avg
The example above calculates the average maximum CPU usage across all hosts. It can be converted to DQL with the rollup parameter as follows:
timeseries avg(dt.host.cpu.usage, rollup:max)
You can reproduce the Metrics Classic median aggregation with the parametrized function percentile(..., 50).
builtin:host.cpu.usage:avg:splitby():median
For example, the Metrics Classic metric selector query above can be converted to DQL as follows:
timeseries median=percentile(dt.host.cpu.usage, 50, rollup:avg)
The Metrics Classic value aggregation performs a sum for count metrics, and is therefore equivalent to the timeseries sum aggregation.
builtin:service.requestCount.total:splitBy():value
For example, the Metrics Classic metric selector query above can be converted to DQL as follows:
timeseries sum(dt.service.request.count)
The Metrics Classic metric selector includes a pseudo-aggregation called auto. Based on the metric's metadata, auto selects the default aggregation for that metric. DQL doesn't have an equivalent pseudo-aggregation, however, so you need to explicitly specify the aggregation.
builtin:host.cpu.usage:splitby():auto
For example, the Metrics Classic metric builtin:host.cpu.usage defaults to the avg aggregation. Therefore, the Metrics Classic metric selector above can be converted to DQL as follows:
timeseries avg(dt.host.cpu.usage)
Custom metrics can be determined from the metric key directly. A Metrics Classic metric.key with the .count suffix uses the value aggregation by default (and therefore will use a sum in the converted DQL). All other custom metrics use the avg aggregation by default.
For non-custom metrics (generally metric keys that contain a colon :), there are two options:
Use the automatic converter to convert a simple metric selector such as
builtin:host.cpu.usage:splitby():auto
Look up the defaultAggregation property of your metric's metadata via the Metrics Classic Metrics browser or the metric metadata API.
Because Metrics Classic metric selectors don't require an explicit aggregation, it's possible to omit the aggregation entirely. The auto transformation is used implicitly in these cases.
builtin:host.cpu.usage:splitby()
Consequently, this metric selector is equivalent to the examples above.
The vast majority of Metrics Classic count aggregations return cardinality–the number of series represented by a metric. The DQL count function returns cardinality as well.
builtin:host.cpu.usage:splitBy():count
For example, many Dynatrace users want to count the number of hosts. This selector is converted to DQL as follows:
timeseries num_hosts = count(dt.host.cpu.usage)
The Metrics Classic count transformation does ultimately depend on the metric metadata. In some scenarios, count returns the number of observations reported to a metric. This behavior is uncommon but nevertheless useful in these scenarios.
record_processing_time:splitBy("host.name"):count
The above metric selector queries a hypothetical custom metric. As a custom metric without the .count suffix (see below), the count transformation here calculates the count of records processed. The converted DQL uses sum(..., rollup:total):
timeseries records_processed = sum(record_processing_time, rollup:total)
The final behavior of count ultimately depends on a number of conditions. The automatic converter performs all these checks for you. You can provide a simple metric selector and determine from the resulting DQL which count is performed. For example:
builtin:host.cpu.usage:splitBy():count
The Metrics Classic splitBy transformation is replaced by the timeseries by parameter.
builtin:host.cpu.usage:splitby():avgbuiltin:service.requestCount.total:splitBy("dt.entity.service"):sum
For example, the Metrics Classic metric selector queries above can be converted to DQL as follows:
timeseries avg(dt.host.cpu.usage)timeseries sum(dt.service.request.count), by:{dt.entity.service}
The Metrics Classic merge transformation is a counterpart to the splitBy transformation. There is no equivalent to merge in DQL, as it can be expressed equivalently with splitBy.
builtin:host.net.nic.bytesRx:merge("dt.entity.network_interface"):avg
Because the Metrics Classic metric builtin:host.net.nic.bytesRx has two dimensions, dt.entity.host and dt.entity.network_interface, the above merge on dt.entity.network_interface is equivalent to a split on dt.entity.host:
builtin:host.net.nic.bytesRx:splitBy("dt.entity.host"):avg
Consequently, the metric selector can be converted to DQL as described in the split by transformation section:
timeseries bytes_rx=avg(dt.host.net.nic.bytes_rx), by:{dt.entity.host}
The Metrics Classic filter transformation is equivalent to the timeseries filter parameter.
builtin:cloud.aws.dynamo.tables:filter(in("dt.entity.aws_availability_zone",entitySelector("type(aws_availability_zone),entityName.equals(~"us-east-1a~")"))):splitBy():sort(value(auto,descending)):limit(20)
As Metrics Classic filter transformations rely on entity selectors, the same syntax is readily supported in DQL with the classicEntitySelector function.
timeseries tables=avg(dt.cloud.aws.dynamo.tables),by:{dt.entity.aws_availability_zone},filter:{in(dt.entity.aws_availability_zone,classicEntitySelector("type(aws_availability_zone),entityName.equals(\"us-east-1a\")"))}
The following table shows Metrics Classic filter conditions and their equivalent DQL.
| Classic filter condition | DQL function |
|---|---|
| prefix | startsWith |
| suffix | endsWith |
| contains | contains |
| eq | == equals |
| ne | != not equals |
| in | in |
| series | Constructed from comparisons to array functions |
| existsKey | Not supported on Grail |
| remainder | Not supported on Grail |
Entity selector existsKey and remainder have no equivalent in DQL and are ignored at conversion.
The Metrics Classic series condition consists of an aggregation and a comparison operator, which can be reconstructed from array functions and comparison operators respectively.
builtin:host.disk.usedPct:splitBy("dt.entity.host"):filter(series(avg,gt(20)))
For example, the Metrics Classic metric selector query above can be converted to DQL as follows:
timeseries percent = avg(dt.host.disk.used.percent),by:{dt.entity.host}| filter arrayAvg(percent) > 20
In this example, avg is replaced by arrayAvg, and gt is replaced by the > (greater than) operator.
DQL uses variable assignment and sequential commands to improve on the arithmetic in Metrics Classic metric queries. You can learn more in DQL operators.
(100 * builtin:kubernetes.resourcequota.limits_memory_used/ builtin:kubernetes.resourcequota.limits_memory):splitBy("dt.entity.cloud_application_namespace","k8s.resourcequota.name")
The above metric selector query is formatted to highlight difference with the following converted DQL.
timeserieslimits_memory_used=avg(dt.kubernetes.resourcequota.limits_memory_used),limits_memory=avg(dt.kubernetes.resourcequota.limits_memory),by:{dt.entity.cloud_application_namespace, k8s.resourcequota.name}| fieldsAdd// use square brackets [] to perform timeseries arithmeticlimits_memory_usage = 100 * limits_memory_used[] / limits_memory[]
There is a clear difference in the approach taken with DQL. Metrics Classic metric selectors mix arithmetic with queries, whereas DQL separates these steps: first you assign the inputs, and then you perform the calculation within a fieldsadd command.
This approach is both easier to read and easier to write:
fetch logs or the makeTimeseries command, for example.The Metrics Classic transformations in this section can be converted to DQL.
The Metrics Classic default transformation is equivalent to the timeseries default parameter.
builtin:host.availability.state:splitBy("dt.entity.host"):sum():default(0)
For example, the Metrics Classic metric selector query above can be converted to DQL as follows:
timeseries availability = sum(dt.host.availability, default:0),by:dt.entity.host
The Metrics Classic default(x, always) transformation is a special case. The behavior of always is mapped to the timeseries nonempty parameter.
builtin:host.availability.state:splitBy("dt.entity.host"):sum():default(0,always)
For example, the Metrics Classic metric selector query above can be converted to DQL as follows:
timeseries availability = sum(dt.host.availability, default:0),by:{dt.entity.host},nonempty:true
The Metrics Classic delta transformation is equivalent to the arrayDelta function in DQL.
builtin:service.errors.fivexx.rate:splitBy():sum:delta
For example, the Metrics Classic metric selector query above can be converted to DQL as follows:
timeseries rate=sum(dt.service.errors.fivexx.rate)| fieldsAdd rate=arrayDelta(rate)
The following Metrics Classic fold transformations have equivalent array functions in DQL:
| Fold transformation | DQL array function |
|---|---|
fold(avg) | arrayAvg |
fold(max) | arrayMax |
fold(min) | arrayMin |
fold(sum) | arraySum |
fold(value) | arraySum |
fold(count) | arraySize |
fold(median) | arrayMedian |
fold(percentile(X)) | arrayPercentile |
For example, this Metrics Classic metric selector query:
builtin:host.cpu.usage:avg:fold(avg)
can be converted to DQL as follows:
timeseries usage=avg(dt.host.cpu.usage), by:{dt.entity.host}| fieldsAdd usage=arrayAvg(usage)
There are two ways to determine the equivalent DQL:
fold transformation.fold with fold(sum) or fold(avg) and compare the new query results to the original.An empty fold transformation resolves to either the arraySum or arrayAvg function in DQL, depending on a combination of metric metadata and the sequence of operations before the fold.
The fold transformation has some implicit uses to be aware of in Data Explorer and Dashboards Classic. The Single value visualization, shown below, displays both a line chart and a single value.

Use the scalar:true aggregation parameter to get a single value instead of a time series. To reproduce this visualization, consider the following Metrics Classic metric selector query:
builtin:kubernetes.container.restarts:splitby():sum
It can be converted to DQL as follows:
timeseries restarts.trend_line = sum(kubernetes.container.restarts), restarts = sum(kubernetes.container.restarts, scalar:true)
The Metrics Classic last transformation is equivalent to the arrayLast function in DQL.
For example, this Metrics Classic metric selector query:
builtin:service.response.time:splitBy("dt.entity.service"):percentile(99):last
Can be converted to DQL as follows:
timeseries time=percentile(dt.service.response.time, 99),by:{dt.entity.service}| fieldsAdd time=arrayLast(time)
The Metrics Classic lastReal transformation has no equivalent in DQL. It also uses arrayLast at conversion.
The Metrics Classic limit transformation is equivalent to the limit command in DQL.
For example, this Metrics Classic metric selector query (formatted for clarity):
builtin:host.cpu.usage:splitBy("dt.entity.host"):avg:sort(value(avg,descending)):limit(3)
can be converted to DQL as follows:
timeseries usage=avg(dt.host.cpu.usage), by:{dt.entity.host}| sort arrayAvg(usage) desc| limit 3
The Metrics Classic names transformation is equivalent to the entityName DQL function.
For example, this Metrics Classic metric selector query:
builtin:dt.host.disk.avail:splitBy("dt.entity.host", "dt.entity.disk"):max:names
can be converted to DQL as follows:
timeseries avail = max(dt.host.cpu.usage),by:{dt.entity.host, dt.entity.disk}| fieldsadd entityName(dt.entity.host), entityName(dt.entity.disk)
The Metrics Classic parents transformation is not directly supported in DQL, but in many cases it can be reproduced.
For example, this Classic metric selector query (formatted for clarity):
builtin:tech.jvm.threads.count:avg:splitBy("dt.entity.process_group_instance"):parents:splitBy("dt.entity.host"):avg
uses the parents transformation, so it can split on the parent dt.entity.host dimension. Because data is already enriched in Grail, you can instead split by the host dimension directly.
timeseries avg(dt.runtime.jvm.threads.count),by:{dt.entity.host}
Missing enriched dimensions may indicate a bug in the metric calculation. In this case, please report the issue to the Dynatrace support team.
The Metrics Classic partition transformation is unique in that it creates new series from existing ones. The equivalent DQL makes use of iterative expressions.
For example, this Metrics Classic metric selector query (formatted for clarity):
builtin:host.disk.avail:fold(avg):limit(3):partition("disk_usage",value("underused",gt(450000000000)),value("optimal",otherwise))
can be converted to DQL as follows:
timeseries avail=avg(dt.host.disk.avail), by: { dt.entity.disk, dt.entity.host }| fieldsAdd avail=arrayAvg(avail)| fieldsAdd disk_usage=if(avail>450000000000, "underused", else: "optimal")| limit 3
This example Metrics Classic metric selector query (formatted for clarity):
builtin:host.disk.avail:auto:limit(3):partition("disk_usage",value("underused",gt(450000000000)),value("optimal",otherwise))
can be converted to DQL as follows:
timeseries avail=avg(dt.host.disk.avail), by: { dt.entity.disk, dt.entity.host }| expand disk_usage=array("underused", "optimal")| fieldsAdd avail=if(disk_usage=="underused",if(avail[]>450000000000, avail[]),else:if(disk_usage=="optimal", if(NOT(avail[]>450000000000), avail[])))| filterOut isNull(arrayMin(avail))| limit 3| filterOut isNull(arrayMin(avail)) // arrayMin returns null if all elements are null
The Metrics Classic rate transformation is equivalent to the timeseries rate parameter.
For example, this Metrics Classic metric selector query:
builtin:service.requestCount.total:splitBy("dt.entity.service"):value:rate(1s)
can be converted to DQL as follows:
timeseries total=sum(dt.service.request.count, rate:1s),by:{dt.entity.service}
The Metrics Classic rollup transformations have equivalent array functions in DQL:
| Rollup transformation | DQL array function |
|---|---|
rollup(avg) | arrayMovingAvg |
rollup(max) | arrayMovingMax |
rollup(min) | arrayMovingMin |
rollup(sum) | arrayMovingSum |
rollup(value) | arrayMovingSum |
rollup(count) | Not supported |
rollup(median) | Not supported |
rollup(percentile(X)) | Not supported |
For example, this Metrics Classic metric selector query:
builtin:host.cpu.usage:splitBy():avg:rollup(avg,5m)
can be converted to DQL as follows:
timeseries usage=avg(dt.host.cpu.usage), interval:1m| fieldsadd usage=arrayMovingAvg(usage, 5)
The moving window is time-agnostic and so the interval parameter has been fixed to 1m to ensure a 5-minute moving average. While the Metrics Classic rollup transformation adjusts the query timeframe to include past data points, the DQL function does not adapt the query timeframe automatically, which means the first n data points are null (if n is the window size). To get the same results, modify the query timeframe on the timeseries command using the from: parameter.
DQL array functions are not a direct replacement for rollup when migrating Metric Events configurations.
The Metrics Classic smooth transformation has no equivalent DQL.
The Metrics Classic sort transformation is equivalent to the sort command in DQL.
For example, this Metrics Classic metric selector query (formatted for clarity):
builtin:host.cpu.usage:splitBy("dt.entity.host"):avg:sort(value(avg,descending)):limit(3)
can be converted to DQL as follows:
timeseries usage=avg(dt.host.cpu.usage), by:{dt.entity.host}| sort arrayAvg(usage) desc| limit 3
The Metrics Classic timeshift transformation is equivalent to the timeseries shift: parameter.
For example, this MetricsClassic metric selector query:
builtin:host.cpu.usage:splitBy():avg:timeshift(-7d)
can be converted to DQL as follows:
timeseries usage=avg(dt.host.cpu.usage), shift:-7d
The Metrics Classic setUnit and toUnit transformations are not supported directly in DQL.
Instead, you can use the "Units and formats" section of any chart.
