From e8b4a4319c930b0badd1f8502c969cb25927b112 Mon Sep 17 00:00:00 2001 From: Alexander Zobnin Date: Sat, 19 Mar 2016 14:07:21 +0300 Subject: [PATCH] Services refactor - using ES6 modules. --- Gruntfile.js | 8 +- src/datasource-zabbix/DataProcessor.js | 234 ++++++ .../dataProcessing.service.js | 241 ------- src/datasource-zabbix/datasource.js | 19 +- .../queryProcessor.service.js | 678 +++++++++--------- src/datasource-zabbix/zabbixCache.service.js | 392 +++++----- 6 files changed, 780 insertions(+), 792 deletions(-) create mode 100644 src/datasource-zabbix/DataProcessor.js delete mode 100644 src/datasource-zabbix/dataProcessing.service.js diff --git a/Gruntfile.js b/Gruntfile.js index 672b82d..58c59c0 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -19,9 +19,11 @@ module.exports = function(grunt) { '!**/module.js', '!**/query.controller.js', '!**/utils.js', + '!**/DataProcessor.js', '!**/zabbixAPICore.service.js', '!**/zabbixAPI.service.js', - //'!**/dataProcessing.service.js', + '!**/queryProcessor.service.js', + '!**/zabbixCache.service.js', '!**/metricFunctions.js', '!**/*.scss' ], @@ -57,9 +59,11 @@ module.exports = function(grunt) { '**/**/datasource.js', '**/**/query.controller.js', '**/**/utils.js', + '**/**/DataProcessor.js', '**/**/zabbixAPICore.service.js', '**/**/zabbixAPI.service.js', - //'**/**/dataProcessing.service.js', + '**/**/queryProcessor.service.js', + '**/**/zabbixCache.service.js', '**/**/metricFunctions.js' ], dest: 'dist/' diff --git a/src/datasource-zabbix/DataProcessor.js b/src/datasource-zabbix/DataProcessor.js new file mode 100644 index 0000000..c47965e --- /dev/null +++ b/src/datasource-zabbix/DataProcessor.js @@ -0,0 +1,234 @@ +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: [[, ], ...] + */ + 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: { '': [[, ], ...] } + // return [{ '': }, { '': }, ...] + 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 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 groupByWrapper(interval, groupFunc, datapoints) { + var groupByCallback = DataProcessor.aggregationFunctions[groupFunc]; + return DataProcessor.groupBy(interval, groupByCallback, datapoints); + } + + 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, + 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, + 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]) { + 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]) { + return series[i]; + } + } + return nearestLeft; +} diff --git a/src/datasource-zabbix/dataProcessing.service.js b/src/datasource-zabbix/dataProcessing.service.js deleted file mode 100644 index b7958ea..0000000 --- a/src/datasource-zabbix/dataProcessing.service.js +++ /dev/null @@ -1,241 +0,0 @@ -define([ - 'angular', - 'lodash', - 'moment', - './utils' -], -function (angular, _, moment, utils) { - 'use strict'; - - var module = angular.module('grafana.services'); - - module.service('DataProcessingService', function() { - var self = this; - - /** - * Downsample datapoints series - */ - this.downsampleSeries = function(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: [[, ], ...] - */ - this.groupBy = function(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: { '': [[, ], ...] } - // return [{ '': }, { '': }, ...] - 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)]; - })); - }; - - this.sumSeries = function(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 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]) { - 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]) { - return series[i]; - } - } - return nearestLeft; - } - - this.AVERAGE = function(values) { - var sum = 0; - _.each(values, function(value) { - sum += value; - }); - return sum / values.length; - }; - - this.MIN = function(values) { - return _.min(values); - }; - - this.MAX = function(values) { - return _.max(values); - }; - - this.MEDIAN = function(values) { - var sorted = _.sortBy(values); - return sorted[Math.floor(sorted.length / 2)]; - }; - - this.setAlias = function(alias, timeseries) { - timeseries.target = alias; - return timeseries; - }; - - this.aggregationFunctions = { - avg: this.AVERAGE, - min: this.MIN, - max: this.MAX, - median: this.MEDIAN - }; - - this.groupByWrapper = function(interval, groupFunc, datapoints) { - var groupByCallback = self.aggregationFunctions[groupFunc]; - return self.groupBy(interval, groupByCallback, datapoints); - }; - - this.aggregateWrapper = function(groupByCallback, interval, datapoints) { - var flattenedPoints = _.flatten(datapoints, true); - return self.groupBy(interval, groupByCallback, flattenedPoints); - }; - - this.metricFunctions = { - groupBy: this.groupByWrapper, - 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, - setAlias: this.setAlias, - }; - - }); -}); diff --git a/src/datasource-zabbix/datasource.js b/src/datasource-zabbix/datasource.js index 0d4c1c2..8e5d250 100644 --- a/src/datasource-zabbix/datasource.js +++ b/src/datasource-zabbix/datasource.js @@ -3,15 +3,15 @@ import _ from 'lodash'; import * as dateMath from 'app/core/utils/datemath'; import * as utils from './utils'; import * as metricFunctions from './metricFunctions'; +import DataProcessor from './DataProcessor'; import './zabbixAPI.service.js'; import './zabbixCache.service.js'; import './queryProcessor.service.js'; -import './dataProcessing.service'; export class ZabbixAPIDatasource { /** @ngInject */ - constructor(instanceSettings, $q, templateSrv, alertSrv, zabbixAPIService, ZabbixCachingProxy, QueryProcessor, DataProcessingService) { + constructor(instanceSettings, $q, templateSrv, alertSrv, zabbixAPIService, ZabbixCachingProxy, QueryProcessor) { // General data source settings this.name = instanceSettings.name; @@ -45,7 +45,6 @@ export class ZabbixAPIDatasource { this.q = $q; this.templateSrv = templateSrv; this.alertSrv = alertSrv; - this.DataProcessingService = DataProcessingService; console.log(this.zabbixCache); } @@ -159,7 +158,7 @@ export class ZabbixAPIDatasource { timeseries_data = _.map(timeseries_data, function (timeseries) { // Filter only transform functions - var transformFunctions = bindFunctionDefs(target.functions, 'Transform', self.DataProcessingService); + var transformFunctions = bindFunctionDefs(target.functions, 'Transform', DataProcessor); // Metric data processing var dp = timeseries.datapoints; @@ -172,7 +171,7 @@ export class ZabbixAPIDatasource { }); // Aggregations - var aggregationFunctions = bindFunctionDefs(target.functions, 'Aggregate', self.DataProcessingService); + var aggregationFunctions = bindFunctionDefs(target.functions, 'Aggregate', DataProcessor); var dp = _.map(timeseries_data, 'datapoints'); if (aggregationFunctions.length) { for (var i = 0; i < aggregationFunctions.length; i++) { @@ -189,7 +188,7 @@ export class ZabbixAPIDatasource { } // Apply alias functions - var aliasFunctions = bindFunctionDefs(target.functions, 'Alias', self.DataProcessingService); + var aliasFunctions = bindFunctionDefs(target.functions, 'Alias', DataProcessor); for (var j = 0; j < aliasFunctions.length; j++) { _.each(timeseries_data, aliasFunctions[j]); } @@ -255,9 +254,9 @@ export class ZabbixAPIDatasource { // Series downsampling var data = _.map(timeseries_data, function(timeseries) { - var DPS = self.DataProcessingService; if (timeseries.datapoints.length > options.maxDataPoints) { - timeseries.datapoints = DPS.groupBy(options.interval, DPS.AVERAGE, timeseries.datapoints); + timeseries.datapoints = + DataProcessor.groupBy(options.interval, DataProcessor.AVERAGE, timeseries.datapoints); } return timeseries; }); @@ -409,7 +408,7 @@ export class ZabbixAPIDatasource { } -function bindFunctionDefs(functionDefs, category, DataProcessingService) { +function bindFunctionDefs(functionDefs, category, DataProcessor) { 'use strict'; var aggregationFunctions = _.map(metricFunctions.getCategories()[category], 'name'); var aggFuncDefs = _.filter(functionDefs, function(func) { @@ -418,7 +417,7 @@ function bindFunctionDefs(functionDefs, category, DataProcessingService) { return _.map(aggFuncDefs, function(func) { var funcInstance = metricFunctions.createFuncInstance(func.def, func.params); - return funcInstance.bindFunction(DataProcessingService.metricFunctions); + return funcInstance.bindFunction(DataProcessor.metricFunctions); }); } diff --git a/src/datasource-zabbix/queryProcessor.service.js b/src/datasource-zabbix/queryProcessor.service.js index 4794bef..d447e55 100644 --- a/src/datasource-zabbix/queryProcessor.service.js +++ b/src/datasource-zabbix/queryProcessor.service.js @@ -1,363 +1,361 @@ -define([ - 'angular', - 'lodash', - './utils' -], -function (angular, _, utils) { - 'use strict'; +import angular from 'angular'; +import _ from 'lodash'; +import * as utils from './utils'; - var module = angular.module('grafana.services'); - - module.factory('QueryProcessor', function($q) { - - function QueryProcessor(zabbixCacheInstance) { - var self = this; +/** @ngInject */ +angular.module('grafana.services').factory('QueryProcessor', function($q) { + class QueryProcessor { + constructor(zabbixCacheInstance) { this.cache = zabbixCacheInstance; + this.$q = $q; + } - /** - * Build query in asynchronous manner - */ - this.build = function (groupFilter, hostFilter, appFilter, itemFilter) { - if (this.cache._initialized) { - return $q.when(self.buildFromCache(groupFilter, hostFilter, appFilter, itemFilter)); - } else { - return this.cache.refresh().then(function() { - return self.buildFromCache(groupFilter, hostFilter, appFilter, itemFilter); - }); - } - }; - - /** - * Build trigger query in asynchronous manner - */ - this.buildTriggerQuery = function (groupFilter, hostFilter, appFilter) { - if (this.cache._initialized) { - return $q.when(self.buildTriggerQueryFromCache(groupFilter, hostFilter, appFilter)); - } else { - return this.cache.refresh().then(function() { - return self.buildTriggerQueryFromCache(groupFilter, hostFilter, appFilter); - }); - } - }; - - this.filterGroups = function(groupFilter) { - return this.cache.getGroups().then(function(groupList) { - return groupList; + /** + * Build query in asynchronous manner + */ + build(groupFilter, hostFilter, appFilter, itemFilter) { + var self = this; + if (this.cache._initialized) { + return this.$q.when(self.buildFromCache(groupFilter, hostFilter, appFilter, itemFilter)); + } else { + return this.cache.refresh().then(function() { + return self.buildFromCache(groupFilter, hostFilter, appFilter, itemFilter); }); - }; + } + } - /** - * Get list of host belonging to given groups. - * @return list of hosts - */ - this.filterHosts = function(groupFilter) { - return this.cache.getGroups().then(function(groups) { - groups = findByFilter(groups, groupFilter); - var hostids = _.flatten(_.map(groups, 'hosts')); - if (hostids.length) { - return self.cache.getIndexedHosts().then(function(hosts) { - return _.map(hostids, function(hostid) { - return hosts[hostid]; - }); + /** + * 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(groupFilter) { + return this.cache.getGroups().then(function(groupList) { + return groupList; + }); + } + + /** + * Get list of host belonging to given groups. + * @return list of hosts + */ + filterHosts(groupFilter) { + var self = this; + return this.cache.getGroups().then(function(groups) { + groups = findByFilter(groups, groupFilter); + var hostids = _.flatten(_.map(groups, 'hosts')); + if (hostids.length) { + return self.cache.getIndexedHosts().then(function(hosts) { + return _.map(hostids, function(hostid) { + return hosts[hostid]; }); - } else { - return []; - } - }); - }; + }); + } else { + return []; + } + }); + } - /** - * Get list of applications belonging to given groups and hosts. - * @return list of applications belonging to given hosts - */ - this.filterApplications = function(groupFilter, hostFilter) { - var promises = [ - this.filterHosts(groupFilter), - this.cache.getApplications() - ]; + /** + * Get list of applications belonging to given groups and hosts. + * @return list of applications belonging to given hosts + */ + filterApplications(groupFilter, hostFilter) { + var promises = [ + this.filterHosts(groupFilter), + this.cache.getApplications() + ]; - return $q.all(promises).then(function(results) { - var hostList = results[0]; - var applicationList = results[1]; + return this.$q.all(promises).then(function(results) { + var hostList = results[0]; + var applicationList = results[1]; - var hosts = findByFilter(hostList, hostFilter); + var hosts = findByFilter(hostList, hostFilter); + if (hosts) { + var hostsids = _.map(hosts, 'hostid'); + return _.filter(applicationList, function (appObj) { + return _.intersection(hostsids, appObj.hosts).length; + }); + } else { + return []; + } + }); + } + + filterItems(groupFilter, hostFilter, appFilter, itemType, showDisabledItems) { + var hosts; + var apps; + var items; + + var promises = [ + this.filterHosts(groupFilter), + this.filterApplications(groupFilter, hostFilter), + this.cache.getIndexedHosts(), + this.cache.getIndexedApplications() + ]; + + return this.$q.all(promises).then(function(results) { + var hostList = results[0]; + var applicationList = results[1]; + var idx_hosts = results[2]; + var idx_apps = results[3]; + + // Filter hosts + hosts = findByFilter(hostList, hostFilter); + idx_hosts = getFromIndex(idx_hosts, _.map(hosts, 'hostid')); + + // Filter applications + if (appFilter === "") { + // Get all items + apps = undefined; if (hosts) { - var hostsids = _.map(hosts, 'hostid'); - return _.filter(applicationList, function (appObj) { - return _.intersection(hostsids, appObj.hosts).length; - }); - } else { - return []; - } - }); - }; - - this.filterItems = function (groupFilter, hostFilter, appFilter, itemType, showDisabledItems) { - var hosts; - var apps; - var items; - - var promises = [ - this.filterHosts(groupFilter), - this.filterApplications(groupFilter, hostFilter), - this.cache.getIndexedHosts(), - this.cache.getIndexedApplications() - ]; - - return $q.all(promises).then(function(results) { - var hostList = results[0]; - var applicationList = results[1]; - var idx_hosts = results[2]; - var idx_apps = results[3]; - - // Filter hosts - hosts = findByFilter(hostList, hostFilter); - idx_hosts = getFromIndex(idx_hosts, _.map(hosts, 'hostid')); - - // Filter applications - if (appFilter === "") { - // Get all items - apps = undefined; - if (hosts) { - // Get all items in given hosts - items = _.flatten(_.map(idx_hosts, function(host) { - return _.values(host.idx_items); - }), true); - } - } else { - apps = findByFilter(applicationList, appFilter); - } - - if (apps) { - // Get ids for finded applications - var appids = _.flatten(_.map(apps, 'applicationids')); - appids = _.flatten(_.map(_.map(hosts, 'applications'), function(apps) { - return _.intersection(apps, appids); - })); - - // For each finded host get list of items in finded applications + // Get all items in given hosts items = _.flatten(_.map(idx_hosts, function(host) { - var host_apps = _.intersection(appids, host.applications); - var host_itemids = _.flatten(_.map(getFromIndex(idx_apps, host_apps), 'itemids')); - return _.values(getFromIndex(host.idx_items, host_itemids)); + return _.values(host.idx_items); }), true); } - - if (!showDisabledItems) { - items = _.filter(items, {'status': '0'}); - } - - return items; - }); - }; - - /** - * Build query - convert target filters to array of Zabbix items - */ - this.buildFromCache = function (groupFilter, hostFilter, appFilter, itemFilter) { - return this.filterItems(groupFilter, hostFilter, appFilter).then(function(items) { - if (items.length) { - if (utils.isRegex(itemFilter)) { - return findByFilter(items, itemFilter); - } else { - return _.filter(items, {'name': itemFilter}); - } - } else { - return []; - } - }); - }; - - /** - * Build query - convert target filters to array of Zabbix items - */ - this.buildTriggerQueryFromCache = function (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.filterHosts(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.filterApplications(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 $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, 'applicationids')); - } - 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: [[, ], ...] - * } - */ - this.convertHistory = function(history, addHostName, convertPointCallback) { - /** - * Response should be in the format: - * data: [ - * { - * target: "Metric name", - * datapoints: [[, ], ...] - * }, ... - * ] - */ - - // Group history by itemid - var grouped_history = _.groupBy(history, 'itemid'); - - return _.map(grouped_history, function(hist, itemid) { - var item = self.cache.getItem(itemid); - var alias = item.name; - if (addHostName) { - var host = self.cache.getHost(item.hostid); - alias = host.name + ": " + alias; - } - return { - target: alias, - datapoints: _.map(hist, convertPointCallback) - }; - }); - }; - - this.handleHistory = function(history, addHostName) { - return this.convertHistory(history, addHostName, convertHistoryPoint); - }; - - this.handleTrends = function(history, addHostName, valueType) { - var convertPointCallback = _.partial(convertTrendPoint, valueType); - return this.convertHistory(history, addHostName, convertPointCallback); - }; - - this.handleSLAResponse = function (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] - ] - }; + apps = findByFilter(applicationList, appFilter); } - }; + + if (apps) { + // Get ids for finded applications + var appids = _.flatten(_.map(apps, 'applicationids')); + appids = _.flatten(_.map(_.map(hosts, 'applications'), function(apps) { + return _.intersection(apps, appids); + })); + + // For each finded host get list of items in finded applications + items = _.flatten(_.map(idx_hosts, function(host) { + var host_apps = _.intersection(appids, host.applications); + var host_itemids = _.flatten(_.map(getFromIndex(idx_apps, host_apps), 'itemids')); + return _.values(getFromIndex(host.idx_items, host_itemids)); + }), true); + } + + if (!showDisabledItems) { + items = _.filter(items, {'status': '0'}); + } + + return items; + }); } - return QueryProcessor; - }); + /** + * Build query - convert target filters to array of Zabbix items + */ + buildFromCache(groupFilter, hostFilter, appFilter, itemFilter) { + return this.filterItems(groupFilter, hostFilter, appFilter).then(function(items) { + if (items.length) { + if (utils.isRegex(itemFilter)) { + return findByFilter(items, itemFilter); + } else { + return _.filter(items, {'name': itemFilter}); + } + } else { + return []; + } + }); + } - /** - * 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; + /** + * 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.filterHosts(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.filterApplications(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, 'applicationids')); + } + 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: [[, ], ...] + * } + */ + convertHistory(history, addHostName, convertPointCallback) { + /** + * Response should be in the format: + * data: [ + * { + * target: "Metric name", + * datapoints: [[, ], ...] + * }, ... + * ] + */ + var self = this; + + // Group history by itemid + var grouped_history = _.groupBy(history, 'itemid'); + + return _.map(grouped_history, function(hist, itemid) { + var item = self.cache.getItem(itemid); + var alias = item.name; + if (addHostName) { + var host = self.cache.getHost(item.hostid); + alias = host.name + ": " + alias; + } + return { + target: alias, + datapoints: _.map(hist, convertPointCallback) + }; + }); + } + + handleHistory(history, addHostName) { + return this.convertHistory(history, addHostName, convertHistoryPoint); + } + + handleTrends(history, addHostName, valueType) { + var convertPointCallback = _.partial(convertTrendPoint, valueType); + return this.convertHistory(history, 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] + ] + }; + } } } - 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 getFromIndex(index, objids) { - return _.map(objids, function(id) { - return index[id]; - }); - } - - 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 - ]; - } - + 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; + } +} + +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 getFromIndex(index, objids) { + return _.map(objids, function(id) { + return index[id]; + }); +} + +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 + ]; +} diff --git a/src/datasource-zabbix/zabbixCache.service.js b/src/datasource-zabbix/zabbixCache.service.js index 4369d47..65a00cf 100644 --- a/src/datasource-zabbix/zabbixCache.service.js +++ b/src/datasource-zabbix/zabbixCache.service.js @@ -1,27 +1,25 @@ -define([ - 'angular', - 'lodash', - './utils' -], -function (angular, _, utils) { - 'use strict'; +import angular from 'angular'; +import _ from 'lodash'; +import * as utils from './utils'; - var module = angular.module('grafana.services'); +// Use factory() instead service() for multiple datasources support. +// Each datasource instance must initialize its own cache. - // Use factory() instead service() for multiple datasources support. - // Each datasource instance must initialize its own cache. - module.factory('ZabbixCachingProxy', function($q, $interval) { +/** @ngInject */ +angular.module('grafana.services').factory('ZabbixCachingProxy', function($q, $interval) { - function ZabbixCachingProxy(zabbixAPI, ttl) { + 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._hostsExtend = undefined; this.storage = { history: {}, trends: {} @@ -34,7 +32,7 @@ function (angular, _, utils) { this.historyPromises = {}; // Wrap _refresh() method to call it once. - this.refresh = callOnce(p._refresh, this.refreshPromise); + this.refresh = callOnce(this._refresh, this.refreshPromise); // Update cache periodically $interval(_.bind(this.refresh, this), this.ttl); @@ -44,9 +42,7 @@ function (angular, _, utils) { this.historyPromises); } - var p = ZabbixCachingProxy.prototype; - - p._refresh = function() { + _refresh() { var self = this; var promises = [ this.zabbixAPI.getGroups(), @@ -56,7 +52,7 @@ function (angular, _, utils) { this.zabbixAPI.getHostsExtend() ]; - return $q.all(promises).then(function(results) { + return this.$q.all(promises).then(function(results) { if (results.length) { self._groups = convertGroups(results[0]); self._hosts = convertHosts(results[1]); @@ -67,94 +63,76 @@ function (angular, _, utils) { } self._initialized = true; }); - }; + } - p.getGroups = function() { + getGroups() { var self = this; if (this._groups) { - return $q.when(self._groups); + return this.$q.when(self._groups); } else { return this.refresh().then(function() { return self._groups; }); } - }; + } - p.getHosts = function() { + getHosts() { var self = this; if (this._hosts) { - return $q.when(self._hosts); + return this.$q.when(self._hosts); } else { return this.refresh().then(function() { return self._hosts; }); } - }; + } - p.getIndexedHosts = function() { + getIndexedHosts() { var self = this; if (this._idx_hosts) { - return $q.when(self._idx_hosts); + return this.$q.when(self._idx_hosts); } else { return this.refresh().then(function() { return self._idx_hosts; }); } - }; + } - p.getIndexedApplications = function() { + getIndexedApplications() { var self = this; if (this._idx_apps) { - return $q.when(self._idx_apps); + return this.$q.when(self._idx_apps); } else { return this.refresh().then(function() { return self._idx_apps; }); } - }; + } - p.getApplications = function() { + getApplications() { var self = this; if (this._applications) { - return $q.when(self._applications); + return this.$q.when(self._applications); } else { return this.refresh().then(function() { return self._applications; }); } - }; + } - p.getItems = function(type) { + getItems(type) { var self = this; if (this._items) { - return $q.when(filterItems(self._items, type)); + return this.$q.when(filterItems(self._items, type)); } else { return this.refresh().then(function() { return filterItems(self._items, type); }); } - }; - - function filterItems(items, type) { - switch (type) { - case 'num': - return _.filter(items, function(item) { - return (item.value_type === '0' || - item.value_type === '3'); - }); - case 'text': - return _.filter(items, function(item) { - return (item.value_type === '1' || - item.value_type === '2' || - item.value_type === '4'); - }); - default: - return items; - } } - p.getHistoryFromCache = function(items, time_from, time_till) { - var deferred = $q.defer(); + getHistoryFromCache(items, time_from, time_till) { + var deferred = this.$q.defer(); var historyStorage = this.storage.history; var full_history; var expired = _.filter(_.indexBy(items, 'itemid'), function(item, itemid) { @@ -182,161 +160,177 @@ function (angular, _, utils) { deferred.resolve(_.flatten(full_history, true)); } return deferred.promise; - }; + } - p.getHistoryFromAPI = function(items, time_from, time_till) { + getHistoryFromAPI(items, time_from, time_till) { return this.zabbixAPI.getHistory(items, time_from, time_till); - }; + } - p.getHost = function(hostid) { + getHost(hostid) { return _.find(this._hosts, {'hostid': hostid}); - }; + } - p.getItem = function(itemid) { + getItem(itemid) { return _.find(this._items, {'itemid': itemid}); - }; - - /** - * Convert host.get response to cache format - * host.groups - array of group ids - */ - function convertHosts(hosts) { - return _.forEach(hosts, function(host) { - host.groups = _.map(host.groups, 'groupid'); - return host; - }); } + } - function convertGroups(groups) { - return _.forEach(groups, function(group) { - group.hosts = _.map(group.hosts, 'hostid'); - return group; - }); - } + function callHistoryOnce(func, promiseKeeper) { + return function() { + var itemids = _.map(arguments[0], 'itemid'); + var stamp = itemids.join() + arguments[1] + arguments[2]; + var hash = stamp.getHash(); - /** - * Group Zabbix applications by name - * host.hosts - array of host ids - */ - function convertApplications(applications) { - return _.map(_.groupBy(applications, 'name'), function(value, key) { - //console.log(value); - // Hack for supporting different apis (2.2 vs 2.4 vs 3.0) - var hostField = 'host'; - if (value[0] && value[0]['hosts']) { - // For Zabbix 2.2 - hostField = 'hosts'; - } - - return { - name: key, - applicationids: _.map(value, 'applicationid'), - itemids: _.uniq(_.map(_.flatten(value, 'items'), 'itemid')), - hosts: _.uniq(_.map(_.flatten(value, hostField), 'hostid')) - }; - }); - } - - function indexHosts(hosts) { - return _.indexBy(_.map(hosts, function(host) { - - // Expand item names - host.items = _.forEach(host.items, function(item) { - item.item = item.name; - item.name = utils.expandItemName(item.item, item.key_); - return item; + var deferred = $q.defer(); + if (!promiseKeeper[hash]) { + promiseKeeper[hash] = deferred.promise; + func.apply(this, arguments).then(function(result) { + deferred.resolve(result); + promiseKeeper[hash] = null; }); - - host.applications = _.map(host.applications, 'applicationid'); - host.idx_items = indexItems(host.items); - host.items = _.map(host.items, 'itemid'); - return host; - }), 'hostid'); - } - - function indexApps(applications) { - return _.indexBy(_.map(applications, function(app) { - return { - name: app.name, - applicationid: app.applicationid, - host: _.first(_.map(app.hosts, 'hostid')), - itemids: _.map(app.items, 'itemid') - }; - }), 'applicationid'); - } - - function indexItems(items) { - return _.indexBy(_.map(items, function(item) { - return item; - }), 'itemid'); - } - - /** - * Convert item.get response to cache format - * item.applications - array of application ids - * item.item - original item name returned by api (ie "CPU $2 time") - * item.name - expanded name (ie "CPU system time") - */ - function convertItems(items) { - return _.forEach(items, function(item) { - item.applications = _.map(item.applications, 'applicationid'); - item.item = item.name; - item.name = utils.expandItemName(item.item, item.key_); - return item; - }); - } - - String.prototype.getHash = function() { - var hash = 0, i, chr, len; - if (this.length === 0) { - return hash; + } else { + return promiseKeeper[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; + 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 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; }); + +/** + * Convert host.get response to cache format + * host.groups - array of group ids + */ +function convertHosts(hosts) { + return _.forEach(hosts, function(host) { + host.groups = _.map(host.groups, 'groupid'); + return host; + }); +} + +function convertGroups(groups) { + return _.forEach(groups, function(group) { + group.hosts = _.map(group.hosts, 'hostid'); + return group; + }); +} + +/** + * Group Zabbix applications by name + * host.hosts - array of host ids + */ +function convertApplications(applications) { + return _.map(_.groupBy(applications, 'name'), function(value, key) { + //console.log(value); + // Hack for supporting different apis (2.2 vs 2.4 vs 3.0) + var hostField = 'host'; + if (value[0] && value[0]['hosts']) { + // For Zabbix 2.2 + hostField = 'hosts'; + } + + return { + name: key, + applicationids: _.map(value, 'applicationid'), + itemids: _.uniq(_.map(_.flatten(value, 'items'), 'itemid')), + hosts: _.uniq(_.map(_.flatten(value, hostField), 'hostid')) + }; + }); +} + +function indexHosts(hosts) { + return _.indexBy(_.map(hosts, function(host) { + + // Expand item names + host.items = _.forEach(host.items, function(item) { + item.item = item.name; + item.name = utils.expandItemName(item.item, item.key_); + return item; + }); + + host.applications = _.map(host.applications, 'applicationid'); + host.idx_items = indexItems(host.items); + host.items = _.map(host.items, 'itemid'); + return host; + }), 'hostid'); +} + +function indexApps(applications) { + return _.indexBy(_.map(applications, function(app) { + return { + name: app.name, + applicationid: app.applicationid, + host: _.first(_.map(app.hosts, 'hostid')), + itemids: _.map(app.items, 'itemid') + }; + }), 'applicationid'); +} + +function indexItems(items) { + return _.indexBy(_.map(items, function(item) { + return item; + }), 'itemid'); +} + +/** + * Convert item.get response to cache format + * item.applications - array of application ids + * item.item - original item name returned by api (ie "CPU $2 time") + * item.name - expanded name (ie "CPU system time") + */ +function convertItems(items) { + return _.forEach(items, function(item) { + item.applications = _.map(item.applications, 'applicationid'); + item.item = item.name; + item.name = utils.expandItemName(item.item, item.key_); + return item; + }); +} + +function filterItems(items, type) { + switch (type) { + case 'num': + return _.filter(items, function(item) { + return (item.value_type === '0' || + item.value_type === '3'); + }); + case 'text': + return _.filter(items, function(item) { + return (item.value_type === '1' || + item.value_type === '2' || + item.value_type === '4'); + }); + default: + return items; + } +} + +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; +};