mirror of
https://github.com/grafana/grafana.git
synced 2026-08-26 05:17:26 -05:00
Prometheus: Fix query interval string interpolation in query cache (#93678)
fix query interval string interpolation in query cache
This commit is contained in:
@@ -141,6 +141,7 @@ export class PrometheusDatasource
|
|||||||
this.cache = new QueryCache({
|
this.cache = new QueryCache({
|
||||||
getTargetSignature: this.getPrometheusTargetSignature.bind(this),
|
getTargetSignature: this.getPrometheusTargetSignature.bind(this),
|
||||||
overlapString: instanceSettings.jsonData.incrementalQueryOverlapWindow ?? defaultPrometheusQueryOverlapWindow,
|
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
|
// This needs to be here and cannot be static because of how annotations typing affects casting of data source
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { DataFrame, DataQueryRequest, DateTime, dateTime, TimeRange } from '@gra
|
|||||||
import { QueryEditorMode } from '../querybuilder/shared/types';
|
import { QueryEditorMode } from '../querybuilder/shared/types';
|
||||||
import { PromQuery } from '../types';
|
import { PromQuery } from '../types';
|
||||||
|
|
||||||
import { CacheRequestInfo, QueryCache } from './QueryCache';
|
import { CacheRequestInfo, findDatapointStep, QueryCache } from './QueryCache';
|
||||||
import { IncrementalStorageDataFrameScenarios, trimmedFirstPointInPromFrames } from './QueryCacheTestData';
|
import { IncrementalStorageDataFrameScenarios, trimmedFirstPointInPromFrames } from './QueryCacheTestData';
|
||||||
|
|
||||||
// Will not interpolate vars!
|
// Will not interpolate vars!
|
||||||
@@ -559,3 +559,14 @@ describe('QueryCache: Prometheus', function () {
|
|||||||
expect(cacheRequest.shouldCache).toBe(true);
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
isValidDuration,
|
isValidDuration,
|
||||||
parseDuration,
|
parseDuration,
|
||||||
rangeUtil,
|
rangeUtil,
|
||||||
|
ScopedVars,
|
||||||
Table,
|
Table,
|
||||||
trimTable,
|
trimTable,
|
||||||
} from '@grafana/data';
|
} from '@grafana/data';
|
||||||
@@ -23,10 +24,9 @@ type TargetIdent = string;
|
|||||||
// query + template variables + interval + raw time range
|
// query + template variables + interval + raw time range
|
||||||
// used for full target cache busting -> full range re-query
|
// used for full target cache busting -> full range re-query
|
||||||
type TargetSig = string;
|
type TargetSig = string;
|
||||||
|
|
||||||
type TimestampMs = number;
|
type TimestampMs = number;
|
||||||
|
|
||||||
type SupportedQueryTypes = PromQuery;
|
type SupportedQueryTypes = PromQuery;
|
||||||
|
type ApplyInterpolation = (str: string, scopedVars?: ScopedVars) => string;
|
||||||
|
|
||||||
// string matching requirements defined in durationutil.ts
|
// string matching requirements defined in durationutil.ts
|
||||||
export const defaultPrometheusQueryOverlapWindow = '10m';
|
export const defaultPrometheusQueryOverlapWindow = '10m';
|
||||||
@@ -59,12 +59,14 @@ export const getFieldIdent = (field: Field) => `${field.type}|${field.name}|${JS
|
|||||||
export class QueryCache<T extends SupportedQueryTypes> {
|
export class QueryCache<T extends SupportedQueryTypes> {
|
||||||
private overlapWindowMs: number;
|
private overlapWindowMs: number;
|
||||||
private getTargetSignature: (request: DataQueryRequest<T>, target: T) => string;
|
private getTargetSignature: (request: DataQueryRequest<T>, target: T) => string;
|
||||||
|
private applyInterpolation = (str: string, scopedVars?: ScopedVars) => str;
|
||||||
|
|
||||||
cache = new Map<TargetIdent, TargetCache>();
|
cache = new Map<TargetIdent, TargetCache>();
|
||||||
|
|
||||||
constructor(options: {
|
constructor(options: {
|
||||||
getTargetSignature: (request: DataQueryRequest<T>, target: T) => string;
|
getTargetSignature: (request: DataQueryRequest<T>, target: T) => string;
|
||||||
overlapString: string;
|
overlapString: string;
|
||||||
|
applyInterpolation?: ApplyInterpolation;
|
||||||
}) {
|
}) {
|
||||||
const unverifiedOverlap = options.overlapString;
|
const unverifiedOverlap = options.overlapString;
|
||||||
if (isValidDuration(unverifiedOverlap)) {
|
if (isValidDuration(unverifiedOverlap)) {
|
||||||
@@ -76,6 +78,9 @@ export class QueryCache<T extends SupportedQueryTypes> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.getTargetSignature = options.getTargetSignature;
|
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
|
// 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
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||||
let table: Table = frame.fields.map((field) => field.values) as Table;
|
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
|
// 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);
|
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
|
// Prometheus specific logic below
|
||||||
if (request.targets[0].datasource?.type !== 'prometheus') {
|
if (request.targets[0].datasource?.type !== 'prometheus') {
|
||||||
return 0;
|
return 0;
|
||||||
@@ -270,7 +279,7 @@ function findDatapointStep(request: DataQueryRequest<PromQuery>, respFrames: Dat
|
|||||||
|
|
||||||
let dataPointStep = request.intervalMs;
|
let dataPointStep = request.intervalMs;
|
||||||
if (target?.interval) {
|
if (target?.interval) {
|
||||||
const minStepMs = rangeUtil.intervalToMs(target.interval);
|
const minStepMs = rangeUtil.intervalToMs(applyInterpolation(target.interval));
|
||||||
if (minStepMs > request.intervalMs) {
|
if (minStepMs > request.intervalMs) {
|
||||||
dataPointStep = minStepMs;
|
dataPointStep = minStepMs;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user