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: {
|
watch: {
|
||||||
rebuild_all: {
|
rebuild_all: {
|
||||||
files: ['src/**/*', 'plugin.json'],
|
files: ['src/**/*', 'plugin.json'],
|
||||||
tasks: ['default'],
|
tasks: ['watchTask'],
|
||||||
options: {spawn: false}
|
options: {spawn: false}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -135,4 +135,14 @@ module.exports = function(grunt) {
|
|||||||
'babel',
|
'babel',
|
||||||
'mochaTest'
|
'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 utils from './utils';
|
||||||
import * as migrations from './migrations';
|
import * as migrations from './migrations';
|
||||||
import * as metricFunctions from './metricFunctions';
|
import * as metricFunctions from './metricFunctions';
|
||||||
import DataProcessor from './DataProcessor';
|
import dataProcessor from './dataProcessor';
|
||||||
import './zabbixAPI.service.js';
|
import responseHandler from './responseHandler';
|
||||||
import './zabbixCache.service.js';
|
import './zabbix.js';
|
||||||
import './queryProcessor.service.js';
|
import {ZabbixAPIError} from './zabbixAPICore.service.js';
|
||||||
|
|
||||||
class ZabbixAPIDatasource {
|
class ZabbixAPIDatasource {
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor(instanceSettings, $q, templateSrv, alertSrv, zabbixAPIService, ZabbixCachingProxy, QueryProcessor) {
|
constructor(instanceSettings, templateSrv, alertSrv, Zabbix) {
|
||||||
|
this.templateSrv = templateSrv;
|
||||||
|
this.alertSrv = alertSrv;
|
||||||
|
|
||||||
// General data source settings
|
// General data source settings
|
||||||
this.name = instanceSettings.name;
|
this.name = instanceSettings.name;
|
||||||
@@ -32,20 +34,7 @@ class ZabbixAPIDatasource {
|
|||||||
var ttl = instanceSettings.jsonData.cacheTTL || '1h';
|
var ttl = instanceSettings.jsonData.cacheTTL || '1h';
|
||||||
this.cacheTTL = utils.parseInterval(ttl);
|
this.cacheTTL = utils.parseInterval(ttl);
|
||||||
|
|
||||||
// Initialize Zabbix API
|
this.zabbix = new Zabbix(this.url, this.username, this.password, this.basicAuth, this.withCredentials, this.cacheTTL);
|
||||||
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;
|
|
||||||
|
|
||||||
// Use custom format for template variables
|
// Use custom format for template variables
|
||||||
this.replaceTemplateVars = _.partial(replaceTemplateVars, this.templateSrv);
|
this.replaceTemplateVars = _.partial(replaceTemplateVars, this.templateSrv);
|
||||||
@@ -69,12 +58,12 @@ class ZabbixAPIDatasource {
|
|||||||
|
|
||||||
// Create request for each target
|
// Create request for each target
|
||||||
var promises = _.map(options.targets, target => {
|
var promises = _.map(options.targets, target => {
|
||||||
|
|
||||||
// Prevent changes of original object
|
// Prevent changes of original object
|
||||||
target = _.cloneDeep(target);
|
target = _.cloneDeep(target);
|
||||||
|
this.replaceTargetVariables(target, options);
|
||||||
|
|
||||||
|
// Metrics or Text query mode
|
||||||
if (target.mode !== 1) {
|
if (target.mode !== 1) {
|
||||||
|
|
||||||
// Migrate old targets
|
// Migrate old targets
|
||||||
target = migrations.migrate(target);
|
target = migrations.migrate(target);
|
||||||
|
|
||||||
@@ -83,30 +72,9 @@ class ZabbixAPIDatasource {
|
|||||||
return [];
|
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) {
|
if (!target.mode || target.mode === 0) {
|
||||||
return this.queryNumericData(target, timeFrom, timeTo, useTrends);
|
return this.queryNumericData(target, timeFrom, timeTo, useTrends);
|
||||||
}
|
} else if (target.mode === 2) {
|
||||||
|
|
||||||
// Query text data
|
|
||||||
else if (target.mode === 2) {
|
|
||||||
return this.queryTextData(target, timeFrom, timeTo);
|
return this.queryTextData(target, timeFrom, timeTo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -118,71 +86,62 @@ class ZabbixAPIDatasource {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.zabbixAPI
|
return this.zabbix.getSLA(target.itservice.serviceid, timeFrom, timeTo)
|
||||||
.getSLA(target.itservice.serviceid, timeFrom, timeTo)
|
|
||||||
.then(slaObject => {
|
.then(slaObject => {
|
||||||
return this.queryProcessor
|
return responseHandler.handleSLAResponse(target.itservice, target.slaProperty, slaObject);
|
||||||
.handleSLAResponse(target.itservice, target.slaProperty, slaObject);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Data for panel (all targets)
|
// Data for panel (all targets)
|
||||||
return this.q.all(_.flatten(promises))
|
return Promise.all(_.flatten(promises))
|
||||||
.then(_.flatten)
|
.then(_.flatten)
|
||||||
.then(timeseries_data => {
|
.then(timeseries_data => {
|
||||||
|
return downsampleSeries(timeseries_data, options);
|
||||||
// Series downsampling
|
})
|
||||||
var data = _.map(timeseries_data, timeseries => {
|
.then(data => {
|
||||||
if (timeseries.datapoints.length > options.maxDataPoints) {
|
|
||||||
timeseries.datapoints = DataProcessor
|
|
||||||
.groupBy(options.interval, DataProcessor.AVERAGE, timeseries.datapoints);
|
|
||||||
}
|
|
||||||
return timeseries;
|
|
||||||
});
|
|
||||||
return { data: data };
|
return { data: data };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
queryNumericData(target, timeFrom, timeTo, useTrends) {
|
queryNumericData(target, timeFrom, timeTo, useTrends) {
|
||||||
// Build query in asynchronous manner
|
let options = {
|
||||||
return this.queryProcessor.build(target.group.filter,
|
itemtype: 'num'
|
||||||
target.host.filter,
|
};
|
||||||
target.application.filter,
|
return this.zabbix.getItemsFromTarget(target, options)
|
||||||
target.item.filter,
|
|
||||||
'num')
|
|
||||||
.then(items => {
|
.then(items => {
|
||||||
// Add hostname for items from multiple hosts
|
let getHistoryPromise;
|
||||||
var addHostName = utils.isRegex(target.host.filter);
|
|
||||||
var getHistory;
|
|
||||||
|
|
||||||
// Use trends
|
|
||||||
if (useTrends) {
|
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
|
// Find trendValue() function and get specified trend value
|
||||||
var trendFunctions = _.map(metricFunctions.getCategories()['Trends'], 'name');
|
var trendFunctions = _.map(metricFunctions.getCategories()['Trends'], 'name');
|
||||||
var trendValueFunc = _.find(target.functions, func => {
|
var trendValueFunc = _.find(target.functions, func => {
|
||||||
return _.includes(trendFunctions, func.def.name);
|
return _.includes(trendFunctions, func.def.name);
|
||||||
});
|
});
|
||||||
var valueType = trendValueFunc ? trendValueFunc.params[0] : "avg";
|
return trendValueFunc ? trendValueFunc.params[0] : "avg";
|
||||||
|
|
||||||
getHistory = this.zabbixAPI
|
|
||||||
.getTrend(items, timeFrom, timeTo)
|
|
||||||
.then(history => {
|
|
||||||
return this.queryProcessor.handleTrends(history, items, addHostName, valueType);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use history
|
applyDataProcessingFunctions(timeseries_data, target) {
|
||||||
else {
|
|
||||||
getHistory = this.zabbixCache
|
|
||||||
.getHistory(items, timeFrom, timeTo)
|
|
||||||
.then(history => {
|
|
||||||
return this.queryProcessor.handleHistory(history, items, addHostName);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return getHistory.then(timeseries_data => {
|
|
||||||
let transformFunctions = bindFunctionDefs(target.functions, 'Transform');
|
let transformFunctions = bindFunctionDefs(target.functions, 'Transform');
|
||||||
let aggregationFunctions = bindFunctionDefs(target.functions, 'Aggregate');
|
let aggregationFunctions = bindFunctionDefs(target.functions, 'Aggregate');
|
||||||
let filterFunctions = bindFunctionDefs(target.functions, 'Filter');
|
let filterFunctions = bindFunctionDefs(target.functions, 'Filter');
|
||||||
@@ -209,33 +168,28 @@ class ZabbixAPIDatasource {
|
|||||||
return _.includes(aggFuncNames, func.def.name);
|
return _.includes(aggFuncNames, func.def.name);
|
||||||
});
|
});
|
||||||
|
|
||||||
timeseries_data = [
|
timeseries_data = [{
|
||||||
{
|
|
||||||
target: lastAgg.text,
|
target: lastAgg.text,
|
||||||
datapoints: dp
|
datapoints: dp
|
||||||
}
|
}];
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply alias functions
|
// Apply alias functions
|
||||||
_.each(timeseries_data, sequence(aliasFunctions));
|
_.each(timeseries_data, sequence(aliasFunctions));
|
||||||
|
|
||||||
return timeseries_data;
|
return timeseries_data;
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
queryTextData(target, timeFrom, timeTo) {
|
queryTextData(target, timeFrom, timeTo) {
|
||||||
return this.queryProcessor.build(target.group.filter,
|
let options = {
|
||||||
target.host.filter,
|
itemtype: 'text'
|
||||||
target.application.filter,
|
};
|
||||||
target.item.filter,
|
return this.zabbix.getItemsFromTarget(target, options)
|
||||||
'text')
|
|
||||||
.then(items => {
|
.then(items => {
|
||||||
if (items.length) {
|
if (items.length) {
|
||||||
return this.zabbixAPI.getHistory(items, timeFrom, timeTo)
|
return this.zabbix.getHistory(items, timeFrom, timeTo)
|
||||||
.then(history => {
|
.then(history => {
|
||||||
return this.queryProcessor.convertHistory(history, items, false, (point) => {
|
return responseHandler.convertHistory(history, items, false, (point) => {
|
||||||
let value = point.value;
|
let value = point.value;
|
||||||
|
|
||||||
// Regex-based extractor
|
// Regex-based extractor
|
||||||
@@ -247,7 +201,7 @@ class ZabbixAPIDatasource {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return this.q.when([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -257,37 +211,33 @@ class ZabbixAPIDatasource {
|
|||||||
* @return {object} Connection status and Zabbix API version
|
* @return {object} Connection status and Zabbix API version
|
||||||
*/
|
*/
|
||||||
testDatasource() {
|
testDatasource() {
|
||||||
return this.zabbixAPI.getVersion()
|
let zabbixVersion;
|
||||||
|
return this.zabbix.getVersion()
|
||||||
.then(version => {
|
.then(version => {
|
||||||
return this.zabbixAPI.login()
|
zabbixVersion = version;
|
||||||
.then(auth => {
|
return this.zabbix.login();
|
||||||
if (auth) {
|
})
|
||||||
|
.then(() => {
|
||||||
return {
|
return {
|
||||||
status: "success",
|
status: "success",
|
||||||
title: "Success",
|
title: "Success",
|
||||||
message: "Zabbix API version: " + version
|
message: "Zabbix API version: " + zabbixVersion
|
||||||
};
|
};
|
||||||
} else {
|
})
|
||||||
return {
|
.catch(error => {
|
||||||
status: "error",
|
if (error instanceof ZabbixAPIError) {
|
||||||
title: "Invalid user name or password",
|
|
||||||
message: "Zabbix API version: " + version
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}, error => {
|
|
||||||
return {
|
return {
|
||||||
status: "error",
|
status: "error",
|
||||||
title: error.message,
|
title: error.message,
|
||||||
message: error.data
|
message: error.data
|
||||||
};
|
};
|
||||||
});
|
} else {
|
||||||
}, error => {
|
|
||||||
console.log(error);
|
|
||||||
return {
|
return {
|
||||||
status: "error",
|
status: "error",
|
||||||
title: "Connection failed",
|
title: "Connection failed",
|
||||||
message: "Could not connect to given url"
|
message: "Could not connect to given url"
|
||||||
};
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -324,22 +274,22 @@ class ZabbixAPIDatasource {
|
|||||||
if (template.app === '/.*/') {
|
if (template.app === '/.*/') {
|
||||||
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) {
|
} else if (parts.length === 3) {
|
||||||
// Get applications
|
// 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) {
|
} else if (parts.length === 2) {
|
||||||
// Get hosts
|
// Get hosts
|
||||||
result = this.queryProcessor.getHosts(template.group);
|
result = this.zabbix.getHosts(template.group, template.host);
|
||||||
} else if (parts.length === 1) {
|
} else if (parts.length === 1) {
|
||||||
// Get groups
|
// Get groups
|
||||||
result = this.zabbixCache.getGroups(template.group);
|
result = this.zabbix.getGroups(template.group);
|
||||||
} else {
|
} else {
|
||||||
result = this.q.when([]);
|
result = Promise.resolve([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.then(metrics => {
|
return result.then(metrics => {
|
||||||
return _.map(metrics, formatMetric);
|
return metrics.map(formatMetric);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -356,15 +306,13 @@ class ZabbixAPIDatasource {
|
|||||||
// Show all triggers
|
// Show all triggers
|
||||||
var showTriggers = [0, 1];
|
var showTriggers = [0, 1];
|
||||||
|
|
||||||
var buildQuery = this.queryProcessor
|
var getTriggers = this.zabbix
|
||||||
.buildTriggerQuery(this.replaceTemplateVars(annotation.group, {}),
|
.getTriggers(this.replaceTemplateVars(annotation.group, {}),
|
||||||
this.replaceTemplateVars(annotation.host, {}),
|
this.replaceTemplateVars(annotation.host, {}),
|
||||||
this.replaceTemplateVars(annotation.application, {}));
|
this.replaceTemplateVars(annotation.application, {}),
|
||||||
|
showTriggers);
|
||||||
|
|
||||||
return buildQuery.then(query => {
|
return getTriggers.then(triggers => {
|
||||||
return this.zabbixAPI
|
|
||||||
.getTriggers(query.groupids, query.hostids, query.applicationids, showTriggers)
|
|
||||||
.then(triggers => {
|
|
||||||
|
|
||||||
// Filter triggers by description
|
// Filter triggers by description
|
||||||
if (utils.isRegex(annotation.trigger)) {
|
if (utils.isRegex(annotation.trigger)) {
|
||||||
@@ -383,7 +331,7 @@ class ZabbixAPIDatasource {
|
|||||||
});
|
});
|
||||||
|
|
||||||
var objectids = _.map(triggers, 'triggerid');
|
var objectids = _.map(triggers, 'triggerid');
|
||||||
return this.zabbixAPI
|
return this.zabbix
|
||||||
.getEvents(objectids, timeFrom, timeTo, showOkEvents)
|
.getEvents(objectids, timeFrom, timeTo, showOkEvents)
|
||||||
.then(events => {
|
.then(events => {
|
||||||
var indexedTriggers = _.keyBy(triggers, 'triggerid');
|
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) {
|
return _.map(aggFuncDefs, function(func) {
|
||||||
var funcInstance = metricFunctions.createFuncInstance(func.def, func.params);
|
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 {
|
export class ZabbixQueryController extends QueryCtrl {
|
||||||
|
|
||||||
// ZabbixQueryCtrl constructor
|
// ZabbixQueryCtrl constructor
|
||||||
constructor($scope, $injector, $rootScope, $sce, $q, templateSrv) {
|
constructor($scope, $injector, $rootScope, $sce, templateSrv) {
|
||||||
|
|
||||||
// Call superclass constructor
|
|
||||||
super($scope, $injector);
|
super($scope, $injector);
|
||||||
|
this.zabbix = this.datasource.zabbix;
|
||||||
this.zabbix = this.datasource.zabbixAPI;
|
|
||||||
this.cache = this.datasource.zabbixCache;
|
|
||||||
this.$q = $q;
|
|
||||||
|
|
||||||
// Use custom format for template variables
|
// Use custom format for template variables
|
||||||
this.replaceTemplateVars = this.datasource.replaceTemplateVars;
|
this.replaceTemplateVars = this.datasource.replaceTemplateVars;
|
||||||
@@ -106,85 +101,57 @@ export class ZabbixQueryController extends QueryCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
initFilters() {
|
initFilters() {
|
||||||
var self = this;
|
let itemtype = this.editorModes[this.target.mode].value;
|
||||||
var itemtype = self.editorModes[self.target.mode].value;
|
return Promise.all([
|
||||||
return this.$q.when(this.suggestGroups())
|
this.suggestGroups(),
|
||||||
.then(() => {return self.suggestHosts();})
|
this.suggestHosts(),
|
||||||
.then(() => {return self.suggestApps();})
|
this.suggestApps(),
|
||||||
.then(() => {return self.suggestItems(itemtype);});
|
this.suggestItems(itemtype)
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
suggestGroups() {
|
suggestGroups() {
|
||||||
var self = this;
|
return this.zabbix.getAllGroups()
|
||||||
return this.cache.getGroups().then(groups => {
|
.then(groups => {
|
||||||
self.metric.groupList = groups;
|
this.metric.groupList = groups;
|
||||||
return groups;
|
return groups;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
suggestHosts() {
|
suggestHosts() {
|
||||||
var self = this;
|
let groupFilter = this.replaceTemplateVars(this.target.group.filter);
|
||||||
var groupFilter = this.replaceTemplateVars(this.target.group.filter);
|
return this.zabbix.getAllHosts(groupFilter)
|
||||||
return this.datasource.queryProcessor
|
|
||||||
.filterGroups(self.metric.groupList, groupFilter)
|
|
||||||
.then(groups => {
|
|
||||||
var groupids = _.map(groups, 'groupid');
|
|
||||||
return self.zabbix
|
|
||||||
.getHosts(groupids)
|
|
||||||
.then(hosts => {
|
.then(hosts => {
|
||||||
self.metric.hostList = hosts;
|
this.metric.hostList = hosts;
|
||||||
return hosts;
|
return hosts;
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suggestApps() {
|
suggestApps() {
|
||||||
var self = this;
|
let groupFilter = this.replaceTemplateVars(this.target.group.filter);
|
||||||
var hostFilter = this.replaceTemplateVars(this.target.host.filter);
|
let hostFilter = this.replaceTemplateVars(this.target.host.filter);
|
||||||
return this.datasource.queryProcessor
|
return this.zabbix.getAllApps(groupFilter, hostFilter)
|
||||||
.filterHosts(self.metric.hostList, hostFilter)
|
|
||||||
.then(hosts => {
|
|
||||||
var hostids = _.map(hosts, 'hostid');
|
|
||||||
return self.zabbix
|
|
||||||
.getApps(hostids)
|
|
||||||
.then(apps => {
|
.then(apps => {
|
||||||
return self.metric.appList = apps;
|
this.metric.appList = apps;
|
||||||
});
|
return apps;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
suggestItems(itemtype = 'num') {
|
suggestItems(itemtype = 'num') {
|
||||||
var self = this;
|
let groupFilter = this.replaceTemplateVars(this.target.group.filter);
|
||||||
var appFilter = this.replaceTemplateVars(this.target.application.filter);
|
let hostFilter = this.replaceTemplateVars(this.target.host.filter);
|
||||||
if (appFilter) {
|
let appFilter = this.replaceTemplateVars(this.target.application.filter);
|
||||||
// Filter by applications
|
let options = {
|
||||||
return this.datasource.queryProcessor
|
itemtype: itemtype,
|
||||||
.filterApps(self.metric.appList, appFilter)
|
showDisabledItems: this.target.options.showDisabledItems
|
||||||
.then(apps => {
|
};
|
||||||
var appids = _.map(apps, 'applicationid');
|
|
||||||
return self.zabbix
|
return this.zabbix
|
||||||
.getItems(undefined, appids, itemtype)
|
.getAllItems(groupFilter, hostFilter, appFilter, options)
|
||||||
.then(items => {
|
.then(items => {
|
||||||
if (!self.target.options.showDisabledItems) {
|
this.metric.itemList = items;
|
||||||
items = _.filter(items, {'status': '0'});
|
|
||||||
}
|
|
||||||
self.metric.itemList = items;
|
|
||||||
return 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) {
|
isRegex(str) {
|
||||||
@@ -331,7 +298,6 @@ export class ZabbixQueryController extends QueryCtrl {
|
|||||||
this.panelCtrl.refresh();
|
this.panelCtrl.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set templateUrl as static property
|
// 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'
|
trendsFrom: '7d'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ctx.$q = Q;
|
|
||||||
ctx.templateSrv = {};
|
ctx.templateSrv = {};
|
||||||
ctx.alertSrv = {};
|
ctx.alertSrv = {};
|
||||||
ctx.zabbixAPIService = () => {};
|
ctx.zabbix = () => {};
|
||||||
ctx.ZabbixCachingProxy = () => {};
|
|
||||||
ctx.QueryProcessor = () => {};
|
|
||||||
|
|
||||||
ctx.ds = new Datasource(ctx.instanceSettings, ctx.$q, ctx.templateSrv, ctx.alertSrv,
|
ctx.ds = new Datasource(ctx.instanceSettings, ctx.templateSrv, ctx.alertSrv, ctx.zabbix);
|
||||||
ctx.zabbixAPIService, ctx.ZabbixCachingProxy, ctx.QueryProcessor);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('When querying data', () => {
|
describe('When querying data', () => {
|
||||||
@@ -144,10 +140,7 @@ describe('ZabbixDatasource', () => {
|
|||||||
describe('When invoking metricFindQuery()', () => {
|
describe('When invoking metricFindQuery()', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
ctx.ds.replaceTemplateVars = (str) => str;
|
ctx.ds.replaceTemplateVars = (str) => str;
|
||||||
ctx.ds.zabbixCache = {
|
ctx.ds.zabbix = {
|
||||||
getGroups: () => Q.when([])
|
|
||||||
};
|
|
||||||
ctx.ds.queryProcessor = {
|
|
||||||
getGroups: () => Q.when([]),
|
getGroups: () => Q.when([]),
|
||||||
getHosts: () => Q.when([]),
|
getHosts: () => Q.when([]),
|
||||||
getApps: () => Q.when([]),
|
getApps: () => Q.when([]),
|
||||||
@@ -163,7 +156,7 @@ describe('ZabbixDatasource', () => {
|
|||||||
{query: 'Back*', expect: 'Back*'}
|
{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) {
|
for (const test of tests) {
|
||||||
ctx.ds.metricFindQuery(test.query);
|
ctx.ds.metricFindQuery(test.query);
|
||||||
expect(getGroups).to.have.been.calledWith(test.expect);
|
expect(getGroups).to.have.been.calledWith(test.expect);
|
||||||
@@ -180,7 +173,7 @@ describe('ZabbixDatasource', () => {
|
|||||||
{query: 'Back*.', expect: 'Back*'}
|
{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) {
|
for (const test of tests) {
|
||||||
ctx.ds.metricFindQuery(test.query);
|
ctx.ds.metricFindQuery(test.query);
|
||||||
expect(getHosts).to.have.been.calledWith(test.expect);
|
expect(getHosts).to.have.been.calledWith(test.expect);
|
||||||
@@ -197,7 +190,7 @@ describe('ZabbixDatasource', () => {
|
|||||||
{query: 'Back*.*.', expect: ['Back*', '/.*/']}
|
{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) {
|
for (const test of tests) {
|
||||||
ctx.ds.metricFindQuery(test.query);
|
ctx.ds.metricFindQuery(test.query);
|
||||||
expect(getApps).to.have.been.calledWith(test.expect[0], test.expect[1]);
|
expect(getApps).to.have.been.calledWith(test.expect[0], test.expect[1]);
|
||||||
@@ -214,7 +207,7 @@ describe('ZabbixDatasource', () => {
|
|||||||
{query: 'Back*.*.cpu.*', expect: ['Back*', '/.*/', 'cpu']}
|
{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) {
|
for (const test of tests) {
|
||||||
ctx.ds.metricFindQuery(test.query);
|
ctx.ds.metricFindQuery(test.query);
|
||||||
expect(getItems)
|
expect(getItems)
|
||||||
@@ -227,7 +220,7 @@ describe('ZabbixDatasource', () => {
|
|||||||
it('should invoke method with proper arguments', (done) => {
|
it('should invoke method with proper arguments', (done) => {
|
||||||
let query = '*.*';
|
let query = '*.*';
|
||||||
|
|
||||||
let getHosts = sinon.spy(ctx.ds.queryProcessor, 'getHosts');
|
let getHosts = sinon.spy(ctx.ds.zabbix, 'getHosts');
|
||||||
ctx.ds.metricFindQuery(query);
|
ctx.ds.metricFindQuery(query);
|
||||||
expect(getHosts).to.have.been.calledWith('/.*/');
|
expect(getHosts).to.have.been.calledWith('/.*/');
|
||||||
done();
|
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
|
// Fix for backward compatibility with lodash 2.4
|
||||||
if (!_.includes) {
|
if (!_.includes) {
|
||||||
_.includes = _.contains;
|
_.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';
|
import './zabbixAPICore.service';
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) {
|
function ZabbixAPIServiceFactory(alertSrv, zabbixAPICoreService) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Zabbix API Wrapper.
|
* Zabbix API Wrapper.
|
||||||
@@ -25,8 +25,9 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
this.loginPromise = null;
|
this.loginPromise = null;
|
||||||
|
this.loginErrorCount = 0;
|
||||||
|
this.maxLoginAttempts = 3;
|
||||||
|
|
||||||
this.$q = $q;
|
|
||||||
this.alertSrv = alertSrv;
|
this.alertSrv = alertSrv;
|
||||||
this.zabbixAPICore = zabbixAPICoreService;
|
this.zabbixAPICore = zabbixAPICoreService;
|
||||||
|
|
||||||
@@ -39,25 +40,22 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) {
|
|||||||
//////////////////////////
|
//////////////////////////
|
||||||
|
|
||||||
request(method, params) {
|
request(method, params) {
|
||||||
var self = this;
|
return this.zabbixAPICore.request(this.url, method, params, this.requestOptions, this.auth)
|
||||||
|
.catch(error => {
|
||||||
return this.zabbixAPICore
|
|
||||||
.request(this.url, method, params, this.requestOptions, this.auth)
|
|
||||||
.then((result) => {
|
|
||||||
return result;
|
|
||||||
}, (error) => {
|
|
||||||
// Handle API errors
|
|
||||||
if (isNotAuthorized(error.data)) {
|
if (isNotAuthorized(error.data)) {
|
||||||
return self.loginOnce().then(
|
// Handle auth errors
|
||||||
function() {
|
this.loginErrorCount++;
|
||||||
return self.request(method, params);
|
if (this.loginErrorCount > this.maxLoginAttempts) {
|
||||||
},
|
this.loginErrorCount = 0;
|
||||||
// Handle user.login method errors
|
return null;
|
||||||
function(error) {
|
|
||||||
self.alertAPIError(error.data);
|
|
||||||
});
|
|
||||||
} else {
|
} 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
|
* @return login promise
|
||||||
*/
|
*/
|
||||||
loginOnce() {
|
loginOnce() {
|
||||||
var self = this;
|
if (!this.loginPromise) {
|
||||||
var deferred = this.$q.defer();
|
this.loginPromise = Promise.resolve(
|
||||||
if (!self.loginPromise) {
|
this.login().then(auth => {
|
||||||
self.loginPromise = deferred.promise;
|
this.auth = auth;
|
||||||
self.login().then(
|
this.loginPromise = null;
|
||||||
function(auth) {
|
return auth;
|
||||||
self.loginPromise = null;
|
})
|
||||||
self.auth = auth;
|
|
||||||
deferred.resolve(auth);
|
|
||||||
},
|
|
||||||
function(error) {
|
|
||||||
self.loginPromise = null;
|
|
||||||
deferred.reject(error);
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
} 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)
|
return this.request('item.get', params)
|
||||||
.then(items => {
|
.then(expandItems);
|
||||||
return _.forEach(items, item => {
|
|
||||||
|
function expandItems(items) {
|
||||||
|
items.forEach(item => {
|
||||||
item.item = item.name;
|
item.item = item.name;
|
||||||
item.name = utils.expandItemName(item.item, item.key_);
|
item.name = utils.expandItemName(item.item, item.key_);
|
||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
});
|
return items;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getLastValue(itemid) {
|
getLastValue(itemid) {
|
||||||
@@ -211,48 +203,42 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) {
|
|||||||
output: ['lastvalue'],
|
output: ['lastvalue'],
|
||||||
itemids: itemid
|
itemids: itemid
|
||||||
};
|
};
|
||||||
return this.request('item.get', params).then(function(items) {
|
return this.request('item.get', params)
|
||||||
if (items.length) {
|
.then(items => items.length ? items[0].lastvalue : null);
|
||||||
return items[0].lastvalue;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Perform history query from Zabbix API
|
* Perform history query from Zabbix API
|
||||||
*
|
*
|
||||||
* @param {Array} items Array of Zabbix item objects
|
* @param {Array} items Array of Zabbix item objects
|
||||||
* @param {Number} time_from Time in seconds
|
* @param {Number} timeFrom Time in seconds
|
||||||
* @param {Number} time_till Time in seconds
|
* @param {Number} timeTill Time in seconds
|
||||||
* @return {Array} Array of Zabbix history objects
|
* @return {Array} Array of Zabbix history objects
|
||||||
*/
|
*/
|
||||||
getHistory(items, time_from, time_till) {
|
getHistory(items, timeFrom, timeTill) {
|
||||||
var self = this;
|
|
||||||
|
|
||||||
// Group items by value type
|
// Group items by value type and perform request for each value type
|
||||||
var grouped_items = _.groupBy(items, 'value_type');
|
let grouped_items = _.groupBy(items, 'value_type');
|
||||||
|
let promises = _.map(grouped_items, (items, value_type) => {
|
||||||
// Perform request for each value type
|
let itemids = _.map(items, 'itemid');
|
||||||
return this.$q.all(_.map(grouped_items, function (items, value_type) {
|
let params = {
|
||||||
var itemids = _.map(items, 'itemid');
|
|
||||||
var params = {
|
|
||||||
output: 'extend',
|
output: 'extend',
|
||||||
history: value_type,
|
history: value_type,
|
||||||
itemids: itemids,
|
itemids: itemids,
|
||||||
sortfield: 'clock',
|
sortfield: 'clock',
|
||||||
sortorder: 'ASC',
|
sortorder: 'ASC',
|
||||||
time_from: time_from
|
time_from: timeFrom
|
||||||
};
|
};
|
||||||
|
|
||||||
// Relative queries (e.g. last hour) don't include an end time
|
// Relative queries (e.g. last hour) don't include an end time
|
||||||
if (time_till) {
|
if (timeTill) {
|
||||||
params.time_till = time_till;
|
params.time_till = timeTill;
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.request('history.get', params);
|
return this.request('history.get', params);
|
||||||
})).then(_.flatten);
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises).then(_.flatten);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -264,31 +250,30 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) {
|
|||||||
* @param {Number} time_till Time in seconds
|
* @param {Number} time_till Time in seconds
|
||||||
* @return {Array} Array of Zabbix trend objects
|
* @return {Array} Array of Zabbix trend objects
|
||||||
*/
|
*/
|
||||||
getTrend_ZBXNEXT1193(items, time_from, time_till) {
|
getTrend_ZBXNEXT1193(items, timeFrom, timeTill) {
|
||||||
var self = this;
|
|
||||||
|
|
||||||
// Group items by value type
|
// Group items by value type and perform request for each value type
|
||||||
var grouped_items = _.groupBy(items, 'value_type');
|
let grouped_items = _.groupBy(items, 'value_type');
|
||||||
|
let promises = _.map(grouped_items, (items, value_type) => {
|
||||||
// Perform request for each value type
|
let itemids = _.map(items, 'itemid');
|
||||||
return this.$q.all(_.map(grouped_items, function (items, value_type) {
|
let params = {
|
||||||
var itemids = _.map(items, 'itemid');
|
|
||||||
var params = {
|
|
||||||
output: 'extend',
|
output: 'extend',
|
||||||
trend: value_type,
|
trend: value_type,
|
||||||
itemids: itemids,
|
itemids: itemids,
|
||||||
sortfield: 'clock',
|
sortfield: 'clock',
|
||||||
sortorder: 'ASC',
|
sortorder: 'ASC',
|
||||||
time_from: time_from
|
time_from: timeFrom
|
||||||
};
|
};
|
||||||
|
|
||||||
// Relative queries (e.g. last hour) don't include an end time
|
// Relative queries (e.g. last hour) don't include an end time
|
||||||
if (time_till) {
|
if (timeTill) {
|
||||||
params.time_till = time_till;
|
params.time_till = timeTill;
|
||||||
}
|
}
|
||||||
|
|
||||||
return self.request('trend.get', params);
|
return this.request('trend.get', params);
|
||||||
})).then(_.flatten);
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises).then(_.flatten);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTrend_30(items, time_from, time_till, value_type) {
|
getTrend_30(items, time_from, time_till, value_type) {
|
||||||
@@ -312,7 +297,7 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) {
|
|||||||
return self.request('trend.get', params);
|
return self.request('trend.get', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
getITService(/* optional */ serviceids) {
|
getITService(serviceids) {
|
||||||
var params = {
|
var params = {
|
||||||
output: 'extend',
|
output: 'extend',
|
||||||
serviceids: serviceids
|
serviceids: serviceids
|
||||||
@@ -320,12 +305,12 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) {
|
|||||||
return this.request('service.get', params);
|
return this.request('service.get', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
getSLA(serviceids, from, to) {
|
getSLA(serviceids, timeFrom, timeTo) {
|
||||||
var params = {
|
var params = {
|
||||||
serviceids: serviceids,
|
serviceids: serviceids,
|
||||||
intervals: [{
|
intervals: [{
|
||||||
from: from,
|
from: timeFrom,
|
||||||
to: to
|
to: timeTo
|
||||||
}]
|
}]
|
||||||
};
|
};
|
||||||
return this.request('service.getsla', params);
|
return this.request('service.getsla', params);
|
||||||
@@ -364,11 +349,11 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) {
|
|||||||
return this.request('trigger.get', params);
|
return this.request('trigger.get', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
getEvents(objectids, from, to, showEvents) {
|
getEvents(objectids, timeFrom, timeTo, showEvents) {
|
||||||
var params = {
|
var params = {
|
||||||
output: 'extend',
|
output: 'extend',
|
||||||
time_from: from,
|
time_from: timeFrom,
|
||||||
time_till: to,
|
time_till: timeTo,
|
||||||
objectids: objectids,
|
objectids: objectids,
|
||||||
select_acknowledges: 'extend',
|
select_acknowledges: 'extend',
|
||||||
selectHosts: 'extend',
|
selectHosts: 'extend',
|
||||||
@@ -389,10 +374,8 @@ function ZabbixAPIService($q, alertSrv, zabbixAPICoreService) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return this.request('event.get', params)
|
return this.request('event.get', params)
|
||||||
.then(function (events) {
|
.then(events => {
|
||||||
return _.filter(events, function(event) {
|
return _.filter(events, (event) => event.acknowledges.length);
|
||||||
return event.acknowledges.length;
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,4 +394,4 @@ function isNotAuthorized(message) {
|
|||||||
|
|
||||||
angular
|
angular
|
||||||
.module('grafana.services')
|
.module('grafana.services')
|
||||||
.factory('zabbixAPIService', ZabbixAPIService);
|
.factory('zabbixAPIService', ZabbixAPIServiceFactory);
|
||||||
|
|||||||
@@ -7,8 +7,7 @@ import angular from 'angular';
|
|||||||
class ZabbixAPICoreService {
|
class ZabbixAPICoreService {
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($q, backendSrv) {
|
constructor(backendSrv) {
|
||||||
this.$q = $q;
|
|
||||||
this.backendSrv = backendSrv;
|
this.backendSrv = backendSrv;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -17,8 +16,7 @@ class ZabbixAPICoreService {
|
|||||||
* @return {object} response.result
|
* @return {object} response.result
|
||||||
*/
|
*/
|
||||||
request(api_url, method, params, options, auth) {
|
request(api_url, method, params, options, auth) {
|
||||||
var deferred = this.$q.defer();
|
let requestData = {
|
||||||
var requestData = {
|
|
||||||
jsonrpc: '2.0',
|
jsonrpc: '2.0',
|
||||||
method: method,
|
method: method,
|
||||||
params: params,
|
params: params,
|
||||||
@@ -27,20 +25,19 @@ class ZabbixAPICoreService {
|
|||||||
|
|
||||||
if (auth === "") {
|
if (auth === "") {
|
||||||
// Reject immediately if not authenticated
|
// Reject immediately if not authenticated
|
||||||
deferred.reject({data: "Not authorised."});
|
return Promise.reject(new ZabbixAPIError({data: "Not authorised."}));
|
||||||
return deferred.promise;
|
|
||||||
} else if (auth) {
|
} else if (auth) {
|
||||||
// Set auth parameter only if it needed
|
// Set auth parameter only if it needed
|
||||||
requestData.auth = auth;
|
requestData.auth = auth;
|
||||||
}
|
}
|
||||||
|
|
||||||
var requestOptions = {
|
let requestOptions = {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
url: api_url,
|
||||||
|
data: requestData,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
}
|
||||||
url: api_url,
|
|
||||||
data: requestData
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Set request options for basic auth
|
// Set request options for basic auth
|
||||||
@@ -51,24 +48,23 @@ class ZabbixAPICoreService {
|
|||||||
requestOptions.headers.Authorization = options.basicAuth;
|
requestOptions.headers.Authorization = options.basicAuth;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.backendSrv.datasourceRequest(requestOptions)
|
return this.datasourceRequest(requestOptions);
|
||||||
.then((response) => {
|
|
||||||
// General connection issues
|
|
||||||
if (!response.data) {
|
|
||||||
deferred.reject(response);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// Handle Zabbix API errors
|
||||||
else if (response.data.error) {
|
return Promise.reject(new ZabbixAPIError(response.data.error));
|
||||||
deferred.reject(response.data.error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deferred.resolve(response.data.result);
|
// Success
|
||||||
}, (error) => {
|
return response.data.result;
|
||||||
deferred.reject(error.err);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
return deferred.promise;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -76,7 +72,7 @@ class ZabbixAPICoreService {
|
|||||||
* @return {string} auth token
|
* @return {string} auth token
|
||||||
*/
|
*/
|
||||||
login(api_url, username, password, options) {
|
login(api_url, username, password, options) {
|
||||||
var params = {
|
let params = {
|
||||||
user: username,
|
user: username,
|
||||||
password: password
|
password: password
|
||||||
};
|
};
|
||||||
@@ -93,15 +89,18 @@ class ZabbixAPICoreService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Define zabbix API exception type
|
// Define zabbix API exception type
|
||||||
function ZabbixException(error) {
|
export class ZabbixAPIError {
|
||||||
|
constructor(error) {
|
||||||
this.code = error.code;
|
this.code = error.code;
|
||||||
this.errorType = error.message;
|
this.name = error.data;
|
||||||
this.message = error.data;
|
this.message = error.data;
|
||||||
|
this.data = error.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
ZabbixException.prototype.toString = function() {
|
toString() {
|
||||||
return this.errorType + ": " + this.message;
|
return this.name + ": " + this.message;
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
angular
|
angular
|
||||||
.module('grafana.services')
|
.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 {
|
class TriggerPanelEditorCtrl {
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($scope, $rootScope, $q, uiSegmentSrv, datasourceSrv, templateSrv, popoverSrv) {
|
constructor($scope, $rootScope, uiSegmentSrv, datasourceSrv, templateSrv, popoverSrv) {
|
||||||
$scope.editor = this;
|
$scope.editor = this;
|
||||||
this.panelCtrl = $scope.ctrl;
|
this.panelCtrl = $scope.ctrl;
|
||||||
this.panel = this.panelCtrl.panel;
|
this.panel = this.panelCtrl.panel;
|
||||||
|
|
||||||
this.$q = $q;
|
|
||||||
this.datasourceSrv = datasourceSrv;
|
this.datasourceSrv = datasourceSrv;
|
||||||
this.templateSrv = templateSrv;
|
this.templateSrv = templateSrv;
|
||||||
this.popoverSrv = popoverSrv;
|
this.popoverSrv = popoverSrv;
|
||||||
@@ -62,8 +61,6 @@ class TriggerPanelEditorCtrl {
|
|||||||
};
|
};
|
||||||
_.defaults(this, scopeDefaults);
|
_.defaults(this, scopeDefaults);
|
||||||
|
|
||||||
var self = this;
|
|
||||||
|
|
||||||
// Get zabbix data sources
|
// Get zabbix data sources
|
||||||
var datasources = _.filter(this.datasourceSrv.getMetricSources(), datasource => {
|
var datasources = _.filter(this.datasourceSrv.getMetricSources(), datasource => {
|
||||||
return datasource.meta.id === 'alexanderzobnin-zabbix-datasource';
|
return datasource.meta.id === 'alexanderzobnin-zabbix-datasource';
|
||||||
@@ -75,59 +72,47 @@ class TriggerPanelEditorCtrl {
|
|||||||
this.panel.datasource = this.datasources[0];
|
this.panel.datasource = this.datasources[0];
|
||||||
}
|
}
|
||||||
// Load datasource
|
// Load datasource
|
||||||
this.datasourceSrv.get(this.panel.datasource).then(function (datasource) {
|
this.datasourceSrv.get(this.panel.datasource)
|
||||||
self.datasource = datasource;
|
.then(datasource => {
|
||||||
self.initFilters();
|
this.datasource = datasource;
|
||||||
self.panelCtrl.refresh();
|
this.queryBuilder = datasource.queryBuilder;
|
||||||
|
this.initFilters();
|
||||||
|
this.panelCtrl.refresh();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
initFilters() {
|
initFilters() {
|
||||||
var self = this;
|
return Promise.all([
|
||||||
return this.$q
|
this.suggestGroups(),
|
||||||
.when(this.suggestGroups())
|
this.suggestHosts(),
|
||||||
.then(() => {return self.suggestHosts();})
|
this.suggestApps()
|
||||||
.then(() => {return self.suggestApps();});
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
suggestGroups() {
|
suggestGroups() {
|
||||||
var self = this;
|
return this.queryBuilder.getAllGroups()
|
||||||
return this.datasource.zabbixCache
|
|
||||||
.getGroups()
|
|
||||||
.then(groups => {
|
.then(groups => {
|
||||||
self.metric.groupList = groups;
|
this.metric.groupList = groups;
|
||||||
return groups;
|
return groups;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
suggestHosts() {
|
suggestHosts() {
|
||||||
var self = this;
|
let groupFilter = this.datasource.replaceTemplateVars(this.panel.triggers.group.filter);
|
||||||
var groupFilter = this.datasource.replaceTemplateVars(this.panel.triggers.group.filter);
|
return this.queryBuilder.getAllHosts(groupFilter)
|
||||||
return this.datasource.queryProcessor
|
|
||||||
.filterGroups(self.metric.groupList, groupFilter)
|
|
||||||
.then(groups => {
|
|
||||||
var groupids = _.map(groups, 'groupid');
|
|
||||||
return self.datasource.zabbixAPI
|
|
||||||
.getHosts(groupids)
|
|
||||||
.then(hosts => {
|
.then(hosts => {
|
||||||
self.metric.hostList = hosts;
|
this.metric.hostList = hosts;
|
||||||
return hosts;
|
return hosts;
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suggestApps() {
|
suggestApps() {
|
||||||
var self = this;
|
let groupFilter = this.datasource.replaceTemplateVars(this.panel.triggers.group.filter);
|
||||||
var hostFilter = this.datasource.replaceTemplateVars(this.panel.triggers.host.filter);
|
let hostFilter = this.datasource.replaceTemplateVars(this.panel.triggers.host.filter);
|
||||||
return this.datasource.queryProcessor
|
return this.queryBuilder.getAllApps(groupFilter, hostFilter)
|
||||||
.filterHosts(self.metric.hostList, hostFilter)
|
|
||||||
.then(hosts => {
|
|
||||||
var hostids = _.map(hosts, 'hostid');
|
|
||||||
return self.datasource.zabbixAPI
|
|
||||||
.getApps(hostids)
|
|
||||||
.then(apps => {
|
.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 {
|
class TriggerPanelCtrl extends MetricsPanelCtrl {
|
||||||
|
|
||||||
/** @ngInject */
|
/** @ngInject */
|
||||||
constructor($scope, $injector, $q, $element, datasourceSrv, templateSrv, contextSrv) {
|
constructor($scope, $injector, $element, datasourceSrv, templateSrv, contextSrv) {
|
||||||
super($scope, $injector);
|
super($scope, $injector);
|
||||||
this.datasourceSrv = datasourceSrv;
|
this.datasourceSrv = datasourceSrv;
|
||||||
this.templateSrv = templateSrv;
|
this.templateSrv = templateSrv;
|
||||||
@@ -106,9 +106,9 @@ class TriggerPanelCtrl extends MetricsPanelCtrl {
|
|||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
// Load datasource
|
// Load datasource
|
||||||
return this.datasourceSrv.get(this.panel.datasource).then(datasource => {
|
return this.datasourceSrv.get(this.panel.datasource)
|
||||||
var zabbix = datasource.zabbixAPI;
|
.then(datasource => {
|
||||||
var queryProcessor = datasource.queryProcessor;
|
var zabbix = datasource.zabbix;
|
||||||
var showEvents = self.panel.showEvents.value;
|
var showEvents = self.panel.showEvents.value;
|
||||||
var triggerFilter = self.panel.triggers;
|
var triggerFilter = self.panel.triggers;
|
||||||
|
|
||||||
@@ -117,13 +117,8 @@ class TriggerPanelCtrl extends MetricsPanelCtrl {
|
|||||||
var hostFilter = datasource.replaceTemplateVars(triggerFilter.host.filter);
|
var hostFilter = datasource.replaceTemplateVars(triggerFilter.host.filter);
|
||||||
var appFilter = datasource.replaceTemplateVars(triggerFilter.application.filter);
|
var appFilter = datasource.replaceTemplateVars(triggerFilter.application.filter);
|
||||||
|
|
||||||
var buildQuery = queryProcessor.buildTriggerQuery(groupFilter, hostFilter, appFilter);
|
var getTriggers = zabbix.getTriggers(groupFilter, hostFilter, appFilter, showEvents);
|
||||||
return buildQuery.then(query => {
|
return getTriggers.then(triggers => {
|
||||||
return zabbix.getTriggers(query.groupids,
|
|
||||||
query.hostids,
|
|
||||||
query.applicationids,
|
|
||||||
showEvents)
|
|
||||||
.then(triggers => {
|
|
||||||
return _.map(triggers, trigger => {
|
return _.map(triggers, trigger => {
|
||||||
let triggerObj = trigger;
|
let triggerObj = trigger;
|
||||||
|
|
||||||
@@ -230,7 +225,6 @@ class TriggerPanelCtrl extends MetricsPanelCtrl {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
switchComment(trigger) {
|
switchComment(trigger) {
|
||||||
|
|||||||
Reference in New Issue
Block a user