diff --git a/packages/grafana-prometheus/src/datasource.test.ts b/packages/grafana-prometheus/src/datasource.test.ts index c5fc15d6a8f..73964477bf3 100644 --- a/packages/grafana-prometheus/src/datasource.test.ts +++ b/packages/grafana-prometheus/src/datasource.test.ts @@ -19,6 +19,7 @@ import { config, getBackendSrv, setBackendSrv, TemplateSrv } from '@grafana/runt import { extractResourceMatcher, extractRuleMappingFromGroups, PrometheusDatasource } from './datasource'; import { prometheusRegularEscape, prometheusSpecialRegexEscape } from './escaping'; import { PrometheusLanguageProviderInterface } from './language_provider'; +import { CacheRequestInfo } from './querycache/QueryCache'; import { createDataRequest, createDefaultPromResponse, @@ -1253,3 +1254,72 @@ describe('modifyQuery', () => { }); }); }); + +describe('PrometheusDatasource incremental query logic', () => { + let ds: PrometheusDatasource; + let mockCache: { + requestInfo: jest.MockedFunction<(request: DataQueryRequest) => CacheRequestInfo>; + procFrames: jest.MockedFunction<(...args: unknown[]) => unknown[]>; + }; + + beforeEach(() => { + jest.clearAllMocks(); + + mockCache = { + requestInfo: jest.fn().mockReturnValue({ + requests: [{ targets: [], range: getMockTimeRange() }], + targetSignatures: new Map(), + shouldCache: true, + }), + procFrames: jest.fn().mockReturnValue([]), + }; + + const incrementalInstanceSettings = { + url: 'proxied', + id: 1, + uid: 'ABCDEF', + access: 'proxy', + user: 'test', + password: 'mupp', + jsonData: { + customQueryParameters: '', + cacheLevel: PrometheusCacheLevel.Low, + incrementalQuerying: true, + } as Partial, + } as unknown as DataSourceInstanceSettings; + + ds = new PrometheusDatasource(incrementalInstanceSettings, templateSrvStub); + ds.cache = mockCache as unknown as typeof ds.cache; + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('should use incremental query for normal queries when incrementalQuerying is true', async () => { + const request = createDataRequest([{ expr: 'up', refId: 'A' }]); + await lastValueFrom(ds.query(request)); + expect(mockCache.requestInfo).toHaveBeenCalled(); + }); + + it('should disable incremental query when query contains $__range', async () => { + const request = createDataRequest([{ expr: 'rate(up[$__range])', refId: 'A' }]); + await lastValueFrom(ds.query(request)); + expect(mockCache.requestInfo).not.toHaveBeenCalled(); + }); + + it('should disable incremental query when any target contains $__range', async () => { + const request = createDataRequest([ + { expr: 'up', refId: 'A' }, + { expr: 'rate(cpu[$__range])', refId: 'B' }, + ]); + await lastValueFrom(ds.query(request)); + expect(mockCache.requestInfo).not.toHaveBeenCalled(); + }); + + it('should disable incremental query for instant queries', async () => { + const request = createDataRequest([{ expr: 'up', refId: 'A', instant: true }]); + await lastValueFrom(ds.query(request)); + expect(mockCache.requestInfo).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/grafana-prometheus/src/datasource.ts b/packages/grafana-prometheus/src/datasource.ts index 1cc48500a80..2239b63be54 100644 --- a/packages/grafana-prometheus/src/datasource.ts +++ b/packages/grafana-prometheus/src/datasource.ts @@ -492,16 +492,16 @@ export class PrometheusDatasource return this.directAccessError(); } - let fullOrPartialRequest: DataQueryRequest; - let requestInfo: CacheRequestInfo | undefined = undefined; - const hasInstantQuery = request.targets.some((target) => target.instant); + // Use incremental query only if enabled and no instant queries or no $__range variables + const shouldUseIncrementalQuery = + this.hasIncrementalQuery && !request.targets.some((target) => target.instant || target.expr.includes('$__range')); - // Don't cache instant queries - if (this.hasIncrementalQuery && !hasInstantQuery) { + let fullOrPartialRequest: DataQueryRequest = request; + let requestInfo: CacheRequestInfo | undefined = undefined; + + if (shouldUseIncrementalQuery) { requestInfo = this.cache.requestInfo(request); fullOrPartialRequest = requestInfo.requests[0]; - } else { - fullOrPartialRequest = request; } const targets = fullOrPartialRequest.targets.map((target) => this.processTargetV2(target, fullOrPartialRequest));