Refactor queries

This commit is contained in:
Alexander Zobnin
2021-08-05 14:21:38 +03:00
parent 3831c6e28e
commit 6c1722d2ef
3 changed files with 170 additions and 182 deletions

View File

@@ -4,7 +4,7 @@ import _ from 'lodash';
import * as utils from './utils'; import * as utils from './utils';
import ts, { groupBy_perf as groupBy } from './timeseries'; import ts, { groupBy_perf as groupBy } from './timeseries';
import { getTemplateSrv } from '@grafana/runtime'; import { getTemplateSrv } from '@grafana/runtime';
import { DataFrame, Field, FieldType, TIME_SERIES_VALUE_FIELD_NAME } from '@grafana/data'; import { DataFrame, FieldType, TIME_SERIES_VALUE_FIELD_NAME } from '@grafana/data';
const SUM = ts.SUM; const SUM = ts.SUM;
const COUNT = ts.COUNT; const COUNT = ts.COUNT;

View File

@@ -1,6 +1,5 @@
import _ from 'lodash'; import _ from 'lodash';
import { Observable, of } from 'rxjs'; import { Observable } from 'rxjs';
import { map, catchError } from 'rxjs/operators';
import config from 'grafana/app/core/config'; import config from 'grafana/app/core/config';
import { contextSrv } from 'grafana/app/core/core'; import { contextSrv } from 'grafana/app/core/core';
import * as dateMath from 'grafana/app/core/utils/datemath'; import * as dateMath from 'grafana/app/core/utils/datemath';
@@ -8,14 +7,13 @@ import * as utils from './utils';
import * as migrations from './migrations'; import * as migrations from './migrations';
import * as metricFunctions from './metricFunctions'; import * as metricFunctions from './metricFunctions';
import * as c from './constants'; import * as c from './constants';
import { align, fillTrendsWithNulls } from './timeseries';
import dataProcessor from './dataProcessor'; import dataProcessor from './dataProcessor';
import responseHandler from './responseHandler'; import responseHandler from './responseHandler';
import problemsHandler from './problemsHandler'; import problemsHandler from './problemsHandler';
import { Zabbix } from './zabbix/zabbix'; import { Zabbix } from './zabbix/zabbix';
import { ZabbixAPIError } from './zabbix/connectors/zabbix_api/zabbixAPIConnector'; import { ZabbixAPIError } from './zabbix/connectors/zabbix_api/zabbixAPIConnector';
import { ZabbixMetricsQuery, ZabbixDSOptions, VariableQueryTypes, ShowProblemTypes, ProblemDTO } from './types'; import { ProblemDTO, ShowProblemTypes, VariableQueryTypes, ZabbixDSOptions, ZabbixMetricsQuery } from './types';
import {BackendSrvRequest, getBackendSrv, getTemplateSrv, toDataQueryError, toDataQueryResponse} from '@grafana/runtime'; import { BackendSrvRequest, getBackendSrv, getTemplateSrv, toDataQueryResponse } from '@grafana/runtime';
import { import {
DataFrame, DataFrame,
dataFrameFromJSON, dataFrameFromJSON,
@@ -117,87 +115,17 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
return migrations.migrate(target); return migrations.migrate(target);
}); });
const backendResponsePromise = this.backendQuery({...request, targets: requestTargets}); const backendResponsePromise = this.backendQuery({ ...request, targets: requestTargets });
const dbConnectionResponsePromise = this.dbConnectionQuery({ ...request, targets: requestTargets });
const frontendResponsePromise = this.frontendQuery({ ...request, targets: requestTargets });
// Create request for each target return Promise.all([backendResponsePromise, dbConnectionResponsePromise, frontendResponsePromise])
const frontendTargets = requestTargets.filter(t => !this.isBackendTarget(t));
const promises = _.map(frontendTargets, target => {
// Don't request for hidden targets
if (target.hide) {
return [];
}
let timeFrom = Math.ceil(dateMath.parse(request.range.from) / 1000);
let timeTo = Math.ceil(dateMath.parse(request.range.to) / 1000);
// Add range variables
request.scopedVars = Object.assign({}, request.scopedVars, utils.getRangeScopedVars(request.range));
this.replaceTargetVariables(target, request);
// Apply Time-related functions (timeShift(), etc)
const timeFunctions = bindFunctionDefs(target.functions, 'Time');
if (timeFunctions.length) {
const [time_from, time_to] = utils.sequence(timeFunctions)([timeFrom, timeTo]);
timeFrom = time_from;
timeTo = time_to;
}
const timeRange = [timeFrom, timeTo];
const useTrends = this.isUseTrends(timeRange);
// Metrics or Text query
if (!target.queryType || target.queryType === c.MODE_METRICS || target.queryType === c.MODE_TEXT) {
// Don't request undefined targets
if (!target.group || !target.host || !target.item) {
return [];
}
if (!target.queryType || target.queryType === c.MODE_METRICS) {
return this.queryNumericData(target, timeRange, useTrends, request);
} else if (target.queryType === c.MODE_TEXT) {
return this.queryTextData(target, timeRange);
} else {
return [];
}
} else if (target.queryType === c.MODE_ITEMID) {
// Item ID query
if (!target.itemids) {
return [];
}
return this.queryItemIdData(target, timeRange, useTrends, request);
} else if (target.queryType === c.MODE_ITSERVICE) {
// IT services query
return this.queryITServiceData(target, timeRange, request);
} else if (target.queryType === c.MODE_TRIGGERS) {
// Triggers query
return this.queryTriggersData(target, timeRange);
} else if (target.queryType === c.MODE_PROBLEMS) {
// Problems query
return this.queryProblems(target, timeRange, request);
} else {
return [];
}
});
// Data for panel (all targets)
const frontendResponsePromise: Promise<DataQueryResponse> = Promise.all(_.flatten(promises))
.then(_.flatten)
.then(data => {
if (data && data.length > 0 && isDataFrame(data[0]) && !utils.isProblemsDataFrame(data[0])) {
data = responseHandler.alignFrames(data);
if (responseHandler.isConvertibleToWide(data)) {
console.log('Converting response to the wide format');
data = responseHandler.convertToWide(data);
}
}
return { data };
});
return Promise.all([backendResponsePromise, frontendResponsePromise])
.then(rsp => { .then(rsp => {
// Merge backend and frontend queries results // Merge backend and frontend queries results
const [backendRes, frontendRes] = rsp; const [backendRes, dbConnectionRes, frontendRes] = rsp;
if (dbConnectionRes.data) {
backendRes.data = backendRes.data.concat(dbConnectionRes.data);
}
if (frontendRes.data) { if (frontendRes.data) {
backendRes.data = backendRes.data.concat(frontendRes.data); backendRes.data = backendRes.data.concat(frontendRes.data);
} }
@@ -218,23 +146,16 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
request.scopedVars = Object.assign({}, request.scopedVars, utils.getRangeScopedVars(request.range)); request.scopedVars = Object.assign({}, request.scopedVars, utils.getRangeScopedVars(request.range));
const queries = _.compact(targets.map((query) => { const queries = _.compact(targets.map((query) => {
const datasourceId = this.id;
// Don't request for hidden targets // Don't request for hidden targets
if (query.hide) { if (query.hide) {
return null; return null;
} }
// Prevent changes of original object this.replaceTargetVariables(query, request);
let target = _.cloneDeep(query);
// Migrate old targets
target = migrations.migrate(target);
this.replaceTargetVariables(target, request);
return { return {
...target, ...query,
datasourceId, datasourceId: this.datasourceId,
intervalMs, intervalMs,
maxDataPoints, maxDataPoints,
}; };
@@ -276,10 +197,108 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
return resp; return resp;
} }
async frontendQuery(request: DataQueryRequest<any>): Promise<DataQueryResponse> {
const frontendTargets = request.targets.filter(t => !(this.isBackendTarget(t) || this.isDBConnectionTarget(t)));
const promises = _.map(frontendTargets, target => {
// Don't request for hidden targets
if (target.hide) {
return [];
}
// Add range variables
request.scopedVars = Object.assign({}, request.scopedVars, utils.getRangeScopedVars(request.range));
this.replaceTargetVariables(target, request);
const timeRange = this.buildTimeRange(request, target);
if (target.queryType === c.MODE_TEXT) {
// Text query
// Don't request undefined targets
if (!target.group || !target.host || !target.item) {
return [];
}
return this.queryTextData(target, timeRange);
} else if (target.queryType === c.MODE_ITSERVICE) {
// IT services query
return this.queryITServiceData(target, timeRange, request);
} else if (target.queryType === c.MODE_TRIGGERS) {
// Triggers query
return this.queryTriggersData(target, timeRange);
} else if (target.queryType === c.MODE_PROBLEMS) {
// Problems query
return this.queryProblems(target, timeRange, request);
} else {
return [];
}
});
// Data for panel (all targets)
return Promise.all(_.flatten(promises))
.then(_.flatten)
.then(data => {
if (data && data.length > 0 && isDataFrame(data[0]) && !utils.isProblemsDataFrame(data[0])) {
data = responseHandler.alignFrames(data);
if (responseHandler.isConvertibleToWide(data)) {
console.log('Converting response to the wide format');
data = responseHandler.convertToWide(data);
}
}
return { data };
});
}
async dbConnectionQuery(request: DataQueryRequest<any>): Promise<DataQueryResponse> {
const targets = request.targets.filter(this.isDBConnectionTarget);
const queries = _.compact(targets.map((target) => {
// Don't request for hidden targets
if (target.hide) {
return [];
}
// Add range variables
request.scopedVars = Object.assign({}, request.scopedVars, utils.getRangeScopedVars(request.range));
this.replaceTargetVariables(target, request);
const timeRange = this.buildTimeRange(request, target);
const useTrends = this.isUseTrends(timeRange);
if (!target.queryType || target.queryType === c.MODE_METRICS) {
return this.queryNumericData(target, timeRange, useTrends, request);
} else if (target.queryType === c.MODE_ITEMID) {
// Item ID query
if (!target.itemids) {
return [];
}
return this.queryItemIdData(target, timeRange, useTrends, request);
} else {
return [];
}
}));
const promises: Promise<DataQueryResponse> = Promise.all(queries)
.then(_.flatten)
.then(data => ({ data }));
return promises;
}
buildTimeRange(request, target) {
let timeFrom = Math.ceil(dateMath.parse(request.range.from) / 1000);
let timeTo = Math.ceil(dateMath.parse(request.range.to) / 1000);
// Apply Time-related functions (timeShift(), etc)
const timeFunctions = bindFunctionDefs(target.functions, 'Time');
if (timeFunctions.length) {
const [time_from, time_to] = utils.sequence(timeFunctions)([timeFrom, timeTo]);
timeFrom = time_from;
timeTo = time_to;
}
return [timeFrom, timeTo];
}
/** /**
* Query target data for Metrics * Query target data for Metrics
*/ */
async queryNumericData(target, timeRange, useTrends, options): Promise<DataFrame[]> { async queryNumericData(target, timeRange, useTrends, request): Promise<any> {
const getItemOptions = { const getItemOptions = {
itemtype: 'num' itemtype: 'num'
}; };
@@ -287,7 +306,7 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
const items = await this.zabbix.getItemsFromTarget(target, getItemOptions); const items = await this.zabbix.getItemsFromTarget(target, getItemOptions);
const queryStart = new Date().getTime(); const queryStart = new Date().getTime();
const result = await this.queryNumericDataForItems(items, target, timeRange, useTrends, options); const result = await this.queryNumericDataForItems(items, target, timeRange, useTrends, request);
const queryEnd = new Date().getTime(); const queryEnd = new Date().getTime();
if (this.enableDebugLog) { if (this.enableDebugLog) {
@@ -297,13 +316,19 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
const frames = []; const frames = [];
for (const frameJSON of result) { for (const frameJSON of result) {
const frame = dataFrameFromJSON(frameJSON); const frame = dataFrameFromJSON(frameJSON);
frame.refId = target.refId;
frames.push(frame); frames.push(frame);
} }
return frames;
// const valueMappings = await this.zabbix.getValueMappings(); const resp = { data: frames };
// this.sortByRefId(resp);
// const dataFrames = (result as any).map(s => responseHandler.seriesToDataFrame(s, target, valueMappings)); this.applyFrontendFunctions(resp, request);
// return dataFrames; if (responseHandler.isConvertibleToWide(resp.data)) {
console.log('Converting response to the wide format');
resp.data = responseHandler.convertToWide(resp.data);
}
return resp.data;
} }
/** /**
@@ -313,20 +338,14 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
let history; let history;
options.valueType = this.getTrendValueType(target); options.valueType = this.getTrendValueType(target);
options.consolidateBy = getConsolidateBy(target) || options.valueType; options.consolidateBy = getConsolidateBy(target) || options.valueType;
const disableDataAlignment = this.disableDataAlignment || target.options?.disableDataAlignment;
if (useTrends) { if (useTrends) {
history = await this.zabbix.getTrends(items, timeRange, options); history = await this.zabbix.getTrends(items, timeRange, options);
// .then(timeseries => {
// return !disableDataAlignment ? this.fillTrendTimeSeriesWithNulls(timeseries) : timeseries;
// });
} else { } else {
history = await this.zabbix.getHistoryTS(items, timeRange, options); history = await this.zabbix.getHistoryTS(items, timeRange, options);
// .then(timeseries => {
// return !disableDataAlignment ? this.alignTimeSeriesData(timeseries) : timeseries;
// });
} }
// Request backend for data processing
const requestOptions: BackendSrvRequest = { const requestOptions: BackendSrvRequest = {
url: `/api/datasources/${this.datasourceId}/resources/db-connection-post`, url: `/api/datasources/${this.datasourceId}/resources/db-connection-post`,
method: 'POST', method: 'POST',
@@ -342,10 +361,6 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
const response: any = await getBackendSrv().fetch<any>(requestOptions).toPromise(); const response: any = await getBackendSrv().fetch<any>(requestOptions).toPromise();
return response.data; return response.data;
// return getHistoryPromise
// .then(timeseries => this.applyDataProcessingFunctions(timeseries, target))
// .then(timeseries => downsampleSeries(timeseries, options));
} }
getTrendValueType(target) { getTrendValueType(target) {
@@ -357,21 +372,6 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
return trendValueFunc ? trendValueFunc.params[0] : "avg"; return trendValueFunc ? trendValueFunc.params[0] : "avg";
} }
alignTimeSeriesData(timeseries: any[]) {
for (const ts of timeseries) {
const interval = utils.parseItemInterval(ts.scopedVars['__zbx_item_interval']?.value);
ts.datapoints = align(ts.datapoints, interval);
}
return timeseries;
}
fillTrendTimeSeriesWithNulls(timeseries: any[]) {
for (const ts of timeseries) {
ts.datapoints = fillTrendsWithNulls(ts.datapoints);
}
return timeseries;
}
sortByRefId(response: DataQueryResponse) { sortByRefId(response: DataQueryResponse) {
response.data.sort((a, b) => { response.data.sort((a, b) => {
if (a.refId < b.refId) { if (a.refId < b.refId) {
@@ -486,9 +486,6 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
.then(items => { .then(items => {
return this.queryNumericDataForItems(items, target, timeRange, useTrends, options); return this.queryNumericDataForItems(items, target, timeRange, useTrends, options);
}); });
// .then(result => {
// return (result as any).map(s => responseHandler.seriesToDataFrame(s, target));
// });
} }
/** /**
@@ -515,14 +512,12 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
return this.zabbix.getITServices(itServiceFilter) return this.zabbix.getITServices(itServiceFilter)
.then(itservices => { .then(itservices => {
if (options.isOldVersion) { if (options.isOldVersion) {
itservices = _.filter(itservices, {'serviceid': target.itservice?.serviceid}); itservices = _.filter(itservices, { 'serviceid': target.itservice?.serviceid });
} }
return this.zabbix.getSLA(itservices, timeRange, target, options);}) return this.zabbix.getSLA(itservices, timeRange, target, options);
})
.then(itservicesdp => this.applyDataProcessingFunctions(itservicesdp, target)) .then(itservicesdp => this.applyDataProcessingFunctions(itservicesdp, target))
.then(result => { .then(result => result.map(s => responseHandler.seriesToDataFrame(s, target)));
const dataFrames = result.map(s => responseHandler.seriesToDataFrame(s, target));
return dataFrames;
});
} }
queryTriggersData(target, timeRange) { queryTriggersData(target, timeRange) {
@@ -872,7 +867,12 @@ export class ZabbixDatasource extends DataSourceApi<ZabbixMetricsQuery, ZabbixDS
return target.queryType === c.MODE_METRICS || return target.queryType === c.MODE_METRICS ||
target.queryType === c.MODE_ITEMID; target.queryType === c.MODE_ITEMID;
} };
isDBConnectionTarget = (target: any): boolean => {
return this.enableDirectDBConnection &&
(target.queryType === c.MODE_METRICS || target.queryType === c.MODE_ITEMID);
};
} }
function bindFunctionDefs(functionDefs, category) { function bindFunctionDefs(functionDefs, category) {
@@ -898,18 +898,6 @@ function getConsolidateBy(target) {
return consolidateBy; return consolidateBy;
} }
function downsampleSeries(timeseries_data, options) {
const defaultAgg = dataProcessor.aggregationFunctions['avg'];
const consolidateByFunc = dataProcessor.aggregationFunctions[options.consolidateBy] || defaultAgg;
return _.map(timeseries_data, timeseries => {
if (timeseries.datapoints.length > options.maxDataPoints) {
timeseries.datapoints = dataProcessor
.groupBy(options.interval, consolidateByFunc, timeseries.datapoints);
}
return timeseries;
});
}
function formatMetric(metricObj) { function formatMetric(metricObj) {
return { return {
text: metricObj.name, text: metricObj.name,

View File

@@ -42,7 +42,7 @@ function convertHistory(history, items, addHostName, convertPointCallback) {
const hosts = _.uniqBy(_.flatten(_.map(items, 'hosts')), 'hostid'); //uniqBy is needed to deduplicate const hosts = _.uniqBy(_.flatten(_.map(items, 'hosts')), 'hostid'); //uniqBy is needed to deduplicate
return _.map(grouped_history, (hist, itemid) => { return _.map(grouped_history, (hist, itemid) => {
const item = _.find(items, {'itemid': itemid}) as any; const item = _.find(items, { 'itemid': itemid }) as any;
let alias = item.name; let alias = item.name;
// Add scopedVars for using in alias functions // Add scopedVars for using in alias functions
@@ -54,7 +54,7 @@ function convertHistory(history, items, addHostName, convertPointCallback) {
}; };
if (_.keys(hosts).length > 0) { if (_.keys(hosts).length > 0) {
const host = _.find(hosts, {'hostid': item.hostid}); const host = _.find(hosts, { 'hostid': item.hostid });
scopedVars['__zbx_host'] = { value: host.host }; scopedVars['__zbx_host'] = { value: host.host };
scopedVars['__zbx_host_name'] = { value: host.name }; scopedVars['__zbx_host_name'] = { value: host.name };
@@ -140,7 +140,7 @@ export function seriesToDataFrame(timeseries, target: ZabbixMetricsQuery, valueM
} }
} }
const fields: Field[] = [ timeFiled, valueFiled ]; const fields: Field[] = [timeFiled, valueFiled];
const frame: DataFrame = { const frame: DataFrame = {
name: seriesName, name: seriesName,
@@ -177,7 +177,7 @@ export function dataResponseToTimeSeries(response: DataFrameJSON[], items) {
} }
const itemid = field.name; const itemid = field.name;
const item = _.find(items, {'itemid': itemid}); const item = _.find(items, { 'itemid': itemid });
let interval = utils.parseItemInterval(item.delay); let interval = utils.parseItemInterval(item.delay);
if (interval === 0) { if (interval === 0) {
interval = null; interval = null;
@@ -249,7 +249,7 @@ export function alignFrames(data: MutableDataFrame[]): MutableDataFrame[] {
const missingTimestamps = []; const missingTimestamps = [];
const missingValues = []; const missingValues = [];
const frameInterval: number = timeField.config.custom?.itemInterval; const frameInterval: number = timeField.config.custom?.itemInterval;
for (let j = minTimestamp; j < firstTs; j+=frameInterval) { for (let j = minTimestamp; j < firstTs; j += frameInterval) {
missingTimestamps.push(j); missingTimestamps.push(j);
missingValues.push(null); missingValues.push(null);
} }
@@ -270,7 +270,7 @@ export function convertToWide(data: MutableDataFrame[]): DataFrame[] {
return []; return [];
} }
const fields: MutableField[] = [ timeField ]; const fields: MutableField[] = [timeField];
for (let i = 0; i < data.length; i++) { for (let i = 0; i < data.length; i++) {
const valueField = data[i].fields.find(f => f.name === TIME_SERIES_VALUE_FIELD_NAME); const valueField = data[i].fields.find(f => f.name === TIME_SERIES_VALUE_FIELD_NAME);
@@ -320,10 +320,10 @@ function handleText(history, items, target, addHostName = true) {
function handleHistoryAsTable(history, items, target) { function handleHistoryAsTable(history, items, target) {
const table: any = new TableModel(); const table: any = new TableModel();
table.addColumn({text: 'Host'}); table.addColumn({ text: 'Host' });
table.addColumn({text: 'Item'}); table.addColumn({ text: 'Item' });
table.addColumn({text: 'Key'}); table.addColumn({ text: 'Key' });
table.addColumn({text: 'Last value'}); table.addColumn({ text: 'Last value' });
const grouped_history = _.groupBy(history, 'itemid'); const grouped_history = _.groupBy(history, 'itemid');
_.each(items, (item) => { _.each(items, (item) => {
@@ -422,9 +422,9 @@ function handleTriggersResponse(triggers, groups, timeRange) {
const stats = getTriggerStats(triggers); const stats = getTriggerStats(triggers);
const groupNames = _.map(groups, 'name'); const groupNames = _.map(groups, 'name');
const table: any = new TableModel(); const table: any = new TableModel();
table.addColumn({text: 'Host group'}); table.addColumn({ text: 'Host group' });
_.each(_.orderBy(c.TRIGGER_SEVERITY, ['val'], ['desc']), (severity) => { _.each(_.orderBy(c.TRIGGER_SEVERITY, ['val'], ['desc']), (severity) => {
table.addColumn({text: severity.text}); table.addColumn({ text: severity.text });
}); });
_.each(stats, (severity_stats, group) => { _.each(stats, (severity_stats, group) => {
if (_.includes(groupNames, group)) { if (_.includes(groupNames, group)) {
@@ -442,7 +442,7 @@ function getTriggerStats(triggers) {
// let severity = _.map(c.TRIGGER_SEVERITY, 'text'); // let severity = _.map(c.TRIGGER_SEVERITY, 'text');
const stats = {}; const stats = {};
_.each(groups, (group) => { _.each(groups, (group) => {
stats[group] = {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0}; // severity:count stats[group] = { 0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0 }; // severity:count
}); });
_.each(triggers, (trigger) => { _.each(triggers, (trigger) => {
_.each(trigger.groups, (group) => { _.each(trigger.groups, (group) => {