|
|
@ -2,6 +2,7 @@ import { AnalyticsMessageMethod, AnalyticsMessage } from '../models/analytics_me |
|
|
|
import { AnalyticsTask, AnalyticsTaskType, AnalyticsTaskId } from '../models/analytics_task_model'; |
|
|
|
import { AnalyticsTask, AnalyticsTaskType, AnalyticsTaskId } from '../models/analytics_task_model'; |
|
|
|
import * as AnalyticUnitCache from '../models/analytic_unit_cache_model'; |
|
|
|
import * as AnalyticUnitCache from '../models/analytic_unit_cache_model'; |
|
|
|
import * as Segment from '../models/segment_model'; |
|
|
|
import * as Segment from '../models/segment_model'; |
|
|
|
|
|
|
|
import * as Threshold from '../models/threshold_model'; |
|
|
|
import * as AnalyticUnit from '../models/analytic_unit_model'; |
|
|
|
import * as AnalyticUnit from '../models/analytic_unit_model'; |
|
|
|
import { AnalyticsService } from '../services/analytics_service'; |
|
|
|
import { AnalyticsService } from '../services/analytics_service'; |
|
|
|
import { sendWebhook } from '../services/notification_service'; |
|
|
|
import { sendWebhook } from '../services/notification_service'; |
|
|
@ -13,6 +14,7 @@ import { queryByMetric } from 'grafana-datasource-kit'; |
|
|
|
|
|
|
|
|
|
|
|
import * as _ from 'lodash'; |
|
|
|
import * as _ from 'lodash'; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const SECONDS_IN_MINUTE = 60; |
|
|
|
|
|
|
|
|
|
|
|
type TaskResult = any; |
|
|
|
type TaskResult = any; |
|
|
|
type DetectionResult = any; |
|
|
|
type DetectionResult = any; |
|
|
@ -87,6 +89,37 @@ async function runTask(task: AnalyticsTask): Promise<TaskResult> { |
|
|
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function query(analyticUnit: AnalyticUnit.AnalyticUnit, detector: AnalyticUnit.DetectorType) { |
|
|
|
|
|
|
|
let range; |
|
|
|
|
|
|
|
if(detector === AnalyticUnit.DetectorType.PATTERN) { |
|
|
|
|
|
|
|
const segments = await Segment.findMany(analyticUnit.id, { labeled: true }); |
|
|
|
|
|
|
|
if(segments.length === 0) { |
|
|
|
|
|
|
|
throw new Error('Need at least 1 labeled segment'); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
range = getQueryRangeForLearningBySegments(segments); |
|
|
|
|
|
|
|
} else if(detector === AnalyticUnit.DetectorType.THRESHOLD) { |
|
|
|
|
|
|
|
const now = Date.now(); |
|
|
|
|
|
|
|
range = { |
|
|
|
|
|
|
|
from: now - 5 * SECONDS_IN_MINUTE, |
|
|
|
|
|
|
|
to: now |
|
|
|
|
|
|
|
}; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
console.debug(`query time range: from ${new Date(range.from)} to ${new Date(range.to)}`); |
|
|
|
|
|
|
|
const queryResult = await queryByMetric( |
|
|
|
|
|
|
|
analyticUnit.metric, |
|
|
|
|
|
|
|
analyticUnit.panelUrl, |
|
|
|
|
|
|
|
range.from, |
|
|
|
|
|
|
|
range.to, |
|
|
|
|
|
|
|
HASTIC_API_KEY |
|
|
|
|
|
|
|
); |
|
|
|
|
|
|
|
const data = queryResult.values; |
|
|
|
|
|
|
|
if(data.length === 0) { |
|
|
|
|
|
|
|
throw new Error('Empty data to detect on'); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return data; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Finds range for selecting subset for learning |
|
|
|
* Finds range for selecting subset for learning |
|
|
|
* @param segments labeled segments |
|
|
|
* @param segments labeled segments |
|
|
@ -112,25 +145,9 @@ export async function runLearning(id: AnalyticUnit.AnalyticUnitId) { |
|
|
|
|
|
|
|
|
|
|
|
let analyticUnit = await AnalyticUnit.findById(id); |
|
|
|
let analyticUnit = await AnalyticUnit.findById(id); |
|
|
|
if(analyticUnit.status === AnalyticUnit.AnalyticUnitStatus.LEARNING) { |
|
|
|
if(analyticUnit.status === AnalyticUnit.AnalyticUnitStatus.LEARNING) { |
|
|
|
throw new Error('Can`t starn learning when it`s already started [' + id + ']'); |
|
|
|
throw new Error('Can`t start learning when it`s already started [' + id + ']'); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
let segments = await Segment.findMany(id, { labeled: true }); |
|
|
|
|
|
|
|
if(segments.length === 0) { |
|
|
|
|
|
|
|
throw new Error('Need at least 1 labeled segment'); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let segmentObjs = segments.map(s => s.toObject()); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let { from, to } = getQueryRangeForLearningBySegments(segments); |
|
|
|
|
|
|
|
console.debug(`query time range: from ${new Date(from)} to ${new Date(to)}`); |
|
|
|
|
|
|
|
let queryResult = await queryByMetric(analyticUnit.metric, analyticUnit.panelUrl, from, to, HASTIC_API_KEY); |
|
|
|
|
|
|
|
let data = queryResult.values; |
|
|
|
|
|
|
|
if(data.length === 0) { |
|
|
|
|
|
|
|
throw new Error('Empty data to learn on'); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let pattern = analyticUnit.type; |
|
|
|
|
|
|
|
let oldCache = await AnalyticUnitCache.findById(id); |
|
|
|
let oldCache = await AnalyticUnitCache.findById(id); |
|
|
|
if(oldCache !== null) { |
|
|
|
if(oldCache !== null) { |
|
|
|
oldCache = oldCache.data; |
|
|
|
oldCache = oldCache.data; |
|
|
@ -138,12 +155,31 @@ export async function runLearning(id: AnalyticUnit.AnalyticUnitId) { |
|
|
|
await AnalyticUnitCache.create(id); |
|
|
|
await AnalyticUnitCache.create(id); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let analyticUnitType = analyticUnit.type; |
|
|
|
|
|
|
|
let detector = AnalyticUnit.getDetectorByType(analyticUnitType); |
|
|
|
|
|
|
|
let taskPayload: any = { detector, analyticUnitType, cache: oldCache }; |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if(detector === AnalyticUnit.DetectorType.PATTERN) { |
|
|
|
|
|
|
|
let segments = await Segment.findMany(id, { labeled: true }); |
|
|
|
|
|
|
|
if(segments.length === 0) { |
|
|
|
|
|
|
|
throw new Error('Need at least 1 labeled segment'); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let segmentObjs = segments.map(s => s.toObject()); |
|
|
|
|
|
|
|
|
|
|
|
let deletedSegments = await Segment.findMany(id, { deleted: true }); |
|
|
|
let deletedSegments = await Segment.findMany(id, { deleted: true }); |
|
|
|
let deletedSegmentsObjs = deletedSegments.map(s => s.toObject()); |
|
|
|
let deletedSegmentsObjs = deletedSegments.map(s => s.toObject()); |
|
|
|
segmentObjs = _.concat(segmentObjs, deletedSegmentsObjs); |
|
|
|
segmentObjs = _.concat(segmentObjs, deletedSegmentsObjs); |
|
|
|
|
|
|
|
taskPayload.segments = segmentObjs; |
|
|
|
|
|
|
|
} else if(detector === AnalyticUnit.DetectorType.THRESHOLD) { |
|
|
|
|
|
|
|
const threshold = await Threshold.findOne(id); |
|
|
|
|
|
|
|
taskPayload.threshold = threshold; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
taskPayload.data = await query(analyticUnit, detector); |
|
|
|
|
|
|
|
|
|
|
|
let task = new AnalyticsTask( |
|
|
|
let task = new AnalyticsTask( |
|
|
|
id, AnalyticsTaskType.LEARN, { pattern, segments: segmentObjs, data, cache: oldCache } |
|
|
|
id, AnalyticsTaskType.LEARN, taskPayload |
|
|
|
); |
|
|
|
); |
|
|
|
AnalyticUnit.setStatus(id, AnalyticUnit.AnalyticUnitStatus.LEARNING); |
|
|
|
AnalyticUnit.setStatus(id, AnalyticUnit.AnalyticUnitStatus.LEARNING); |
|
|
|
console.debug(`run task, id:${id}`); |
|
|
|
console.debug(`run task, id:${id}`); |
|
|
@ -165,20 +201,10 @@ export async function runDetect(id: AnalyticUnit.AnalyticUnitId) { |
|
|
|
try { |
|
|
|
try { |
|
|
|
let unit = await AnalyticUnit.findById(id); |
|
|
|
let unit = await AnalyticUnit.findById(id); |
|
|
|
previousLastDetectionTime = unit.lastDetectionTime; |
|
|
|
previousLastDetectionTime = unit.lastDetectionTime; |
|
|
|
let pattern = unit.type; |
|
|
|
let analyticUnitType = unit.type; |
|
|
|
|
|
|
|
let detector = AnalyticUnit.getDetectorByType(analyticUnitType); |
|
|
|
|
|
|
|
|
|
|
|
let segments = await Segment.findMany(id, { labeled: true }); |
|
|
|
const data = await query(unit, detector); |
|
|
|
if(segments.length === 0) { |
|
|
|
|
|
|
|
throw new Error('Need at least 1 labeled segment'); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let { from, to } = getQueryRangeForLearningBySegments(segments); |
|
|
|
|
|
|
|
console.debug(`query time range: from ${new Date(from)} to ${new Date(to)}`); |
|
|
|
|
|
|
|
let queryResult = await queryByMetric(unit.metric, unit.panelUrl, from, to, HASTIC_API_KEY); |
|
|
|
|
|
|
|
let data = queryResult.values; |
|
|
|
|
|
|
|
if(data.length === 0) { |
|
|
|
|
|
|
|
throw new Error('Empty data to detect on'); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let oldCache = await AnalyticUnitCache.findById(id); |
|
|
|
let oldCache = await AnalyticUnitCache.findById(id); |
|
|
|
if(oldCache !== null) { |
|
|
|
if(oldCache !== null) { |
|
|
@ -189,7 +215,7 @@ export async function runDetect(id: AnalyticUnit.AnalyticUnitId) { |
|
|
|
let task = new AnalyticsTask( |
|
|
|
let task = new AnalyticsTask( |
|
|
|
id, |
|
|
|
id, |
|
|
|
AnalyticsTaskType.DETECT, |
|
|
|
AnalyticsTaskType.DETECT, |
|
|
|
{ pattern, lastDetectionTime: unit.lastDetectionTime, data, cache: oldCache } |
|
|
|
{ detector, analyticUnitType, lastDetectionTime: unit.lastDetectionTime, data, cache: oldCache } |
|
|
|
); |
|
|
|
); |
|
|
|
console.debug(`run task, id:${id}`); |
|
|
|
console.debug(`run task, id:${id}`); |
|
|
|
let result = await runTask(task); |
|
|
|
let result = await runTask(task); |
|
|
@ -264,18 +290,16 @@ async function processDetectionResult(analyticUnitId: AnalyticUnit.AnalyticUnitI |
|
|
|
segment => new Segment.Segment(analyticUnitId, segment.from, segment.to, false, false) |
|
|
|
segment => new Segment.Segment(analyticUnitId, segment.from, segment.to, false, false) |
|
|
|
); |
|
|
|
); |
|
|
|
const analyticUnit = await AnalyticUnit.findById(analyticUnitId); |
|
|
|
const analyticUnit = await AnalyticUnit.findById(analyticUnitId); |
|
|
|
if(analyticUnit.alert) { |
|
|
|
if (!_.isEmpty(segments) && analyticUnit.alert) { |
|
|
|
if(!_.isEmpty(segments)) { |
|
|
|
|
|
|
|
try { |
|
|
|
try { |
|
|
|
sendWebhook(analyticUnit.name, _.last(segments)); |
|
|
|
sendWebhook(analyticUnit.name, _.last(segments)); |
|
|
|
} catch(err) { |
|
|
|
} catch(err) { |
|
|
|
console.error(`Error while sending webhook: ${err.message}`); |
|
|
|
console.error(`Error while sending webhook: ${err.message}`); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return { |
|
|
|
return { |
|
|
|
lastDetectionTime: detectionResult.lastDetectionTime, |
|
|
|
lastDetectionTime: detectionResult.lastDetectionTime, |
|
|
|
segments: segments, |
|
|
|
segments, |
|
|
|
cache: detectionResult.cache |
|
|
|
cache: detectionResult.cache |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
@ -319,8 +343,23 @@ export async function updateSegments( |
|
|
|
]); |
|
|
|
]); |
|
|
|
removed = removed.map(s => s._id); |
|
|
|
removed = removed.map(s => s._id); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
runFirstLearning(id); |
|
|
|
|
|
|
|
return { addedIds, removed }; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
export async function updateThreshold( |
|
|
|
|
|
|
|
id: AnalyticUnit.AnalyticUnitId, |
|
|
|
|
|
|
|
value: number, |
|
|
|
|
|
|
|
condition: Threshold.Condition |
|
|
|
|
|
|
|
) { |
|
|
|
|
|
|
|
await Threshold.updateThreshold(id, value, condition); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
runFirstLearning(id); |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async function runFirstLearning(id: AnalyticUnit.AnalyticUnitId) { |
|
|
|
// TODO: move setting status somehow "inside" learning
|
|
|
|
// TODO: move setting status somehow "inside" learning
|
|
|
|
await AnalyticUnit.setStatus(id, AnalyticUnit.AnalyticUnitStatus.PENDING); |
|
|
|
await AnalyticUnit.setStatus(id, AnalyticUnit.AnalyticUnitStatus.PENDING); |
|
|
|
runLearning(id).then(() => runDetect(id)); |
|
|
|
runLearning(id) |
|
|
|
return { addedIds, removed }; |
|
|
|
.then(() => runDetect(id)); |
|
|
|
} |
|
|
|
} |
|
|
|