Merge branch 'refactor-es6'. Datasource refactoring.
This commit is contained in:
12
Gruntfile.js
12
Gruntfile.js
@@ -33,7 +33,7 @@ module.exports = function(grunt) {
|
||||
watch: {
|
||||
rebuild_all: {
|
||||
files: ['src/**/*', 'plugin.json'],
|
||||
tasks: ['default'],
|
||||
tasks: ['watchTask'],
|
||||
options: {spawn: false}
|
||||
},
|
||||
},
|
||||
@@ -135,4 +135,14 @@ module.exports = function(grunt) {
|
||||
'babel',
|
||||
'mochaTest'
|
||||
]);
|
||||
|
||||
grunt.registerTask('watchTask', [
|
||||
'clean',
|
||||
'copy:src_to_dist',
|
||||
'copy:pluginDef',
|
||||
'jshint',
|
||||
'jscs',
|
||||
'sass',
|
||||
'babel:dist'
|
||||
]);
|
||||
};
|
||||
|
||||
@@ -1,281 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
import * as utils from './utils';
|
||||
|
||||
export default class DataProcessor {
|
||||
|
||||
/**
|
||||
* Downsample datapoints series
|
||||
*/
|
||||
static downsampleSeries(datapoints, time_to, ms_interval, func) {
|
||||
var downsampledSeries = [];
|
||||
var timeWindow = {
|
||||
from: time_to * 1000 - ms_interval,
|
||||
to: time_to * 1000
|
||||
};
|
||||
|
||||
var points_sum = 0;
|
||||
var points_num = 0;
|
||||
var value_avg = 0;
|
||||
var frame = [];
|
||||
|
||||
for (var i = datapoints.length - 1; i >= 0; i -= 1) {
|
||||
if (timeWindow.from < datapoints[i][1] && datapoints[i][1] <= timeWindow.to) {
|
||||
points_sum += datapoints[i][0];
|
||||
points_num++;
|
||||
frame.push(datapoints[i][0]);
|
||||
}
|
||||
else {
|
||||
value_avg = points_num ? points_sum / points_num : 0;
|
||||
|
||||
if (func === "max") {
|
||||
downsampledSeries.push([_.max(frame), timeWindow.to]);
|
||||
}
|
||||
else if (func === "min") {
|
||||
downsampledSeries.push([_.min(frame), timeWindow.to]);
|
||||
}
|
||||
|
||||
// avg by default
|
||||
else {
|
||||
downsampledSeries.push([value_avg, timeWindow.to]);
|
||||
}
|
||||
|
||||
// Shift time window
|
||||
timeWindow.to = timeWindow.from;
|
||||
timeWindow.from -= ms_interval;
|
||||
|
||||
points_sum = 0;
|
||||
points_num = 0;
|
||||
frame = [];
|
||||
|
||||
// Process point again
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return downsampledSeries.reverse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Group points by given time interval
|
||||
* datapoints: [[<value>, <unixtime>], ...]
|
||||
*/
|
||||
static groupBy(interval, groupByCallback, datapoints) {
|
||||
var ms_interval = utils.parseInterval(interval);
|
||||
|
||||
// Calculate frame timestamps
|
||||
var frames = _.groupBy(datapoints, function(point) {
|
||||
// Calculate time for group of points
|
||||
return Math.floor(point[1] / ms_interval) * ms_interval;
|
||||
});
|
||||
|
||||
// frame: { '<unixtime>': [[<value>, <unixtime>], ...] }
|
||||
// return [{ '<unixtime>': <value> }, { '<unixtime>': <value> }, ...]
|
||||
var grouped = _.mapValues(frames, function(frame) {
|
||||
var points = _.map(frame, function(point) {
|
||||
return point[0];
|
||||
});
|
||||
return groupByCallback(points);
|
||||
});
|
||||
|
||||
// Convert points to Grafana format
|
||||
return sortByTime(_.map(grouped, function(value, timestamp) {
|
||||
return [Number(value), Number(timestamp)];
|
||||
}));
|
||||
}
|
||||
|
||||
static sumSeries(timeseries) {
|
||||
|
||||
// Calculate new points for interpolation
|
||||
var new_timestamps = _.uniq(_.map(_.flatten(timeseries, true), function(point) {
|
||||
return point[1];
|
||||
}));
|
||||
new_timestamps = _.sortBy(new_timestamps);
|
||||
|
||||
var interpolated_timeseries = _.map(timeseries, function(series) {
|
||||
var timestamps = _.map(series, function(point) {
|
||||
return point[1];
|
||||
});
|
||||
var new_points = _.map(_.difference(new_timestamps, timestamps), function(timestamp) {
|
||||
return [null, timestamp];
|
||||
});
|
||||
var new_series = series.concat(new_points);
|
||||
return sortByTime(new_series);
|
||||
});
|
||||
|
||||
_.each(interpolated_timeseries, interpolateSeries);
|
||||
|
||||
var new_timeseries = [];
|
||||
var sum;
|
||||
for (var i = new_timestamps.length - 1; i >= 0; i--) {
|
||||
sum = 0;
|
||||
for (var j = interpolated_timeseries.length - 1; j >= 0; j--) {
|
||||
sum += interpolated_timeseries[j][i][0];
|
||||
}
|
||||
new_timeseries.push([sum, new_timestamps[i]]);
|
||||
}
|
||||
|
||||
return sortByTime(new_timeseries);
|
||||
}
|
||||
|
||||
static limit(order, n, orderByFunc, timeseries) {
|
||||
let orderByCallback = DataProcessor.aggregationFunctions[orderByFunc];
|
||||
let sortByIteratee = (ts) => {
|
||||
let values = _.map(ts.datapoints, (point) => {
|
||||
return point[0];
|
||||
});
|
||||
return orderByCallback(values);
|
||||
};
|
||||
let sortedTimeseries = _.sortBy(timeseries, sortByIteratee);
|
||||
if (order === 'bottom') {
|
||||
return sortedTimeseries.slice(0, n);
|
||||
} else {
|
||||
return sortedTimeseries.slice(-n);
|
||||
}
|
||||
}
|
||||
|
||||
static AVERAGE(values) {
|
||||
var sum = 0;
|
||||
_.each(values, function(value) {
|
||||
sum += value;
|
||||
});
|
||||
return sum / values.length;
|
||||
}
|
||||
|
||||
static MIN(values) {
|
||||
return _.min(values);
|
||||
}
|
||||
|
||||
static MAX(values) {
|
||||
return _.max(values);
|
||||
}
|
||||
|
||||
static MEDIAN(values) {
|
||||
var sorted = _.sortBy(values);
|
||||
return sorted[Math.floor(sorted.length / 2)];
|
||||
}
|
||||
|
||||
static setAlias(alias, timeseries) {
|
||||
timeseries.target = alias;
|
||||
return timeseries;
|
||||
}
|
||||
|
||||
static scale(factor, datapoints) {
|
||||
return _.map(datapoints, point => {
|
||||
return [
|
||||
point[0] * factor,
|
||||
point[1]
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
static delta(datapoints) {
|
||||
let newSeries = [];
|
||||
let deltaValue;
|
||||
for (var i = 1; i < datapoints.length; i++) {
|
||||
deltaValue = datapoints[i][0] - datapoints[i - 1][0];
|
||||
newSeries.push([deltaValue, datapoints[i][1]]);
|
||||
}
|
||||
return newSeries;
|
||||
}
|
||||
|
||||
static groupByWrapper(interval, groupFunc, datapoints) {
|
||||
var groupByCallback = DataProcessor.aggregationFunctions[groupFunc];
|
||||
return DataProcessor.groupBy(interval, groupByCallback, datapoints);
|
||||
}
|
||||
|
||||
static aggregateByWrapper(interval, aggregateFunc, datapoints) {
|
||||
// Flatten all points in frame and then just use groupBy()
|
||||
var flattenedPoints = _.flatten(datapoints, true);
|
||||
var groupByCallback = DataProcessor.aggregationFunctions[aggregateFunc];
|
||||
return DataProcessor.groupBy(interval, groupByCallback, flattenedPoints);
|
||||
}
|
||||
|
||||
static aggregateWrapper(groupByCallback, interval, datapoints) {
|
||||
var flattenedPoints = _.flatten(datapoints, true);
|
||||
return DataProcessor.groupBy(interval, groupByCallback, flattenedPoints);
|
||||
}
|
||||
|
||||
static get aggregationFunctions() {
|
||||
return {
|
||||
avg: this.AVERAGE,
|
||||
min: this.MIN,
|
||||
max: this.MAX,
|
||||
median: this.MEDIAN
|
||||
};
|
||||
}
|
||||
|
||||
static get metricFunctions() {
|
||||
return {
|
||||
groupBy: this.groupByWrapper,
|
||||
scale: this.scale,
|
||||
delta: this.delta,
|
||||
aggregateBy: this.aggregateByWrapper,
|
||||
average: _.partial(this.aggregateWrapper, this.AVERAGE),
|
||||
min: _.partial(this.aggregateWrapper, this.MIN),
|
||||
max: _.partial(this.aggregateWrapper, this.MAX),
|
||||
median: _.partial(this.aggregateWrapper, this.MEDIAN),
|
||||
sumSeries: this.sumSeries,
|
||||
top: _.partial(this.limit, 'top'),
|
||||
bottom: _.partial(this.limit, 'bottom'),
|
||||
setAlias: this.setAlias,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function sortByTime(series) {
|
||||
return _.sortBy(series, function(point) {
|
||||
return point[1];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolate series with gaps
|
||||
*/
|
||||
function interpolateSeries(series) {
|
||||
var left, right;
|
||||
|
||||
// Interpolate series
|
||||
for (var i = series.length - 1; i >= 0; i--) {
|
||||
if (!series[i][0]) {
|
||||
left = findNearestLeft(series, series[i]);
|
||||
right = findNearestRight(series, series[i]);
|
||||
if (!left) {
|
||||
left = right;
|
||||
}
|
||||
if (!right) {
|
||||
right = left;
|
||||
}
|
||||
series[i][0] = linearInterpolation(series[i][1], left, right);
|
||||
}
|
||||
}
|
||||
return series;
|
||||
}
|
||||
|
||||
function linearInterpolation(timestamp, left, right) {
|
||||
if (left[1] === right[1]) {
|
||||
return (left[0] + right[0]) / 2;
|
||||
} else {
|
||||
return (left[0] + (right[0] - left[0]) / (right[1] - left[1]) * (timestamp - left[1]));
|
||||
}
|
||||
}
|
||||
|
||||
function findNearestRight(series, point) {
|
||||
var point_index = _.indexOf(series, point);
|
||||
var nearestRight;
|
||||
for (var i = point_index; i < series.length; i++) {
|
||||
if (series[i][0] !== null) {
|
||||
return series[i];
|
||||
}
|
||||
}
|
||||
return nearestRight;
|
||||
}
|
||||
|
||||
function findNearestLeft(series, point) {
|
||||
var point_index = _.indexOf(series, point);
|
||||
var nearestLeft;
|
||||
for (var i = point_index; i > 0; i--) {
|
||||
if (series[i][0] !== null) {
|
||||
return series[i];
|
||||
}
|
||||
}
|
||||
return nearestLeft;
|
||||
}
|
||||
291
src/datasource-zabbix/dataProcessor.js
Normal file
291
src/datasource-zabbix/dataProcessor.js
Normal file
@@ -0,0 +1,291 @@
|
||||
import _ from 'lodash';
|
||||
import * as utils from './utils';
|
||||
|
||||
/**
|
||||
* Downsample datapoints series
|
||||
*/
|
||||
function downsampleSeries(datapoints, time_to, ms_interval, func) {
|
||||
var downsampledSeries = [];
|
||||
var timeWindow = {
|
||||
from: time_to * 1000 - ms_interval,
|
||||
to: time_to * 1000
|
||||
};
|
||||
|
||||
var points_sum = 0;
|
||||
var points_num = 0;
|
||||
var value_avg = 0;
|
||||
var frame = [];
|
||||
|
||||
for (var i = datapoints.length - 1; i >= 0; i -= 1) {
|
||||
if (timeWindow.from < datapoints[i][1] && datapoints[i][1] <= timeWindow.to) {
|
||||
points_sum += datapoints[i][0];
|
||||
points_num++;
|
||||
frame.push(datapoints[i][0]);
|
||||
}
|
||||
else {
|
||||
value_avg = points_num ? points_sum / points_num : 0;
|
||||
|
||||
if (func === "max") {
|
||||
downsampledSeries.push([_.max(frame), timeWindow.to]);
|
||||
}
|
||||
else if (func === "min") {
|
||||
downsampledSeries.push([_.min(frame), timeWindow.to]);
|
||||
}
|
||||
|
||||
// avg by default
|
||||
else {
|
||||
downsampledSeries.push([value_avg, timeWindow.to]);
|
||||
}
|
||||
|
||||
// Shift time window
|
||||
timeWindow.to = timeWindow.from;
|
||||
timeWindow.from -= ms_interval;
|
||||
|
||||
points_sum = 0;
|
||||
points_num = 0;
|
||||
frame = [];
|
||||
|
||||
// Process point again
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return downsampledSeries.reverse();
|
||||
}
|
||||
|
||||
/**
|
||||
* Group points by given time interval
|
||||
* datapoints: [[<value>, <unixtime>], ...]
|
||||
*/
|
||||
function groupBy(interval, groupByCallback, datapoints) {
|
||||
var ms_interval = utils.parseInterval(interval);
|
||||
|
||||
// Calculate frame timestamps
|
||||
var frames = _.groupBy(datapoints, function(point) {
|
||||
// Calculate time for group of points
|
||||
return Math.floor(point[1] / ms_interval) * ms_interval;
|
||||
});
|
||||
|
||||
// frame: { '<unixtime>': [[<value>, <unixtime>], ...] }
|
||||
// return [{ '<unixtime>': <value> }, { '<unixtime>': <value> }, ...]
|
||||
var grouped = _.mapValues(frames, function(frame) {
|
||||
var points = _.map(frame, function(point) {
|
||||
return point[0];
|
||||
});
|
||||
return groupByCallback(points);
|
||||
});
|
||||
|
||||
// Convert points to Grafana format
|
||||
return sortByTime(_.map(grouped, function(value, timestamp) {
|
||||
return [Number(value), Number(timestamp)];
|
||||
}));
|
||||
}
|
||||
|
||||
function sumSeries(timeseries) {
|
||||
|
||||
// Calculate new points for interpolation
|
||||
var new_timestamps = _.uniq(_.map(_.flatten(timeseries, true), function(point) {
|
||||
return point[1];
|
||||
}));
|
||||
new_timestamps = _.sortBy(new_timestamps);
|
||||
|
||||
var interpolated_timeseries = _.map(timeseries, function(series) {
|
||||
var timestamps = _.map(series, function(point) {
|
||||
return point[1];
|
||||
});
|
||||
var new_points = _.map(_.difference(new_timestamps, timestamps), function(timestamp) {
|
||||
return [null, timestamp];
|
||||
});
|
||||
var new_series = series.concat(new_points);
|
||||
return sortByTime(new_series);
|
||||
});
|
||||
|
||||
_.each(interpolated_timeseries, interpolateSeries);
|
||||
|
||||
var new_timeseries = [];
|
||||
var sum;
|
||||
for (var i = new_timestamps.length - 1; i >= 0; i--) {
|
||||
sum = 0;
|
||||
for (var j = interpolated_timeseries.length - 1; j >= 0; j--) {
|
||||
sum += interpolated_timeseries[j][i][0];
|
||||
}
|
||||
new_timeseries.push([sum, new_timestamps[i]]);
|
||||
}
|
||||
|
||||
return sortByTime(new_timeseries);
|
||||
}
|
||||
|
||||
function limit(order, n, orderByFunc, timeseries) {
|
||||
let orderByCallback = aggregationFunctions[orderByFunc];
|
||||
let sortByIteratee = (ts) => {
|
||||
let values = _.map(ts.datapoints, (point) => {
|
||||
return point[0];
|
||||
});
|
||||
return orderByCallback(values);
|
||||
};
|
||||
let sortedTimeseries = _.sortBy(timeseries, sortByIteratee);
|
||||
if (order === 'bottom') {
|
||||
return sortedTimeseries.slice(0, n);
|
||||
} else {
|
||||
return sortedTimeseries.slice(-n);
|
||||
}
|
||||
}
|
||||
|
||||
function AVERAGE(values) {
|
||||
var sum = 0;
|
||||
_.each(values, function(value) {
|
||||
sum += value;
|
||||
});
|
||||
return sum / values.length;
|
||||
}
|
||||
|
||||
function MIN(values) {
|
||||
return _.min(values);
|
||||
}
|
||||
|
||||
function MAX(values) {
|
||||
return _.max(values);
|
||||
}
|
||||
|
||||
function MEDIAN(values) {
|
||||
var sorted = _.sortBy(values);
|
||||
return sorted[Math.floor(sorted.length / 2)];
|
||||
}
|
||||
|
||||
function setAlias(alias, timeseries) {
|
||||
timeseries.target = alias;
|
||||
return timeseries;
|
||||
}
|
||||
|
||||
function scale(factor, datapoints) {
|
||||
return _.map(datapoints, point => {
|
||||
return [
|
||||
point[0] * factor,
|
||||
point[1]
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
function delta(datapoints) {
|
||||
let newSeries = [];
|
||||
let deltaValue;
|
||||
for (var i = 1; i < datapoints.length; i++) {
|
||||
deltaValue = datapoints[i][0] - datapoints[i - 1][0];
|
||||
newSeries.push([deltaValue, datapoints[i][1]]);
|
||||
}
|
||||
return newSeries;
|
||||
}
|
||||
|
||||
function groupByWrapper(interval, groupFunc, datapoints) {
|
||||
var groupByCallback = aggregationFunctions[groupFunc];
|
||||
return groupBy(interval, groupByCallback, datapoints);
|
||||
}
|
||||
|
||||
function aggregateByWrapper(interval, aggregateFunc, datapoints) {
|
||||
// Flatten all points in frame and then just use groupBy()
|
||||
var flattenedPoints = _.flatten(datapoints, true);
|
||||
var groupByCallback = aggregationFunctions[aggregateFunc];
|
||||
return groupBy(interval, groupByCallback, flattenedPoints);
|
||||
}
|
||||
|
||||
function aggregateWrapper(groupByCallback, interval, datapoints) {
|
||||
var flattenedPoints = _.flatten(datapoints, true);
|
||||
return groupBy(interval, groupByCallback, flattenedPoints);
|
||||
}
|
||||
|
||||
function sortByTime(series) {
|
||||
return _.sortBy(series, function(point) {
|
||||
return point[1];
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolate series with gaps
|
||||
*/
|
||||
function interpolateSeries(series) {
|
||||
var left, right;
|
||||
|
||||
// Interpolate series
|
||||
for (var i = series.length - 1; i >= 0; i--) {
|
||||
if (!series[i][0]) {
|
||||
left = findNearestLeft(series, series[i]);
|
||||
right = findNearestRight(series, series[i]);
|
||||
if (!left) {
|
||||
left = right;
|
||||
}
|
||||
if (!right) {
|
||||
right = left;
|
||||
}
|
||||
series[i][0] = linearInterpolation(series[i][1], left, right);
|
||||
}
|
||||
}
|
||||
return series;
|
||||
}
|
||||
|
||||
function linearInterpolation(timestamp, left, right) {
|
||||
if (left[1] === right[1]) {
|
||||
return (left[0] + right[0]) / 2;
|
||||
} else {
|
||||
return (left[0] + (right[0] - left[0]) / (right[1] - left[1]) * (timestamp - left[1]));
|
||||
}
|
||||
}
|
||||
|
||||
function findNearestRight(series, point) {
|
||||
var point_index = _.indexOf(series, point);
|
||||
var nearestRight;
|
||||
for (var i = point_index; i < series.length; i++) {
|
||||
if (series[i][0] !== null) {
|
||||
return series[i];
|
||||
}
|
||||
}
|
||||
return nearestRight;
|
||||
}
|
||||
|
||||
function findNearestLeft(series, point) {
|
||||
var point_index = _.indexOf(series, point);
|
||||
var nearestLeft;
|
||||
for (var i = point_index; i > 0; i--) {
|
||||
if (series[i][0] !== null) {
|
||||
return series[i];
|
||||
}
|
||||
}
|
||||
return nearestLeft;
|
||||
}
|
||||
|
||||
let metricFunctions = {
|
||||
groupBy: groupByWrapper,
|
||||
scale: scale,
|
||||
delta: delta,
|
||||
aggregateBy: aggregateByWrapper,
|
||||
average: _.partial(aggregateWrapper, AVERAGE),
|
||||
min: _.partial(aggregateWrapper, MIN),
|
||||
max: _.partial(aggregateWrapper, MAX),
|
||||
median: _.partial(aggregateWrapper, MEDIAN),
|
||||
sumSeries: sumSeries,
|
||||
top: _.partial(limit, 'top'),
|
||||
bottom: _.partial(limit, 'bottom'),
|
||||
setAlias: setAlias
|
||||
};
|
||||
|
||||
let aggregationFunctions = {
|
||||
avg: AVERAGE,
|
||||
min: MIN,
|
||||
max: MAX,
|
||||
median: MEDIAN
|
||||
};
|
||||
|
||||
export default {
|
||||
downsampleSeries: downsampleSeries,
|
||||
groupBy: groupBy,
|
||||
AVERAGE: AVERAGE,
|
||||
MIN: MIN,
|
||||
MAX: MAX,
|
||||
MEDIAN: MEDIAN,
|
||||
|
||||
get aggregationFunctions() {
|
||||
return aggregationFunctions;
|
||||
},
|
||||
|
||||
get metricFunctions() {
|
||||
return metricFunctions;
|
||||
}
|
||||
};
|
||||
@@ -4,15 +4,17 @@ import * as dateMath from 'app/core/utils/datemath';
|
||||
import * as utils from './utils';
|
||||
import * as migrations from './migrations';
|
||||
import * as metricFunctions from './metricFunctions';
|
||||
import DataProcessor from './DataProcessor';
|
||||
import './zabbixAPI.service.js';
|
||||
import './zabbixCache.service.js';
|
||||
import './queryProcessor.service.js';
|
||||
import dataProcessor from './dataProcessor';
|
||||
import responseHandler from './responseHandler';
|
||||
import './zabbix.js';
|
||||
import {ZabbixAPIError} from './zabbixAPICore.service.js';
|
||||
|
||||
class ZabbixAPIDatasource {
|
||||
|
||||
/** @ngInject */
|
||||
constructor(instanceSettings, $q, templateSrv, alertSrv, zabbixAPIService, ZabbixCachingProxy, QueryProcessor) {
|
||||
constructor(instanceSettings, templateSrv, alertSrv, Zabbix) {
|
||||
this.templateSrv = templateSrv;
|
||||
this.alertSrv = alertSrv;
|
||||
|
||||
// General data source settings
|
||||
this.name = instanceSettings.name;
|
||||
@@ -32,20 +34,7 @@ class ZabbixAPIDatasource {
|
||||
var ttl = instanceSettings.jsonData.cacheTTL || '1h';
|
||||
this.cacheTTL = utils.parseInterval(ttl);
|
||||
|
||||
// Initialize Zabbix API
|
||||
var ZabbixAPI = zabbixAPIService;
|
||||
this.zabbixAPI = new ZabbixAPI(this.url, this.username, this.password, this.basicAuth, this.withCredentials);
|
||||
|
||||
// Initialize cache service
|
||||
this.zabbixCache = new ZabbixCachingProxy(this.zabbixAPI, this.cacheTTL);
|
||||
|
||||
// Initialize query builder
|
||||
this.queryProcessor = new QueryProcessor(this.zabbixCache);
|
||||
|
||||
// Dependencies
|
||||
this.q = $q;
|
||||
this.templateSrv = templateSrv;
|
||||
this.alertSrv = alertSrv;
|
||||
this.zabbix = new Zabbix(this.url, this.username, this.password, this.basicAuth, this.withCredentials, this.cacheTTL);
|
||||
|
||||
// Use custom format for template variables
|
||||
this.replaceTemplateVars = _.partial(replaceTemplateVars, this.templateSrv);
|
||||
@@ -69,12 +58,12 @@ class ZabbixAPIDatasource {
|
||||
|
||||
// Create request for each target
|
||||
var promises = _.map(options.targets, target => {
|
||||
|
||||
// Prevent changes of original object
|
||||
target = _.cloneDeep(target);
|
||||
this.replaceTargetVariables(target, options);
|
||||
|
||||
// Metrics or Text query mode
|
||||
if (target.mode !== 1) {
|
||||
|
||||
// Migrate old targets
|
||||
target = migrations.migrate(target);
|
||||
|
||||
@@ -83,30 +72,9 @@ class ZabbixAPIDatasource {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Replace templated variables
|
||||
target.group.filter = this.replaceTemplateVars(target.group.filter, options.scopedVars);
|
||||
target.host.filter = this.replaceTemplateVars(target.host.filter, options.scopedVars);
|
||||
target.application.filter = this.replaceTemplateVars(target.application.filter, options.scopedVars);
|
||||
target.item.filter = this.replaceTemplateVars(target.item.filter, options.scopedVars);
|
||||
target.textFilter = this.replaceTemplateVars(target.textFilter, options.scopedVars);
|
||||
|
||||
_.forEach(target.functions, func => {
|
||||
func.params = _.map(func.params, param => {
|
||||
if (typeof param === 'number') {
|
||||
return +this.templateSrv.replace(param.toString(), options.scopedVars);
|
||||
} else {
|
||||
return this.templateSrv.replace(param, options.scopedVars);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Query numeric data
|
||||
if (!target.mode || target.mode === 0) {
|
||||
return this.queryNumericData(target, timeFrom, timeTo, useTrends);
|
||||
}
|
||||
|
||||
// Query text data
|
||||
else if (target.mode === 2) {
|
||||
} else if (target.mode === 2) {
|
||||
return this.queryTextData(target, timeFrom, timeTo);
|
||||
}
|
||||
}
|
||||
@@ -118,71 +86,62 @@ class ZabbixAPIDatasource {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.zabbixAPI
|
||||
.getSLA(target.itservice.serviceid, timeFrom, timeTo)
|
||||
return this.zabbix.getSLA(target.itservice.serviceid, timeFrom, timeTo)
|
||||
.then(slaObject => {
|
||||
return this.queryProcessor
|
||||
.handleSLAResponse(target.itservice, target.slaProperty, slaObject);
|
||||
return responseHandler.handleSLAResponse(target.itservice, target.slaProperty, slaObject);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Data for panel (all targets)
|
||||
return this.q.all(_.flatten(promises))
|
||||
return Promise.all(_.flatten(promises))
|
||||
.then(_.flatten)
|
||||
.then(timeseries_data => {
|
||||
|
||||
// Series downsampling
|
||||
var data = _.map(timeseries_data, timeseries => {
|
||||
if (timeseries.datapoints.length > options.maxDataPoints) {
|
||||
timeseries.datapoints = DataProcessor
|
||||
.groupBy(options.interval, DataProcessor.AVERAGE, timeseries.datapoints);
|
||||
}
|
||||
return timeseries;
|
||||
});
|
||||
return downsampleSeries(timeseries_data, options);
|
||||
})
|
||||
.then(data => {
|
||||
return { data: data };
|
||||
});
|
||||
}
|
||||
|
||||
queryNumericData(target, timeFrom, timeTo, useTrends) {
|
||||
// Build query in asynchronous manner
|
||||
return this.queryProcessor.build(target.group.filter,
|
||||
target.host.filter,
|
||||
target.application.filter,
|
||||
target.item.filter,
|
||||
'num')
|
||||
let options = {
|
||||
itemtype: 'num'
|
||||
};
|
||||
return this.zabbix.getItemsFromTarget(target, options)
|
||||
.then(items => {
|
||||
// Add hostname for items from multiple hosts
|
||||
var addHostName = utils.isRegex(target.host.filter);
|
||||
var getHistory;
|
||||
let getHistoryPromise;
|
||||
|
||||
// Use trends
|
||||
if (useTrends) {
|
||||
let valueType = this.getTrendValueType(target);
|
||||
getHistoryPromise = this.zabbix.getTrend(items, timeFrom, timeTo)
|
||||
.then(history => {
|
||||
return responseHandler.handleTrends(history, items, valueType);
|
||||
});
|
||||
} else {
|
||||
// Use history
|
||||
getHistoryPromise = this.zabbix.getHistory(items, timeFrom, timeTo)
|
||||
.then(history => {
|
||||
return responseHandler.handleHistory(history, items);
|
||||
});
|
||||
}
|
||||
|
||||
return getHistoryPromise.then(timeseries_data => {
|
||||
return this.applyDataProcessingFunctions(timeseries_data, target);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
getTrendValueType(target) {
|
||||
// Find trendValue() function and get specified trend value
|
||||
var trendFunctions = _.map(metricFunctions.getCategories()['Trends'], 'name');
|
||||
var trendValueFunc = _.find(target.functions, func => {
|
||||
return _.includes(trendFunctions, func.def.name);
|
||||
});
|
||||
var valueType = trendValueFunc ? trendValueFunc.params[0] : "avg";
|
||||
|
||||
getHistory = this.zabbixAPI
|
||||
.getTrend(items, timeFrom, timeTo)
|
||||
.then(history => {
|
||||
return this.queryProcessor.handleTrends(history, items, addHostName, valueType);
|
||||
});
|
||||
return trendValueFunc ? trendValueFunc.params[0] : "avg";
|
||||
}
|
||||
|
||||
// Use history
|
||||
else {
|
||||
getHistory = this.zabbixCache
|
||||
.getHistory(items, timeFrom, timeTo)
|
||||
.then(history => {
|
||||
return this.queryProcessor.handleHistory(history, items, addHostName);
|
||||
});
|
||||
}
|
||||
|
||||
return getHistory.then(timeseries_data => {
|
||||
applyDataProcessingFunctions(timeseries_data, target) {
|
||||
let transformFunctions = bindFunctionDefs(target.functions, 'Transform');
|
||||
let aggregationFunctions = bindFunctionDefs(target.functions, 'Aggregate');
|
||||
let filterFunctions = bindFunctionDefs(target.functions, 'Filter');
|
||||
@@ -209,33 +168,28 @@ class ZabbixAPIDatasource {
|
||||
return _.includes(aggFuncNames, func.def.name);
|
||||
});
|
||||
|
||||
timeseries_data = [
|
||||
{
|
||||
timeseries_data = [{
|
||||
target: lastAgg.text,
|
||||
datapoints: dp
|
||||
}
|
||||
];
|
||||
}];
|
||||
}
|
||||
|
||||
// Apply alias functions
|
||||
_.each(timeseries_data, sequence(aliasFunctions));
|
||||
|
||||
return timeseries_data;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
queryTextData(target, timeFrom, timeTo) {
|
||||
return this.queryProcessor.build(target.group.filter,
|
||||
target.host.filter,
|
||||
target.application.filter,
|
||||
target.item.filter,
|
||||
'text')
|
||||
let options = {
|
||||
itemtype: 'text'
|
||||
};
|
||||
return this.zabbix.getItemsFromTarget(target, options)
|
||||
.then(items => {
|
||||
if (items.length) {
|
||||
return this.zabbixAPI.getHistory(items, timeFrom, timeTo)
|
||||
return this.zabbix.getHistory(items, timeFrom, timeTo)
|
||||
.then(history => {
|
||||
return this.queryProcessor.convertHistory(history, items, false, (point) => {
|
||||
return responseHandler.convertHistory(history, items, false, (point) => {
|
||||
let value = point.value;
|
||||
|
||||
// Regex-based extractor
|
||||
@@ -247,7 +201,7 @@ class ZabbixAPIDatasource {
|
||||
});
|
||||
});
|
||||
} else {
|
||||
return this.q.when([]);
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -257,37 +211,33 @@ class ZabbixAPIDatasource {
|
||||
* @return {object} Connection status and Zabbix API version
|
||||
*/
|
||||
testDatasource() {
|
||||
return this.zabbixAPI.getVersion()
|
||||
let zabbixVersion;
|
||||
return this.zabbix.getVersion()
|
||||
.then(version => {
|
||||
return this.zabbixAPI.login()
|
||||
.then(auth => {
|
||||
if (auth) {
|
||||
zabbixVersion = version;
|
||||
return this.zabbix.login();
|
||||
})
|
||||
.then(() => {
|
||||
return {
|
||||
status: "success",
|
||||
title: "Success",
|
||||
message: "Zabbix API version: " + version
|
||||
message: "Zabbix API version: " + zabbixVersion
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
status: "error",
|
||||
title: "Invalid user name or password",
|
||||
message: "Zabbix API version: " + version
|
||||
};
|
||||
}
|
||||
}, error => {
|
||||
})
|
||||
.catch(error => {
|
||||
if (error instanceof ZabbixAPIError) {
|
||||
return {
|
||||
status: "error",
|
||||
title: error.message,
|
||||
message: error.data
|
||||
};
|
||||
});
|
||||
}, error => {
|
||||
console.log(error);
|
||||
} else {
|
||||
return {
|
||||
status: "error",
|
||||
title: "Connection failed",
|
||||
message: "Could not connect to given url"
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -324,22 +274,22 @@ class ZabbixAPIDatasource {
|
||||
if (template.app === '/.*/') {
|
||||
template.app = '';
|
||||
}
|
||||
result = this.queryProcessor.getItems(template.group, template.host, template.app);
|
||||
result = this.zabbix.getItems(template.group, template.host, template.app, template.item);
|
||||
} else if (parts.length === 3) {
|
||||
// Get applications
|
||||
result = this.queryProcessor.getApps(template.group, template.host);
|
||||
result = this.zabbix.getApps(template.group, template.host, template.app);
|
||||
} else if (parts.length === 2) {
|
||||
// Get hosts
|
||||
result = this.queryProcessor.getHosts(template.group);
|
||||
result = this.zabbix.getHosts(template.group, template.host);
|
||||
} else if (parts.length === 1) {
|
||||
// Get groups
|
||||
result = this.zabbixCache.getGroups(template.group);
|
||||
result = this.zabbix.getGroups(template.group);
|
||||
} else {
|
||||
result = this.q.when([]);
|
||||
result = Promise.resolve([]);
|
||||
}
|
||||
|
||||
return result.then(metrics => {
|
||||
return _.map(metrics, formatMetric);
|
||||
return metrics.map(formatMetric);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -356,15 +306,13 @@ class ZabbixAPIDatasource {
|
||||
// Show all triggers
|
||||
var showTriggers = [0, 1];
|
||||
|
||||
var buildQuery = this.queryProcessor
|
||||
.buildTriggerQuery(this.replaceTemplateVars(annotation.group, {}),
|
||||
var getTriggers = this.zabbix
|
||||
.getTriggers(this.replaceTemplateVars(annotation.group, {}),
|
||||
this.replaceTemplateVars(annotation.host, {}),
|
||||
this.replaceTemplateVars(annotation.application, {}));
|
||||
this.replaceTemplateVars(annotation.application, {}),
|
||||
showTriggers);
|
||||
|
||||
return buildQuery.then(query => {
|
||||
return this.zabbixAPI
|
||||
.getTriggers(query.groupids, query.hostids, query.applicationids, showTriggers)
|
||||
.then(triggers => {
|
||||
return getTriggers.then(triggers => {
|
||||
|
||||
// Filter triggers by description
|
||||
if (utils.isRegex(annotation.trigger)) {
|
||||
@@ -383,7 +331,7 @@ class ZabbixAPIDatasource {
|
||||
});
|
||||
|
||||
var objectids = _.map(triggers, 'triggerid');
|
||||
return this.zabbixAPI
|
||||
return this.zabbix
|
||||
.getEvents(objectids, timeFrom, timeTo, showOkEvents)
|
||||
.then(events => {
|
||||
var indexedTriggers = _.keyBy(triggers, 'triggerid');
|
||||
@@ -415,6 +363,24 @@ class ZabbixAPIDatasource {
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Replace template variables
|
||||
replaceTargetVariables(target, options) {
|
||||
let parts = ['group', 'host', 'application', 'item'];
|
||||
parts.forEach(p => {
|
||||
target[p].filter = this.replaceTemplateVars(target[p].filter, options.scopedVars);
|
||||
});
|
||||
target.textFilter = this.replaceTemplateVars(target.textFilter, options.scopedVars);
|
||||
|
||||
_.forEach(target.functions, func => {
|
||||
func.params = func.params.map(param => {
|
||||
if (typeof param === 'number') {
|
||||
return +this.templateSrv.replace(param.toString(), options.scopedVars);
|
||||
} else {
|
||||
return this.templateSrv.replace(param, options.scopedVars);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -428,7 +394,17 @@ function bindFunctionDefs(functionDefs, category) {
|
||||
|
||||
return _.map(aggFuncDefs, function(func) {
|
||||
var funcInstance = metricFunctions.createFuncInstance(func.def, func.params);
|
||||
return funcInstance.bindFunction(DataProcessor.metricFunctions);
|
||||
return funcInstance.bindFunction(dataProcessor.metricFunctions);
|
||||
});
|
||||
}
|
||||
|
||||
function downsampleSeries(timeseries_data, options) {
|
||||
return _.map(timeseries_data, timeseries => {
|
||||
if (timeseries.datapoints.length > options.maxDataPoints) {
|
||||
timeseries.datapoints = dataProcessor
|
||||
.groupBy(options.interval, dataProcessor.AVERAGE, timeseries.datapoints);
|
||||
}
|
||||
return timeseries;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -13,14 +13,9 @@ import './css/query-editor.css!';
|
||||
export class ZabbixQueryController extends QueryCtrl {
|
||||
|
||||
// ZabbixQueryCtrl constructor
|
||||
constructor($scope, $injector, $rootScope, $sce, $q, templateSrv) {
|
||||
|
||||
// Call superclass constructor
|
||||
constructor($scope, $injector, $rootScope, $sce, templateSrv) {
|
||||
super($scope, $injector);
|
||||
|
||||
this.zabbix = this.datasource.zabbixAPI;
|
||||
this.cache = this.datasource.zabbixCache;
|
||||
this.$q = $q;
|
||||
this.zabbix = this.datasource.zabbix;
|
||||
|
||||
// Use custom format for template variables
|
||||
this.replaceTemplateVars = this.datasource.replaceTemplateVars;
|
||||
@@ -106,85 +101,57 @@ export class ZabbixQueryController extends QueryCtrl {
|
||||
}
|
||||
|
||||
initFilters() {
|
||||
var self = this;
|
||||
var itemtype = self.editorModes[self.target.mode].value;
|
||||
return this.$q.when(this.suggestGroups())
|
||||
.then(() => {return self.suggestHosts();})
|
||||
.then(() => {return self.suggestApps();})
|
||||
.then(() => {return self.suggestItems(itemtype);});
|
||||
let itemtype = this.editorModes[this.target.mode].value;
|
||||
return Promise.all([
|
||||
this.suggestGroups(),
|
||||
this.suggestHosts(),
|
||||
this.suggestApps(),
|
||||
this.suggestItems(itemtype)
|
||||
]);
|
||||
}
|
||||
|
||||
suggestGroups() {
|
||||
var self = this;
|
||||
return this.cache.getGroups().then(groups => {
|
||||
self.metric.groupList = groups;
|
||||
return this.zabbix.getAllGroups()
|
||||
.then(groups => {
|
||||
this.metric.groupList = groups;
|
||||
return groups;
|
||||
});
|
||||
}
|
||||
|
||||
suggestHosts() {
|
||||
var self = this;
|
||||
var groupFilter = this.replaceTemplateVars(this.target.group.filter);
|
||||
return this.datasource.queryProcessor
|
||||
.filterGroups(self.metric.groupList, groupFilter)
|
||||
.then(groups => {
|
||||
var groupids = _.map(groups, 'groupid');
|
||||
return self.zabbix
|
||||
.getHosts(groupids)
|
||||
let groupFilter = this.replaceTemplateVars(this.target.group.filter);
|
||||
return this.zabbix.getAllHosts(groupFilter)
|
||||
.then(hosts => {
|
||||
self.metric.hostList = hosts;
|
||||
this.metric.hostList = hosts;
|
||||
return hosts;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
suggestApps() {
|
||||
var self = this;
|
||||
var hostFilter = this.replaceTemplateVars(this.target.host.filter);
|
||||
return this.datasource.queryProcessor
|
||||
.filterHosts(self.metric.hostList, hostFilter)
|
||||
.then(hosts => {
|
||||
var hostids = _.map(hosts, 'hostid');
|
||||
return self.zabbix
|
||||
.getApps(hostids)
|
||||
let groupFilter = this.replaceTemplateVars(this.target.group.filter);
|
||||
let hostFilter = this.replaceTemplateVars(this.target.host.filter);
|
||||
return this.zabbix.getAllApps(groupFilter, hostFilter)
|
||||
.then(apps => {
|
||||
return self.metric.appList = apps;
|
||||
});
|
||||
this.metric.appList = apps;
|
||||
return apps;
|
||||
});
|
||||
}
|
||||
|
||||
suggestItems(itemtype = 'num') {
|
||||
var self = this;
|
||||
var appFilter = this.replaceTemplateVars(this.target.application.filter);
|
||||
if (appFilter) {
|
||||
// Filter by applications
|
||||
return this.datasource.queryProcessor
|
||||
.filterApps(self.metric.appList, appFilter)
|
||||
.then(apps => {
|
||||
var appids = _.map(apps, 'applicationid');
|
||||
return self.zabbix
|
||||
.getItems(undefined, appids, itemtype)
|
||||
let groupFilter = this.replaceTemplateVars(this.target.group.filter);
|
||||
let hostFilter = this.replaceTemplateVars(this.target.host.filter);
|
||||
let appFilter = this.replaceTemplateVars(this.target.application.filter);
|
||||
let options = {
|
||||
itemtype: itemtype,
|
||||
showDisabledItems: this.target.options.showDisabledItems
|
||||
};
|
||||
|
||||
return this.zabbix
|
||||
.getAllItems(groupFilter, hostFilter, appFilter, options)
|
||||
.then(items => {
|
||||
if (!self.target.options.showDisabledItems) {
|
||||
items = _.filter(items, {'status': '0'});
|
||||
}
|
||||
self.metric.itemList = items;
|
||||
this.metric.itemList = items;
|
||||
return items;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Return all items belonged to selected hosts
|
||||
var hostids = _.map(self.metric.hostList, 'hostid');
|
||||
return self.zabbix
|
||||
.getItems(hostids, undefined, itemtype)
|
||||
.then(items => {
|
||||
if (!self.target.options.showDisabledItems) {
|
||||
items = _.filter(items, {'status': '0'});
|
||||
}
|
||||
self.metric.itemList = items;
|
||||
return items;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
isRegex(str) {
|
||||
@@ -331,7 +298,6 @@ export class ZabbixQueryController extends QueryCtrl {
|
||||
this.panelCtrl.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Set templateUrl as static property
|
||||
|
||||
@@ -1,366 +0,0 @@
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
import * as utils from './utils';
|
||||
|
||||
/** @ngInject */
|
||||
angular.module('grafana.services').factory('QueryProcessor', function($q) {
|
||||
|
||||
class QueryProcessor {
|
||||
constructor(zabbixCacheInstance) {
|
||||
this.cache = zabbixCacheInstance;
|
||||
this.$q = $q;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build query in asynchronous manner
|
||||
*/
|
||||
build(groupFilter, hostFilter, appFilter, itemFilter, itemtype) {
|
||||
var self = this;
|
||||
if (this.cache._initialized) {
|
||||
return this.$q.when(self.buildFromCache(groupFilter, hostFilter, appFilter, itemFilter, itemtype));
|
||||
} else {
|
||||
return this.cache.refresh().then(function() {
|
||||
return self.buildFromCache(groupFilter, hostFilter, appFilter, itemFilter, itemtype);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build trigger query in asynchronous manner
|
||||
*/
|
||||
buildTriggerQuery(groupFilter, hostFilter, appFilter) {
|
||||
var self = this;
|
||||
if (this.cache._initialized) {
|
||||
return this.$q.when(self.buildTriggerQueryFromCache(groupFilter, hostFilter, appFilter));
|
||||
} else {
|
||||
return this.cache.refresh().then(function() {
|
||||
return self.buildTriggerQueryFromCache(groupFilter, hostFilter, appFilter);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
filterGroups(groups, groupFilter) {
|
||||
return this.$q.when(
|
||||
findByFilter(groups, groupFilter)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of host belonging to given groups.
|
||||
* @return list of hosts
|
||||
*/
|
||||
filterHosts(hosts, hostFilter) {
|
||||
return this.$q.when(
|
||||
findByFilter(hosts, hostFilter)
|
||||
);
|
||||
}
|
||||
|
||||
filterApps(apps, appFilter) {
|
||||
return this.$q.when(
|
||||
findByFilter(apps, appFilter)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build query - convert target filters to array of Zabbix items
|
||||
*/
|
||||
buildFromCache(groupFilter, hostFilter, appFilter, itemFilter, itemtype, showDisabledItems) {
|
||||
return this.getItems(groupFilter, hostFilter, appFilter, itemtype, showDisabledItems)
|
||||
.then(items => {
|
||||
return getByFilter(items, itemFilter);
|
||||
});
|
||||
}
|
||||
|
||||
getGroups() {
|
||||
return this.cache.getGroups();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of host belonging to given groups.
|
||||
* @return list of hosts
|
||||
*/
|
||||
getHosts(groupFilter) {
|
||||
var self = this;
|
||||
return this.cache
|
||||
.getGroups()
|
||||
.then(groups => {
|
||||
return findByFilter(groups, groupFilter);
|
||||
})
|
||||
.then(groups => {
|
||||
var groupids = _.map(groups, 'groupid');
|
||||
return self.cache.getHosts(groupids);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of applications belonging to given groups and hosts.
|
||||
* @return list of applications belonging to given hosts
|
||||
*/
|
||||
getApps(groupFilter, hostFilter) {
|
||||
var self = this;
|
||||
return this.getHosts(groupFilter)
|
||||
.then(hosts => {
|
||||
return findByFilter(hosts, hostFilter);
|
||||
})
|
||||
.then(hosts => {
|
||||
var hostids = _.map(hosts, 'hostid');
|
||||
return self.cache.getApps(hostids);
|
||||
});
|
||||
}
|
||||
|
||||
getItems(groupFilter, hostFilter, appFilter, itemtype, showDisabledItems) {
|
||||
var self = this;
|
||||
return this.getHosts(groupFilter)
|
||||
.then(hosts => {
|
||||
return findByFilter(hosts, hostFilter);
|
||||
})
|
||||
.then(hosts => {
|
||||
var hostids = _.map(hosts, 'hostid');
|
||||
if (appFilter) {
|
||||
return self.cache
|
||||
.getApps(hostids)
|
||||
.then(apps => {
|
||||
// Use getByFilter for proper item filtering
|
||||
return getByFilter(apps, appFilter);
|
||||
});
|
||||
} else {
|
||||
return {
|
||||
appFilterEmpty: true,
|
||||
hostids: hostids
|
||||
};
|
||||
}
|
||||
})
|
||||
.then(apps => {
|
||||
if (apps.appFilterEmpty) {
|
||||
return self.cache
|
||||
.getItems(apps.hostids, undefined, itemtype)
|
||||
.then(items => {
|
||||
if (showDisabledItems) {
|
||||
items = _.filter(items, {'status': '0'});
|
||||
}
|
||||
return items;
|
||||
});
|
||||
} else {
|
||||
var appids = _.map(apps, 'applicationid');
|
||||
return self.cache
|
||||
.getItems(undefined, appids, itemtype)
|
||||
.then(items => {
|
||||
if (showDisabledItems) {
|
||||
items = _.filter(items, {'status': '0'});
|
||||
}
|
||||
return items;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Build query - convert target filters to array of Zabbix items
|
||||
*/
|
||||
buildTriggerQueryFromCache(groupFilter, hostFilter, appFilter) {
|
||||
var promises = [
|
||||
this.cache.getGroups().then(function(groups) {
|
||||
return _.filter(groups, function(group) {
|
||||
if (utils.isRegex(groupFilter)) {
|
||||
return utils.buildRegex(groupFilter).test(group.name);
|
||||
} else {
|
||||
return group.name === groupFilter;
|
||||
}
|
||||
});
|
||||
}),
|
||||
this.getHosts(groupFilter).then(function(hosts) {
|
||||
return _.filter(hosts, function(host) {
|
||||
if (utils.isRegex(hostFilter)) {
|
||||
return utils.buildRegex(hostFilter).test(host.name);
|
||||
} else {
|
||||
return host.name === hostFilter;
|
||||
}
|
||||
});
|
||||
}),
|
||||
this.getApps(groupFilter, hostFilter).then(function(apps) {
|
||||
return _.filter(apps, function(app) {
|
||||
if (utils.isRegex(appFilter)) {
|
||||
return utils.buildRegex(appFilter).test(app.name);
|
||||
} else {
|
||||
return app.name === appFilter;
|
||||
}
|
||||
});
|
||||
})
|
||||
];
|
||||
|
||||
return this.$q.all(promises).then(function(results) {
|
||||
var filteredGroups = results[0];
|
||||
var filteredHosts = results[1];
|
||||
var filteredApps = results[2];
|
||||
var query = {};
|
||||
|
||||
if (appFilter) {
|
||||
query.applicationids = _.flatten(_.map(filteredApps, 'applicationid'));
|
||||
}
|
||||
if (hostFilter) {
|
||||
query.hostids = _.map(filteredHosts, 'hostid');
|
||||
}
|
||||
if (groupFilter) {
|
||||
query.groupids = _.map(filteredGroups, 'groupid');
|
||||
}
|
||||
|
||||
return query;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Zabbix API history.get response to Grafana format
|
||||
*
|
||||
* @return {Array} Array of timeseries in Grafana format
|
||||
* {
|
||||
* target: "Metric name",
|
||||
* datapoints: [[<value>, <unixtime>], ...]
|
||||
* }
|
||||
*/
|
||||
convertHistory(history, items, addHostName, convertPointCallback) {
|
||||
/**
|
||||
* Response should be in the format:
|
||||
* data: [
|
||||
* {
|
||||
* target: "Metric name",
|
||||
* datapoints: [[<value>, <unixtime>], ...]
|
||||
* }, ...
|
||||
* ]
|
||||
*/
|
||||
|
||||
// Group history by itemid
|
||||
var grouped_history = _.groupBy(history, 'itemid');
|
||||
var hosts = _.uniq(_.flatten(_.map(items, 'hosts')),'hostid'); //uniq is needed to deduplicate
|
||||
|
||||
return _.map(grouped_history, function(hist, itemid) {
|
||||
var item = _.find(items, {'itemid': itemid});
|
||||
var alias = item.name;
|
||||
if (_.keys(hosts).length > 1 && addHostName) { //only when actual multi hosts selected
|
||||
var host = _.find(hosts, {'hostid': item.hostid});
|
||||
alias = host.name + ": " + alias;
|
||||
}
|
||||
return {
|
||||
target: alias,
|
||||
datapoints: _.map(hist, convertPointCallback)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
handleHistory(history, items, addHostName) {
|
||||
return this.convertHistory(history, items, addHostName, convertHistoryPoint);
|
||||
}
|
||||
|
||||
handleTrends(history, items, addHostName, valueType) {
|
||||
var convertPointCallback = _.partial(convertTrendPoint, valueType);
|
||||
return this.convertHistory(history, items, addHostName, convertPointCallback);
|
||||
}
|
||||
|
||||
handleSLAResponse(itservice, slaProperty, slaObject) {
|
||||
var targetSLA = slaObject[itservice.serviceid].sla[0];
|
||||
if (slaProperty.property === 'status') {
|
||||
var targetStatus = parseInt(slaObject[itservice.serviceid].status);
|
||||
return {
|
||||
target: itservice.name + ' ' + slaProperty.name,
|
||||
datapoints: [
|
||||
[targetStatus, targetSLA.to * 1000]
|
||||
]
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
target: itservice.name + ' ' + slaProperty.name,
|
||||
datapoints: [
|
||||
[targetSLA[slaProperty.property], targetSLA.from * 1000],
|
||||
[targetSLA[slaProperty.property], targetSLA.to * 1000]
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return QueryProcessor;
|
||||
});
|
||||
|
||||
/**
|
||||
* Find group, host, app or item by given name.
|
||||
* @param list list of groups, apps or other
|
||||
* @param name visible name
|
||||
* @return array with finded element or undefined
|
||||
*/
|
||||
function findByName(list, name) {
|
||||
var finded = _.find(list, {'name': name});
|
||||
if (finded) {
|
||||
return [finded];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Different hosts can contains applications and items with same name.
|
||||
* For this reason use _.filter, which return all elements instead _.find,
|
||||
* which return only first finded.
|
||||
* @param {[type]} list list of elements
|
||||
* @param {[type]} name app name
|
||||
* @return {[type]} array with finded element or undefined
|
||||
*/
|
||||
function filterByName(list, name) {
|
||||
var finded = _.filter(list, {'name': name});
|
||||
if (finded) {
|
||||
return finded;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function findByRegex(list, regex) {
|
||||
var filterPattern = utils.buildRegex(regex);
|
||||
return _.filter(list, function (zbx_obj) {
|
||||
return filterPattern.test(zbx_obj.name);
|
||||
});
|
||||
}
|
||||
|
||||
function findByFilter(list, filter) {
|
||||
if (utils.isRegex(filter)) {
|
||||
return findByRegex(list, filter);
|
||||
} else {
|
||||
return findByName(list, filter);
|
||||
}
|
||||
}
|
||||
|
||||
function getByFilter(list, filter) {
|
||||
if (utils.isRegex(filter)) {
|
||||
return findByRegex(list, filter);
|
||||
} else {
|
||||
return filterByName(list, filter);
|
||||
}
|
||||
}
|
||||
|
||||
function convertHistoryPoint(point) {
|
||||
// Value must be a number for properly work
|
||||
return [
|
||||
Number(point.value),
|
||||
point.clock * 1000
|
||||
];
|
||||
}
|
||||
|
||||
function convertTrendPoint(valueType, point) {
|
||||
var value;
|
||||
switch (valueType) {
|
||||
case "min":
|
||||
value = point.value_min;
|
||||
break;
|
||||
case "max":
|
||||
value = point.value_max;
|
||||
break;
|
||||
case "avg":
|
||||
value = point.value_avg;
|
||||
break;
|
||||
default:
|
||||
value = point.value_avg;
|
||||
}
|
||||
|
||||
return [
|
||||
Number(value),
|
||||
point.clock * 1000
|
||||
];
|
||||
}
|
||||
106
src/datasource-zabbix/responseHandler.js
Normal file
106
src/datasource-zabbix/responseHandler.js
Normal file
@@ -0,0 +1,106 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
/**
|
||||
* Convert Zabbix API history.get response to Grafana format
|
||||
*
|
||||
* @return {Array} Array of timeseries in Grafana format
|
||||
* {
|
||||
* target: "Metric name",
|
||||
* datapoints: [[<value>, <unixtime>], ...]
|
||||
* }
|
||||
*/
|
||||
function convertHistory(history, items, addHostName, convertPointCallback) {
|
||||
/**
|
||||
* Response should be in the format:
|
||||
* data: [
|
||||
* {
|
||||
* target: "Metric name",
|
||||
* datapoints: [[<value>, <unixtime>], ...]
|
||||
* }, ...
|
||||
* ]
|
||||
*/
|
||||
|
||||
// Group history by itemid
|
||||
var grouped_history = _.groupBy(history, 'itemid');
|
||||
var hosts = _.uniqBy(_.flatten(_.map(items, 'hosts')), 'hostid'); //uniqBy is needed to deduplicate
|
||||
|
||||
return _.map(grouped_history, function(hist, itemid) {
|
||||
var item = _.find(items, {'itemid': itemid});
|
||||
var alias = item.name;
|
||||
if (_.keys(hosts).length > 1 && addHostName) { //only when actual multi hosts selected
|
||||
var host = _.find(hosts, {'hostid': item.hostid});
|
||||
alias = host.name + ": " + alias;
|
||||
}
|
||||
return {
|
||||
target: alias,
|
||||
datapoints: _.map(hist, convertPointCallback)
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
function handleHistory(history, items, addHostName = true) {
|
||||
return convertHistory(history, items, addHostName, convertHistoryPoint);
|
||||
}
|
||||
|
||||
function handleTrends(history, items, valueType, addHostName = true) {
|
||||
var convertPointCallback = _.partial(convertTrendPoint, valueType);
|
||||
return convertHistory(history, items, addHostName, convertPointCallback);
|
||||
}
|
||||
|
||||
function handleSLAResponse(itservice, slaProperty, slaObject) {
|
||||
var targetSLA = slaObject[itservice.serviceid].sla[0];
|
||||
if (slaProperty.property === 'status') {
|
||||
var targetStatus = parseInt(slaObject[itservice.serviceid].status);
|
||||
return {
|
||||
target: itservice.name + ' ' + slaProperty.name,
|
||||
datapoints: [
|
||||
[targetStatus, targetSLA.to * 1000]
|
||||
]
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
target: itservice.name + ' ' + slaProperty.name,
|
||||
datapoints: [
|
||||
[targetSLA[slaProperty.property], targetSLA.from * 1000],
|
||||
[targetSLA[slaProperty.property], targetSLA.to * 1000]
|
||||
]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function convertHistoryPoint(point) {
|
||||
// Value must be a number for properly work
|
||||
return [
|
||||
Number(point.value),
|
||||
point.clock * 1000
|
||||
];
|
||||
}
|
||||
|
||||
function convertTrendPoint(valueType, point) {
|
||||
var value;
|
||||
switch (valueType) {
|
||||
case "min":
|
||||
value = point.value_min;
|
||||
break;
|
||||
case "max":
|
||||
value = point.value_max;
|
||||
break;
|
||||
case "avg":
|
||||
value = point.value_avg;
|
||||
break;
|
||||
default:
|
||||
value = point.value_avg;
|
||||
}
|
||||
|
||||
return [
|
||||
Number(value),
|
||||
point.clock * 1000
|
||||
];
|
||||
}
|
||||
|
||||
export default {
|
||||
handleHistory: handleHistory,
|
||||
convertHistory: convertHistory,
|
||||
handleTrends: handleTrends,
|
||||
handleSLAResponse: handleSLAResponse
|
||||
};
|
||||
@@ -17,15 +17,11 @@ describe('ZabbixDatasource', () => {
|
||||
trendsFrom: '7d'
|
||||
}
|
||||
};
|
||||
ctx.$q = Q;
|
||||
ctx.templateSrv = {};
|
||||
ctx.alertSrv = {};
|
||||
ctx.zabbixAPIService = () => {};
|
||||
ctx.ZabbixCachingProxy = () => {};
|
||||
ctx.QueryProcessor = () => {};
|
||||
ctx.zabbix = () => {};
|
||||
|
||||
ctx.ds = new Datasource(ctx.instanceSettings, ctx.$q, ctx.templateSrv, ctx.alertSrv,
|
||||
ctx.zabbixAPIService, ctx.ZabbixCachingProxy, ctx.QueryProcessor);
|
||||
ctx.ds = new Datasource(ctx.instanceSettings, ctx.templateSrv, ctx.alertSrv, ctx.zabbix);
|
||||
});
|
||||
|
||||
describe('When querying data', () => {
|
||||
@@ -144,10 +140,7 @@ describe('ZabbixDatasource', () => {
|
||||
describe('When invoking metricFindQuery()', () => {
|
||||
beforeEach(() => {
|
||||
ctx.ds.replaceTemplateVars = (str) => str;
|
||||
ctx.ds.zabbixCache = {
|
||||
getGroups: () => Q.when([])
|
||||
};
|
||||
ctx.ds.queryProcessor = {
|
||||
ctx.ds.zabbix = {
|
||||
getGroups: () => Q.when([]),
|
||||
getHosts: () => Q.when([]),
|
||||
getApps: () => Q.when([]),
|
||||
@@ -163,7 +156,7 @@ describe('ZabbixDatasource', () => {
|
||||
{query: 'Back*', expect: 'Back*'}
|
||||
];
|
||||
|
||||
let getGroups = sinon.spy(ctx.ds.zabbixCache, 'getGroups');
|
||||
let getGroups = sinon.spy(ctx.ds.zabbix, 'getGroups');
|
||||
for (const test of tests) {
|
||||
ctx.ds.metricFindQuery(test.query);
|
||||
expect(getGroups).to.have.been.calledWith(test.expect);
|
||||
@@ -180,7 +173,7 @@ describe('ZabbixDatasource', () => {
|
||||
{query: 'Back*.', expect: 'Back*'}
|
||||
];
|
||||
|
||||
let getHosts = sinon.spy(ctx.ds.queryProcessor, 'getHosts');
|
||||
let getHosts = sinon.spy(ctx.ds.zabbix, 'getHosts');
|
||||
for (const test of tests) {
|
||||
ctx.ds.metricFindQuery(test.query);
|
||||
expect(getHosts).to.have.been.calledWith(test.expect);
|
||||
@@ -197,7 +190,7 @@ describe('ZabbixDatasource', () => {
|
||||
{query: 'Back*.*.', expect: ['Back*', '/.*/']}
|
||||
];
|
||||
|
||||
let getApps = sinon.spy(ctx.ds.queryProcessor, 'getApps');
|
||||
let getApps = sinon.spy(ctx.ds.zabbix, 'getApps');
|
||||
for (const test of tests) {
|
||||
ctx.ds.metricFindQuery(test.query);
|
||||
expect(getApps).to.have.been.calledWith(test.expect[0], test.expect[1]);
|
||||
@@ -214,7 +207,7 @@ describe('ZabbixDatasource', () => {
|
||||
{query: 'Back*.*.cpu.*', expect: ['Back*', '/.*/', 'cpu']}
|
||||
];
|
||||
|
||||
let getItems = sinon.spy(ctx.ds.queryProcessor, 'getItems');
|
||||
let getItems = sinon.spy(ctx.ds.zabbix, 'getItems');
|
||||
for (const test of tests) {
|
||||
ctx.ds.metricFindQuery(test.query);
|
||||
expect(getItems)
|
||||
@@ -227,7 +220,7 @@ describe('ZabbixDatasource', () => {
|
||||
it('should invoke method with proper arguments', (done) => {
|
||||
let query = '*.*';
|
||||
|
||||
let getHosts = sinon.spy(ctx.ds.queryProcessor, 'getHosts');
|
||||
let getHosts = sinon.spy(ctx.ds.zabbix, 'getHosts');
|
||||
ctx.ds.metricFindQuery(query);
|
||||
expect(getHosts).to.have.been.calledWith('/.*/');
|
||||
done();
|
||||
|
||||
@@ -93,6 +93,25 @@ export function convertToZabbixAPIUrl(url) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap function to prevent multiple calls
|
||||
* when waiting for result.
|
||||
*/
|
||||
export function callOnce(func, promiseKeeper) {
|
||||
return function() {
|
||||
if (!promiseKeeper) {
|
||||
promiseKeeper = Promise.resolve(
|
||||
func.apply(this, arguments)
|
||||
.then(result => {
|
||||
promiseKeeper = null;
|
||||
return result;
|
||||
})
|
||||
);
|
||||
}
|
||||
return promiseKeeper;
|
||||
};
|
||||
}
|
||||
|
||||
// Fix for backward compatibility with lodash 2.4
|
||||
if (!_.includes) {
|
||||
_.includes = _.contains;
|
||||
|
||||
216
src/datasource-zabbix/zabbix.js
Normal file
216
src/datasource-zabbix/zabbix.js
Normal file
@@ -0,0 +1,216 @@
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
import * as utils from './utils';
|
||||
import './zabbixAPI.service.js';
|
||||
import './zabbixCachingProxy.service.js';
|
||||
|
||||
// Use factory() instead service() for multiple data sources support.
|
||||
// Each Zabbix data source instance should initialize its own API instance.
|
||||
|
||||
/** @ngInject */
|
||||
function ZabbixFactory(zabbixAPIService, ZabbixCachingProxy) {
|
||||
|
||||
class Zabbix {
|
||||
constructor(url, username, password, basicAuth, withCredentials, cacheTTL) {
|
||||
|
||||
// Initialize Zabbix API
|
||||
var ZabbixAPI = zabbixAPIService;
|
||||
this.zabbixAPI = new ZabbixAPI(url, username, password, basicAuth, withCredentials);
|
||||
|
||||
// Initialize caching proxy for requests
|
||||
let cacheOptions = {
|
||||
enabled: true,
|
||||
ttl: cacheTTL
|
||||
};
|
||||
this.cachingProxy = new ZabbixCachingProxy(this.zabbixAPI, cacheOptions);
|
||||
|
||||
// Proxy methods
|
||||
this.getHistory = this.cachingProxy.getHistory.bind(this.cachingProxy);
|
||||
|
||||
this.getTrend = this.zabbixAPI.getTrend.bind(this.zabbixAPI);
|
||||
this.getEvents = this.zabbixAPI.getEvents.bind(this.zabbixAPI);
|
||||
this.getAcknowledges = this.zabbixAPI.getAcknowledges.bind(this.zabbixAPI);
|
||||
this.getSLA = this.zabbixAPI.getSLA.bind(this.zabbixAPI);
|
||||
this.getVersion = this.zabbixAPI.getVersion.bind(this.zabbixAPI);
|
||||
this.login = this.zabbixAPI.login.bind(this.zabbixAPI);
|
||||
}
|
||||
|
||||
getItemsFromTarget(target, options) {
|
||||
let parts = ['group', 'host', 'application', 'item'];
|
||||
let filters = _.map(parts, p => target[p].filter);
|
||||
return this.getItems(...filters, options);
|
||||
}
|
||||
|
||||
getAllGroups() {
|
||||
return this.cachingProxy.getGroups();
|
||||
}
|
||||
|
||||
getGroups(groupFilter) {
|
||||
return this.getAllGroups()
|
||||
.then(groups => findByFilter(groups, groupFilter));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of host belonging to given groups.
|
||||
*/
|
||||
getAllHosts(groupFilter) {
|
||||
return this.getGroups(groupFilter)
|
||||
.then(groups => {
|
||||
let groupids = _.map(groups, 'groupid');
|
||||
return this.cachingProxy.getHosts(groupids);
|
||||
});
|
||||
}
|
||||
|
||||
getHosts(groupFilter, hostFilter) {
|
||||
return this.getAllHosts(groupFilter)
|
||||
.then(hosts => findByFilter(hosts, hostFilter));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of applications belonging to given groups and hosts.
|
||||
*/
|
||||
getAllApps(groupFilter, hostFilter) {
|
||||
return this.getHosts(groupFilter, hostFilter)
|
||||
.then(hosts => {
|
||||
let hostids = _.map(hosts, 'hostid');
|
||||
return this.cachingProxy.getApps(hostids);
|
||||
});
|
||||
}
|
||||
|
||||
getApps(groupFilter, hostFilter, appFilter) {
|
||||
return this.getHosts(groupFilter, hostFilter)
|
||||
.then(hosts => {
|
||||
let hostids = _.map(hosts, 'hostid');
|
||||
if (appFilter) {
|
||||
return this.cachingProxy.getApps(hostids)
|
||||
.then(apps => filterByQuery(apps, appFilter));
|
||||
} else {
|
||||
return {
|
||||
appFilterEmpty: true,
|
||||
hostids: hostids
|
||||
};
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
getAllItems(groupFilter, hostFilter, appFilter, options = {}) {
|
||||
return this.getApps(groupFilter, hostFilter, appFilter)
|
||||
.then(apps => {
|
||||
if (apps.appFilterEmpty) {
|
||||
return this.cachingProxy.getItems(apps.hostids, undefined, options.itemtype);
|
||||
} else {
|
||||
let appids = _.map(apps, 'applicationid');
|
||||
return this.cachingProxy.getItems(undefined, appids, options.itemtype);
|
||||
}
|
||||
})
|
||||
.then(items => {
|
||||
if (!options.showDisabledItems) {
|
||||
items = _.filter(items, {'status': '0'});
|
||||
}
|
||||
return items;
|
||||
});
|
||||
}
|
||||
|
||||
getItems(groupFilter, hostFilter, appFilter, itemFilter, options = {}) {
|
||||
return this.getAllItems(groupFilter, hostFilter, appFilter, options)
|
||||
.then(items => filterByQuery(items, itemFilter));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build query - convert target filters to array of Zabbix items
|
||||
*/
|
||||
getTriggers(groupFilter, hostFilter, appFilter, showTriggers) {
|
||||
let promises = [
|
||||
this.getGroups(groupFilter),
|
||||
this.getHosts(groupFilter, hostFilter),
|
||||
this.getApps(groupFilter, hostFilter, appFilter)
|
||||
];
|
||||
|
||||
return Promise.all(promises)
|
||||
.then(results => {
|
||||
let filteredGroups = results[0];
|
||||
let filteredHosts = results[1];
|
||||
let filteredApps = results[2];
|
||||
let query = {};
|
||||
|
||||
if (appFilter) {
|
||||
query.applicationids = _.flatten(_.map(filteredApps, 'applicationid'));
|
||||
}
|
||||
if (hostFilter) {
|
||||
query.hostids = _.map(filteredHosts, 'hostid');
|
||||
}
|
||||
if (groupFilter) {
|
||||
query.groupids = _.map(filteredGroups, 'groupid');
|
||||
}
|
||||
|
||||
return query;
|
||||
}).then(query => {
|
||||
return this.zabbixAPI
|
||||
.getTriggers(query.groupids, query.hostids, query.applicationids, showTriggers);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return Zabbix;
|
||||
}
|
||||
|
||||
angular
|
||||
.module('grafana.services')
|
||||
.factory('Zabbix', ZabbixFactory);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
* Find group, host, app or item by given name.
|
||||
* @param list list of groups, apps or other
|
||||
* @param name visible name
|
||||
* @return array with finded element or undefined
|
||||
*/
|
||||
function findByName(list, name) {
|
||||
var finded = _.find(list, {'name': name});
|
||||
if (finded) {
|
||||
return [finded];
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Different hosts can contains applications and items with same name.
|
||||
* For this reason use _.filter, which return all elements instead _.find,
|
||||
* which return only first finded.
|
||||
* @param {[type]} list list of elements
|
||||
* @param {[type]} name app name
|
||||
* @return {[type]} array with finded element or undefined
|
||||
*/
|
||||
function filterByName(list, name) {
|
||||
var finded = _.filter(list, {'name': name});
|
||||
if (finded) {
|
||||
return finded;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function filterByRegex(list, regex) {
|
||||
var filterPattern = utils.buildRegex(regex);
|
||||
return _.filter(list, function (zbx_obj) {
|
||||
return filterPattern.test(zbx_obj.name);
|
||||
});
|
||||
}
|
||||
|
||||
function findByFilter(list, filter) {
|
||||
if (utils.isRegex(filter)) {
|
||||
return filterByRegex(list, filter);
|
||||
} else {
|
||||
return findByName(list, filter);
|
||||
}
|
||||
}
|
||||
|
||||
function filterByQuery(list, filter) {
|
||||
if (utils.isRegex(filter)) {
|
||||
return filterByRegex(list, filter);
|
||||
} else {
|
||||
return filterByName(list, filter);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import * as utils from './utils';
|
||||
import './zabbixAPICore.service';
|
||||
|
||||
/** @ngInject */
|
||||
function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) {
|
||||
function ZabbixAPIServiceFactory(alertSrv, zabbixAPICoreService) {
|
||||
|
||||
/**
|
||||
* Zabbix API Wrapper.
|
||||
@@ -25,8 +25,9 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) {
|
||||
};
|
||||
|
||||
this.loginPromise = null;
|
||||
this.loginErrorCount = 0;
|
||||
this.maxLoginAttempts = 3;
|
||||
|
||||
this.$q = $q;
|
||||
this.alertSrv = alertSrv;
|
||||
this.zabbixAPICore = zabbixAPICoreService;
|
||||
|
||||
@@ -39,25 +40,22 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) {
|
||||
//////////////////////////
|
||||
|
||||
request(method, params) {
|
||||
var self = this;
|
||||
|
||||
return this.zabbixAPICore
|
||||
.request(this.url, method, params, this.requestOptions, this.auth)
|
||||
.then((result) => {
|
||||
return result;
|
||||
}, (error) => {
|
||||
// Handle API errors
|
||||
return this.zabbixAPICore.request(this.url, method, params, this.requestOptions, this.auth)
|
||||
.catch(error => {
|
||||
if (isNotAuthorized(error.data)) {
|
||||
return self.loginOnce().then(
|
||||
function() {
|
||||
return self.request(method, params);
|
||||
},
|
||||
// Handle user.login method errors
|
||||
function(error) {
|
||||
self.alertAPIError(error.data);
|
||||
});
|
||||
// Handle auth errors
|
||||
this.loginErrorCount++;
|
||||
if (this.loginErrorCount > this.maxLoginAttempts) {
|
||||
this.loginErrorCount = 0;
|
||||
return null;
|
||||
} else {
|
||||
this.alertSrv.set("Connection Error", error.data, 'error', 5000);
|
||||
return this.loginOnce()
|
||||
.then(() => this.request(method, params));
|
||||
}
|
||||
} else {
|
||||
// Handle API errors
|
||||
let message = error.data ? error.data : error.statusText;
|
||||
this.alertAPIError(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -78,25 +76,16 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) {
|
||||
* @return login promise
|
||||
*/
|
||||
loginOnce() {
|
||||
var self = this;
|
||||
var deferred = this.$q.defer();
|
||||
if (!self.loginPromise) {
|
||||
self.loginPromise = deferred.promise;
|
||||
self.login().then(
|
||||
function(auth) {
|
||||
self.loginPromise = null;
|
||||
self.auth = auth;
|
||||
deferred.resolve(auth);
|
||||
},
|
||||
function(error) {
|
||||
self.loginPromise = null;
|
||||
deferred.reject(error);
|
||||
}
|
||||
if (!this.loginPromise) {
|
||||
this.loginPromise = Promise.resolve(
|
||||
this.login().then(auth => {
|
||||
this.auth = auth;
|
||||
this.loginPromise = null;
|
||||
return auth;
|
||||
})
|
||||
);
|
||||
} else {
|
||||
return self.loginPromise;
|
||||
}
|
||||
return deferred.promise;
|
||||
return this.loginPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,13 +186,16 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) {
|
||||
}
|
||||
|
||||
return this.request('item.get', params)
|
||||
.then(items => {
|
||||
return _.forEach(items, item => {
|
||||
.then(expandItems);
|
||||
|
||||
function expandItems(items) {
|
||||
items.forEach(item => {
|
||||
item.item = item.name;
|
||||
item.name = utils.expandItemName(item.item, item.key_);
|
||||
return item;
|
||||
});
|
||||
});
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
getLastValue(itemid) {
|
||||
@@ -211,48 +203,42 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) {
|
||||
output: ['lastvalue'],
|
||||
itemids: itemid
|
||||
};
|
||||
return this.request('item.get', params).then(function(items) {
|
||||
if (items.length) {
|
||||
return items[0].lastvalue;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
return this.request('item.get', params)
|
||||
.then(items => items.length ? items[0].lastvalue : null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform history query from Zabbix API
|
||||
*
|
||||
* @param {Array} items Array of Zabbix item objects
|
||||
* @param {Number} time_from Time in seconds
|
||||
* @param {Number} time_till Time in seconds
|
||||
* @param {Number} timeFrom Time in seconds
|
||||
* @param {Number} timeTill Time in seconds
|
||||
* @return {Array} Array of Zabbix history objects
|
||||
*/
|
||||
getHistory(items, time_from, time_till) {
|
||||
var self = this;
|
||||
getHistory(items, timeFrom, timeTill) {
|
||||
|
||||
// Group items by value type
|
||||
var grouped_items = _.groupBy(items, 'value_type');
|
||||
|
||||
// Perform request for each value type
|
||||
return this.$q.all(_.map(grouped_items, function (items, value_type) {
|
||||
var itemids = _.map(items, 'itemid');
|
||||
var params = {
|
||||
// Group items by value type and perform request for each value type
|
||||
let grouped_items = _.groupBy(items, 'value_type');
|
||||
let promises = _.map(grouped_items, (items, value_type) => {
|
||||
let itemids = _.map(items, 'itemid');
|
||||
let params = {
|
||||
output: 'extend',
|
||||
history: value_type,
|
||||
itemids: itemids,
|
||||
sortfield: 'clock',
|
||||
sortorder: 'ASC',
|
||||
time_from: time_from
|
||||
time_from: timeFrom
|
||||
};
|
||||
|
||||
// Relative queries (e.g. last hour) don't include an end time
|
||||
if (time_till) {
|
||||
params.time_till = time_till;
|
||||
if (timeTill) {
|
||||
params.time_till = timeTill;
|
||||
}
|
||||
|
||||
return self.request('history.get', params);
|
||||
})).then(_.flatten);
|
||||
return this.request('history.get', params);
|
||||
});
|
||||
|
||||
return Promise.all(promises).then(_.flatten);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -264,31 +250,30 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) {
|
||||
* @param {Number} time_till Time in seconds
|
||||
* @return {Array} Array of Zabbix trend objects
|
||||
*/
|
||||
getTrend_ZBXNEXT1193(items, time_from, time_till) {
|
||||
var self = this;
|
||||
getTrend_ZBXNEXT1193(items, timeFrom, timeTill) {
|
||||
|
||||
// Group items by value type
|
||||
var grouped_items = _.groupBy(items, 'value_type');
|
||||
|
||||
// Perform request for each value type
|
||||
return this.$q.all(_.map(grouped_items, function (items, value_type) {
|
||||
var itemids = _.map(items, 'itemid');
|
||||
var params = {
|
||||
// Group items by value type and perform request for each value type
|
||||
let grouped_items = _.groupBy(items, 'value_type');
|
||||
let promises = _.map(grouped_items, (items, value_type) => {
|
||||
let itemids = _.map(items, 'itemid');
|
||||
let params = {
|
||||
output: 'extend',
|
||||
trend: value_type,
|
||||
itemids: itemids,
|
||||
sortfield: 'clock',
|
||||
sortorder: 'ASC',
|
||||
time_from: time_from
|
||||
time_from: timeFrom
|
||||
};
|
||||
|
||||
// Relative queries (e.g. last hour) don't include an end time
|
||||
if (time_till) {
|
||||
params.time_till = time_till;
|
||||
if (timeTill) {
|
||||
params.time_till = timeTill;
|
||||
}
|
||||
|
||||
return self.request('trend.get', params);
|
||||
})).then(_.flatten);
|
||||
return this.request('trend.get', params);
|
||||
});
|
||||
|
||||
return Promise.all(promises).then(_.flatten);
|
||||
}
|
||||
|
||||
getTrend_30(items, time_from, time_till, value_type) {
|
||||
@@ -312,7 +297,7 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) {
|
||||
return self.request('trend.get', params);
|
||||
}
|
||||
|
||||
getITService(/* optional */ serviceids) {
|
||||
getITService(serviceids) {
|
||||
var params = {
|
||||
output: 'extend',
|
||||
serviceids: serviceids
|
||||
@@ -320,12 +305,12 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) {
|
||||
return this.request('service.get', params);
|
||||
}
|
||||
|
||||
getSLA(serviceids, from, to) {
|
||||
getSLA(serviceids, timeFrom, timeTo) {
|
||||
var params = {
|
||||
serviceids: serviceids,
|
||||
intervals: [{
|
||||
from: from,
|
||||
to: to
|
||||
from: timeFrom,
|
||||
to: timeTo
|
||||
}]
|
||||
};
|
||||
return this.request('service.getsla', params);
|
||||
@@ -364,11 +349,11 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) {
|
||||
return this.request('trigger.get', params);
|
||||
}
|
||||
|
||||
getEvents(objectids, from, to, showEvents) {
|
||||
getEvents(objectids, timeFrom, timeTo, showEvents) {
|
||||
var params = {
|
||||
output: 'extend',
|
||||
time_from: from,
|
||||
time_till: to,
|
||||
time_from: timeFrom,
|
||||
time_till: timeTo,
|
||||
objectids: objectids,
|
||||
select_acknowledges: 'extend',
|
||||
selectHosts: 'extend',
|
||||
@@ -389,10 +374,8 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) {
|
||||
};
|
||||
|
||||
return this.request('event.get', params)
|
||||
.then(function (events) {
|
||||
return _.filter(events, function(event) {
|
||||
return event.acknowledges.length;
|
||||
});
|
||||
.then(events => {
|
||||
return _.filter(events, (event) => event.acknowledges.length);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -411,4 +394,4 @@ function isNotAuthorized(message) {
|
||||
|
||||
angular
|
||||
.module('grafana.services')
|
||||
.factory('zabbixAPIService', ZabbixAPIService);
|
||||
.factory('zabbixAPIService', ZabbixAPIServiceFactory);
|
||||
|
||||
@@ -7,8 +7,7 @@ import angular from 'angular';
|
||||
class ZabbixAPICoreService {
|
||||
|
||||
/** @ngInject */
|
||||
constructor($q, backendSrv) {
|
||||
this.$q = $q;
|
||||
constructor(backendSrv) {
|
||||
this.backendSrv = backendSrv;
|
||||
}
|
||||
|
||||
@@ -17,8 +16,7 @@ class ZabbixAPICoreService {
|
||||
* @return {object} response.result
|
||||
*/
|
||||
request(api_url, method, params, options, auth) {
|
||||
var deferred = this.$q.defer();
|
||||
var requestData = {
|
||||
let requestData = {
|
||||
jsonrpc: '2.0',
|
||||
method: method,
|
||||
params: params,
|
||||
@@ -27,20 +25,19 @@ class ZabbixAPICoreService {
|
||||
|
||||
if (auth === "") {
|
||||
// Reject immediately if not authenticated
|
||||
deferred.reject({data: "Not authorised."});
|
||||
return deferred.promise;
|
||||
return Promise.reject(new ZabbixAPIError({data: "Not authorised."}));
|
||||
} else if (auth) {
|
||||
// Set auth parameter only if it needed
|
||||
requestData.auth = auth;
|
||||
}
|
||||
|
||||
var requestOptions = {
|
||||
let requestOptions = {
|
||||
method: 'POST',
|
||||
url: api_url,
|
||||
data: requestData,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
url: api_url,
|
||||
data: requestData
|
||||
}
|
||||
};
|
||||
|
||||
// Set request options for basic auth
|
||||
@@ -51,24 +48,23 @@ class ZabbixAPICoreService {
|
||||
requestOptions.headers.Authorization = options.basicAuth;
|
||||
}
|
||||
|
||||
this.backendSrv.datasourceRequest(requestOptions)
|
||||
.then((response) => {
|
||||
// General connection issues
|
||||
if (!response.data) {
|
||||
deferred.reject(response);
|
||||
return this.datasourceRequest(requestOptions);
|
||||
}
|
||||
|
||||
datasourceRequest(requestOptions) {
|
||||
return this.backendSrv.datasourceRequest(requestOptions)
|
||||
.then(response => {
|
||||
if (!response.data) {
|
||||
return Promise.reject(new ZabbixAPIError({data: "General Error, no data"}));
|
||||
} else if (response.data.error) {
|
||||
|
||||
// Handle Zabbix API errors
|
||||
else if (response.data.error) {
|
||||
deferred.reject(response.data.error);
|
||||
return Promise.reject(new ZabbixAPIError(response.data.error));
|
||||
}
|
||||
|
||||
deferred.resolve(response.data.result);
|
||||
}, (error) => {
|
||||
deferred.reject(error.err);
|
||||
// Success
|
||||
return response.data.result;
|
||||
});
|
||||
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,7 +72,7 @@ class ZabbixAPICoreService {
|
||||
* @return {string} auth token
|
||||
*/
|
||||
login(api_url, username, password, options) {
|
||||
var params = {
|
||||
let params = {
|
||||
user: username,
|
||||
password: password
|
||||
};
|
||||
@@ -93,15 +89,18 @@ class ZabbixAPICoreService {
|
||||
}
|
||||
|
||||
// Define zabbix API exception type
|
||||
function ZabbixException(error) {
|
||||
export class ZabbixAPIError {
|
||||
constructor(error) {
|
||||
this.code = error.code;
|
||||
this.errorType = error.message;
|
||||
this.name = error.data;
|
||||
this.message = error.data;
|
||||
this.data = error.data;
|
||||
}
|
||||
|
||||
ZabbixException.prototype.toString = function() {
|
||||
return this.errorType + ": " + this.message;
|
||||
};
|
||||
toString() {
|
||||
return this.name + ": " + this.message;
|
||||
}
|
||||
}
|
||||
|
||||
angular
|
||||
.module('grafana.services')
|
||||
|
||||
@@ -1,240 +0,0 @@
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
|
||||
// Use factory() instead service() for multiple datasources support.
|
||||
// Each datasource instance must initialize its own cache.
|
||||
|
||||
/** @ngInject */
|
||||
angular.module('grafana.services').factory('ZabbixCachingProxy', function($q, $interval) {
|
||||
|
||||
class ZabbixCachingProxy {
|
||||
constructor(zabbixAPI, ttl) {
|
||||
this.zabbixAPI = zabbixAPI;
|
||||
this.ttl = ttl;
|
||||
|
||||
this.$q = $q;
|
||||
|
||||
// Internal objects for data storing
|
||||
this._groups = undefined;
|
||||
this._hosts = undefined;
|
||||
this._applications = undefined;
|
||||
this._items = undefined;
|
||||
this.storage = {
|
||||
history: {},
|
||||
trends: {}
|
||||
};
|
||||
|
||||
// Check is a service initialized or not
|
||||
this._initialized = undefined;
|
||||
|
||||
this.refreshPromise = false;
|
||||
this.historyPromises = {};
|
||||
|
||||
// Wrap _refresh() method to call it once.
|
||||
this.refresh = callOnce(this._refresh, this.refreshPromise);
|
||||
|
||||
// Update cache periodically
|
||||
$interval(_.bind(this.refresh, this), this.ttl);
|
||||
|
||||
// Don't run duplicated history requests
|
||||
this.getHistory = callHistoryOnce(_.bind(this.zabbixAPI.getHistory, this.zabbixAPI),
|
||||
this.historyPromises);
|
||||
|
||||
// Don't run duplicated requests
|
||||
this.groupPromises = {};
|
||||
this.getGroupsOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getGroups, this.zabbixAPI),
|
||||
this.groupPromises);
|
||||
|
||||
this.hostPromises = {};
|
||||
this.getHostsOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getHosts, this.zabbixAPI),
|
||||
this.hostPromises);
|
||||
|
||||
this.appPromises = {};
|
||||
this.getAppsOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getApps, this.zabbixAPI),
|
||||
this.appPromises);
|
||||
|
||||
this.itemPromises = {};
|
||||
this.getItemsOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getItems, this.zabbixAPI),
|
||||
this.itemPromises);
|
||||
}
|
||||
|
||||
_refresh() {
|
||||
var self = this;
|
||||
var promises = [
|
||||
this.zabbixAPI.getGroups()
|
||||
];
|
||||
|
||||
return this.$q.all(promises).then(function(results) {
|
||||
if (results.length) {
|
||||
self._groups = results[0];
|
||||
}
|
||||
self._initialized = true;
|
||||
});
|
||||
}
|
||||
|
||||
getGroups() {
|
||||
var self = this;
|
||||
if (this._groups) {
|
||||
return this.$q.when(self._groups);
|
||||
} else {
|
||||
return this.getGroupsOnce()
|
||||
.then(groups => {
|
||||
self._groups = groups;
|
||||
return self._groups;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getHosts(groupids) {
|
||||
//var self = this;
|
||||
return this.getHostsOnce(groupids)
|
||||
.then(hosts => {
|
||||
// iss #196 - disable caching due performance issues
|
||||
//self._hosts = _.union(self._hosts, hosts);
|
||||
return hosts;
|
||||
});
|
||||
}
|
||||
|
||||
getApps(hostids) {
|
||||
return this.getAppsOnce(hostids)
|
||||
.then(apps => {
|
||||
return apps;
|
||||
});
|
||||
}
|
||||
|
||||
getItems(hostids, appids, itemtype) {
|
||||
//var self = this;
|
||||
return this.getItemsOnce(hostids, appids, itemtype)
|
||||
.then(items => {
|
||||
// iss #196 - disable caching due performance issues
|
||||
//self._items = _.union(self._items, items);
|
||||
return items;
|
||||
});
|
||||
}
|
||||
|
||||
getHistoryFromCache(items, time_from, time_till) {
|
||||
var deferred = this.$q.defer();
|
||||
var historyStorage = this.storage.history;
|
||||
var full_history;
|
||||
var expired = _.filter(_.keyBy(items, 'itemid'), function(item, itemid) {
|
||||
return !historyStorage[itemid];
|
||||
});
|
||||
if (expired.length) {
|
||||
this.zabbixAPI.getHistory(expired, time_from, time_till).then(function(history) {
|
||||
var grouped_history = _.groupBy(history, 'itemid');
|
||||
_.forEach(expired, function(item) {
|
||||
var itemid = item.itemid;
|
||||
historyStorage[itemid] = item;
|
||||
historyStorage[itemid].time_from = time_from;
|
||||
historyStorage[itemid].time_till = time_till;
|
||||
historyStorage[itemid].history = grouped_history[itemid];
|
||||
});
|
||||
full_history = _.map(items, function(item) {
|
||||
return historyStorage[item.itemid].history;
|
||||
});
|
||||
deferred.resolve(_.flatten(full_history, true));
|
||||
});
|
||||
} else {
|
||||
full_history = _.map(items, function(item) {
|
||||
return historyStorage[item.itemid].history;
|
||||
});
|
||||
deferred.resolve(_.flatten(full_history, true));
|
||||
}
|
||||
return deferred.promise;
|
||||
}
|
||||
|
||||
getHistoryFromAPI(items, time_from, time_till) {
|
||||
return this.zabbixAPI.getHistory(items, time_from, time_till);
|
||||
}
|
||||
|
||||
getHost(hostid) {
|
||||
return _.find(this._hosts, {'hostid': hostid});
|
||||
}
|
||||
|
||||
getItem(itemid) {
|
||||
return _.find(this._items, {'itemid': itemid});
|
||||
}
|
||||
}
|
||||
|
||||
function callAPIRequestOnce(func, promiseKeeper) {
|
||||
return function() {
|
||||
var hash = getAPIRequestHash(arguments);
|
||||
var deferred = $q.defer();
|
||||
if (!promiseKeeper[hash]) {
|
||||
promiseKeeper[hash] = deferred.promise;
|
||||
func.apply(this, arguments).then(function(result) {
|
||||
deferred.resolve(result);
|
||||
promiseKeeper[hash] = null;
|
||||
});
|
||||
} else {
|
||||
return promiseKeeper[hash];
|
||||
}
|
||||
return deferred.promise;
|
||||
};
|
||||
}
|
||||
|
||||
function callHistoryOnce(func, promiseKeeper) {
|
||||
return function() {
|
||||
var itemids = _.map(arguments[0], 'itemid');
|
||||
var stamp = itemids.join() + arguments[1] + arguments[2];
|
||||
var hash = stamp.getHash();
|
||||
|
||||
var deferred = $q.defer();
|
||||
if (!promiseKeeper[hash]) {
|
||||
promiseKeeper[hash] = deferred.promise;
|
||||
func.apply(this, arguments).then(function(result) {
|
||||
deferred.resolve(result);
|
||||
promiseKeeper[hash] = null;
|
||||
});
|
||||
} else {
|
||||
return promiseKeeper[hash];
|
||||
}
|
||||
return deferred.promise;
|
||||
};
|
||||
}
|
||||
|
||||
function callOnce(func, promiseKeeper) {
|
||||
return function() {
|
||||
var deferred = $q.defer();
|
||||
if (!promiseKeeper) {
|
||||
promiseKeeper = deferred.promise;
|
||||
func.apply(this, arguments).then(function(result) {
|
||||
deferred.resolve(result);
|
||||
promiseKeeper = null;
|
||||
});
|
||||
} else {
|
||||
return promiseKeeper;
|
||||
}
|
||||
return deferred.promise;
|
||||
};
|
||||
}
|
||||
|
||||
return ZabbixCachingProxy;
|
||||
});
|
||||
|
||||
function getAPIRequestHash(args) {
|
||||
var requestStamp = _.map(args, arg => {
|
||||
if (arg === undefined) {
|
||||
return 'undefined';
|
||||
} else {
|
||||
return arg.toString();
|
||||
}
|
||||
}).join();
|
||||
return requestStamp.getHash();
|
||||
}
|
||||
|
||||
String.prototype.getHash = function() {
|
||||
var hash = 0, i, chr, len;
|
||||
if (this.length === 0) {
|
||||
return hash;
|
||||
}
|
||||
for (i = 0, len = this.length; i < len; i++) {
|
||||
chr = this.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + chr;
|
||||
hash |= 0; // Convert to 32bit integer
|
||||
}
|
||||
return hash;
|
||||
};
|
||||
|
||||
// Fix for backward compatibility with lodash 2.4
|
||||
if (!_.keyBy) {_.keyBy = _.indexBy;}
|
||||
191
src/datasource-zabbix/zabbixCachingProxy.service.js
Normal file
191
src/datasource-zabbix/zabbixCachingProxy.service.js
Normal file
@@ -0,0 +1,191 @@
|
||||
import angular from 'angular';
|
||||
import _ from 'lodash';
|
||||
|
||||
// Use factory() instead service() for multiple datasources support.
|
||||
// Each datasource instance must initialize its own cache.
|
||||
|
||||
/** @ngInject */
|
||||
function ZabbixCachingProxyFactory() {
|
||||
|
||||
class ZabbixCachingProxy {
|
||||
constructor(zabbixAPI, cacheOptions) {
|
||||
this.zabbixAPI = zabbixAPI;
|
||||
this.cacheEnabled = cacheOptions.enabled;
|
||||
this.ttl = cacheOptions.ttl || 600000; // 10 minutes by default
|
||||
|
||||
// Internal objects for data storing
|
||||
this.cache = {
|
||||
groups: {},
|
||||
hosts: {},
|
||||
applications: {},
|
||||
items: {},
|
||||
history: {},
|
||||
trends: {}
|
||||
};
|
||||
|
||||
this.historyPromises = {};
|
||||
|
||||
// Don't run duplicated history requests
|
||||
this.getHistory = callAPIRequestOnce(_.bind(this.zabbixAPI.getHistory, this.zabbixAPI),
|
||||
this.historyPromises, getHistoryRequestHash);
|
||||
|
||||
// Don't run duplicated requests
|
||||
this.groupPromises = {};
|
||||
this.getGroupsOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getGroups, this.zabbixAPI),
|
||||
this.groupPromises, getRequestHash);
|
||||
|
||||
this.hostPromises = {};
|
||||
this.getHostsOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getHosts, this.zabbixAPI),
|
||||
this.hostPromises, getRequestHash);
|
||||
|
||||
this.appPromises = {};
|
||||
this.getAppsOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getApps, this.zabbixAPI),
|
||||
this.appPromises, getRequestHash);
|
||||
|
||||
this.itemPromises = {};
|
||||
this.getItemsOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getItems, this.zabbixAPI),
|
||||
this.itemPromises, getRequestHash);
|
||||
}
|
||||
|
||||
isExpired(cacheObject) {
|
||||
if (cacheObject) {
|
||||
let object_age = Date.now() - cacheObject.timestamp;
|
||||
return !(cacheObject.timestamp && object_age < this.ttl);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that result is present in cache and up to date
|
||||
* or send request to API.
|
||||
*/
|
||||
proxyRequest(request, params, cacheObject) {
|
||||
let hash = getRequestHash(params);
|
||||
if (this.cacheEnabled && !this.isExpired(cacheObject[hash])) {
|
||||
return Promise.resolve(cacheObject[hash].value);
|
||||
} else {
|
||||
return request(...params)
|
||||
.then(result => {
|
||||
cacheObject[hash] = {
|
||||
value: result,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
return result;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getGroups() {
|
||||
return this.proxyRequest(this.getGroupsOnce, [], this.cache.groups);
|
||||
}
|
||||
|
||||
getHosts(groupids) {
|
||||
return this.proxyRequest(this.getHostsOnce, [groupids], this.cache.hosts);
|
||||
}
|
||||
|
||||
getApps(hostids) {
|
||||
return this.proxyRequest(this.getAppsOnce, [hostids], this.cache.applications);
|
||||
}
|
||||
|
||||
getItems(hostids, appids, itemtype) {
|
||||
let params = [hostids, appids, itemtype];
|
||||
return this.proxyRequest(this.getItemsOnce, params, this.cache.items);
|
||||
}
|
||||
|
||||
getHistoryFromCache(items, time_from, time_till) {
|
||||
var historyStorage = this.cache.history;
|
||||
var full_history;
|
||||
var expired = _.filter(_.keyBy(items, 'itemid'), (item, itemid) => {
|
||||
return !historyStorage[itemid];
|
||||
});
|
||||
if (expired.length) {
|
||||
return this.zabbixAPI.getHistory(expired, time_from, time_till).then(function(history) {
|
||||
var grouped_history = _.groupBy(history, 'itemid');
|
||||
_.forEach(expired, item => {
|
||||
var itemid = item.itemid;
|
||||
historyStorage[itemid] = item;
|
||||
historyStorage[itemid].time_from = time_from;
|
||||
historyStorage[itemid].time_till = time_till;
|
||||
historyStorage[itemid].history = grouped_history[itemid];
|
||||
});
|
||||
full_history = _.map(items, item => {
|
||||
return historyStorage[item.itemid].history;
|
||||
});
|
||||
return _.flatten(full_history, true);
|
||||
});
|
||||
} else {
|
||||
full_history = _.map(items, function(item) {
|
||||
return historyStorage[item.itemid].history;
|
||||
});
|
||||
return Promise.resolve(_.flatten(full_history, true));
|
||||
}
|
||||
}
|
||||
|
||||
getHistoryFromAPI(items, time_from, time_till) {
|
||||
return this.zabbixAPI.getHistory(items, time_from, time_till);
|
||||
}
|
||||
}
|
||||
|
||||
return ZabbixCachingProxy;
|
||||
}
|
||||
|
||||
angular
|
||||
.module('grafana.services')
|
||||
.factory('ZabbixCachingProxy', ZabbixCachingProxyFactory);
|
||||
|
||||
/**
|
||||
* Wrap zabbix API request to prevent multiple calls
|
||||
* with same params when waiting for result.
|
||||
*/
|
||||
function callAPIRequestOnce(func, promiseKeeper, argsHashFunc) {
|
||||
return function() {
|
||||
var hash = argsHashFunc(arguments);
|
||||
if (!promiseKeeper[hash]) {
|
||||
promiseKeeper[hash] = Promise.resolve(
|
||||
func.apply(this, arguments)
|
||||
.then(result => {
|
||||
promiseKeeper[hash] = null;
|
||||
return result;
|
||||
})
|
||||
);
|
||||
}
|
||||
return promiseKeeper[hash];
|
||||
};
|
||||
}
|
||||
|
||||
function getRequestHash(args) {
|
||||
var requestStamp = _.map(args, arg => {
|
||||
if (arg === undefined) {
|
||||
return 'undefined';
|
||||
} else {
|
||||
if (_.isArray(arg)) {
|
||||
return arg.sort().toString();
|
||||
} else {
|
||||
return arg.toString();
|
||||
}
|
||||
}
|
||||
}).join();
|
||||
return requestStamp.getHash();
|
||||
}
|
||||
|
||||
function getHistoryRequestHash(args) {
|
||||
let itemids = _.map(args[0], 'itemid');
|
||||
let stamp = itemids.join() + args[1] + args[2];
|
||||
return stamp.getHash();
|
||||
}
|
||||
|
||||
String.prototype.getHash = function() {
|
||||
var hash = 0, i, chr, len;
|
||||
if (this.length !== 0) {
|
||||
for (i = 0, len = this.length; i < len; i++) {
|
||||
chr = this.charCodeAt(i);
|
||||
hash = ((hash << 5) - hash) + chr;
|
||||
hash |= 0; // Convert to 32bit integer
|
||||
}
|
||||
}
|
||||
return hash;
|
||||
};
|
||||
|
||||
// Fix for backward compatibility with lodash 2.4
|
||||
if (!_.keyBy) {_.keyBy = _.indexBy;}
|
||||
@@ -19,12 +19,11 @@ import '../datasource-zabbix/css/query-editor.css!';
|
||||
class TriggerPanelEditorCtrl {
|
||||
|
||||
/** @ngInject */
|
||||
constructor($scope, $rootScope, $q, uiSegmentSrv, datasourceSrv, templateSrv, popoverSrv) {
|
||||
constructor($scope, $rootScope, uiSegmentSrv, datasourceSrv, templateSrv, popoverSrv) {
|
||||
$scope.editor = this;
|
||||
this.panelCtrl = $scope.ctrl;
|
||||
this.panel = this.panelCtrl.panel;
|
||||
|
||||
this.$q = $q;
|
||||
this.datasourceSrv = datasourceSrv;
|
||||
this.templateSrv = templateSrv;
|
||||
this.popoverSrv = popoverSrv;
|
||||
@@ -62,8 +61,6 @@ class TriggerPanelEditorCtrl {
|
||||
};
|
||||
_.defaults(this, scopeDefaults);
|
||||
|
||||
var self = this;
|
||||
|
||||
// Get zabbix data sources
|
||||
var datasources = _.filter(this.datasourceSrv.getMetricSources(), datasource => {
|
||||
return datasource.meta.id === 'alexanderzobnin-zabbix-datasource';
|
||||
@@ -75,59 +72,47 @@ class TriggerPanelEditorCtrl {
|
||||
this.panel.datasource = this.datasources[0];
|
||||
}
|
||||
// Load datasource
|
||||
this.datasourceSrv.get(this.panel.datasource).then(function (datasource) {
|
||||
self.datasource = datasource;
|
||||
self.initFilters();
|
||||
self.panelCtrl.refresh();
|
||||
this.datasourceSrv.get(this.panel.datasource)
|
||||
.then(datasource => {
|
||||
this.datasource = datasource;
|
||||
this.queryBuilder = datasource.queryBuilder;
|
||||
this.initFilters();
|
||||
this.panelCtrl.refresh();
|
||||
});
|
||||
}
|
||||
|
||||
initFilters() {
|
||||
var self = this;
|
||||
return this.$q
|
||||
.when(this.suggestGroups())
|
||||
.then(() => {return self.suggestHosts();})
|
||||
.then(() => {return self.suggestApps();});
|
||||
return Promise.all([
|
||||
this.suggestGroups(),
|
||||
this.suggestHosts(),
|
||||
this.suggestApps()
|
||||
]);
|
||||
}
|
||||
|
||||
suggestGroups() {
|
||||
var self = this;
|
||||
return this.datasource.zabbixCache
|
||||
.getGroups()
|
||||
return this.queryBuilder.getAllGroups()
|
||||
.then(groups => {
|
||||
self.metric.groupList = groups;
|
||||
this.metric.groupList = groups;
|
||||
return groups;
|
||||
});
|
||||
}
|
||||
|
||||
suggestHosts() {
|
||||
var self = this;
|
||||
var groupFilter = this.datasource.replaceTemplateVars(this.panel.triggers.group.filter);
|
||||
return this.datasource.queryProcessor
|
||||
.filterGroups(self.metric.groupList, groupFilter)
|
||||
.then(groups => {
|
||||
var groupids = _.map(groups, 'groupid');
|
||||
return self.datasource.zabbixAPI
|
||||
.getHosts(groupids)
|
||||
let groupFilter = this.datasource.replaceTemplateVars(this.panel.triggers.group.filter);
|
||||
return this.queryBuilder.getAllHosts(groupFilter)
|
||||
.then(hosts => {
|
||||
self.metric.hostList = hosts;
|
||||
this.metric.hostList = hosts;
|
||||
return hosts;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
suggestApps() {
|
||||
var self = this;
|
||||
var hostFilter = this.datasource.replaceTemplateVars(this.panel.triggers.host.filter);
|
||||
return this.datasource.queryProcessor
|
||||
.filterHosts(self.metric.hostList, hostFilter)
|
||||
.then(hosts => {
|
||||
var hostids = _.map(hosts, 'hostid');
|
||||
return self.datasource.zabbixAPI
|
||||
.getApps(hostids)
|
||||
let groupFilter = this.datasource.replaceTemplateVars(this.panel.triggers.group.filter);
|
||||
let hostFilter = this.datasource.replaceTemplateVars(this.panel.triggers.host.filter);
|
||||
return this.queryBuilder.getAllApps(groupFilter, hostFilter)
|
||||
.then(apps => {
|
||||
return self.metric.appList = apps;
|
||||
});
|
||||
this.metric.appList = apps;
|
||||
return apps;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ var defaultTimeFormat = "DD MMM YYYY HH:mm:ss";
|
||||
class TriggerPanelCtrl extends MetricsPanelCtrl {
|
||||
|
||||
/** @ngInject */
|
||||
constructor($scope, $injector, $q, $element, datasourceSrv, templateSrv, contextSrv) {
|
||||
constructor($scope, $injector, $element, datasourceSrv, templateSrv, contextSrv) {
|
||||
super($scope, $injector);
|
||||
this.datasourceSrv = datasourceSrv;
|
||||
this.templateSrv = templateSrv;
|
||||
@@ -106,9 +106,9 @@ class TriggerPanelCtrl extends MetricsPanelCtrl {
|
||||
var self = this;
|
||||
|
||||
// Load datasource
|
||||
return this.datasourceSrv.get(this.panel.datasource).then(datasource => {
|
||||
var zabbix = datasource.zabbixAPI;
|
||||
var queryProcessor = datasource.queryProcessor;
|
||||
return this.datasourceSrv.get(this.panel.datasource)
|
||||
.then(datasource => {
|
||||
var zabbix = datasource.zabbix;
|
||||
var showEvents = self.panel.showEvents.value;
|
||||
var triggerFilter = self.panel.triggers;
|
||||
|
||||
@@ -117,13 +117,8 @@ class TriggerPanelCtrl extends MetricsPanelCtrl {
|
||||
var hostFilter = datasource.replaceTemplateVars(triggerFilter.host.filter);
|
||||
var appFilter = datasource.replaceTemplateVars(triggerFilter.application.filter);
|
||||
|
||||
var buildQuery = queryProcessor.buildTriggerQuery(groupFilter, hostFilter, appFilter);
|
||||
return buildQuery.then(query => {
|
||||
return zabbix.getTriggers(query.groupids,
|
||||
query.hostids,
|
||||
query.applicationids,
|
||||
showEvents)
|
||||
.then(triggers => {
|
||||
var getTriggers = zabbix.getTriggers(groupFilter, hostFilter, appFilter, showEvents);
|
||||
return getTriggers.then(triggers => {
|
||||
return _.map(triggers, trigger => {
|
||||
let triggerObj = trigger;
|
||||
|
||||
@@ -230,7 +225,6 @@ class TriggerPanelCtrl extends MetricsPanelCtrl {
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
switchComment(trigger) {
|
||||
|
||||
Reference in New Issue
Block a user