mirror of
https://github.com/grafana/grafana.git
synced 2024-11-24 18:00:31 -06:00
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:
parent
b63e4a9f52
commit
026d13469f
@ -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;
|
||||
});
|
||||
|
@ -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 = {};
|
||||
|
@ -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
|
||||
<i class="fa fa-caret-down" ng-show="ctrl.showMetrictankHelp"></i>
|
||||
<i class="fa fa-caret-right" ng-hide="ctrl.showMetrictankHelp"> </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>
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -3,3 +3,8 @@ import { DataQuery } from '@grafana/data';
|
||||
export interface GraphiteQuery extends DataQuery {
|
||||
target?: string;
|
||||
}
|
||||
|
||||
export enum GraphiteType {
|
||||
Default = 'default',
|
||||
Metrictank = 'metrictank',
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user