diff --git a/.betterer.results b/.betterer.results index 86db65d8f44..6429e7b67b5 100644 --- a/.betterer.results +++ b/.betterer.results @@ -453,7 +453,8 @@ exports[`better eslint`] = { [0, 0, 0, "Unexpected any. Specify a different type.", "2"], [0, 0, 0, "Unexpected any. Specify a different type.", "3"], [0, 0, 0, "Unexpected any. Specify a different type.", "4"], - [0, 0, 0, "Unexpected any. Specify a different type.", "5"] + [0, 0, 0, "Unexpected any. Specify a different type.", "5"], + [0, 0, 0, "Unexpected any. Specify a different type.", "6"] ], "packages/grafana-prometheus/src/language_utils.ts:5381": [ [0, 0, 0, "Do not use any type assertions.", "0"] diff --git a/packages/grafana-prometheus/src/language_provider.ts b/packages/grafana-prometheus/src/language_provider.ts index 3ff374771d7..0419295ab16 100644 --- a/packages/grafana-prometheus/src/language_provider.ts +++ b/packages/grafana-prometheus/src/language_provider.ts @@ -515,19 +515,6 @@ export default class PromQlLanguageProvider extends LanguageProvider implements }; } -export interface PrometheusLanguageProviderInterface - extends PrometheusBaseLanguageProvider, - PrometheusLegacyLanguageProvider { - retrieveMetricsMetadata: () => PromMetricsMetadata; - retrieveHistogramMetrics: () => string[]; - retrieveMetrics: () => string[]; - retrieveLabelKeys: () => string[]; - - queryMetricsMetadata: (limit?: number) => Promise; - queryLabelKeys: (timeRange: TimeRange, match?: string, limit?: number) => Promise; - queryLabelValues: (timeRange: TimeRange, labelKey: string, match?: string, limit?: number) => Promise; -} - /** * Modern implementation of the Prometheus language provider that abstracts API endpoint selection. * @@ -535,12 +522,73 @@ export interface PrometheusLanguageProviderInterface * - Automatically selects the most efficient API endpoint based on Prometheus version and configuration * - Supports both labels and series endpoints for backward compatibility * - Handles match[] parameters for filtering time series data - * - Implements automatic request limiting (default: 40_000 series) + * - Implements automatic request limiting (default: 40,000 series if not configured otherwise) * - Provides unified interface for both modern and legacy Prometheus versions + * - Provides caching mechanism based on time range, limit, and match parameters * * @see LabelsApiClient For modern Prometheus versions using the labels API * @see SeriesApiClient For legacy Prometheus versions using the series API */ +export interface PrometheusLanguageProviderInterface + extends PrometheusBaseLanguageProvider, + PrometheusLegacyLanguageProvider { + /** + * Initializes the language provider by fetching metrics, label keys, and metrics metadata using Resource Clients. + * All calls use the limit parameter from datasource configuration (default: 40,000 if not set). + * + * For backward compatibility, it calls _backwardCompatibleStart. + * Some places still rely on deprecated fields. Until we replace them, we need _backwardCompatibleStart method. + */ + start: (timeRange?: TimeRange) => Promise; + + /** + * Returns already cached metrics metadata including type and help information. + * If there is no cached metadata, it returns an empty object. + * To get fresh metadata, use queryMetricsMetadata instead. + */ + retrieveMetricsMetadata: () => PromMetricsMetadata; + + /** + * Returns already cached list of histogram metrics (identified by '_bucket' suffix). + * If there are no cached histogram metrics, it returns an empty array. + */ + retrieveHistogramMetrics: () => string[]; + + /** + * Returns already cached list of all available metric names. + * If there are no cached metrics, it returns an empty array. + */ + retrieveMetrics: () => string[]; + + /** + * Returns already cached list of available label keys. + * If there are no cached label keys, it returns an empty array. + */ + retrieveLabelKeys: () => string[]; + + /** + * Fetches fresh metrics metadata from Prometheus with optional limit. + * Uses datasource's default limit if not specified. + */ + queryMetricsMetadata: (limit?: number) => Promise; + + /** + * Queries Prometheus for label keys within time range, optionally filtered by match selector. + * Automatically selects labels or series endpoint based on datasource configuration. + * If no limit is provided, uses the datasource's default limit configuration. + * Use zero (0) to fetch all label keys, but this might return huge amounts of data. + */ + queryLabelKeys: (timeRange: TimeRange, match?: string, limit?: number) => Promise; + + /** + * Queries Prometheus for values of a specific label key, optionally filtered by match selector. + * Automatically selects labels or series endpoint based on datasource configuration. + * If no limit is provided, uses the datasource's default limit configuration. + * Use zero (0) to fetch all label values, but this might return huge amounts of data. + */ + queryLabelValues: (timeRange: TimeRange, labelKey: string, match?: string, limit?: number) => Promise; +} + export class PrometheusLanguageProvider extends PromQlLanguageProvider implements PrometheusLanguageProviderInterface { private _metricsMetadata?: PromMetricsMetadata; private _resourceClient?: ResourceApiClient; diff --git a/packages/grafana-prometheus/src/resource_clients.test.ts b/packages/grafana-prometheus/src/resource_clients.test.ts index f919615b44e..4a2790227ee 100644 --- a/packages/grafana-prometheus/src/resource_clients.test.ts +++ b/packages/grafana-prometheus/src/resource_clients.test.ts @@ -836,6 +836,11 @@ describe('BaseResourceClient', () => { constructor() { super(mockRequest, mockDatasource); } + + // Expose protected method for testing + public testGetEffectiveLimit(limit?: number): number { + return this.getEffectiveLimit(limit); + } } let client: TestBaseResourceClient; @@ -845,6 +850,26 @@ describe('BaseResourceClient', () => { client = new TestBaseResourceClient(); }); + describe('getEffectiveLimit', () => { + it('should return the provided limit when a number is given', () => { + expect(client.testGetEffectiveLimit(1000)).toBe(1000); + expect(client.testGetEffectiveLimit(500)).toBe(500); + expect(client.testGetEffectiveLimit(100000)).toBe(100000); + }); + + it('should return 0 when limit is 0 (valid for unlimited)', () => { + expect(client.testGetEffectiveLimit(0)).toBe(0); + }); + + it('should return datasource seriesLimit when limit is undefined', () => { + expect(client.testGetEffectiveLimit(undefined)).toBe(DEFAULT_SERIES_LIMIT); + }); + + it('should return datasource seriesLimit when no limit is provided', () => { + expect(client.testGetEffectiveLimit()).toBe(DEFAULT_SERIES_LIMIT); + }); + }); + describe('querySeries', () => { const mockTimeRange = { from: dateTime(1681300292392), diff --git a/packages/grafana-prometheus/src/resource_clients.ts b/packages/grafana-prometheus/src/resource_clients.ts index cc59213cf47..2eb4a6b2ce0 100644 --- a/packages/grafana-prometheus/src/resource_clients.ts +++ b/packages/grafana-prometheus/src/resource_clients.ts @@ -43,8 +43,16 @@ export abstract class BaseResourceClient { this.seriesLimit = this.datasource.seriesLimit; } + /** + * Returns the effective limit to use for API requests. + * Uses the provided limit if specified, otherwise falls back to the datasource's configured series limit. + * When zero is provided, it returns zero (which means no limit in Prometheus API). + * + * @param {number} [limit] - Optional limit parameter from the API call + * @returns {number} The limit to use - either the provided limit or datasource's default series limit + */ protected getEffectiveLimit(limit?: number): number { - return limit || this.seriesLimit; + return limit ?? this.seriesLimit; } protected async requestLabels( @@ -93,10 +101,23 @@ export class LabelsApiClient extends BaseResourceClient implements ResourceApiCl this.labelKeys = await this.queryLabelKeys(timeRange); }; - public queryMetrics = async (timeRange: TimeRange): Promise<{ metrics: string[]; histogramMetrics: string[] }> => { - this.metrics = await this.queryLabelValues(timeRange, METRIC_LABEL); + /** + * Fetches all available metrics from Prometheus using the labels values endpoint for __name__. + * Also processes and identifies histogram metrics (those ending with '_bucket'). + * Results are cached and stored in the client instance for future use. + * + * @param {TimeRange} timeRange - Time range to search for metrics + * @param {number} [limit] - Optional maximum number of metrics to return, uses datasource default if not specified + * @returns {Promise<{metrics: string[], histogramMetrics: string[]}>} Object containing all metrics and filtered histogram metrics + */ + public queryMetrics = async ( + timeRange: TimeRange, + limit?: number + ): Promise<{ metrics: string[]; histogramMetrics: string[] }> => { + const effectiveLimit = this.getEffectiveLimit(limit); + this.metrics = await this.queryLabelValues(timeRange, METRIC_LABEL, undefined, effectiveLimit); this.histogramMetrics = processHistogramMetrics(this.metrics); - this._cache.setLabelValues(timeRange, undefined, DEFAULT_SERIES_LIMIT, this.metrics); + this._cache.setLabelValues(timeRange, undefined, effectiveLimit, this.metrics); return { metrics: this.metrics, histogramMetrics: this.histogramMetrics }; };