From f01263803a6071749b20850f2c415578c58cb823 Mon Sep 17 00:00:00 2001 From: Brendan O'Handley Date: Thu, 15 Aug 2024 15:39:42 -0500 Subject: [PATCH] Prometheus: Add a limit for the series resource api in metrics browser (#91555) * add a limit for the series resource api in metrics browser * decouple serieslimit from options and only use in metrics browser * add series limit input to metrics browser * add warning * add and fix tests * add new param to jsdoc * do not use the limit in other calls outside metrics browser * update test * trim limit * fix tests, remove limit from non labels calls --- .../src/selectors/components.ts | 1 + .../components/PrometheusMetricsBrowser.tsx | 25 ++++++++++- .../src/language_provider.test.ts | 43 +++++++++++++++++++ .../src/language_provider.ts | 26 +++++++++-- 4 files changed, 89 insertions(+), 6 deletions(-) diff --git a/packages/grafana-e2e-selectors/src/selectors/components.ts b/packages/grafana-e2e-selectors/src/selectors/components.ts index d5936714cb9..d0c9cd1f8f3 100644 --- a/packages/grafana-e2e-selectors/src/selectors/components.ts +++ b/packages/grafana-e2e-selectors/src/selectors/components.ts @@ -106,6 +106,7 @@ export const Components = { metricsBrowser: { openButton: 'data-testid open metrics browser', selectMetric: 'data-testid select a metric', + seriesLimit: 'data-testid series limit', metricList: 'data-testid metric list', labelNamesFilter: 'data-testid label names filter', labelValuesFilter: 'data-testid label values filter', diff --git a/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.tsx b/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.tsx index 924e1067544..963590f3667 100644 --- a/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.tsx +++ b/packages/grafana-prometheus/src/components/PrometheusMetricsBrowser.tsx @@ -45,8 +45,12 @@ interface BrowserState { error: string; validationStatus: string; valueSearchTerm: string; + seriesLimit?: string; } +export const DEFAULT_SERIES_LIMIT = '40000'; +export const REMOVE_SERIES_LIMIT = 'none'; + interface FacettableValue { name: string; selected?: boolean; @@ -214,6 +218,10 @@ export class UnthemedPrometheusMetricsBrowser extends React.Component) => { + this.setState({ seriesLimit: event.target.value.trim() }); + }; + onChangeValueSearch = (event: ChangeEvent) => { this.setState({ valueSearchTerm: event.target.value }); }; @@ -419,7 +427,7 @@ export class UnthemedPrometheusMetricsBrowser extends React.Component
- +
+ +
+ +
{ end: toPrometheusTimeString, 'match[]': 'interpolated-metric', start: fromPrometheusTimeString, + limit: DEFAULT_SERIES_LIMIT, + }, + undefined + ); + }); + + it("should not use default limit parameter when 'none' is passed to fetchSeriesLabels", () => { + const languageProvider = new LanguageProvider({ + ...defaultDatasource, + } as PrometheusDatasource); + const fetchSeriesLabels = languageProvider.fetchSeriesLabels; + const requestSpy = jest.spyOn(languageProvider, 'request'); + fetchSeriesLabels('metric-with-limit', undefined, 'none'); + expect(requestSpy).toHaveBeenCalled(); + expect(requestSpy).toHaveBeenCalledWith( + '/api/v1/series', + [], + { + end: toPrometheusTimeString, + 'match[]': 'metric-with-limit', + start: fromPrometheusTimeString, + }, + undefined + ); + }); + + it("should not have a limit paranter if 'none' is passed to function", () => { + const languageProvider = new LanguageProvider({ + ...defaultDatasource, + // interpolateString: (string: string) => string.replace(/\$/, 'interpolated-'), + } as PrometheusDatasource); + const fetchSeriesLabels = languageProvider.fetchSeriesLabels; + const requestSpy = jest.spyOn(languageProvider, 'request'); + fetchSeriesLabels('metric-without-limit', false, 'none'); + expect(requestSpy).toHaveBeenCalled(); + expect(requestSpy).toHaveBeenCalledWith( + '/api/v1/series', + [], + { + end: toPrometheusTimeString, + 'match[]': 'metric-without-limit', + start: fromPrometheusTimeString, }, undefined ); diff --git a/packages/grafana-prometheus/src/language_provider.ts b/packages/grafana-prometheus/src/language_provider.ts index b7ec09163e9..102ec109a90 100644 --- a/packages/grafana-prometheus/src/language_provider.ts +++ b/packages/grafana-prometheus/src/language_provider.ts @@ -12,6 +12,7 @@ import { } from '@grafana/data'; import { BackendSrvRequest } from '@grafana/runtime'; +import { DEFAULT_SERIES_LIMIT, REMOVE_SERIES_LIMIT } from './components/PrometheusMetricsBrowser'; import { Label } from './components/monaco-query-field/monaco-completion-provider/situation'; import { PrometheusDatasource } from './datasource'; import { @@ -30,6 +31,13 @@ const EMPTY_SELECTOR = '{}'; // Max number of items (metrics, labels, values) that we display as suggestions. Prevents from running out of memory. export const SUGGESTIONS_LIMIT = 10000; +type UrlParamsType = { + start?: string; + end?: string; + 'match[]'?: string; + limit?: string; +}; + const buildCacheHeaders = (durationInSeconds: number) => { return { headers: { @@ -181,7 +189,7 @@ export default class PromQlLanguageProvider extends LanguageProvider { if (selector === EMPTY_SELECTOR) { return await this.fetchDefaultSeries(); } else { - return await this.fetchSeriesLabels(selector, withName); + return await this.fetchSeriesLabels(selector, withName, REMOVE_SERIES_LIMIT); } } catch (error) { // TODO: better error handling @@ -325,7 +333,7 @@ export default class PromQlLanguageProvider extends LanguageProvider { if (this.datasource.hasLabelsMatchAPISupport()) { return this.fetchSeriesLabelsMatch(name, withName); } else { - return this.fetchSeriesLabels(name, withName); + return this.fetchSeriesLabels(name, withName, REMOVE_SERIES_LIMIT); } }; @@ -334,14 +342,24 @@ export default class PromQlLanguageProvider extends LanguageProvider { * they can change over requested time. * @param name * @param withName + * @param withLimit */ - fetchSeriesLabels = async (name: string, withName?: boolean): Promise> => { + fetchSeriesLabels = async ( + name: string, + withName?: boolean, + withLimit?: string + ): Promise> => { const interpolatedName = this.datasource.interpolateString(name); const range = this.datasource.getAdjustedInterval(this.timeRange); - const urlParams = { + let urlParams: UrlParamsType = { ...range, 'match[]': interpolatedName, }; + + if (withLimit !== 'none') { + urlParams = { ...urlParams, limit: withLimit ?? DEFAULT_SERIES_LIMIT }; + } + const url = `/api/v1/series`; const data = await this.request(url, [], urlParams, this.getDefaultCacheHeaders());