refactor: proxy and cache WIP
This commit is contained in:
108
src/datasource-zabbix/zabbix/proxy/cachingProxy.js
Normal file
108
src/datasource-zabbix/zabbix/proxy/cachingProxy.js
Normal file
@@ -0,0 +1,108 @@
|
||||
/**
|
||||
* This module allows to deduplicate function calls with the same params and
|
||||
* cache result of function call.
|
||||
*/
|
||||
|
||||
export class CachingProxy {
|
||||
|
||||
constructor(cacheOptions) {
|
||||
this.cacheEnabled = cacheOptions.enabled;
|
||||
this.ttl = cacheOptions.ttl || 600000; // 10 minutes by default
|
||||
|
||||
// Internal objects for data storing
|
||||
this.cache = {};
|
||||
this.promises = {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that result is present in the cache and is up to date or send request otherwise.
|
||||
*/
|
||||
cacheRequest(func, funcName, funcScope) {
|
||||
return cacheRequest(func, funcName, funcScope, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap request to prevent multiple calls with same params when request is waiting for response.
|
||||
*/
|
||||
proxyfy(func, funcName, funcScope) {
|
||||
if (!this.promises[funcName]) {
|
||||
this.promises[funcName] = {};
|
||||
}
|
||||
const promiseKeeper = this.promises[funcName];
|
||||
return callOnce(func, promiseKeeper, funcScope);
|
||||
}
|
||||
|
||||
proxyfyWithCache(func, funcName, funcScope) {
|
||||
let proxyfied = this.proxyfy(func, funcName, funcScope);
|
||||
return this.cacheRequest(proxyfied, funcName, funcScope);
|
||||
}
|
||||
|
||||
_isExpired(cacheObject) {
|
||||
if (cacheObject) {
|
||||
let object_age = Date.now() - cacheObject.timestamp;
|
||||
return !(cacheObject.timestamp && object_age < this.ttl);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap request to prevent multiple calls
|
||||
* with same params when waiting for result.
|
||||
*/
|
||||
function callOnce(func, promiseKeeper, funcScope) {
|
||||
return function() {
|
||||
var hash = getRequestHash(arguments);
|
||||
if (!promiseKeeper[hash]) {
|
||||
promiseKeeper[hash] = Promise.resolve(
|
||||
func.apply(funcScope, arguments)
|
||||
.then(result => {
|
||||
promiseKeeper[hash] = null;
|
||||
return result;
|
||||
})
|
||||
);
|
||||
}
|
||||
return promiseKeeper[hash];
|
||||
};
|
||||
}
|
||||
|
||||
function cacheRequest(func, funcName, funcScope, self) {
|
||||
return function() {
|
||||
if (!self.cache[funcName]) {
|
||||
self.cache[funcName] = {};
|
||||
}
|
||||
|
||||
let cacheObject = self.cache[funcName];
|
||||
let hash = getRequestHash(arguments);
|
||||
if (self.cacheEnabled && !self._isExpired(cacheObject[hash])) {
|
||||
return Promise.resolve(cacheObject[hash].value);
|
||||
} else {
|
||||
return func.apply(funcScope, arguments)
|
||||
.then(result => {
|
||||
cacheObject[hash] = {
|
||||
value: result,
|
||||
timestamp: Date.now()
|
||||
};
|
||||
return result;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function getRequestHash(args) {
|
||||
const argsJson = JSON.stringify(args);
|
||||
return argsJson.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;
|
||||
};
|
||||
@@ -1,231 +0,0 @@
|
||||
import _ from 'lodash';
|
||||
|
||||
export class ZabbixCachingProxy {
|
||||
constructor(zabbixAPI, zabbixDBConnector, cacheOptions) {
|
||||
this.zabbixAPI = zabbixAPI;
|
||||
this.dbConnector = zabbixDBConnector;
|
||||
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: {},
|
||||
macros: {},
|
||||
globalMacros: {},
|
||||
itServices: {}
|
||||
};
|
||||
|
||||
this.historyPromises = {};
|
||||
|
||||
// Don't run duplicated history requests
|
||||
this.getHistory = callAPIRequestOnce(_.bind(this.zabbixAPI.getHistory, this.zabbixAPI),
|
||||
this.historyPromises, getHistoryRequestHash);
|
||||
|
||||
if (this.dbConnector) {
|
||||
this.getHistoryDB = callAPIRequestOnce(_.bind(this.dbConnector.getHistory, this.dbConnector),
|
||||
this.historyPromises, getDBQueryHash);
|
||||
this.getTrendsDB = callAPIRequestOnce(_.bind(this.dbConnector.getTrends, this.dbConnector),
|
||||
this.historyPromises, getDBQueryHash);
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
this.itemByIdPromises = {};
|
||||
this.getItemsByIdOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getItemsByIDs, this.zabbixAPI),
|
||||
this.itemPromises, getRequestHash);
|
||||
|
||||
this.itServicesPromises = {};
|
||||
this.getITServicesOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getITService, this.zabbixAPI),
|
||||
this.itServicesPromises, getRequestHash);
|
||||
|
||||
this.macroPromises = {};
|
||||
this.getMacrosOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getMacros, this.zabbixAPI),
|
||||
this.macroPromises, getRequestHash);
|
||||
|
||||
this.globalMacroPromises = {};
|
||||
this.getGlobalMacrosOnce = callAPIRequestOnce(_.bind(this.zabbixAPI.getGlobalMacros, this.zabbixAPI),
|
||||
this.globalMacroPromises, 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);
|
||||
}
|
||||
|
||||
getItemsByIDs(itemids) {
|
||||
let params = [itemids];
|
||||
return this.proxyRequest(this.getItemsByIdOnce, params, this.cache.items);
|
||||
}
|
||||
|
||||
getITServices() {
|
||||
return this.proxyRequest(this.getITServicesOnce, [], this.cache.itServices);
|
||||
}
|
||||
|
||||
getMacros(hostids) {
|
||||
// Merge global macros and host macros
|
||||
let promises = [
|
||||
this.proxyRequest(this.getMacrosOnce, [hostids], this.cache.macros),
|
||||
this.proxyRequest(this.getGlobalMacrosOnce, [], this.cache.globalMacros)
|
||||
];
|
||||
|
||||
return Promise.all(promises).then(_.flatten);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
function getDBQueryHash(args) {
|
||||
let itemids = _.map(args[0], 'itemid');
|
||||
let consolidateBy = args[3].consolidateBy;
|
||||
let intervalMs = args[3].intervalMs;
|
||||
let stamp = itemids.join() + args[1] + args[2] + consolidateBy + intervalMs;
|
||||
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;}
|
||||
Reference in New Issue
Block a user