Prometheus: Remove timeSrv imports (#76118)

* Remove timeSrv imports from prometheus datasource

* Fix language_provider unit test

* Fix datasource unit tests

* Remove timeSrv imports from metric_find_query

* Remove timeSrv imports

* MetricsBrowser with timeRange
This commit is contained in:
ismail simsek 2023-10-30 15:44:28 +01:00 committed by GitHub
parent e5f92c010d
commit df3184a94a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 211 additions and 161 deletions

View File

@ -713,7 +713,7 @@ abstract class LanguageProvider {
* Returns startTask that resolves with a task list when main syntax is loaded.
* Task list consists of secondary promises that load more detailed language features.
*/
abstract start: () => Promise<Array<Promise<any>>>;
abstract start: (timeRange?: TimeRange) => Promise<Array<Promise<any>>>;
startTask?: Promise<any[]>;
}

View File

@ -174,10 +174,11 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
refreshMetrics = async () => {
const {
range,
datasource: { languageProvider },
} = this.props;
this.languageProviderInitializationPromise = makePromiseCancelable(languageProvider.start());
this.languageProviderInitializationPromise = makePromiseCancelable(languageProvider.start(range));
try {
const remainingTasks = await this.languageProviderInitializationPromise.promise;
@ -327,6 +328,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
lastUsedLabels={lastUsedLabels || []}
storeLastUsedLabels={onLastUsedLabelsSave}
deleteLastUsedLabels={onLastUsedLabelsDelete}
timeRange={this.props.range}
/>
</div>
)}

View File

@ -2,7 +2,7 @@ import { css, cx } from '@emotion/css';
import React, { ChangeEvent } from 'react';
import { FixedSizeList } from 'react-window';
import { GrafanaTheme2 } from '@grafana/data';
import { GrafanaTheme2, TimeRange } from '@grafana/data';
import {
Button,
HorizontalGroup,
@ -31,6 +31,7 @@ export interface BrowserProps {
lastUsedLabels: string[];
storeLastUsedLabels: (labels: string[]) => void;
deleteLastUsedLabels: () => void;
timeRange?: TimeRange;
}
interface BrowserState {
@ -319,7 +320,7 @@ export class UnthemedPrometheusMetricsBrowser extends React.Component<BrowserPro
const { languageProvider, lastUsedLabels } = this.props;
if (languageProvider) {
const selectedLabels: string[] = lastUsedLabels;
languageProvider.start().then(() => {
languageProvider.start(this.props.timeRange).then(() => {
let rawLabels: string[] = languageProvider.getLabelKeys();
// Get metrics
this.fetchValues(METRIC_LABEL, EMPTY_SELECTOR);

View File

@ -13,10 +13,11 @@ import {
Field,
getFieldDisplayName,
LoadingState,
rangeUtil,
TimeRange,
toDataFrame,
VariableHide,
} from '@grafana/data';
import { TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { TemplateSrv } from 'app/features/templating/template_srv';
import { QueryOptions } from 'app/types';
@ -49,23 +50,23 @@ const templateSrvStub = {
const fromSeconds = 1674500289215;
const toSeconds = 1674500349215;
const timeSrvStubOld = {
timeRange() {
return {
from: dateTime(1531468681),
to: dateTime(1531489712),
};
const mockTimeRangeOld: TimeRange = {
from: dateTime(1531468681),
to: dateTime(1531489712),
raw: {
from: '1531468681',
to: '1531489712',
},
} as TimeSrv;
};
const timeSrvStub: TimeSrv = {
timeRange() {
return {
from: dateTime(fromSeconds),
to: dateTime(toSeconds),
};
const mockTimeRange: TimeRange = {
from: dateTime(fromSeconds),
to: dateTime(toSeconds),
raw: {
from: fromSeconds.toString(),
to: toSeconds.toString(),
},
} as TimeSrv;
};
beforeEach(() => {
jest.clearAllMocks();
@ -87,7 +88,7 @@ describe('PrometheusDatasource', () => {
} as unknown as DataSourceInstanceSettings<PromOptions>;
beforeEach(() => {
ds = new PrometheusDatasource(instanceSettings, templateSrvStub, timeSrvStub);
ds = new PrometheusDatasource(instanceSettings, templateSrvStub);
});
// Some functions are required by the parent datasource class to provide functionality such as ad-hoc filters, which requires the definition of the getTagKeys, and getTagValues functions
@ -142,7 +143,7 @@ describe('PrometheusDatasource', () => {
},
} as unknown as DataSourceInstanceSettings<PromOptions>;
const range = { from: time({ seconds: 63 }), to: time({ seconds: 183 }) };
const directDs = new PrometheusDatasource(instanceSettings, templateSrvStub, timeSrvStub);
const directDs = new PrometheusDatasource(instanceSettings, templateSrvStub);
await expect(
lastValueFrom(directDs.query(createDataRequest([{}, {}], { app: CoreApp.Dashboard })))
@ -191,7 +192,7 @@ describe('PrometheusDatasource', () => {
it('should still perform a GET request with the DS HTTP method set to POST and not POST-friendly endpoint', () => {
const postSettings = cloneDeep(instanceSettings);
postSettings.jsonData.httpMethod = 'POST';
const promDs = new PrometheusDatasource(postSettings, templateSrvStub, timeSrvStub);
const promDs = new PrometheusDatasource(postSettings, templateSrvStub);
promDs.metadataRequest('/foo');
expect(fetchMock.mock.calls.length).toBe(1);
expect(fetchMock.mock.calls[0][0].method).toBe('GET');
@ -199,7 +200,7 @@ describe('PrometheusDatasource', () => {
it('should try to perform a POST request with the DS HTTP method set to POST and POST-friendly endpoint', () => {
const postSettings = cloneDeep(instanceSettings);
postSettings.jsonData.httpMethod = 'POST';
const promDs = new PrometheusDatasource(postSettings, templateSrvStub, timeSrvStub);
const promDs = new PrometheusDatasource(postSettings, templateSrvStub);
promDs.metadataRequest('api/v1/series', { bar: 'baz baz', foo: 'foo' });
expect(fetchMock.mock.calls.length).toBe(1);
expect(fetchMock.mock.calls[0][0].method).toBe('POST');
@ -222,8 +223,7 @@ describe('PrometheusDatasource', () => {
describe('with GET http method', () => {
const promDs = new PrometheusDatasource(
{ ...instanceSettings, jsonData: { customQueryParameters: 'customQuery=123', httpMethod: 'GET' } },
templateSrvStub,
timeSrvStub
templateSrvStub
);
it('added to metadata request', () => {
@ -257,8 +257,7 @@ describe('PrometheusDatasource', () => {
describe('with POST http method', () => {
const promDs = new PrometheusDatasource(
{ ...instanceSettings, jsonData: { customQueryParameters: 'customQuery=123', httpMethod: 'POST' } },
templateSrvStub,
timeSrvStub
templateSrvStub
);
it('added to metadata request with non-POST endpoint', () => {
@ -307,7 +306,12 @@ describe('PrometheusDatasource', () => {
const target: PromQuery = { expr: DEFAULT_QUERY_EXPRESSION, refId: 'A' };
it('should not modify expression with no filters', () => {
const result = ds.createQuery(target, { interval: '15s' } as DataQueryRequest<PromQuery>, 0, 0);
const result = ds.createQuery(
target,
{ interval: '15s', range: getMockTimeRange() } as DataQueryRequest<PromQuery>,
0,
0
);
expect(result).toMatchObject({ expr: DEFAULT_QUERY_EXPRESSION });
});
@ -324,7 +328,12 @@ describe('PrometheusDatasource', () => {
value: 'v2',
},
];
const result = ds.createQuery(target, { interval: '15s', filters } as DataQueryRequest<PromQuery>, 0, 0);
const result = ds.createQuery(
target,
{ interval: '15s', range: getMockTimeRange(), filters } as DataQueryRequest<PromQuery>,
0,
0
);
expect(result).toMatchObject({ expr: 'metric{job="foo", k1="v1", k2!="v2"} - metric{k1="v1", k2!="v2"}' });
});
@ -342,7 +351,12 @@ describe('PrometheusDatasource', () => {
},
];
const result = ds.createQuery(target, { interval: '15s', filters } as DataQueryRequest<PromQuery>, 0, 0);
const result = ds.createQuery(
target,
{ interval: '15s', range: getMockTimeRange(), filters } as DataQueryRequest<PromQuery>,
0,
0
);
expect(result).toMatchObject({
expr: `metric{job="foo", k1=~"v.*", k2=~"v\\\\'.*"} - metric{k1=~"v.*", k2=~"v\\\\'.*"}`,
});
@ -445,10 +459,9 @@ describe('PrometheusDatasource', () => {
...instanceSettings,
jsonData: { ...instanceSettings.jsonData, cacheLevel: PrometheusCacheLevel.Low },
},
templateSrvStub as unknown as TemplateSrv,
timeSrvStub as unknown as TimeSrv
templateSrvStub as unknown as TemplateSrv
);
const quantizedRange = dataSource.getAdjustedInterval();
const quantizedRange = dataSource.getAdjustedInterval(mockTimeRange);
// For "1 minute" the window contains all the minutes, so a query from 1:11:09 - 1:12:09 becomes 1:11 - 1:13
expect(parseInt(quantizedRange.end, 10) - parseInt(quantizedRange.start, 10)).toBe(120);
});
@ -459,10 +472,9 @@ describe('PrometheusDatasource', () => {
...instanceSettings,
jsonData: { ...instanceSettings.jsonData, cacheLevel: PrometheusCacheLevel.Medium },
},
templateSrvStub as unknown as TemplateSrv,
timeSrvStub as unknown as TimeSrv
templateSrvStub as unknown as TemplateSrv
);
const quantizedRange = dataSource.getAdjustedInterval();
const quantizedRange = dataSource.getAdjustedInterval(mockTimeRange);
expect(parseInt(quantizedRange.end, 10) - parseInt(quantizedRange.start, 10)).toBe(600);
});
@ -472,10 +484,9 @@ describe('PrometheusDatasource', () => {
...instanceSettings,
jsonData: { ...instanceSettings.jsonData, cacheLevel: PrometheusCacheLevel.High },
},
templateSrvStub as unknown as TemplateSrv,
timeSrvStub as unknown as TimeSrv
templateSrvStub as unknown as TemplateSrv
);
const quantizedRange = dataSource.getAdjustedInterval();
const quantizedRange = dataSource.getAdjustedInterval(mockTimeRange);
expect(parseInt(quantizedRange.end, 10) - parseInt(quantizedRange.start, 10)).toBe(3600);
});
@ -485,10 +496,9 @@ describe('PrometheusDatasource', () => {
...instanceSettings,
jsonData: { ...instanceSettings.jsonData, cacheLevel: PrometheusCacheLevel.None },
},
templateSrvStub as unknown as TemplateSrv,
timeSrvStub as unknown as TimeSrv
templateSrvStub as unknown as TemplateSrv
);
const quantizedRange = dataSource.getAdjustedInterval();
const quantizedRange = dataSource.getAdjustedInterval(mockTimeRange);
expect(parseInt(quantizedRange.end, 10) - parseInt(quantizedRange.start, 10)).toBe(
(toSeconds - fromSeconds) / 1000
);
@ -840,11 +850,10 @@ describe('PrometheusDatasource', () => {
beforeEach(() => {
const prometheusDatasource = new PrometheusDatasource(
{ ...instanceSettings, jsonData: { ...instanceSettings.jsonData, cacheLevel: PrometheusCacheLevel.None } },
templateSrvStub,
timeSrvStubOld
templateSrvStub
);
const query = 'query_result(topk(5,rate(http_request_duration_microseconds_count[$__interval])))';
prometheusDatasource.metricFindQuery(query);
prometheusDatasource.metricFindQuery(query, { range: mockTimeRangeOld });
});
it('should call templateSrv.replace with scopedVars', () => {
@ -888,7 +897,7 @@ describe('PrometheusDatasource2', () => {
let ds: PrometheusDatasource;
beforeEach(() => {
ds = new PrometheusDatasource(instanceSettings, templateSrvStub, timeSrvStub);
ds = new PrometheusDatasource(instanceSettings, templateSrvStub);
});
describe('When querying prometheus with one target using query editor target spec', () => {
@ -1510,7 +1519,7 @@ describe('PrometheusDatasource2', () => {
it('should be determined by the 11000 data points limit when too small', async () => {
const query = {
// 1 week range
range: { from: time({}), to: time({ hours: 7 * 24 }) },
range: { from: time({ minutes: 1 }), to: time({ hours: 7 * 24, minutes: 1 }) },
targets: [
{
expr: 'test',
@ -1521,9 +1530,9 @@ describe('PrometheusDatasource2', () => {
} as DataQueryRequest<PromQuery>;
let end = 7 * 24 * 60 * 60;
end -= end % 55;
const start = 0;
const start = 60;
const step = 55;
const adjusted = alignRange(start, end, step, timeSrvStub.timeRange().to.utcOffset() * 60);
const adjusted = alignRange(start, end, step, query.range.to.utcOffset() * 60);
const urlExpected =
'proxied/api/v1/query_range?query=test' +
'&start=' +
@ -1760,7 +1769,7 @@ describe('PrometheusDatasource2', () => {
it('should be determined by the 11000 data points limit, accounting for intervalFactor', async () => {
const query = {
// 1 week range
range: { from: time({}), to: time({ hours: 7 * 24 }) },
range: { from: time({ minutes: 1 }), to: time({ hours: 7 * 24, minutes: 1 }) },
targets: [
{
expr: 'rate(test[$__interval])',
@ -1775,9 +1784,9 @@ describe('PrometheusDatasource2', () => {
};
let end = 7 * 24 * 60 * 60;
end -= end % 55;
const start = 0;
const start = 60;
const step = 55;
const adjusted = alignRange(start, end, step, timeSrvStub.timeRange().to.utcOffset() * 60);
const adjusted = alignRange(start, end, step, query.range.to.utcOffset() * 60);
const urlExpected =
'proxied/api/v1/query_range?query=' +
encodeURIComponent('rate(test[$__interval])') +
@ -1872,40 +1881,70 @@ describe('PrometheusDatasource2', () => {
});
it('should be 4 times the scrape interval if interval + scrape interval is lower', () => {
ds.createQuery(target, { interval: '15s' } as DataQueryRequest<PromQuery>, 0, 300);
ds.createQuery(target, { interval: '15s', range: getMockTimeRange() } as DataQueryRequest<PromQuery>, 0, 300);
expect(replaceMock.mock.calls[1][1]['__rate_interval'].value).toBe('60s');
});
it('should be interval + scrape interval if 4 times the scrape interval is lower', () => {
ds.createQuery(target, { interval: '5m' } as DataQueryRequest<PromQuery>, 0, 10080);
ds.createQuery(target, { interval: '5m', range: getMockTimeRange() } as DataQueryRequest<PromQuery>, 0, 10080);
expect(replaceMock.mock.calls[1][1]['__rate_interval'].value).toBe('315s');
});
it('should fall back to a scrape interval of 15s if min step is set to 0, resulting in 4*15s = 60s', () => {
ds.createQuery({ ...target, interval: '' }, { interval: '15s' } as DataQueryRequest<PromQuery>, 0, 300);
ds.createQuery(
{ ...target, interval: '' },
{ interval: '15s', range: getMockTimeRange() } as DataQueryRequest<PromQuery>,
0,
300
);
expect(replaceMock.mock.calls[1][1]['__rate_interval'].value).toBe('60s');
});
it('should be 4 times the scrape interval if min step set to 1m and interval is 15s', () => {
// For a 5m graph, $__interval is 15s
ds.createQuery({ ...target, interval: '1m' }, { interval: '15s' } as DataQueryRequest<PromQuery>, 0, 300);
ds.createQuery(
{ ...target, interval: '1m' },
{ interval: '15s', range: getMockTimeRange() } as DataQueryRequest<PromQuery>,
0,
300
);
expect(replaceMock.mock.calls[2][1]['__rate_interval'].value).toBe('240s');
});
it('should be interval + scrape interval if min step set to 1m and interval is 5m', () => {
// For a 7d graph, $__interval is 5m
ds.createQuery({ ...target, interval: '1m' }, { interval: '5m' } as DataQueryRequest<PromQuery>, 0, 10080);
ds.createQuery(
{ ...target, interval: '1m' },
{ interval: '5m', range: getMockTimeRange() } as DataQueryRequest<PromQuery>,
0,
10080
);
expect(replaceMock.mock.calls[2][1]['__rate_interval'].value).toBe('360s');
});
it('should be interval + scrape interval if resolution is set to 1/2 and interval is 10m', () => {
// For a 7d graph, $__interval is 10m
ds.createQuery({ ...target, intervalFactor: 2 }, { interval: '10m' } as DataQueryRequest<PromQuery>, 0, 10080);
ds.createQuery(
{ ...target, intervalFactor: 2 },
{ interval: '10m', range: getMockTimeRange() } as DataQueryRequest<PromQuery>,
0,
10080
);
expect(replaceMock.mock.calls[1][1]['__rate_interval'].value).toBe('1215s');
});
it('should be 4 times the scrape interval if resolution is set to 1/2 and interval is 15s', () => {
// For a 5m graph, $__interval is 15s
ds.createQuery({ ...target, intervalFactor: 2 }, { interval: '15s' } as DataQueryRequest<PromQuery>, 0, 300);
ds.createQuery(
{ ...target, intervalFactor: 2 },
{ interval: '15s', range: getMockTimeRange() } as DataQueryRequest<PromQuery>,
0,
300
);
expect(replaceMock.mock.calls[1][1]['__rate_interval'].value).toBe('60s');
});
it('should interpolate min step if set', () => {
replaceMock.mockImplementation((_: string) => '15s');
ds.createQuery({ ...target, interval: '$int' }, { interval: '15s' } as DataQueryRequest<PromQuery>, 0, 300);
ds.createQuery(
{ ...target, interval: '$int' },
{ interval: '15s', range: getMockTimeRange() } as DataQueryRequest<PromQuery>,
0,
300
);
expect(replaceMock.mock.calls).toHaveLength(3);
replaceMock.mockImplementation((str) => str);
});
@ -1952,7 +1991,7 @@ describe('PrometheusDatasource for POST', () => {
let ds: PrometheusDatasource;
beforeEach(() => {
ds = new PrometheusDatasource(instanceSettings, templateSrvStub, timeSrvStub);
ds = new PrometheusDatasource(instanceSettings, templateSrvStub);
});
describe('When querying prometheus with one target using query editor target spec', () => {
@ -2021,11 +2060,7 @@ describe('PrometheusDatasource for POST', () => {
let ds: PrometheusDatasource;
beforeEach(() => {
ds = new PrometheusDatasource(
instanceSettings,
templateSrvStub as unknown as TemplateSrv,
timeSrvStub as unknown as TimeSrv
);
ds = new PrometheusDatasource(instanceSettings, templateSrvStub as unknown as TemplateSrv);
});
it('with proxy access tracing headers should be added', () => {
@ -2043,11 +2078,7 @@ describe('PrometheusDatasource for POST', () => {
jsonData: { httpMethod: 'POST' },
} as unknown as DataSourceInstanceSettings<PromOptions>;
const mockDs = new PrometheusDatasource(
{ ...instanceSettings, url: 'http://127.0.0.1:8000' },
templateSrvStub,
timeSrvStub
);
const mockDs = new PrometheusDatasource({ ...instanceSettings, url: 'http://127.0.0.1:8000' }, templateSrvStub);
mockDs._addTracingHeaders(httpOptions, options);
expect(httpOptions.headers['X-Dashboard-Id']).toBe(undefined);
expect(httpOptions.headers['X-Panel-Id']).toBe(undefined);
@ -2083,10 +2114,11 @@ function getPrepareTargetsContext({
interval: '1s',
panelId,
app,
range: getMockTimeRange(),
...queryOptions,
} as unknown as DataQueryRequest<PromQuery>;
const ds = new PrometheusDatasource(instanceSettings, templateSrvStub, timeSrvStub);
const ds = new PrometheusDatasource(instanceSettings, templateSrvStub);
if (languageProvider) {
ds.languageProvider = languageProvider;
}
@ -2392,7 +2424,7 @@ describe('modifyQuery', () => {
const query: PromQuery = { refId: 'A', expr: 'go_goroutines' };
const action = { options: { key: 'cluster', value: 'us-cluster' }, type: 'ADD_FILTER' };
const instanceSettings = { jsonData: {} } as unknown as DataSourceInstanceSettings<PromOptions>;
const ds = new PrometheusDatasource(instanceSettings, templateSrvStub, timeSrvStub);
const ds = new PrometheusDatasource(instanceSettings, templateSrvStub);
const result = ds.modifyQuery(query, action);
@ -2406,7 +2438,7 @@ describe('modifyQuery', () => {
const query: PromQuery = { refId: 'A', expr: 'go_goroutines{cluster="us-cluster"}' };
const action = { options: { key: 'pod', value: 'pod-123' }, type: 'ADD_FILTER' };
const instanceSettings = { jsonData: {} } as unknown as DataSourceInstanceSettings<PromOptions>;
const ds = new PrometheusDatasource(instanceSettings, templateSrvStub, timeSrvStub);
const ds = new PrometheusDatasource(instanceSettings, templateSrvStub);
const result = ds.modifyQuery(query, action);
@ -2422,7 +2454,7 @@ describe('modifyQuery', () => {
const query: PromQuery = { refId: 'A', expr: 'go_goroutines' };
const action = { options: { key: 'cluster', value: 'us-cluster' }, type: 'ADD_FILTER_OUT' };
const instanceSettings = { jsonData: {} } as unknown as DataSourceInstanceSettings<PromOptions>;
const ds = new PrometheusDatasource(instanceSettings, templateSrvStub, timeSrvStub);
const ds = new PrometheusDatasource(instanceSettings, templateSrvStub);
const result = ds.modifyQuery(query, action);
@ -2436,7 +2468,7 @@ describe('modifyQuery', () => {
const query: PromQuery = { refId: 'A', expr: 'go_goroutines{cluster="us-cluster"}' };
const action = { options: { key: 'pod', value: 'pod-123' }, type: 'ADD_FILTER_OUT' };
const instanceSettings = { jsonData: {} } as unknown as DataSourceInstanceSettings<PromOptions>;
const ds = new PrometheusDatasource(instanceSettings, templateSrvStub, timeSrvStub);
const ds = new PrometheusDatasource(instanceSettings, templateSrvStub);
const result = ds.modifyQuery(query, action);
@ -2559,3 +2591,10 @@ function createEmptyAnnotationResponse() {
return { ...response };
}
function getMockTimeRange(range = '6h'): TimeRange {
return rangeUtil.convertRawToRange({
from: `now-${range}`,
to: 'now',
});
}

View File

@ -19,6 +19,8 @@ import {
DataSourceWithQueryExportSupport,
DataSourceWithQueryImportSupport,
dateTime,
getDefaultTimeRange,
LegacyMetricFindQueryOptions,
LoadingState,
MetricFindValue,
QueryFixAction,
@ -38,7 +40,6 @@ import {
toDataQueryResponse,
} from '@grafana/runtime';
import { safeStringifyValue } from 'app/core/utils/explore';
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { getTemplateSrv, TemplateSrv } from 'app/features/templating/template_srv';
import { addLabelToQuery } from './add_label_to_query';
@ -109,7 +110,6 @@ export class PrometheusDatasource
constructor(
instanceSettings: DataSourceInstanceSettings<PromOptions>,
private readonly templateSrv: TemplateSrv = getTemplateSrv(),
private readonly timeSrv: TimeSrv = getTimeSrv(),
languageProvider?: PrometheusLanguageProvider
) {
super(instanceSettings);
@ -136,7 +136,7 @@ export class PrometheusDatasource
this.datasourceConfigurationPrometheusVersion = instanceSettings.jsonData.prometheusVersion;
this.defaultEditor = instanceSettings.jsonData.defaultEditor;
this.disableRecordingRules = instanceSettings.jsonData.disableRecordingRules ?? false;
this.variables = new PrometheusVariableSupport(this, this.templateSrv, this.timeSrv);
this.variables = new PrometheusVariableSupport(this, this.templateSrv);
this.exemplarsAvailable = true;
this.cacheLevel = instanceSettings.jsonData.cacheLevel ?? PrometheusCacheLevel.Low;
@ -448,7 +448,7 @@ export class PrometheusDatasource
exemplar: this.shouldRunExemplarQuery(target, request),
requestId: request.panelId + target.refId,
// We need to pass utcOffsetSec to backend to calculate aligned range
utcOffsetSec: this.timeSrv.timeRange().to.utcOffset() * 60,
utcOffsetSec: request.range.to.utcOffset() * 60,
};
if (target.instant && target.range) {
// We have query type "Both" selected
@ -673,7 +673,7 @@ export class PrometheusDatasource
// Align query interval with step to allow query caching and to ensure
// that about-same-time query results look the same.
const adjusted = alignRange(start, end, query.step, this.timeSrv.timeRange().to.utcOffset() * 60);
const adjusted = alignRange(start, end, query.step, options.range.to.utcOffset() * 60);
query.start = adjusted.start;
query.end = adjusted.end;
this._addTracingHeaders(query, options);
@ -789,7 +789,7 @@ export class PrometheusDatasource
return error;
};
metricFindQuery(query: string) {
metricFindQuery(query: string, options?: LegacyMetricFindQueryOptions) {
if (!query) {
return Promise.resolve([]);
}
@ -797,14 +797,14 @@ export class PrometheusDatasource
const scopedVars = {
__interval: { text: this.interval, value: this.interval },
__interval_ms: { text: rangeUtil.intervalToMs(this.interval), value: rangeUtil.intervalToMs(this.interval) },
...this.getRangeScopedVars(this.timeSrv.timeRange()),
...this.getRangeScopedVars(options?.range ?? getDefaultTimeRange()),
};
const interpolated = this.templateSrv.replace(query, scopedVars, this.interpolateQueryExpr);
const metricFindQuery = new PrometheusMetricFindQuery(this, interpolated);
return metricFindQuery.process();
return metricFindQuery.process(options?.range ?? getDefaultTimeRange());
}
getRangeScopedVars(range: TimeRange = this.timeSrv.timeRange()) {
getRangeScopedVars(range: TimeRange) {
const msRange = range.to.diff(range.from);
const sRange = Math.round(msRange / 1000);
return {
@ -963,7 +963,7 @@ export class PrometheusDatasource
// and in Tempo here grafana/public/app/plugins/datasource/tempo/QueryEditor/ServiceGraphSection.tsx
async getTagKeys(options: DataSourceGetTagKeysOptions): Promise<MetricFindValue[]> {
if (!options || options.filters.length === 0) {
await this.languageProvider.fetchLabels();
await this.languageProvider.fetchLabels(options.timeRange);
return this.languageProvider.getLabelKeys().map((k) => ({ value: k, text: k }));
}
@ -1005,7 +1005,7 @@ export class PrometheusDatasource
}));
}
const params = this.getTimeRangeParams();
const params = this.getTimeRangeParams(options.timeRange ?? getDefaultTimeRange());
const result = await this.metadataRequest(`/api/v1/label/${options.key}/values`, params);
return result?.data?.data?.map((value: any) => ({ text: value })) ?? [];
}
@ -1123,9 +1123,8 @@ export class PrometheusDatasource
/**
* Returns the adjusted "snapped" interval parameters
*/
getAdjustedInterval(): { start: string; end: string } {
const range = this.timeSrv.timeRange();
return getRangeSnapInterval(this.cacheLevel, range);
getAdjustedInterval(timeRange: TimeRange): { start: string; end: string } {
return getRangeSnapInterval(this.cacheLevel, timeRange);
}
/**
@ -1133,16 +1132,15 @@ export class PrometheusDatasource
* and then a little extra padding to round up/down to the nearest nth minute,
* defined by the result of the getCacheDurationInMinutes.
*
* For longer cache durations, and shorter query durations, the window we're calculating might be much bigger then the user's current window,
* resulting in us returning labels/values that might not be applicable for the given window, this is a necessary trade off if we want to cache larger durations
*
* For longer cache durations, and shorter query durations,
* the window we're calculating might be much bigger then the user's current window,
* resulting in us returning labels/values that might not be applicable for the given window,
* this is a necessary trade-off if we want to cache larger durations
*/
getTimeRangeParams(): { start: string; end: string } {
const range = this.timeSrv.timeRange();
getTimeRangeParams(timeRange: TimeRange): { start: string; end: string } {
return {
start: getPrometheusTime(range.from, false).toString(),
end: getPrometheusTime(range.to, true).toString(),
start: getPrometheusTime(timeRange.from, false).toString(),
end: getPrometheusTime(timeRange.to, true).toString(),
};
}

View File

@ -17,7 +17,21 @@ const fromPrometheusTime = getPrometheusTime(dateTime(now - timeRangeDurationSec
const toPrometheusTimeString = toPrometheusTime.toString(10);
const fromPrometheusTimeString = fromPrometheusTime.toString(10);
const getTimeRangeParams = (override?: Partial<{ start: string; end: string }>): { start: string; end: string } => ({
const getMockTimeRange = (): TimeRange => {
return {
to: dateTime(now).utc(),
from: dateTime(now).subtract(timeRangeDurationSeconds, 'second').utc(),
raw: {
from: fromPrometheusTimeString,
to: toPrometheusTimeString,
},
};
};
const getTimeRangeParams = (
timRange: TimeRange,
override?: Partial<{ start: string; end: string }>
): { start: string; end: string } => ({
start: fromPrometheusTimeString,
end: toPrometheusTimeString,
...override,
@ -125,7 +139,8 @@ describe('Language completion provider', () => {
it('should call series endpoint', () => {
const languageProvider = new LanguageProvider({
...defaultDatasource,
getAdjustedInterval: () => getRangeSnapInterval(PrometheusCacheLevel.None, getMockQuantizedTimeRangeParams()),
getAdjustedInterval: (timeRange: TimeRange) =>
getRangeSnapInterval(PrometheusCacheLevel.None, getMockQuantizedTimeRangeParams()),
} as PrometheusDatasource);
const getSeriesLabels = languageProvider.getSeriesLabels;
const requestSpy = jest.spyOn(languageProvider, 'request');
@ -152,7 +167,8 @@ describe('Language completion provider', () => {
...defaultDatasource,
hasLabelsMatchAPISupport: () => true,
cacheLevel: PrometheusCacheLevel.Low,
getAdjustedInterval: () => getRangeSnapInterval(PrometheusCacheLevel.Low, getMockQuantizedTimeRangeParams()),
getAdjustedInterval: (timeRange: TimeRange) =>
getRangeSnapInterval(PrometheusCacheLevel.Low, getMockQuantizedTimeRangeParams()),
getCacheDurationInMinutes: () => timeSnapMinutes,
} as PrometheusDatasource);
const getSeriesLabels = languageProvider.getSeriesLabels;
@ -251,16 +267,21 @@ describe('Language completion provider', () => {
});
describe('fetchSeries', () => {
it('should use match[] parameter', () => {
it('should use match[] parameter', async () => {
const languageProvider = new LanguageProvider(defaultDatasource);
const fetchSeries = languageProvider.fetchSeries;
const timeRange = getMockTimeRange();
await languageProvider.start(timeRange);
const requestSpy = jest.spyOn(languageProvider, 'request');
fetchSeries('{job="grafana"}');
await languageProvider.fetchSeries('{job="grafana"}');
expect(requestSpy).toHaveBeenCalled();
expect(requestSpy).toHaveBeenCalledWith(
'/api/v1/series',
{},
{ end: toPrometheusTimeString, 'match[]': '{job="grafana"}', start: fromPrometheusTimeString },
{
end: toPrometheusTimeString,
'match[]': '{job="grafana"}',
start: fromPrometheusTimeString,
},
undefined
);
});

View File

@ -7,8 +7,10 @@ import {
AbstractLabelOperator,
AbstractQuery,
dateTime,
getDefaultTimeRange,
HistoryItem,
LanguageProvider,
TimeRange,
} from '@grafana/data';
import { BackendSrvRequest } from '@grafana/runtime';
import { CompletionItem, CompletionItemGroup, SearchFunctionType, TypeaheadInput, TypeaheadOutput } from '@grafana/ui';
@ -107,7 +109,7 @@ interface AutocompleteContext {
const secondsInDay = 86400;
export default class PromQlLanguageProvider extends LanguageProvider {
histogramMetrics: string[];
timeRange?: { start: number; end: number };
timeRange: TimeRange;
metrics: string[];
metricsMetadata?: PromMetricsMetadata;
declare startTask: Promise<any>;
@ -120,7 +122,7 @@ export default class PromQlLanguageProvider extends LanguageProvider {
this.datasource = datasource;
this.histogramMetrics = [];
this.timeRange = { start: 0, end: 0 };
this.timeRange = getDefaultTimeRange();
this.metrics = [];
Object.assign(this, initialValues);
@ -155,7 +157,9 @@ export default class PromQlLanguageProvider extends LanguageProvider {
return defaultValue;
};
start = async (): Promise<any[]> => {
start = async (timeRange?: TimeRange): Promise<any[]> => {
this.timeRange = timeRange ?? getDefaultTimeRange();
if (this.datasource.lookupsDisabled) {
return [];
}
@ -504,7 +508,7 @@ export default class PromQlLanguageProvider extends LanguageProvider {
* @param key
*/
fetchLabelValues = async (key: string): Promise<string[]> => {
const params = this.datasource.getAdjustedInterval();
const params = this.datasource.getAdjustedInterval(this.timeRange);
const interpolatedName = this.datasource.interpolateString(key);
const url = `/api/v1/label/${interpolatedName}/values`;
const value = await this.request(url, [], params, this.getDefaultCacheHeaders());
@ -518,9 +522,12 @@ export default class PromQlLanguageProvider extends LanguageProvider {
/**
* Fetches all label keys
*/
async fetchLabels(): Promise<string[]> {
async fetchLabels(timeRange?: TimeRange): Promise<string[]> {
if (timeRange) {
this.timeRange = timeRange;
}
const url = '/api/v1/labels';
const params = this.datasource.getAdjustedInterval();
const params = this.datasource.getAdjustedInterval(this.timeRange);
this.labelFetchTs = Date.now().valueOf();
const res = await this.request(url, [], params, this.getDefaultCacheHeaders());
@ -554,7 +561,7 @@ export default class PromQlLanguageProvider extends LanguageProvider {
fetchSeriesValuesWithMatch = async (name: string, match?: string): Promise<string[]> => {
const interpolatedName = name ? this.datasource.interpolateString(name) : null;
const interpolatedMatch = match ? this.datasource.interpolateString(match) : null;
const range = this.datasource.getAdjustedInterval();
const range = this.datasource.getAdjustedInterval(this.timeRange);
const urlParams = {
...range,
...(interpolatedMatch && { 'match[]': interpolatedMatch }),
@ -603,7 +610,7 @@ export default class PromQlLanguageProvider extends LanguageProvider {
*/
fetchSeriesLabels = async (name: string, withName?: boolean): Promise<Record<string, string[]>> => {
const interpolatedName = this.datasource.interpolateString(name);
const range = this.datasource.getAdjustedInterval();
const range = this.datasource.getAdjustedInterval(this.timeRange);
const urlParams = {
...range,
'match[]': interpolatedName,
@ -623,7 +630,7 @@ export default class PromQlLanguageProvider extends LanguageProvider {
*/
fetchSeriesLabelsMatch = async (name: string, withName?: boolean): Promise<Record<string, string[]>> => {
const interpolatedName = this.datasource.interpolateString(name);
const range = this.datasource.getAdjustedInterval();
const range = this.datasource.getAdjustedInterval(this.timeRange);
const urlParams = {
...range,
'match[]': interpolatedName,
@ -641,7 +648,7 @@ export default class PromQlLanguageProvider extends LanguageProvider {
*/
fetchSeries = async (match: string): Promise<Array<Record<string, string>>> => {
const url = '/api/v1/series';
const range = this.datasource.getTimeRangeParams();
const range = this.datasource.getTimeRangeParams(this.timeRange);
const params = { ...range, 'match[]': match };
return await this.request(url, {}, params, this.getDefaultCacheHeaders());
};

View File

@ -1,7 +1,7 @@
import 'whatwg-fetch'; // fetch polyfill needed backendSrv
import { of } from 'rxjs';
import { DataSourceInstanceSettings, toUtc } from '@grafana/data';
import { DataSourceInstanceSettings, TimeRange, toUtc } from '@grafana/data';
import { FetchResponse } from '@grafana/runtime';
import { backendSrv } from 'app/core/services/backend_srv'; // will use the version in __mocks__
import { TemplateSrv } from 'app/features/templating/template_srv';
@ -26,24 +26,16 @@ const instanceSettings = {
password: 'mupp',
jsonData: { httpMethod: 'GET' },
} as Partial<DataSourceInstanceSettings<PromOptions>> as DataSourceInstanceSettings<PromOptions>;
const raw = {
const raw: TimeRange = {
from: toUtc('2018-04-25 10:00'),
to: toUtc('2018-04-25 11:00'),
raw: {
from: '2018-04-25 10:00',
to: '2018-04-25 11:00',
},
};
jest.mock('app/features/dashboard/services/TimeSrv', () => ({
__esModule: true,
getTimeSrv: jest.fn().mockReturnValue({
timeRange() {
return {
from: raw.from,
to: raw.to,
raw: raw,
};
},
}),
}));
const templateSrvStub = {
getAdhocFilters: jest.fn().mockImplementation(() => []),
replace: jest.fn().mockImplementation((a: string) => a),
@ -80,7 +72,7 @@ describe('PrometheusMetricFindQuery', () => {
data: ['name1', 'name2', 'name3'],
},
});
const results = await query.process();
const results = await query.process(raw);
expect(results).toHaveLength(3);
expect(fetchMock).toHaveBeenCalledTimes(1);
@ -102,7 +94,7 @@ describe('PrometheusMetricFindQuery', () => {
data: ['value1', 'value2', 'value3'],
},
});
const results = await query.process();
const results = await query.process(raw);
expect(results).toHaveLength(3);
expect(fetchMock).toHaveBeenCalledTimes(1);
@ -126,7 +118,7 @@ describe('PrometheusMetricFindQuery', () => {
],
},
});
const results = await query.process();
const results = await query.process(raw);
expect(results).toHaveLength(3);
expect(fetchMock).toHaveBeenCalledTimes(1);
@ -152,7 +144,7 @@ describe('PrometheusMetricFindQuery', () => {
],
},
});
const results = await query.process();
const results = await query.process(raw);
expect(results).toHaveLength(3);
expect(fetchMock).toHaveBeenCalledTimes(1);
@ -176,7 +168,7 @@ describe('PrometheusMetricFindQuery', () => {
],
},
});
const results = await query.process();
const results = await query.process(raw);
expect(results).toHaveLength(2);
expect(results[0].text).toBe('value1');
@ -201,7 +193,7 @@ describe('PrometheusMetricFindQuery', () => {
data: ['metric1', 'metric2', 'metric3', 'nomatch'],
},
});
const results = await query.process();
const results = await query.process(raw);
expect(results).toHaveLength(3);
expect(fetchMock).toHaveBeenCalledTimes(1);
@ -228,7 +220,7 @@ describe('PrometheusMetricFindQuery', () => {
},
},
});
const results = await query.process();
const results = await query.process(raw);
expect(results).toHaveLength(1);
expect(results[0].text).toBe('metric{job="testjob"} 3846 1443454528000');
@ -251,7 +243,7 @@ describe('PrometheusMetricFindQuery', () => {
},
},
});
const results = await query.process();
const results = await query.process(raw);
expect(results).toHaveLength(1);
expect(results[0].text).toBe('2');
expect(fetchMock).toHaveBeenCalledTimes(1);
@ -274,7 +266,7 @@ describe('PrometheusMetricFindQuery', () => {
],
},
});
const results = await query.process();
const results = await query.process(raw);
expect(results).toHaveLength(3);
expect(results[0].text).toBe('up{instance="127.0.0.1:1234",job="job1"}');
@ -309,7 +301,7 @@ describe('PrometheusMetricFindQuery', () => {
},
prometheusDatasource
);
const results = await query.process();
const results = await query.process(raw);
expect(results).toHaveLength(3);
expect(fetchMock).toHaveBeenCalledTimes(1);
@ -337,7 +329,7 @@ describe('PrometheusMetricFindQuery', () => {
},
prometheusDatasource
);
const results = await query.process();
const results = await query.process(raw);
expect(results).toHaveLength(1);
expect(fetchMock).toHaveBeenCalledTimes(1);

View File

@ -2,8 +2,7 @@ import { chain, map as _map, uniq } from 'lodash';
import { lastValueFrom } from 'rxjs';
import { map } from 'rxjs/operators';
import { MetricFindValue, TimeRange } from '@grafana/data';
import { getTimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { getDefaultTimeRange, MetricFindValue, TimeRange } from '@grafana/data';
import { PrometheusDatasource } from './datasource';
import { getPrometheusTime } from './language_utils';
@ -24,10 +23,11 @@ export default class PrometheusMetricFindQuery {
) {
this.datasource = datasource;
this.query = query;
this.range = getTimeSrv().timeRange();
this.range = getDefaultTimeRange();
}
process(): Promise<MetricFindValue[]> {
process(timeRange: TimeRange): Promise<MetricFindValue[]> {
this.range = timeRange;
const labelNamesRegex = PrometheusLabelNamesRegex;
const labelNamesRegexWithMatch = PrometheusLabelNamesRegexWithMatch;
const labelValuesRegex = /^label_values\((?:(.+),\s*)?([a-zA-Z_][a-zA-Z0-9_]*)\)\s*$/;
@ -46,7 +46,7 @@ export default class PrometheusMetricFindQuery {
}
if (labelNamesQuery) {
return this.datasource.getTagKeys({ filters: [] });
return this.datasource.getTagKeys({ filters: [], timeRange });
}
const labelValuesQuery = this.query.match(labelValuesRegex);

View File

@ -60,6 +60,7 @@ export function MetricsLabelsSection({
const onGetLabelNames = async (forLabel: Partial<QueryBuilderLabelFilter>): Promise<SelectableValue[]> => {
// If no metric we need to use a different method
if (!query.metric) {
// FIXME pass timeRange to fetchLabels method
await datasource.languageProvider.fetchLabels();
return datasource.languageProvider.getLabelKeys().map((k) => ({ value: k }));
}

View File

@ -318,7 +318,6 @@ function createDatasource(options?: Partial<DataSourceInstanceSettings<PromOptio
...options,
} as DataSourceInstanceSettings<PromOptions>,
undefined,
undefined,
languageProvider
);
return { datasource, languageProvider };

View File

@ -44,7 +44,6 @@ function setup(queryOverrides: Partial<PromQuery> = {}) {
meta: {} as DataSourcePluginMeta,
} as DataSourceInstanceSettings,
undefined,
undefined,
languageProvider
);

View File

@ -24,7 +24,6 @@ function createDatasource() {
meta: {} as DataSourcePluginMeta,
} as DataSourceInstanceSettings,
undefined,
undefined,
languageProvider
);
return { datasource, languageProvider };

View File

@ -84,7 +84,6 @@ const getDefaultDatasource = (jsonDataOverrides = {}) =>
readOnly: false,
},
undefined,
undefined,
new EmptyLanguageProviderMock() as unknown as PromQlLanguageProvider
);

View File

@ -233,7 +233,6 @@ function createDatasource(withLabels?: boolean) {
meta: {} as DataSourcePluginMeta,
} as DataSourceInstanceSettings<PromOptions>,
undefined,
undefined,
languageProvider
);
return datasource;

View File

@ -116,7 +116,6 @@ function createDatasource(withLabels?: boolean) {
meta: {} as DataSourcePluginMeta,
} as DataSourceInstanceSettings<PromOptions>,
undefined,
undefined,
languageProvider
);
return datasource;

View File

@ -73,7 +73,6 @@ function setup(query: PromVisualQuery = defaultQuery) {
meta: {},
} as DataSourceInstanceSettings<PromOptions>,
undefined,
undefined,
languageProvider
) as DataSourceApi,
onRunQuery: () => {},

View File

@ -12,7 +12,6 @@ import { faro } from '@grafana/faro-web-sdk';
import { config, reportInteraction } from '@grafana/runtime/src';
import { amendTable, Table, trimTable } from 'app/features/live/data/amendTimeSeries';
import { getTimeSrv } from '../../../../features/dashboard/services/TimeSrv';
import { PromQuery } from '../types';
// dashboardUID + panelId + refId
@ -260,7 +259,7 @@ export class QueryCache<T extends SupportedQueryTypes> {
let doPartialQuery = shouldCache;
let prevTo: TimestampMs | undefined = undefined;
const refreshIntervalMs = getTimeSrv().refreshMS;
const refreshIntervalMs = request.intervalMs;
// pre-compute reqTargSigs
const reqTargSigs = new Map<TargetIdent, TargetSig>();

View File

@ -4,8 +4,6 @@ import { map } from 'rxjs/operators';
import { CustomVariableSupport, DataQueryRequest, DataQueryResponse, rangeUtil } from '@grafana/data';
import { getTemplateSrv, TemplateSrv } from '@grafana/runtime';
import { getTimeSrv, TimeSrv } from '../../../features/dashboard/services/TimeSrv';
import { PromVariableQueryEditor } from './components/VariableQueryEditor';
import { PrometheusDatasource } from './datasource';
import PrometheusMetricFindQuery from './metric_find_query';
@ -14,8 +12,7 @@ import { PromVariableQuery } from './types';
export class PrometheusVariableSupport extends CustomVariableSupport<PrometheusDatasource> {
constructor(
private readonly datasource: PrometheusDatasource,
private readonly templateSrv: TemplateSrv = getTemplateSrv(),
private readonly timeSrv: TimeSrv = getTimeSrv()
private readonly templateSrv: TemplateSrv = getTemplateSrv()
) {
super();
}
@ -49,12 +46,12 @@ export class PrometheusVariableSupport extends CustomVariableSupport<PrometheusD
text: rangeUtil.intervalToMs(this.datasource.interval),
value: rangeUtil.intervalToMs(this.datasource.interval),
},
...this.datasource.getRangeScopedVars(this.timeSrv.timeRange()),
...this.datasource.getRangeScopedVars(request.range),
};
const interpolated = this.templateSrv.replace(query, scopedVars, this.datasource.interpolateQueryExpr);
const metricFindQuery = new PrometheusMetricFindQuery(this.datasource, interpolated);
const metricFindStream = from(metricFindQuery.process());
const metricFindStream = from(metricFindQuery.process(request.range));
return metricFindStream.pipe(map((results) => ({ data: results })));
}