Browse Source

Update Data Puller state #294 (#295)

* Get analytic unit from db each puller tick

* Use async iterator to pull and push data

* Fix webpack config for proper babel-polyfill usage

* Add MetricDataChunk type

* Add pattern field to payload
pull/1/head
rozetko 6 years ago committed by GitHub
parent
commit
053e1a4e9d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      server/build/webpack.614.prod.conf.js
  2. 2
      server/build/webpack.base.conf.js
  3. 15
      server/src/controllers/analytics_controller.ts
  4. 2
      server/src/routes/analytic_units_router.ts
  5. 105
      server/src/services/data_puller.ts

2
server/build/webpack.614.prod.conf.js

@ -7,7 +7,7 @@ module.exports = {
__dirname: false,
__filename: false,
},
entry: [ 'babel-polyfill', './dist/server-dev.js' ],
entry: [ './dist/server-dev.js' ],
output: {
filename: "server.js",
path: path.join(__dirname, '../dist')

2
server/build/webpack.base.conf.js

@ -14,7 +14,7 @@ module.exports = {
__dirname: false,
__filename: false,
},
entry: [ './src/index.ts' ],
entry: [ 'babel-polyfill', './src/index.ts' ],
output: {
filename: "server-dev.js",
path: resolve('dist')

15
server/src/controllers/analytics_controller.ts

@ -286,13 +286,20 @@ export async function createAnalyticUnitFromObject(obj: any): Promise<AnalyticUn
}
let unit: AnalyticUnit.AnalyticUnit = AnalyticUnit.AnalyticUnit.fromObject(obj);
let id = await AnalyticUnit.create(unit);
unit.id = id;
return id;
}
export async function setAlert(analyticUnitId: AnalyticUnit.AnalyticUnitId, alert: boolean) {
AnalyticUnit.setAlert(analyticUnitId, alert);
if(dataPuller !== undefined) {
dataPuller.addUnit(unit);
if(alert) {
const analyticUnit = await AnalyticUnit.findById(analyticUnitId);
dataPuller.addUnit(analyticUnit);
} else {
dataPuller.deleteUnit(analyticUnitId);
}
}
return id;
}
export async function updateSegments(

2
server/src/routes/analytic_units_router.ts

@ -113,7 +113,7 @@ async function setAlert(ctx: Router.IRouterContext) {
throw new Error('Cannot set undefined alert status');
}
await AnalyticUnit.setAlert(analyticUnitId, alert);
await AnalyticsController.setAlert(analyticUnitId, alert);
ctx.response.body = {
code: 200,

105
server/src/services/data_puller.ts

@ -8,32 +8,28 @@ import { queryByMetric } from 'grafana-datasource-kit';
import * as _ from 'lodash';
declare type UnitTime = {
unit: AnalyticUnit.AnalyticUnit,
time: number
};
type MetricDataChunk = { values: [number, number][], columns: string[] };
const PULL_PERIOD_MS = 5000;
export class DataPuller {
private PULL_PERIOD_MS: number = 5000;
private _interval: number = 1000;
private _timer: any = null;
private _unitTimes: { [id: string]: UnitTime } = {};
private _unitTimes: { [analyticUnitId: string]: number } = {};
constructor(private analyticsService: AnalyticsService){};
constructor(private analyticsService: AnalyticsService) {};
public addUnit(unit: AnalyticUnit.AnalyticUnit) {
let time = unit.lastDetectionTime || Date.now();
let unitTime: UnitTime = {unit, time };
this._unitTimes[unit.id] = unitTime;
public addUnit(analyticUnit: AnalyticUnit.AnalyticUnit) {
this._runAnalyticUnitPuller(analyticUnit);
}
public deleteUnit(id: AnalyticUnit.AnalyticUnitId) {
delete this._unitTimes[id];
public deleteUnit(analyticUnitId: AnalyticUnit.AnalyticUnitId) {
if(_.has(this._unitTimes, analyticUnitId)) {
delete this._unitTimes[analyticUnitId];
}
}
private pullData(unit: AnalyticUnit.AnalyticUnit, from: number, to: number) {
if(!unit) {
private async pullData(unit: AnalyticUnit.AnalyticUnit, from: number, to: number): Promise<MetricDataChunk> {
if(unit === undefined) {
throw Error(`puller: can't pull undefined unit`);
}
return queryByMetric(unit.metric, unit.panelUrl, from, to, HASTIC_API_KEY);
@ -48,46 +44,65 @@ export class DataPuller {
}
//TODO: group analyticUnits by panelID and send same dataset for group
public runPuller() {
this._timer = setTimeout(this.puller.bind(this), this._interval);
public async runPuller() {
const analyticUnits = await AnalyticUnit.findMany({ alert: true });
_.each(analyticUnits, analyticUnit => {
this._runAnalyticUnitPuller(analyticUnit);
});
console.log('Data puller runned');
}
public stopPuller() {
if(this._timer) {
clearTimeout(this._timer);
this._timer = null;
this._interval = 0;
console.log('Data puller stopped');
}
console.log('Data puller already stopped');
this._unitTimes = {};
}
private async puller() {
if(_.isEmpty(this._unitTimes)) {
this._interval = this.PULL_PERIOD_MS;
this._timer = setTimeout(this.puller.bind(this), this._interval);
return;
}
private async _runAnalyticUnitPuller(analyticUnit: AnalyticUnit.AnalyticUnit) {
const time = analyticUnit.lastDetectionTime || Date.now();
this._unitTimes[analyticUnit.id] = time;
let now = Date.now();
const dataGenerator = this.getDataGenerator(
analyticUnit, PULL_PERIOD_MS
);
_.forOwn(this._unitTimes, async value => {
if(!value.unit.alert) {
return;
for await (const data of dataGenerator) {
if(!_.has(this._unitTimes, analyticUnit.id)) {
break;
}
let data = await this.pullData(value.unit, value.time, now);
if(data.values.length === 0) {
return;
continue;
}
let payload = { data, from: value.time, to: now};
value.time = now;
this.pushData(value.unit, payload);
});
this._timer = setTimeout(this.puller.bind(this), this._interval);
const now = Date.now();
let payload = { data, from: time, to: now, pattern: analyticUnit.type };
this._unitTimes[analyticUnit.id] = now;
this.pushData(analyticUnit, payload);
}
}
async * getDataGenerator(analyticUnit: AnalyticUnit.AnalyticUnit, duration: number):
AsyncIterableIterator<MetricDataChunk> {
const getData = async () => {
try {
const time = this._unitTimes[analyticUnit.id]
const now = Date.now();
return await this.pullData(analyticUnit, time, now);
} catch(err) {
throw new Error(`Error while pulling data: ${err.message}`);
}
}
const timeout = async () => new Promise(
resolve => setTimeout(resolve, duration)
);
while(true) {
yield await getData();
await timeout();
}
}
}

Loading…
Cancel
Save