Graphite: add metrictank meta in response (#20138)

* metric tank meta

* add metric tank info

* fixed info box

* process meta

* attach metrictank meta to response

* remove extra logging

* response is now DataFrame

* Minor refactoring and renaming of the prop

* Graphite: minor fixes
This commit is contained in:
Ryan McKinley 2019-11-08 01:38:10 -08:00 committed by Torkel Ödegaard
parent b63e4a9f52
commit 026d13469f
5 changed files with 84 additions and 21 deletions

View File

@ -1,16 +1,23 @@
import DatasourceSrv from 'app/features/plugins/datasource_srv';
import { GraphiteType } from './types';
export class GraphiteConfigCtrl {
static templateUrl = 'public/app/plugins/datasource/graphite/partials/config.html';
datasourceSrv: any;
current: any;
graphiteTypes: any;
/** @ngInject */
constructor($scope: any, datasourceSrv: DatasourceSrv) {
this.datasourceSrv = datasourceSrv;
this.current.jsonData = this.current.jsonData || {};
this.current.jsonData.graphiteVersion = this.current.jsonData.graphiteVersion || '0.9';
this.current.jsonData.graphiteType = this.current.jsonData.graphiteType || GraphiteType.Default;
this.autoDetectGraphiteVersion();
this.graphiteTypes = Object.keys(GraphiteType).map((key: string) => ({
name: key,
value: (GraphiteType as any)[key],
}));
}
autoDetectGraphiteVersion() {
@ -24,6 +31,10 @@ export class GraphiteConfigCtrl {
return ds.getVersion();
})
.then((version: any) => {
if (!version) {
return;
}
this.graphiteVersions.push({ name: version, value: version });
this.current.jsonData.graphiteVersion = version;
});

View File

@ -1,12 +1,12 @@
import _ from 'lodash';
import { dateMath, ScopedVars } from '@grafana/data';
import { DataFrame, dateMath, ScopedVars, DataQueryResponse, DataQueryRequest, toDataFrame } from '@grafana/data';
import { isVersionGtOrEq, SemVersion } from 'app/core/utils/version';
import gfunc from './gfunc';
import { IQService } from 'angular';
import { BackendSrv } from 'app/core/services/backend_srv';
import { TemplateSrv } from 'app/features/templating/template_srv';
//Types
import { GraphiteQuery } from './types';
import { GraphiteQuery, GraphiteType } from './types';
import { getSearchFilterScopedVar } from '../../../features/templating/variable';
export class GraphiteDatasource {
@ -15,6 +15,7 @@ export class GraphiteDatasource {
name: string;
graphiteVersion: any;
supportsTags: boolean;
isMetricTank: boolean;
cacheTimeout: any;
withCredentials: boolean;
funcDefs: any = null;
@ -32,12 +33,12 @@ export class GraphiteDatasource {
this.url = instanceSettings.url;
this.name = instanceSettings.name;
this.graphiteVersion = instanceSettings.jsonData.graphiteVersion || '0.9';
this.isMetricTank = instanceSettings.jsonData.graphiteType === GraphiteType.Metrictank;
this.supportsTags = supportsTags(this.graphiteVersion);
this.cacheTimeout = instanceSettings.cacheTimeout;
this.withCredentials = instanceSettings.withCredentials;
this.funcDefs = null;
this.funcDefsPromise = null;
this._seriesRefLetters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
}
@ -54,12 +55,12 @@ export class GraphiteDatasource {
};
}
query(options: any) {
async query(options: DataQueryRequest<GraphiteQuery>): Promise<DataQueryResponse> {
const graphOptions = {
from: this.translateTime(options.rangeRaw.from, false, options.timezone),
until: this.translateTime(options.rangeRaw.to, true, options.timezone),
targets: options.targets,
format: options.format,
format: (options as any).format,
cacheTimeout: options.cacheTimeout || this.cacheTimeout,
maxDataPoints: options.maxDataPoints,
};
@ -69,6 +70,10 @@ export class GraphiteDatasource {
return this.$q.when({ data: [] });
}
if (this.isMetricTank) {
params.push('meta=true');
}
const httpOptions: any = {
method: 'POST',
url: '/render',
@ -84,7 +89,7 @@ export class GraphiteDatasource {
httpOptions.requestId = this.name + '.panelId.' + options.panelId;
}
return this.doGraphiteRequest(httpOptions).then(this.convertDataPointsToMs);
return this.doGraphiteRequest(httpOptions).then(this.convertResponseToDataFrames);
}
addTracingHeaders(httpOptions: { headers: any }, options: { dashboardId: any; panelId: any }) {
@ -95,17 +100,34 @@ export class GraphiteDatasource {
}
}
convertDataPointsToMs(result: any) {
convertResponseToDataFrames(result: any): DataQueryResponse {
const data: DataFrame[] = [];
if (!result || !result.data) {
return [];
return { data };
}
for (let i = 0; i < result.data.length; i++) {
const series = result.data[i];
for (let y = 0; y < series.datapoints.length; y++) {
series.datapoints[y][1] *= 1000;
// Series are either at the root or under a node called 'series'
const series = result.data.series || result.data;
if (!_.isArray(series)) {
throw { message: 'Missing series in result', data: result };
}
for (let i = 0; i < series.length; i++) {
const s = series[i];
for (let y = 0; y < s.datapoints.length; y++) {
s.datapoints[y][1] *= 1000;
}
const frame = toDataFrame(s);
// Metrictank metadata
if (s.meta) {
frame.meta = {
metrictank: s.meta, // array of metadata
metrictankReq: result.data.meta, // info on the request
};
}
data.push(frame);
}
return result;
return { data };
}
parseTags(tagString: string) {
@ -139,12 +161,12 @@ export class GraphiteDatasource {
// Graphite metric as annotation
if (options.annotation.target) {
const target = this.templateSrv.replace(options.annotation.target, {}, 'glob');
const graphiteQuery = {
const graphiteQuery = ({
rangeRaw: options.rangeRaw,
targets: [{ target: target }],
format: 'json',
maxDataPoints: 100,
};
} as unknown) as DataQueryRequest<GraphiteQuery>;
return this.query(graphiteQuery).then((result: { data: any[] }) => {
const list = [];
@ -513,12 +535,12 @@ export class GraphiteDatasource {
}
testDatasource() {
const query = {
const query = ({
panelId: 3,
rangeRaw: { from: 'now-1h', to: 'now' },
targets: [{ target: 'constantLine(100)' }],
maxDataPoints: 300,
};
} as unknown) as DataQueryRequest<GraphiteQuery>;
return this.query(query).then(() => {
return { status: 'success', message: 'Data source is working' };
});
@ -546,7 +568,7 @@ export class GraphiteDatasource {
return this.backendSrv.datasourceRequest(options);
}
buildGraphiteParams(options: any, scopedVars: ScopedVars) {
buildGraphiteParams(options: any, scopedVars: ScopedVars): string[] {
const graphiteOptions = ['from', 'until', 'rawData', 'format', 'maxDataPoints', 'cacheTimeout'];
const cleanOptions = [],
targets: any = {};

View File

@ -16,5 +16,30 @@
<span class="gf-form-select-wrapper">
<select class="gf-form-input gf-size-auto" ng-model="ctrl.current.jsonData.graphiteVersion" ng-options="f.value as f.name for f in ctrl.graphiteVersions"></select>
</span>
</div>
</div>
<div class="gf-form-inline">
<span class="gf-form-label width-8">Type</span>
<span class="gf-form-select-wrapper">
<select class="gf-form-input gf-size-auto" ng-model="ctrl.current.jsonData.graphiteType" ng-options="f.value as f.name
for f in ctrl.graphiteTypes"></select>
</span>
<div class="gf-form">
<label class="gf-form-label query-keyword pointer" ng-click="ctrl.showMetrictankHelp = !ctrl.showMetrictankHelp">
Help&nbsp;
<i class="fa fa-caret-down" ng-show="ctrl.showMetrictankHelp"></i>
<i class="fa fa-caret-right" ng-hide="ctrl.showMetrictankHelp">&nbsp;</i>
</label>
</div>
<div ng-if="ctrl.showMetrictankHelp" class="grafana-info-box m-t-2">
<div class="alert-body">
<p>
There are different types of Graphite compatible backends. Here you can specify the type you are using.
If you are using <a href="https://github.com/grafana/metrictank" class="pointer" target="_blank">Metrictank</a>
then select that here. This will enable Metrictank specific features like query processing meta data.
Metrictank is a multi-tenant timeseries engine for Graphite and friends.
</p>
</div>
</div>
</div>
</div>

View File

@ -73,11 +73,11 @@ describe('graphiteDatasource', () => {
it('should return series list', () => {
expect(results.data.length).toBe(1);
expect(results.data[0].target).toBe('prod1.count');
expect(results.data[0].name).toBe('prod1.count');
});
it('should convert to millisecond resolution', () => {
expect(results.data[0].datapoints[0][0]).toBe(10);
expect(results.data[0].fields[0].values.get(0)).toBe(10);
});
});

View File

@ -3,3 +3,8 @@ import { DataQuery } from '@grafana/data';
export interface GraphiteQuery extends DataQuery {
target?: string;
}
export enum GraphiteType {
Default = 'default',
Metrictank = 'metrictank',
}