diff --git a/.betterer.results b/.betterer.results
index cbfe4b579d2..71ad4e909bc 100644
--- a/.betterer.results
+++ b/.betterer.results
@@ -6261,9 +6261,8 @@ exports[`better eslint`] = {
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
[0, 0, 0, "Unexpected any. Specify a different type.", "1"],
[0, 0, 0, "Unexpected any. Specify a different type.", "2"],
- [0, 0, 0, "Unexpected any. Specify a different type.", "3"],
- [0, 0, 0, "Do not use any type assertions.", "4"],
- [0, 0, 0, "Unexpected any. Specify a different type.", "5"]
+ [0, 0, 0, "Do not use any type assertions.", "3"],
+ [0, 0, 0, "Unexpected any. Specify a different type.", "4"]
],
"public/app/plugins/datasource/prometheus/query_hints.ts:5381": [
[0, 0, 0, "Unexpected any. Specify a different type.", "0"],
diff --git a/docs/sources/datasources/prometheus/template-variables/index.md b/docs/sources/datasources/prometheus/template-variables/index.md
index 8b69205c235..8004fd233a9 100644
--- a/docs/sources/datasources/prometheus/template-variables/index.md
+++ b/docs/sources/datasources/prometheus/template-variables/index.md
@@ -23,17 +23,17 @@ For an introduction to templating and template variables, refer to the [Templati
## Use query variables
-You can use variables of the type _Query_ to query Prometheus for a list of metrics, labels, or label values.
+Use variables of the type _Query_ to query Prometheus for a list of metrics, labels, or label values.
-You can use these Prometheus data source functions in the **Query** input field:
+Select a Prometheus data source query type and enter the required inputs:
-| Name | Description | Used API endpoints |
-| ----------------------------- | ----------------------------------------------------------------------- | --------------------------------- |
-| `label_names()` | Returns a list of label names. | /api/v1/labels |
-| `label_values(label)` | Returns a list of label values for the `label` in every metric. | /api/v1/label/`label`/values |
-| `label_values(metric, label)` | Returns a list of label values for the `label` in the specified metric. | /api/v1/series |
-| `metrics(metric)` | Returns a list of metrics matching the specified `metric` regex. | /api/v1/label/\_\_name\_\_/values |
-| `query_result(query)` | Returns a list of Prometheus query result for the `query`. | /api/v1/query |
+| Query Type | Input(\* required) | Description | Used API endpoints |
+| -------------- | ------------------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------- |
+| `Label names` | none | Returns a list of all label names. | /api/v1/labels |
+| `Label values` | `label`\*, `metric` | Returns a list of label values for the `label` in all metrics or the optional metric. | /api/v1/label/`label`/values or /api/v1/series |
+| `Metrics` | `metric` | Returns a list of metrics matching the specified `metric` regex. | /api/v1/label/\_\_name\_\_/values |
+| `Query result` | `query` | Returns a list of Prometheus query result for the `query`. | /api/v1/query |
+| `Series query` | `metric`, `label` or both | Returns a list of time series associated with the entered data. | /api/v1/series |
For details on _metric names_, _label names_, and _label values_, refer to the [Prometheus documentation](http://prometheus.io/docs/concepts/data_model/#metric-names-and-labels).
diff --git a/public/app/plugins/datasource/prometheus/components/VariableQueryEditor.test.tsx b/public/app/plugins/datasource/prometheus/components/VariableQueryEditor.test.tsx
new file mode 100644
index 00000000000..099e7709da5
--- /dev/null
+++ b/public/app/plugins/datasource/prometheus/components/VariableQueryEditor.test.tsx
@@ -0,0 +1,142 @@
+import { render, screen, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import React from 'react';
+import { selectOptionInTest } from 'test/helpers/selectOptionInTest';
+
+import { PrometheusDatasource } from '../datasource';
+
+import { PromVariableQueryEditor, Props } from './VariableQueryEditor';
+
+const refId = 'PrometheusVariableQueryEditor-VariableQuery';
+
+describe('PromVariableQueryEditor', () => {
+ let props: Props;
+
+ beforeEach(() => {
+ props = {
+ datasource: {
+ hasLabelsMatchAPISupport: () => 1,
+ languageProvider: {
+ start: () => Promise.resolve([]),
+ syntax: () => {},
+ getLabelKeys: () => [],
+ metrics: [],
+ },
+ getInitHints: () => [],
+ } as unknown as PrometheusDatasource,
+ query: {
+ refId: 'test',
+ query: 'label_names()',
+ },
+ onRunQuery: () => {},
+ onChange: () => {},
+ history: [],
+ };
+ });
+
+ test('Displays a group of function options', async () => {
+ render();
+
+ const select = screen.getByLabelText('Query type').parentElement!;
+ await userEvent.click(select);
+
+ await waitFor(() => expect(screen.getAllByText('Label names')).toHaveLength(2));
+ await waitFor(() => expect(screen.getByText('Label values')).toBeInTheDocument());
+ await waitFor(() => expect(screen.getByText('Metrics')).toBeInTheDocument());
+ await waitFor(() => expect(screen.getByText('Query result')).toBeInTheDocument());
+ await waitFor(() => expect(screen.getByText('Series query')).toBeInTheDocument());
+ });
+
+ test('Calls onChange for label_names() query', async () => {
+ const onChange = jest.fn();
+
+ props.query = {
+ refId: 'test',
+ query: '',
+ };
+
+ render();
+
+ await selectOptionInTest(screen.getByLabelText('Query type'), 'Label names');
+
+ expect(onChange).toHaveBeenCalledWith({
+ query: 'label_names()',
+ refId,
+ });
+ });
+
+ test('Does not call onChange for other queries', async () => {
+ const onChange = jest.fn();
+
+ render();
+
+ await selectOptionInTest(screen.getByLabelText('Query type'), 'Metrics');
+ await selectOptionInTest(screen.getByLabelText('Query type'), 'Query result');
+ await selectOptionInTest(screen.getByLabelText('Query type'), 'Series query');
+
+ expect(onChange).not.toHaveBeenCalled();
+ });
+
+ test('Calls onChange for metrics() with argument onBlur', async () => {
+ const onChange = jest.fn();
+
+ props.query = {
+ refId: 'test',
+ query: 'metrics(a)',
+ };
+
+ render();
+
+ const labelSelect = screen.getByLabelText('Metric selector');
+ await userEvent.click(labelSelect);
+ const functionSelect = screen.getByLabelText('Query type').parentElement!;
+ await userEvent.click(functionSelect);
+
+ expect(onChange).toHaveBeenCalledWith({
+ query: 'metrics(a)',
+ refId,
+ });
+ });
+
+ test('Calls onChange for query_result() with argument onBlur', async () => {
+ const onChange = jest.fn();
+
+ props.query = {
+ refId: 'test',
+ query: 'query_result(a)',
+ };
+
+ render();
+
+ const labelSelect = screen.getByLabelText('Prometheus Query');
+ await userEvent.click(labelSelect);
+ const functionSelect = screen.getByLabelText('Query type').parentElement!;
+ await userEvent.click(functionSelect);
+
+ expect(onChange).toHaveBeenCalledWith({
+ query: 'query_result(a)',
+ refId,
+ });
+ });
+
+ test('Calls onChange for Match[] series with argument onBlur', async () => {
+ const onChange = jest.fn();
+
+ props.query = {
+ refId: 'test',
+ query: '{a: "example"}',
+ };
+
+ render();
+
+ const labelSelect = screen.getByLabelText('Series Query');
+ await userEvent.click(labelSelect);
+ const functionSelect = screen.getByLabelText('Query type').parentElement!;
+ await userEvent.click(functionSelect);
+
+ expect(onChange).toHaveBeenCalledWith({
+ query: '{a: "example"}',
+ refId,
+ });
+ });
+});
diff --git a/public/app/plugins/datasource/prometheus/components/VariableQueryEditor.tsx b/public/app/plugins/datasource/prometheus/components/VariableQueryEditor.tsx
new file mode 100644
index 00000000000..ebe23e6e179
--- /dev/null
+++ b/public/app/plugins/datasource/prometheus/components/VariableQueryEditor.tsx
@@ -0,0 +1,257 @@
+import React, { FC, FormEvent, useEffect, useState } from 'react';
+
+import { QueryEditorProps, SelectableValue } from '@grafana/data';
+import { InlineField, InlineFieldRow, Input, Select, TextArea } from '@grafana/ui';
+
+import { PrometheusDatasource } from '../datasource';
+import {
+ migrateVariableEditorBackToVariableSupport,
+ migrateVariableQueryToEditor,
+} from '../migrations/variableMigration';
+import { PromOptions, PromQuery, PromVariableQuery, PromVariableQueryType as QueryType } from '../types';
+
+export const variableOptions = [
+ { label: 'Label names', value: QueryType.LabelNames },
+ { label: 'Label values', value: QueryType.LabelValues },
+ { label: 'Metrics', value: QueryType.MetricNames },
+ { label: 'Query result', value: QueryType.VarQueryResult },
+ { label: 'Series query', value: QueryType.SeriesQuery },
+];
+
+export type Props = QueryEditorProps;
+
+const refId = 'PrometheusVariableQueryEditor-VariableQuery';
+
+export const PromVariableQueryEditor: FC = ({ onChange, query, datasource }) => {
+ // to select the query type, i.e. label_names, label_values, etc.
+ const [qryType, setQryType] = useState(undefined);
+
+ // list of variables for each function
+ const [label, setLabel] = useState('');
+ // metric is used for both label_values() and metric()
+ // label_values() metric requires a whole/complete metric
+ // metric() is expected to be a part of a metric string
+ const [metric, setMetric] = useState('');
+ // varQuery is a whole query, can include math/rates/etc
+ const [varQuery, setVarQuery] = useState('');
+ // seriesQuery is only a whole
+ const [seriesQuery, setSeriesQuery] = useState('');
+
+ // list of label names for label_values(), /api/v1/labels, contains the same results as label_names() function
+ const [labelOptions, setLabelOptions] = useState>>([]);
+
+ useEffect(() => {
+ if (!query) {
+ return;
+ }
+ // Changing from standard to custom variable editor changes the string attr from expr to query
+ const variableQuery = query.query ? migrateVariableQueryToEditor(query.query) : query;
+
+ setQryType(variableQuery.qryType);
+ setLabel(variableQuery.label ?? '');
+ setMetric(variableQuery.metric ?? '');
+ setVarQuery(variableQuery.varQuery ?? '');
+ setSeriesQuery(variableQuery.seriesQuery ?? '');
+
+ // set the migrated label in the label options
+ if (variableQuery.label) {
+ setLabelOptions([{ label: variableQuery.label, value: variableQuery.label }]);
+ }
+ }, [query]);
+
+ // set the label names options for the label values var query
+ useEffect(() => {
+ if (qryType !== QueryType.LabelValues) {
+ return;
+ }
+
+ datasource.getLabelNames().then((labelNames: Array<{ text: string }>) => {
+ setLabelOptions(labelNames.map(({ text }) => ({ label: text, value: text })));
+ });
+ }, [datasource, qryType]);
+
+ const onChangeWithVariableString = (qryType: QueryType) => {
+ const queryVar = {
+ qryType: qryType,
+ label,
+ metric,
+ varQuery,
+ seriesQuery,
+ refId: 'PrometheusVariableQueryEditor-VariableQuery',
+ };
+
+ const queryString = migrateVariableEditorBackToVariableSupport(queryVar);
+
+ onChange({
+ query: queryString,
+ refId,
+ });
+ };
+
+ const onQueryTypeChange = (newType: SelectableValue) => {
+ setQryType(newType.value);
+ if (newType.value === QueryType.LabelNames) {
+ onChangeWithVariableString(newType.value);
+ }
+ };
+
+ const onLabelChange = (newLabel: SelectableValue) => {
+ setLabel(newLabel.value ?? '');
+ };
+
+ const onMetricChange = (e: FormEvent) => {
+ setMetric(e.currentTarget.value);
+ };
+
+ const onVarQueryChange = (e: FormEvent) => {
+ setVarQuery(e.currentTarget.value);
+ };
+
+ const onSeriesQueryChange = (e: FormEvent) => {
+ setSeriesQuery(e.currentTarget.value);
+ };
+
+ const handleBlur = () => {
+ if (qryType === QueryType.LabelNames) {
+ onChangeWithVariableString(qryType);
+ } else if (qryType === QueryType.LabelValues && label) {
+ onChangeWithVariableString(qryType);
+ } else if (qryType === QueryType.MetricNames && metric) {
+ onChangeWithVariableString(qryType);
+ } else if (qryType === QueryType.VarQueryResult && varQuery) {
+ onChangeWithVariableString(qryType);
+ } else if (qryType === QueryType.SeriesQuery && seriesQuery) {
+ onChangeWithVariableString(qryType);
+ }
+ };
+
+ return (
+
+ The Prometheus data source plugin provides the following query types for template variables.
+ }
+ >
+
+
+ {qryType === QueryType.LabelValues && (
+ <>
+
+ Returns a list of label values for the label name in all metrics unless the metric is specified.
+
+ }
+ >
+
+
+ Optional: returns a list of label values for the label name in the specified metric.}
+ >
+
+
+ >
+ )}
+ {qryType === QueryType.MetricNames && (
+ <>
+ Returns a list of metrics matching the specified metric regex.}
+ >
+
+
+ >
+ )}
+ {qryType === QueryType.VarQueryResult && (
+ <>
+
+ Returns a list of Prometheus query results for the query. This can include Prometheus functions, i.e.
+ sum(go_goroutines).
+
+ }
+ >
+
+
+ >
+ )}
+ {qryType === QueryType.SeriesQuery && (
+ <>
+
+ Enter enter a metric with labels, only a metric or only labels, i.e.
+ go_goroutines{instance="localhost:9090"}, go_goroutines, or
+ {instance="localhost:9090"}. Returns a list of time series associated with the
+ entered data.
+
+ }
+ >
+
+
+ >
+ )}
+
+ );
+};
diff --git a/public/app/plugins/datasource/prometheus/datasource.test.ts b/public/app/plugins/datasource/prometheus/datasource.test.ts
index 8e8cce4aed7..041372af5b3 100644
--- a/public/app/plugins/datasource/prometheus/datasource.test.ts
+++ b/public/app/plugins/datasource/prometheus/datasource.test.ts
@@ -153,7 +153,7 @@ describe('PrometheusDatasource', () => {
).rejects.toMatchObject({
message: expect.stringMatching('Browser access'),
});
- await expect(directDs.getTagKeys()).rejects.toMatchObject({
+ await expect(directDs.getLabelNames()).rejects.toMatchObject({
message: expect.stringMatching('Browser access'),
});
await expect(directDs.getTagValues()).rejects.toMatchObject({
diff --git a/public/app/plugins/datasource/prometheus/datasource.tsx b/public/app/plugins/datasource/prometheus/datasource.tsx
index ad84f5af598..356a6b28364 100644
--- a/public/app/plugins/datasource/prometheus/datasource.tsx
+++ b/public/app/plugins/datasource/prometheus/datasource.tsx
@@ -286,7 +286,7 @@ export class PrometheusDatasource
hideFromInspector: true,
...options,
})
- ); // toPromise until we change getTagValues, getTagKeys to Observable
+ ); // toPromise until we change getTagValues, getLabelNames to Observable
}
interpolateQueryExpr(value: string | string[] = [], variable: any) {
@@ -908,7 +908,10 @@ export class PrometheusDatasource
);
}
- async getTagKeys(options?: any) {
+ // this is used to get label keys, a.k.a label names
+ // it is used in metric_find_query.ts
+ // and in Tempo here grafana/public/app/plugins/datasource/tempo/QueryEditor/ServiceGraphSection.tsx
+ async getLabelNames(options?: any) {
if (options?.series) {
// Get tags for the provided series only
const seriesLabels: Array> = await Promise.all(
diff --git a/public/app/plugins/datasource/prometheus/metric_find_query.ts b/public/app/plugins/datasource/prometheus/metric_find_query.ts
index f3f678d1d2a..2c6bea204c8 100644
--- a/public/app/plugins/datasource/prometheus/metric_find_query.ts
+++ b/public/app/plugins/datasource/prometheus/metric_find_query.ts
@@ -24,7 +24,7 @@ export default class PrometheusMetricFindQuery {
const queryResultRegex = /^query_result\((.+)\)\s*$/;
const labelNamesQuery = this.query.match(labelNamesRegex);
if (labelNamesQuery) {
- return this.labelNamesQuery();
+ return this.datasource.getLabelNames();
}
const labelValuesQuery = this.query.match(labelValuesRegex);
@@ -47,24 +47,12 @@ export default class PrometheusMetricFindQuery {
}
// if query contains full metric name, return metric name and label list
- return this.metricNameAndLabelsQuery(this.query);
- }
+ const expressions = ['label_values()', 'metrics()', 'query_result()'];
+ if (!expressions.includes(this.query)) {
+ return this.metricNameAndLabelsQuery(this.query);
+ }
- labelNamesQuery() {
- const start = this.datasource.getPrometheusTime(this.range.from, false);
- const end = this.datasource.getPrometheusTime(this.range.to, true);
- const params = {
- start: start.toString(),
- end: end.toString(),
- };
-
- const url = `/api/v1/labels`;
-
- return this.datasource.metadataRequest(url, params).then((result: any) => {
- return _map(result.data.data, (value) => {
- return { text: value };
- });
- });
+ return Promise.resolve([]);
}
labelValuesQuery(label: string, metric?: string) {
diff --git a/public/app/plugins/datasource/prometheus/migrations/variableMigration.ts b/public/app/plugins/datasource/prometheus/migrations/variableMigration.ts
new file mode 100644
index 00000000000..17c44fb850a
--- /dev/null
+++ b/public/app/plugins/datasource/prometheus/migrations/variableMigration.ts
@@ -0,0 +1,104 @@
+import { PromVariableQuery, PromVariableQueryType as QueryType } from '../types';
+
+const labelNamesRegex = /^label_names\(\)\s*$/;
+const labelValuesRegex = /^label_values\((?:(.+),\s*)?([a-zA-Z_][a-zA-Z0-9_]*)\)\s*$/;
+const metricNamesRegex = /^metrics\((.+)\)\s*$/;
+const queryResultRegex = /^query_result\((.+)\)\s*$/;
+
+export function migrateVariableQueryToEditor(rawQuery: string | PromVariableQuery): PromVariableQuery {
+ // If not string, we assume PromVariableQuery
+ if (typeof rawQuery !== 'string') {
+ return rawQuery;
+ }
+
+ const queryBase = {
+ refId: 'PrometheusDatasource-VariableQuery',
+ qryType: QueryType.LabelNames,
+ };
+
+ const labelNames = rawQuery.match(labelNamesRegex);
+ if (labelNames) {
+ return {
+ ...queryBase,
+ qryType: QueryType.LabelNames,
+ };
+ }
+
+ const labelValues = rawQuery.match(labelValuesRegex);
+
+ if (labelValues) {
+ const label = labelValues[2];
+ const metric = labelValues[1];
+ if (metric) {
+ return {
+ ...queryBase,
+ qryType: QueryType.LabelValues,
+ label,
+ metric,
+ };
+ } else {
+ return {
+ ...queryBase,
+ qryType: QueryType.LabelValues,
+ label,
+ };
+ }
+ }
+
+ const metricNames = rawQuery.match(metricNamesRegex);
+ if (metricNames) {
+ return {
+ ...queryBase,
+ qryType: QueryType.MetricNames,
+ metric: metricNames[1],
+ };
+ }
+
+ const queryResult = rawQuery.match(queryResultRegex);
+ if (queryResult) {
+ return {
+ ...queryBase,
+ qryType: QueryType.VarQueryResult,
+ varQuery: queryResult[1],
+ };
+ }
+
+ // seriesQuery does not have a function and no regex above
+ if (!labelNames && !labelValues && !metricNames && !queryResult) {
+ return {
+ ...queryBase,
+ qryType: QueryType.SeriesQuery,
+ seriesQuery: rawQuery,
+ };
+ }
+
+ return queryBase;
+}
+
+// migrate it back to a string with the correct varialbes in place
+export function migrateVariableEditorBackToVariableSupport(QueryVariable: PromVariableQuery): string {
+ switch (QueryVariable.qryType) {
+ case QueryType.LabelNames:
+ return 'label_names()';
+ case QueryType.LabelValues:
+ if (QueryVariable.metric) {
+ return `label_values(${QueryVariable.metric},${QueryVariable.label})`;
+ } else {
+ return `label_values(${QueryVariable.label})`;
+ }
+ case QueryType.MetricNames:
+ return `metrics(${QueryVariable.metric})`;
+ case QueryType.VarQueryResult:
+ const varQuery = removeLineBreaks(QueryVariable.varQuery);
+ return `query_result(${varQuery})`;
+ case QueryType.SeriesQuery:
+ return '' + QueryVariable.seriesQuery;
+ }
+
+ return '';
+}
+
+// allow line breaks in query result textarea
+function removeLineBreaks(input?: string) {
+ return input ? input.replace(/[\r\n]+/gm, '') : '';
+}
diff --git a/public/app/plugins/datasource/prometheus/types.ts b/public/app/plugins/datasource/prometheus/types.ts
index 62aec75433b..0119e30020a 100644
--- a/public/app/plugins/datasource/prometheus/types.ts
+++ b/public/app/plugins/datasource/prometheus/types.ts
@@ -22,6 +22,7 @@ export interface PromQuery extends DataQuery {
showingTable?: boolean;
/** Code, Builder or Explain */
editorMode?: QueryEditorMode;
+ query?: string;
}
export interface PromOptions extends DataSourceJsonData {
@@ -165,3 +166,21 @@ export enum LegendFormatMode {
Verbose = '__verbose',
Custom = '__custom',
}
+
+export enum PromVariableQueryType {
+ LabelNames,
+ LabelValues,
+ MetricNames,
+ VarQueryResult,
+ SeriesQuery,
+}
+
+export interface PromVariableQuery extends DataQuery {
+ query?: string;
+ expr?: string;
+ qryType?: PromVariableQueryType;
+ label?: string;
+ metric?: string;
+ varQuery?: string;
+ seriesQuery?: string;
+}
diff --git a/public/app/plugins/datasource/prometheus/variables.ts b/public/app/plugins/datasource/prometheus/variables.ts
index ac92f27dd0e..5098147016d 100644
--- a/public/app/plugins/datasource/prometheus/variables.ts
+++ b/public/app/plugins/datasource/prometheus/variables.ts
@@ -1,22 +1,17 @@
import { from, Observable, of } from 'rxjs';
import { map } from 'rxjs/operators';
-import {
- DataQueryRequest,
- DataQueryResponse,
- rangeUtil,
- StandardVariableQuery,
- StandardVariableSupport,
-} from '@grafana/data';
+import { CustomVariableSupport, DataQueryRequest, DataQueryResponse, rangeUtil } from '@grafana/data';
import { getTemplateSrv, TemplateSrv } from '@grafana/runtime';
import { getTimeSrv, TimeSrv } from '../../../features/dashboard/services/TimeSrv';
+import { PromVariableQueryEditor } from './components/VariableQueryEditor';
import { PrometheusDatasource } from './datasource';
import PrometheusMetricFindQuery from './metric_find_query';
import { PromQuery } from './types';
-export class PrometheusVariableSupport extends StandardVariableSupport {
+export class PrometheusVariableSupport extends CustomVariableSupport {
constructor(
private readonly datasource: PrometheusDatasource,
private readonly templateSrv: TemplateSrv = getTemplateSrv(),
@@ -26,8 +21,10 @@ export class PrometheusVariableSupport extends StandardVariableSupport): Observable {
- const query = request.targets[0].expr;
+ const query = request.targets[0].query;
if (!query) {
return of({ data: [] });
}
@@ -48,11 +45,4 @@ export class PrometheusVariableSupport extends StandardVariableSupport ({ data: results })));
}
-
- toDataQuery(query: StandardVariableQuery): PromQuery {
- return {
- refId: 'PrometheusDatasource-VariableQuery',
- expr: query.query,
- };
- }
}
diff --git a/public/app/plugins/datasource/tempo/QueryEditor/ServiceGraphSection.tsx b/public/app/plugins/datasource/tempo/QueryEditor/ServiceGraphSection.tsx
index edaa5ea1d8c..1c5785dec69 100644
--- a/public/app/plugins/datasource/tempo/QueryEditor/ServiceGraphSection.tsx
+++ b/public/app/plugins/datasource/tempo/QueryEditor/ServiceGraphSection.tsx
@@ -29,7 +29,7 @@ export function ServiceGraphSection({
const [hasKeys, setHasKeys] = useState(undefined);
useEffect(() => {
async function fn(ds: PrometheusDatasource) {
- const keys = await ds.getTagKeys({
+ const keys = await ds.getLabelNames({
series: [
'traces_service_graph_request_server_seconds_sum',
'traces_service_graph_request_total',