Prometheus: Fix query interval string interpolation in query cache (#93678)

fix query interval string interpolation in query cache
This commit is contained in:
ismail simsek 2024-09-24 18:03:16 +02:00 committed by GitHub
parent 05dbba4c85
commit 18f8f38418
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 27 additions and 6 deletions

View File

@ -141,6 +141,7 @@ export class PrometheusDatasource
this.cache = new QueryCache({
getTargetSignature: this.getPrometheusTargetSignature.bind(this),
overlapString: instanceSettings.jsonData.incrementalQueryOverlapWindow ?? defaultPrometheusQueryOverlapWindow,
applyInterpolation: this.interpolateString.bind(this),
});
// This needs to be here and cannot be static because of how annotations typing affects casting of data source

View File

@ -6,7 +6,7 @@ import { DataFrame, DataQueryRequest, DateTime, dateTime, TimeRange } from '@gra
import { QueryEditorMode } from '../querybuilder/shared/types';
import { PromQuery } from '../types';
import { CacheRequestInfo, QueryCache } from './QueryCache';
import { CacheRequestInfo, findDatapointStep, QueryCache } from './QueryCache';
import { IncrementalStorageDataFrameScenarios, trimmedFirstPointInPromFrames } from './QueryCacheTestData';
// Will not interpolate vars!
@ -559,3 +559,14 @@ describe('QueryCache: Prometheus', function () {
expect(cacheRequest.shouldCache).toBe(true);
});
});
describe('findDataPointStep', () => {
it('should interpolate custom interval', () => {
const mockApplyInterpolation = jest.fn().mockImplementation(() => '1m');
const req = mockPromRequest();
req.targets[0].interval = '$interval';
const respFrames = trimmedFirstPointInPromFrames as unknown as DataFrame[];
findDatapointStep(req, respFrames, mockApplyInterpolation);
expect(mockApplyInterpolation).toBeCalledTimes(1);
});
});

View File

@ -10,6 +10,7 @@ import {
isValidDuration,
parseDuration,
rangeUtil,
ScopedVars,
Table,
trimTable,
} from '@grafana/data';
@ -23,10 +24,9 @@ type TargetIdent = string;
// query + template variables + interval + raw time range
// used for full target cache busting -> full range re-query
type TargetSig = string;
type TimestampMs = number;
type SupportedQueryTypes = PromQuery;
type ApplyInterpolation = (str: string, scopedVars?: ScopedVars) => string;
// string matching requirements defined in durationutil.ts
export const defaultPrometheusQueryOverlapWindow = '10m';
@ -59,12 +59,14 @@ export const getFieldIdent = (field: Field) => `${field.type}|${field.name}|${JS
export class QueryCache<T extends SupportedQueryTypes> {
private overlapWindowMs: number;
private getTargetSignature: (request: DataQueryRequest<T>, target: T) => string;
private applyInterpolation = (str: string, scopedVars?: ScopedVars) => str;
cache = new Map<TargetIdent, TargetCache>();
constructor(options: {
getTargetSignature: (request: DataQueryRequest<T>, target: T) => string;
overlapString: string;
applyInterpolation?: ApplyInterpolation;
}) {
const unverifiedOverlap = options.overlapString;
if (isValidDuration(unverifiedOverlap)) {
@ -76,6 +78,9 @@ export class QueryCache<T extends SupportedQueryTypes> {
}
this.getTargetSignature = options.getTargetSignature;
if (options.applyInterpolation) {
this.applyInterpolation = options.applyInterpolation;
}
}
// can be used to change full range request to partial, split into multiple requests
@ -221,7 +226,7 @@ export class QueryCache<T extends SupportedQueryTypes> {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
let table: Table = frame.fields.map((field) => field.values) as Table;
const dataPointStep = findDatapointStep(request, respFrames);
const dataPointStep = findDatapointStep(request, respFrames, this.applyInterpolation);
// query interval is greater than request.intervalMs, use query interval to make sure we've always got one datapoint outside the panel viewport
let trimmed = trimTable(table, newFrom - dataPointStep, newTo);
@ -260,7 +265,11 @@ export class QueryCache<T extends SupportedQueryTypes> {
}
}
function findDatapointStep(request: DataQueryRequest<PromQuery>, respFrames: DataFrame[]): number {
export function findDatapointStep(
request: DataQueryRequest<PromQuery>,
respFrames: DataFrame[],
applyInterpolation: ApplyInterpolation
): number {
// Prometheus specific logic below
if (request.targets[0].datasource?.type !== 'prometheus') {
return 0;
@ -270,7 +279,7 @@ function findDatapointStep(request: DataQueryRequest<PromQuery>, respFrames: Dat
let dataPointStep = request.intervalMs;
if (target?.interval) {
const minStepMs = rangeUtil.intervalToMs(target.interval);
const minStepMs = rangeUtil.intervalToMs(applyInterpolation(target.interval));
if (minStepMs > request.intervalMs) {
dataPointStep = minStepMs;
}