Browse Source

Integrate grafana datasource kit #158 (#159)

* add grafana-datasource-kit to deps

* usage of grafana-datasource-kit
pull/1/head
Alexey Velikiy 6 years ago committed by GitHub
parent
commit
dffcb94489
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      server/package.json
  2. 9
      server/src/controllers/analytics_controller.ts
  3. 3
      server/src/models/analytic_unit_model.ts
  4. 88
      server/src/models/grafana_metric_model.ts
  5. 63
      server/src/services/grafana_service.ts

1
server/package.json

@ -38,6 +38,7 @@
"es6-promise": "^4.2.4",
"event-stream": "^3.3.4",
"file-loader": "^1.1.11",
"grafana-datasource-kit": "^0.0.1",
"jest": "^23.1.1",
"koa": "^2.0.46",
"koa-bodyparser": "^4.2.0",

9
server/src/controllers/analytics_controller.ts

@ -4,7 +4,10 @@ import * as AnalyticUnitCache from '../models/analytic_unit_cache_model';
import * as Segment from '../models/segment_model';
import * as AnalyticUnit from '../models/analytic_unit_model';
import { AnalyticsService } from '../services/analytics_service';
import { queryByMetric } from '../services/grafana_service';
import { HASTIC_API_KEY } from '../config'
import { queryByMetric } from 'grafana-datasource-kit';
import * as _ from 'lodash';
@ -104,7 +107,7 @@ export async function runLearning(id: AnalyticUnit.AnalyticUnitId) {
let segmentObjs = segments.map(s => s.toObject());
let { from, to } = getQueryRangeForLearningBySegments(segments);
let data = await queryByMetric(analyticUnit.metric, analyticUnit.panelUrl, from, to);
let data = await queryByMetric(analyticUnit.metric, analyticUnit.panelUrl, from, to, HASTIC_API_KEY);
if(data.length === 0) {
throw new Error('Empty data to learn on');
}
@ -146,7 +149,7 @@ export async function runPredict(id: AnalyticUnit.AnalyticUnitId) {
}
let { from, to } = getQueryRangeForLearningBySegments(segments);
let data = await queryByMetric(unit.metric, unit.panelUrl, from, to);
let data = await queryByMetric(unit.metric, unit.panelUrl, from, to, HASTIC_API_KEY);
if(data.length === 0) {
throw new Error('Empty data to predict on');
}

3
server/src/models/analytic_unit_model.ts

@ -1,6 +1,7 @@
import { GrafanaMetric } from './grafana_metric_model';
import { Collection, makeDBQ } from '../services/data_service';
import { GrafanaMetric } from 'grafana-datasource-kit';
let db = makeDBQ(Collection.ANALYTIC_UNITS);

88
server/src/models/grafana_metric_model.ts

@ -1,88 +0,0 @@
export type GrafanaDatasource = {
url: string,
type: string,
params: {
db: string,
q: string,
epoch: string
}
}
export type GrafanaMetricId = string;
export class GrafanaMetric {
private _metricQuery: MetricQuery = undefined;
constructor(
public datasource: GrafanaDatasource,
public targets: any[],
public id?: GrafanaMetricId
) {
if(datasource === undefined) {
throw new Error('datasource is undefined');
}
if(targets === undefined) {
throw new Error('targets is undefined');
}
if(targets.length === 0) {
throw new Error('targets is empty');
}
}
public get metricQuery() {
if(this._metricQuery === undefined) {
this._metricQuery = new MetricQuery(this);
}
return this._metricQuery;
}
public toObject() {
return {
datasource: this.datasource,
targets: this.targets,
_id: this.id
};
}
static fromObject(obj: any): GrafanaMetric {
if(obj === undefined) {
throw new Error('obj is undefined');
}
return new GrafanaMetric(
obj.datasource,
obj.targets,
obj._id
);
}
}
export class MetricQuery {
private static INFLUX_QUERY_TIME_REGEX = /time >[^A-Z]+/;
private _queryParts: string[];
private _type: string;
constructor(metric: GrafanaMetric) {
this._type = metric.datasource.type;
if (this._type !== 'influxdb') {
throw new Error(`Queries of type "${metric.datasource.type}" are not supported yet.`);
}
var queryStr = metric.datasource.params.q;
this._queryParts = queryStr.split(MetricQuery.INFLUX_QUERY_TIME_REGEX);
if(this._queryParts.length == 1) {
throw new Error(
`Query "${queryStr}" is not replaced with LIMIT/OFFSET oeprators. Missing time clause.`
);
}
if(this._queryParts.length > 2) {
throw new Error(`Query "${queryStr}" has multiple time clauses. Can't parse.`);
}
}
getQuery(from: number, to: number, limit: number, offset: number): string {
let timeClause = `time >= ${from}ms AND time <= ${to}ms`;
return `${this._queryParts[0]} ${timeClause} ${this._queryParts[1]} LIMIT ${limit} OFFSET ${offset}`;
}
}

63
server/src/services/grafana_service.ts

@ -1,63 +0,0 @@
import { GrafanaMetric } from '../models/grafana_metric_model';
import { HASTIC_API_KEY } from '../config';
import { URL } from 'url';
import axios from 'axios';
const CHUNK_SIZE = 50000;
/**
* @param metric to query to Grafana
* @returns [time, value][] array
*/
export async function queryByMetric(
metric: GrafanaMetric, panelUrl: string, from: number, to: number
): Promise<[number, number][]> {
let datasource = metric.datasource;
let origin = new URL(panelUrl).origin;
let url = `${origin}/${datasource.url}`;
let params = datasource.params
let data = [];
let chunkParams = Object.assign({}, params);
while(true) {
chunkParams.q = metric.metricQuery.getQuery(from, to, CHUNK_SIZE, data.length);
var chunk = await queryGrafana(url, chunkParams);
data = data.concat(chunk);
if(chunk.length < CHUNK_SIZE) {
// because if we get less that we could, then there is nothing more
break;
}
}
return data;
}
async function queryGrafana(url: string, params: any) {
let headers = { Authorization: `Bearer ${HASTIC_API_KEY}` };
try {
var res = await axios.get(url, { params, headers });
} catch (e) {
if(e.response.status === 401) {
throw new Error('Unauthorized. Check the $HASTIC_API_KEY.');
}
throw new Error(e.message);
}
if (res.data.results === undefined) {
throw new Error('results field is undefined in response.');
}
// TODO: support more than 1 metric (each res.data.results item is a metric)
let results = res.data.results[0];
if (results.series === undefined) {
return [];
}
return results.series[0].values as [number, number][];
}
Loading…
Cancel
Save