mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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
This commit is contained in:
parent
de0e6d0fce
commit
f01263803a
@ -106,6 +106,7 @@ export const Components = {
|
|||||||
metricsBrowser: {
|
metricsBrowser: {
|
||||||
openButton: 'data-testid open metrics browser',
|
openButton: 'data-testid open metrics browser',
|
||||||
selectMetric: 'data-testid select a metric',
|
selectMetric: 'data-testid select a metric',
|
||||||
|
seriesLimit: 'data-testid series limit',
|
||||||
metricList: 'data-testid metric list',
|
metricList: 'data-testid metric list',
|
||||||
labelNamesFilter: 'data-testid label names filter',
|
labelNamesFilter: 'data-testid label names filter',
|
||||||
labelValuesFilter: 'data-testid label values filter',
|
labelValuesFilter: 'data-testid label values filter',
|
||||||
|
@ -45,8 +45,12 @@ interface BrowserState {
|
|||||||
error: string;
|
error: string;
|
||||||
validationStatus: string;
|
validationStatus: string;
|
||||||
valueSearchTerm: string;
|
valueSearchTerm: string;
|
||||||
|
seriesLimit?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const DEFAULT_SERIES_LIMIT = '40000';
|
||||||
|
export const REMOVE_SERIES_LIMIT = 'none';
|
||||||
|
|
||||||
interface FacettableValue {
|
interface FacettableValue {
|
||||||
name: string;
|
name: string;
|
||||||
selected?: boolean;
|
selected?: boolean;
|
||||||
@ -214,6 +218,10 @@ export class UnthemedPrometheusMetricsBrowser extends React.Component<BrowserPro
|
|||||||
this.setState({ metricSearchTerm: event.target.value });
|
this.setState({ metricSearchTerm: event.target.value });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onChangeSeriesLimit = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
this.setState({ seriesLimit: event.target.value.trim() });
|
||||||
|
};
|
||||||
|
|
||||||
onChangeValueSearch = (event: ChangeEvent<HTMLInputElement>) => {
|
onChangeValueSearch = (event: ChangeEvent<HTMLInputElement>) => {
|
||||||
this.setState({ valueSearchTerm: event.target.value });
|
this.setState({ valueSearchTerm: event.target.value });
|
||||||
};
|
};
|
||||||
@ -419,7 +427,7 @@ export class UnthemedPrometheusMetricsBrowser extends React.Component<BrowserPro
|
|||||||
this.updateLabelState(lastFacetted, { loading: true }, `Facetting labels for ${selector}`);
|
this.updateLabelState(lastFacetted, { loading: true }, `Facetting labels for ${selector}`);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const possibleLabels = await languageProvider.fetchSeriesLabels(selector, true);
|
const possibleLabels = await languageProvider.fetchSeriesLabels(selector, true, this.state.seriesLimit);
|
||||||
// If selector changed, clear loading state and discard result by returning early
|
// If selector changed, clear loading state and discard result by returning early
|
||||||
if (selector !== buildSelector(this.state.labels)) {
|
if (selector !== buildSelector(this.state.labels)) {
|
||||||
if (lastFacetted) {
|
if (lastFacetted) {
|
||||||
@ -492,7 +500,9 @@ export class UnthemedPrometheusMetricsBrowser extends React.Component<BrowserPro
|
|||||||
<Stack gap={3}>
|
<Stack gap={3}>
|
||||||
<div>
|
<div>
|
||||||
<div className={styles.section}>
|
<div className={styles.section}>
|
||||||
<Label description="Once a metric is selected only possible labels are shown.">1. Select a metric</Label>
|
<Label description="Once a metric is selected only possible labels are shown. Labels are limited by the series limit below.">
|
||||||
|
1. Select a metric
|
||||||
|
</Label>
|
||||||
<div>
|
<div>
|
||||||
<Input
|
<Input
|
||||||
onChange={this.onChangeMetricSearch}
|
onChange={this.onChangeMetricSearch}
|
||||||
@ -501,6 +511,17 @@ export class UnthemedPrometheusMetricsBrowser extends React.Component<BrowserPro
|
|||||||
data-testid={selectors.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.selectMetric}
|
data-testid={selectors.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.selectMetric}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<Label description="Set to 'none' to remove limit and show all labels for a selected metric. Removing the limit may cause performance issues.">
|
||||||
|
Series limit
|
||||||
|
</Label>
|
||||||
|
<div>
|
||||||
|
<Input
|
||||||
|
onChange={this.onChangeSeriesLimit}
|
||||||
|
aria-label="Limit results from series endpoint"
|
||||||
|
value={this.state.seriesLimit ?? DEFAULT_SERIES_LIMIT}
|
||||||
|
data-testid={selectors.components.DataSource.Prometheus.queryEditor.code.metricsBrowser.seriesLimit}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
role="list"
|
role="list"
|
||||||
className={styles.valueListWrapper}
|
className={styles.valueListWrapper}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/language_provider.test.ts
|
// Core Grafana history https://github.com/grafana/grafana/blob/v11.0.0-preview/public/app/plugins/datasource/prometheus/language_provider.test.ts
|
||||||
import { AbstractLabelOperator, dateTime, TimeRange } from '@grafana/data';
|
import { AbstractLabelOperator, dateTime, TimeRange } from '@grafana/data';
|
||||||
|
|
||||||
|
import { DEFAULT_SERIES_LIMIT } from './components/PrometheusMetricsBrowser';
|
||||||
import { Label } from './components/monaco-query-field/monaco-completion-provider/situation';
|
import { Label } from './components/monaco-query-field/monaco-completion-provider/situation';
|
||||||
import { PrometheusDatasource } from './datasource';
|
import { PrometheusDatasource } from './datasource';
|
||||||
import LanguageProvider from './language_provider';
|
import LanguageProvider from './language_provider';
|
||||||
@ -301,6 +302,48 @@ describe('Language completion provider', () => {
|
|||||||
end: toPrometheusTimeString,
|
end: toPrometheusTimeString,
|
||||||
'match[]': 'interpolated-metric',
|
'match[]': 'interpolated-metric',
|
||||||
start: fromPrometheusTimeString,
|
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
|
undefined
|
||||||
);
|
);
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
import { BackendSrvRequest } from '@grafana/runtime';
|
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 { Label } from './components/monaco-query-field/monaco-completion-provider/situation';
|
||||||
import { PrometheusDatasource } from './datasource';
|
import { PrometheusDatasource } from './datasource';
|
||||||
import {
|
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.
|
// Max number of items (metrics, labels, values) that we display as suggestions. Prevents from running out of memory.
|
||||||
export const SUGGESTIONS_LIMIT = 10000;
|
export const SUGGESTIONS_LIMIT = 10000;
|
||||||
|
|
||||||
|
type UrlParamsType = {
|
||||||
|
start?: string;
|
||||||
|
end?: string;
|
||||||
|
'match[]'?: string;
|
||||||
|
limit?: string;
|
||||||
|
};
|
||||||
|
|
||||||
const buildCacheHeaders = (durationInSeconds: number) => {
|
const buildCacheHeaders = (durationInSeconds: number) => {
|
||||||
return {
|
return {
|
||||||
headers: {
|
headers: {
|
||||||
@ -181,7 +189,7 @@ export default class PromQlLanguageProvider extends LanguageProvider {
|
|||||||
if (selector === EMPTY_SELECTOR) {
|
if (selector === EMPTY_SELECTOR) {
|
||||||
return await this.fetchDefaultSeries();
|
return await this.fetchDefaultSeries();
|
||||||
} else {
|
} else {
|
||||||
return await this.fetchSeriesLabels(selector, withName);
|
return await this.fetchSeriesLabels(selector, withName, REMOVE_SERIES_LIMIT);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// TODO: better error handling
|
// TODO: better error handling
|
||||||
@ -325,7 +333,7 @@ export default class PromQlLanguageProvider extends LanguageProvider {
|
|||||||
if (this.datasource.hasLabelsMatchAPISupport()) {
|
if (this.datasource.hasLabelsMatchAPISupport()) {
|
||||||
return this.fetchSeriesLabelsMatch(name, withName);
|
return this.fetchSeriesLabelsMatch(name, withName);
|
||||||
} else {
|
} 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.
|
* they can change over requested time.
|
||||||
* @param name
|
* @param name
|
||||||
* @param withName
|
* @param withName
|
||||||
|
* @param withLimit
|
||||||
*/
|
*/
|
||||||
fetchSeriesLabels = async (name: string, withName?: boolean): Promise<Record<string, string[]>> => {
|
fetchSeriesLabels = async (
|
||||||
|
name: string,
|
||||||
|
withName?: boolean,
|
||||||
|
withLimit?: string
|
||||||
|
): Promise<Record<string, string[]>> => {
|
||||||
const interpolatedName = this.datasource.interpolateString(name);
|
const interpolatedName = this.datasource.interpolateString(name);
|
||||||
const range = this.datasource.getAdjustedInterval(this.timeRange);
|
const range = this.datasource.getAdjustedInterval(this.timeRange);
|
||||||
const urlParams = {
|
let urlParams: UrlParamsType = {
|
||||||
...range,
|
...range,
|
||||||
'match[]': interpolatedName,
|
'match[]': interpolatedName,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (withLimit !== 'none') {
|
||||||
|
urlParams = { ...urlParams, limit: withLimit ?? DEFAULT_SERIES_LIMIT };
|
||||||
|
}
|
||||||
|
|
||||||
const url = `/api/v1/series`;
|
const url = `/api/v1/series`;
|
||||||
|
|
||||||
const data = await this.request(url, [], urlParams, this.getDefaultCacheHeaders());
|
const data = await this.request(url, [], urlParams, this.getDefaultCacheHeaders());
|
||||||
|
Loading…
Reference in New Issue
Block a user