diff --git a/packages/grafana-prometheus/src/datasource.ts b/packages/grafana-prometheus/src/datasource.ts index 7a1d6731f33..b809452bede 100644 --- a/packages/grafana-prometheus/src/datasource.ts +++ b/packages/grafana-prometheus/src/datasource.ts @@ -679,7 +679,7 @@ export class PrometheusDatasource // and in Tempo here grafana/public/app/plugins/datasource/tempo/QueryEditor/ServiceGraphSection.tsx async getTagKeys(options: DataSourceGetTagKeysOptions): Promise { if (!options || options.filters.length === 0) { - await this.languageProvider.fetchLabels(options.timeRange); + await this.languageProvider.fetchLabels(options.timeRange, options.queries); return this.languageProvider.getLabelKeys().map((k) => ({ value: k, text: k })); } diff --git a/packages/grafana-prometheus/src/language_provider.test.ts b/packages/grafana-prometheus/src/language_provider.test.ts index 574fec92ef0..91576162dd1 100644 --- a/packages/grafana-prometheus/src/language_provider.test.ts +++ b/packages/grafana-prometheus/src/language_provider.test.ts @@ -4,7 +4,7 @@ import { Label } from './components/monaco-query-field/monaco-completion-provide import { PrometheusDatasource } from './datasource'; import LanguageProvider from './language_provider'; import { getClientCacheDurationInMinutes, getPrometheusTime, getRangeSnapInterval } from './language_utils'; -import { PrometheusCacheLevel } from './types'; +import { PrometheusCacheLevel, PromQuery } from './types'; const now = new Date(1681300293392).getTime(); const timeRangeDurationSeconds = 1; @@ -306,6 +306,207 @@ describe('Language completion provider', () => { }); }); + describe('fetchLabels', () => { + const tr = getMockTimeRange(); + const getParams = (requestSpy: ReturnType) => { + // Following is equal to `URLSearchParams().toString()` + return requestSpy.mock.calls[0][2]?.toString() ?? 'undefined'; + }; + + describe('with POST', () => { + let languageProvider: LanguageProvider; + beforeEach(() => { + languageProvider = new LanguageProvider({ + ...defaultDatasource, + httpMethod: 'POST', + } as PrometheusDatasource); + }); + + it('should send query metrics to the POST request', async () => { + const mockQueries: PromQuery[] = [ + { + refId: 'C', + expr: 'go_gc_pauses_seconds_bucket', + }, + ]; + const fetchLabel = languageProvider.fetchLabels; + const requestSpy = jest.spyOn(languageProvider, 'request'); + await fetchLabel(tr, mockQueries); + expect(requestSpy).toHaveBeenCalled(); + const params = getParams(requestSpy); + expect(params).toMatch(encodeURI('match[]=go_gc_pauses_seconds_bucket')); + }); + + it('should send metrics from complex query to the POST request', async () => { + const mockQueries: PromQuery[] = [ + { + refId: 'C', + expr: 'histogram_quantile(0.95, sum(rate(go_gc_pauses_seconds_bucket[$__rate_interval])) by (le))', + }, + ]; + const fetchLabel = languageProvider.fetchLabels; + const requestSpy = jest.spyOn(languageProvider, 'request'); + await fetchLabel(tr, mockQueries); + expect(requestSpy).toHaveBeenCalled(); + const params = getParams(requestSpy); + expect(params).toMatch(encodeURI('match[]=go_gc_pauses_seconds_bucket')); + }); + + it('should send metrics from multiple queries to the POST request', async () => { + const mockQueries: PromQuery[] = [ + { + refId: 'B', + expr: 'process_cpu_seconds_total', + }, + { + refId: 'C', + expr: 'go_gc_pauses_seconds_bucket', + }, + ]; + const fetchLabel = languageProvider.fetchLabels; + const requestSpy = jest.spyOn(languageProvider, 'request'); + await fetchLabel(tr, mockQueries); + expect(requestSpy).toHaveBeenCalled(); + const params = getParams(requestSpy); + expect(params).toMatch(encodeURI('match[]=process_cpu_seconds_total&match[]=go_gc_pauses_seconds_bucket')); + }); + + it('should send metrics from a query contains multiple metrics to the POST request', async () => { + const mockQueries: PromQuery[] = [ + { + refId: 'B', + expr: 'process_cpu_seconds_total + go_gc_pauses_seconds_bucket', + }, + ]; + const fetchLabel = languageProvider.fetchLabels; + const requestSpy = jest.spyOn(languageProvider, 'request'); + await fetchLabel(tr, mockQueries); + expect(requestSpy).toHaveBeenCalled(); + const params = getParams(requestSpy); + expect(params).toMatch(encodeURI('match[]=process_cpu_seconds_total&match[]=go_gc_pauses_seconds_bucket')); + }); + + it('should send metrics from a query contains multiple metrics and queries to the POST request', async () => { + const mockQueries: PromQuery[] = [ + { + refId: 'A', + expr: 'histogram_quantile(0.95, sum(rate(process_max_fds[$__rate_interval])) by (le)) + go_gc_heap_frees_by_size_bytes_bucket', + }, + { + refId: 'B', + expr: 'process_cpu_seconds_total + go_gc_pauses_seconds_bucket', + }, + ]; + const fetchLabel = languageProvider.fetchLabels; + const requestSpy = jest.spyOn(languageProvider, 'request'); + await fetchLabel(tr, mockQueries); + expect(requestSpy).toHaveBeenCalled(); + const params = getParams(requestSpy); + expect(params).toMatch( + encodeURI( + 'match[]=process_max_fds&match[]=go_gc_heap_frees_by_size_bytes_bucket&match[]=process_cpu_seconds_total&match[]=go_gc_pauses_seconds_bucket' + ) + ); + }); + }); + + describe('with GET', () => { + let languageProvider: LanguageProvider; + beforeEach(() => { + languageProvider = new LanguageProvider({ + ...defaultDatasource, + httpMethod: 'GET', + } as PrometheusDatasource); + }); + + it('should send query metrics to the GET request', async () => { + const mockQueries: PromQuery[] = [ + { + refId: 'C', + expr: 'go_gc_pauses_seconds_bucket', + }, + ]; + const fetchLabel = languageProvider.fetchLabels; + const requestSpy = jest.spyOn(languageProvider, 'request'); + await fetchLabel(tr, mockQueries); + expect(requestSpy).toHaveBeenCalled(); + expect(requestSpy.mock.calls[0][0]).toMatch(encodeURI('match[]=go_gc_pauses_seconds_bucket')); + }); + + it('should send metrics from complex query to the GET request', async () => { + const mockQueries: PromQuery[] = [ + { + refId: 'C', + expr: 'histogram_quantile(0.95, sum(rate(go_gc_pauses_seconds_bucket[$__rate_interval])) by (le))', + }, + ]; + const fetchLabel = languageProvider.fetchLabels; + const requestSpy = jest.spyOn(languageProvider, 'request'); + await fetchLabel(tr, mockQueries); + expect(requestSpy).toHaveBeenCalled(); + expect(requestSpy.mock.calls[0][0]).toMatch(encodeURI('match[]=go_gc_pauses_seconds_bucket')); + }); + + it('should send metrics from multiple queries to the GET request', async () => { + const mockQueries: PromQuery[] = [ + { + refId: 'B', + expr: 'process_cpu_seconds_total', + }, + { + refId: 'C', + expr: 'go_gc_pauses_seconds_bucket', + }, + ]; + const fetchLabel = languageProvider.fetchLabels; + const requestSpy = jest.spyOn(languageProvider, 'request'); + await fetchLabel(tr, mockQueries); + expect(requestSpy).toHaveBeenCalled(); + expect(requestSpy.mock.calls[0][0]).toMatch( + encodeURI('match[]=process_cpu_seconds_total&match[]=go_gc_pauses_seconds_bucket') + ); + }); + + it('should send metrics from a query contains multiple metrics to the GET request', async () => { + const mockQueries: PromQuery[] = [ + { + refId: 'B', + expr: 'process_cpu_seconds_total + go_gc_pauses_seconds_bucket', + }, + ]; + const fetchLabel = languageProvider.fetchLabels; + const requestSpy = jest.spyOn(languageProvider, 'request'); + await fetchLabel(tr, mockQueries); + expect(requestSpy).toHaveBeenCalled(); + expect(requestSpy.mock.calls[0][0]).toMatch( + encodeURI('match[]=process_cpu_seconds_total&match[]=go_gc_pauses_seconds_bucket') + ); + }); + + it('should send metrics from a query contains multiple metrics and queries to the GET request', async () => { + const mockQueries: PromQuery[] = [ + { + refId: 'A', + expr: 'histogram_quantile(0.95, sum(rate(process_max_fds[$__rate_interval])) by (le)) + go_gc_heap_frees_by_size_bytes_bucket', + }, + { + refId: 'B', + expr: 'process_cpu_seconds_total + go_gc_pauses_seconds_bucket', + }, + ]; + const fetchLabel = languageProvider.fetchLabels; + const requestSpy = jest.spyOn(languageProvider, 'request'); + await fetchLabel(tr, mockQueries); + expect(requestSpy).toHaveBeenCalled(); + expect(requestSpy.mock.calls[0][0]).toMatch( + encodeURI( + 'match[]=process_max_fds&match[]=go_gc_heap_frees_by_size_bytes_bucket&match[]=process_cpu_seconds_total&match[]=go_gc_pauses_seconds_bucket' + ) + ); + }); + }); + }); + describe('fetchLabelValues', () => { it('should interpolate variable in series', () => { const languageProvider = new LanguageProvider({ diff --git a/packages/grafana-prometheus/src/language_provider.ts b/packages/grafana-prometheus/src/language_provider.ts index 9ad75e37023..7794c2d9339 100644 --- a/packages/grafana-prometheus/src/language_provider.ts +++ b/packages/grafana-prometheus/src/language_provider.ts @@ -21,6 +21,7 @@ import { toPromLikeQuery, } from './language_utils'; import PromqlSyntax from './promql'; +import { buildVisualQueryFromString } from './querybuilder/parsing'; import { PrometheusCacheLevel, PromMetricsMetadata, PromQuery } from './types'; const DEFAULT_KEYS = ['job', 'instance']; @@ -204,21 +205,36 @@ export default class PromQlLanguageProvider extends LanguageProvider { /** * Fetches all label keys */ - async fetchLabels(timeRange?: TimeRange): Promise { + fetchLabels = async (timeRange?: TimeRange, queries?: PromQuery[]): Promise => { if (timeRange) { this.timeRange = timeRange; } - const url = '/api/v1/labels'; - const params = this.datasource.getAdjustedInterval(this.timeRange); + let url = '/api/v1/labels'; + const timeParams = this.datasource.getAdjustedInterval(this.timeRange); this.labelFetchTs = Date.now().valueOf(); - const res = await this.request(url, [], params, this.getDefaultCacheHeaders()); + const searchParams = new URLSearchParams({ ...timeParams }); + queries?.forEach((q) => { + const visualQuery = buildVisualQueryFromString(q.expr); + searchParams.append('match[]', visualQuery.query.metric); + if (visualQuery.query.binaryQueries) { + visualQuery.query.binaryQueries.forEach((bq) => { + searchParams.append('match[]', bq.query.metric); + }); + } + }); + + if (this.datasource.httpMethod === 'GET') { + url += `?${searchParams.toString()}`; + } + + const res = await this.request(url, [], searchParams, this.getDefaultCacheHeaders()); if (Array.isArray(res)) { this.labelKeys = res.slice().sort(); } return []; - } + }; /** * Gets series values