Graphite: Fix Graphite series interpolation (#104471)

* Fix graphite series interpolation

* Trigger build
pull/99916/head
Andreas Christou 2 months ago committed by GitHub
parent 73c018b298
commit 3b036f0e78
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 109
      public/app/plugins/datasource/graphite/datasource.test.ts
  2. 28
      public/app/plugins/datasource/graphite/datasource.ts

@ -5,9 +5,9 @@ import { createFetchResponse } from 'test/helpers/createFetchResponse';
import {
AbstractLabelMatcher,
AbstractLabelOperator,
getFrameDisplayName,
dateTime,
DataQueryRequest,
dateTime,
getFrameDisplayName,
MetricFindValue,
} from '@grafana/data';
import { BackendSrvRequest } from '@grafana/runtime';
@ -373,62 +373,116 @@ describe('graphiteDatasource', () => {
describe('building graphite params', () => {
it('should return empty array if no targets', () => {
const results = ctx.ds.buildGraphiteParams({
const originalTargetMap = { A: '' };
const results = ctx.ds.buildGraphiteParams(
{
targets: [{}],
});
},
originalTargetMap
);
expect(results.length).toBe(0);
});
it('should uri escape targets', () => {
const results = ctx.ds.buildGraphiteParams({
const originalTargetMap = {
A: 'prod1.{test,test2}',
B: 'prod2.count',
};
const results = ctx.ds.buildGraphiteParams(
{
targets: [{ target: 'prod1.{test,test2}' }, { target: 'prod2.count' }],
});
},
originalTargetMap
);
expect(results).toContain('target=prod1.%7Btest%2Ctest2%7D');
});
it('should replace target placeholder', () => {
const results = ctx.ds.buildGraphiteParams({
const originalTargetMap = {
A: 'series1',
B: 'series2',
C: 'asPercent(#A,#B)',
};
const results = ctx.ds.buildGraphiteParams(
{
targets: [{ target: 'series1' }, { target: 'series2' }, { target: 'asPercent(#A,#B)' }],
});
},
originalTargetMap
);
expect(results[2]).toBe('target=asPercent(series1%2Cseries2)');
});
it('should replace target placeholder for hidden series', () => {
const results = ctx.ds.buildGraphiteParams({
const originalTargetMap = {
A: 'series1',
B: 'sumSeries(#A)',
C: 'asPercent(#A,#B)',
};
const results = ctx.ds.buildGraphiteParams(
{
targets: [
{ target: 'series1', hide: true },
{ target: 'sumSeries(#A)', hide: true },
{ target: 'asPercent(#A,#B)' },
],
});
},
originalTargetMap
);
expect(results[0]).toBe('target=' + encodeURIComponent('asPercent(series1,sumSeries(series1))'));
});
it('should replace target placeholder when nesting query references', () => {
const results = ctx.ds.buildGraphiteParams({
const originalTargetMap = {
A: 'series1',
B: 'sumSeries(#A)',
C: 'asPercent(#A,#B)',
};
const results = ctx.ds.buildGraphiteParams(
{
targets: [{ target: 'series1' }, { target: 'sumSeries(#A)' }, { target: 'asPercent(#A,#B)' }],
});
},
originalTargetMap
);
expect(results[2]).toBe('target=' + encodeURIComponent('asPercent(series1,sumSeries(series1))'));
});
it('should fix wrong minute interval parameters', () => {
const results = ctx.ds.buildGraphiteParams({
const originalTargetMap = {
A: "summarize(prod.25m.count, '25m', 'sum')",
};
const results = ctx.ds.buildGraphiteParams(
{
targets: [{ target: "summarize(prod.25m.count, '25m', 'sum')" }],
});
},
originalTargetMap
);
expect(results[0]).toBe('target=' + encodeURIComponent("summarize(prod.25m.count, '25min', 'sum')"));
});
it('should fix wrong month interval parameters', () => {
const results = ctx.ds.buildGraphiteParams({
const originalTargetMap = {
A: "summarize(prod.5M.count, '5M', 'sum')",
};
const results = ctx.ds.buildGraphiteParams(
{
targets: [{ target: "summarize(prod.5M.count, '5M', 'sum')" }],
});
},
originalTargetMap
);
expect(results[0]).toBe('target=' + encodeURIComponent("summarize(prod.5M.count, '5mon', 'sum')"));
});
it('should ignore empty targets', () => {
const results = ctx.ds.buildGraphiteParams({
const originalTargetMap = {
A: 'series1',
B: '',
};
const results = ctx.ds.buildGraphiteParams(
{
targets: [{ target: 'series1' }, { target: '' }],
});
},
originalTargetMap
);
expect(results.length).toBe(2);
});
@ -442,9 +496,15 @@ describe('graphiteDatasource', () => {
},
]);
const results = ctx.ds.buildGraphiteParams({
const originalTargetMap = {
A: 'my.$metric.*',
};
const results = ctx.ds.buildGraphiteParams(
{
targets: [{ target: 'my.$metric.*' }],
});
},
originalTargetMap
);
expect(results).toStrictEqual(['target=my.b.*', 'format=json']);
});
@ -456,10 +516,13 @@ describe('graphiteDatasource', () => {
current: { value: ['a', 'b'] },
},
]);
const results = ctx.ds.buildGraphiteParams({
const originalTargetMap = { A: 'my.[[metric]].*' };
const results = ctx.ds.buildGraphiteParams(
{
targets: [{ target: 'my.[[metric]].*' }],
});
},
originalTargetMap
);
expect(results).toStrictEqual(['target=my.%7Ba%2Cb%7D.*', 'format=json']);
});

@ -1,4 +1,4 @@
import { each, indexOf, isArray, isString, map as _map } from 'lodash';
import { map as _map, each, indexOf, isArray, isString } from 'lodash';
import { lastValueFrom, merge, Observable, of, OperatorFunction, pipe, throwError } from 'rxjs';
import { catchError, map } from 'rxjs/operators';
@ -13,13 +13,13 @@ import {
DataSourceWithQueryExportSupport,
dateMath,
dateTime,
getSearchFilterScopedVar,
MetricFindValue,
QueryResultMetaStat,
ScopedVars,
TimeRange,
TimeZone,
toDataFrame,
getSearchFilterScopedVar,
} from '@grafana/data';
import { BackendSrvRequest, FetchResponse, getBackendSrv } from '@grafana/runtime';
import { isVersionGtOrEq, SemVersion } from 'app/core/utils/version';
@ -210,12 +210,19 @@ export class GraphiteDatasource
return merge(...streams);
}
// Use this object to map the original refID of the query to our sanitised one
const refIds: { [key: string]: string } = {};
// Use this object to map the sanitised refID to the original
const formattedRefIdsMap: { [key: string]: string } = {};
// Use this object to map the original refID to the original target
const originalTargetMap: { [key: string]: string } = {};
for (const target of options.targets) {
// Sanitise the refID otherwise the Graphite query will fail
const formattedRefId = target.refId.replaceAll(' ', '_');
refIds[formattedRefId] = target.refId;
formattedRefIdsMap[formattedRefId] = target.refId;
// Track the original target to ensure if we need to interpolate a series, we interpolate using the original target
// rather than the target wrapped in aliasSub e.g.:
// Suppose a query has three targets: A: metric1 B: sumSeries(#A) and C: asPercent(#A, #B)
// We want the targets to be interpolated to: A: aliasSub(metric1, "(^.*$)", "\\1 A"), B: aliasSub(sumSeries(metric1), "(^.*$)", "\\1 B") and C: asPercent(metric1, sumSeries(metric1))
originalTargetMap[target.refId] = target.target || '';
// Use aliasSub to include the refID in the response series name. This allows us to set the refID on the frame.
const updatedTarget = `aliasSub(${target.target}, "(^.*$)", "\\1 ${formattedRefId}")`;
target.target = updatedTarget;
@ -231,7 +238,7 @@ export class GraphiteDatasource
maxDataPoints: options.maxDataPoints,
};
const params = this.buildGraphiteParams(graphOptions, options.scopedVars);
const params = this.buildGraphiteParams(graphOptions, originalTargetMap, options.scopedVars);
if (params.length === 0) {
return of({ data: [] });
}
@ -255,7 +262,9 @@ export class GraphiteDatasource
httpOptions.requestId = this.name + '.panelId.' + options.panelId;
}
return this.doGraphiteRequest(httpOptions).pipe(map((result) => this.convertResponseToDataFrames(result, refIds)));
return this.doGraphiteRequest(httpOptions).pipe(
map((result) => this.convertResponseToDataFrames(result, formattedRefIdsMap))
);
}
addTracingHeaders(
@ -975,7 +984,7 @@ export class GraphiteDatasource
);
}
buildGraphiteParams(options: any, scopedVars?: ScopedVars): string[] {
buildGraphiteParams(options: any, originalTargetMap: { [key: string]: string }, scopedVars?: ScopedVars): string[] {
const graphiteOptions = ['from', 'until', 'rawData', 'format', 'maxDataPoints', 'cacheTimeout'];
const cleanOptions = [],
targets: Record<string, string> = {};
@ -1006,7 +1015,8 @@ export class GraphiteDatasource
}
function nestedSeriesRegexReplacer(match: string, g1: string | number) {
return targets[g1] || match;
// Recursively replace all nested series references
return originalTargetMap[g1].replace(regex, nestedSeriesRegexReplacer) || match;
}
for (i = 0; i < options.targets.length; i++) {

Loading…
Cancel
Save