diff --git a/server/package.json b/server/package.json index 23b3adb..72ac065 100644 --- a/server/package.json +++ b/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", diff --git a/server/src/controllers/analytics_controller.ts b/server/src/controllers/analytics_controller.ts index 64ff280..d00c45b 100644 --- a/server/src/controllers/analytics_controller.ts +++ b/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'); } diff --git a/server/src/models/analytic_unit_model.ts b/server/src/models/analytic_unit_model.ts index e582ef7..0b7cde7 100644 --- a/server/src/models/analytic_unit_model.ts +++ b/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); diff --git a/server/src/models/grafana_metric_model.ts b/server/src/models/grafana_metric_model.ts deleted file mode 100644 index 9991233..0000000 --- a/server/src/models/grafana_metric_model.ts +++ /dev/null @@ -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}`; - } -} diff --git a/server/src/services/grafana_service.ts b/server/src/services/grafana_service.ts deleted file mode 100644 index 8fac5f7..0000000 --- a/server/src/services/grafana_service.ts +++ /dev/null @@ -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][]; -}