Refactor DB connector
This commit is contained in:
@@ -1,180 +0,0 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
import { getDataSourceSrv } from '@grafana/runtime';
|
|
||||||
import responseHandler from "../../responseHandler";
|
|
||||||
|
|
||||||
export const DEFAULT_QUERY_LIMIT = 10000;
|
|
||||||
export const HISTORY_TO_TABLE_MAP = {
|
|
||||||
'0': 'history',
|
|
||||||
'1': 'history_str',
|
|
||||||
'2': 'history_log',
|
|
||||||
'3': 'history_uint',
|
|
||||||
'4': 'history_text'
|
|
||||||
};
|
|
||||||
|
|
||||||
export const TREND_TO_TABLE_MAP = {
|
|
||||||
'0': 'trends',
|
|
||||||
'3': 'trends_uint'
|
|
||||||
};
|
|
||||||
|
|
||||||
export const consolidateByFunc = {
|
|
||||||
'avg': 'AVG',
|
|
||||||
'min': 'MIN',
|
|
||||||
'max': 'MAX',
|
|
||||||
'sum': 'SUM',
|
|
||||||
'count': 'COUNT'
|
|
||||||
};
|
|
||||||
|
|
||||||
export const consolidateByTrendColumns = {
|
|
||||||
'avg': 'value_avg',
|
|
||||||
'min': 'value_min',
|
|
||||||
'max': 'value_max',
|
|
||||||
'sum': 'num*value_avg' // sum of sums inside the one-hour trend period
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Base class for external history database connectors. Subclasses should implement `getHistory()`, `getTrends()` and
|
|
||||||
* `testDataSource()` methods, which describe how to fetch data from source other than Zabbix API.
|
|
||||||
*/
|
|
||||||
export class DBConnector {
|
|
||||||
constructor(options) {
|
|
||||||
this.datasourceId = options.datasourceId;
|
|
||||||
this.datasourceName = options.datasourceName;
|
|
||||||
this.datasourceTypeId = null;
|
|
||||||
this.datasourceTypeName = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
static loadDatasource(dsId, dsName) {
|
|
||||||
if (!dsName && dsId !== undefined) {
|
|
||||||
let ds = _.find(getDataSourceSrv().getAll(), {'id': dsId});
|
|
||||||
if (!ds) {
|
|
||||||
return Promise.reject(`Data Source with ID ${dsId} not found`);
|
|
||||||
}
|
|
||||||
dsName = ds.name;
|
|
||||||
}
|
|
||||||
if (dsName) {
|
|
||||||
return getDataSourceSrv().loadDatasource(dsName);
|
|
||||||
} else {
|
|
||||||
return Promise.reject(`Data Source name should be specified`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
loadDBDataSource() {
|
|
||||||
return DBConnector.loadDatasource(this.datasourceId, this.datasourceName)
|
|
||||||
.then(ds => {
|
|
||||||
this.datasourceTypeId = ds.meta.id;
|
|
||||||
this.datasourceTypeName = ds.meta.name;
|
|
||||||
if (!this.datasourceName) {
|
|
||||||
this.datasourceName = ds.name;
|
|
||||||
}
|
|
||||||
if (!this.datasourceId) {
|
|
||||||
this.datasourceId = ds.id;
|
|
||||||
}
|
|
||||||
return ds;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Send test request to datasource in order to ensure it's working.
|
|
||||||
*/
|
|
||||||
testDataSource() {
|
|
||||||
throw new ZabbixNotImplemented('testDataSource()');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get history data from external sources.
|
|
||||||
*/
|
|
||||||
getHistory() {
|
|
||||||
throw new ZabbixNotImplemented('getHistory()');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get trends data from external sources.
|
|
||||||
*/
|
|
||||||
getTrends() {
|
|
||||||
throw new ZabbixNotImplemented('getTrends()');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Define Zabbix DB Connector exception type for non-implemented methods
|
|
||||||
export class ZabbixNotImplemented {
|
|
||||||
constructor(methodName) {
|
|
||||||
this.code = null;
|
|
||||||
this.name = 'ZabbixNotImplemented';
|
|
||||||
this.message = `Zabbix DB Connector Error: method ${methodName || ''} should be implemented in subclass of DBConnector`;
|
|
||||||
}
|
|
||||||
|
|
||||||
toString() {
|
|
||||||
return this.message;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function handleDBDataSourceResponse(response, items) {
|
|
||||||
const series = responseHandler.dataResponseToTimeSeries(response, items);
|
|
||||||
// return convertGrafanaTSResponse(series, items, addHostName);
|
|
||||||
return series;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts time series returned by the data source into format that Grafana expects
|
|
||||||
* time_series is Array of series:
|
|
||||||
* ```
|
|
||||||
* [{
|
|
||||||
* name: string,
|
|
||||||
* points: Array<[value: number, timestamp: number]>
|
|
||||||
* }]
|
|
||||||
* ```
|
|
||||||
*/
|
|
||||||
export function convertGrafanaTSResponse(time_series, items, addHostName) {
|
|
||||||
if (time_series.length === 0) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
//uniqBy is needed to deduplicate
|
|
||||||
const hosts = _.uniqBy(_.flatten(_.map(items, 'hosts')), 'hostid');
|
|
||||||
let grafanaSeries = _.map(_.compact(time_series), series => {
|
|
||||||
const itemid = series.name;
|
|
||||||
const item = _.find(items, {'itemid': itemid});
|
|
||||||
let alias = item.name;
|
|
||||||
|
|
||||||
// Add scopedVars for using in alias functions
|
|
||||||
const scopedVars = {
|
|
||||||
'__zbx_item': { value: item.name },
|
|
||||||
'__zbx_item_name': { value: item.name },
|
|
||||||
'__zbx_item_key': { value: item.key_ },
|
|
||||||
'__zbx_item_interval': { value: item.delay },
|
|
||||||
};
|
|
||||||
|
|
||||||
if (_.keys(hosts).length > 0) {
|
|
||||||
const host = _.find(hosts, {'hostid': item.hostid});
|
|
||||||
scopedVars['__zbx_host'] = { value: host.host };
|
|
||||||
scopedVars['__zbx_host_name'] = { value: host.name };
|
|
||||||
|
|
||||||
// Only add host when multiple hosts selected
|
|
||||||
if (_.keys(hosts).length > 1 && addHostName) {
|
|
||||||
alias = host.name + ": " + alias;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// CachingProxy deduplicates requests and returns one time series for equal queries.
|
|
||||||
// Clone is needed to prevent changing of series object shared between all targets.
|
|
||||||
const datapoints = _.cloneDeep(series.points);
|
|
||||||
return {
|
|
||||||
target: alias,
|
|
||||||
datapoints,
|
|
||||||
scopedVars,
|
|
||||||
item
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return _.sortBy(grafanaSeries, 'target');
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaults = {
|
|
||||||
DBConnector,
|
|
||||||
DEFAULT_QUERY_LIMIT,
|
|
||||||
HISTORY_TO_TABLE_MAP,
|
|
||||||
TREND_TO_TABLE_MAP,
|
|
||||||
consolidateByFunc,
|
|
||||||
consolidateByTrendColumns
|
|
||||||
};
|
|
||||||
|
|
||||||
export default defaults;
|
|
||||||
97
src/datasource-zabbix/zabbix/connectors/dbConnector.ts
Normal file
97
src/datasource-zabbix/zabbix/connectors/dbConnector.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import _ from 'lodash';
|
||||||
|
import { getDataSourceSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
|
export const DEFAULT_QUERY_LIMIT = 10000;
|
||||||
|
|
||||||
|
export const HISTORY_TO_TABLE_MAP = {
|
||||||
|
'0': 'history',
|
||||||
|
'1': 'history_str',
|
||||||
|
'2': 'history_log',
|
||||||
|
'3': 'history_uint',
|
||||||
|
'4': 'history_text'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const TREND_TO_TABLE_MAP = {
|
||||||
|
'0': 'trends',
|
||||||
|
'3': 'trends_uint'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const consolidateByFunc = {
|
||||||
|
'avg': 'AVG',
|
||||||
|
'min': 'MIN',
|
||||||
|
'max': 'MAX',
|
||||||
|
'sum': 'SUM',
|
||||||
|
'count': 'COUNT'
|
||||||
|
};
|
||||||
|
|
||||||
|
export const consolidateByTrendColumns = {
|
||||||
|
'avg': 'value_avg',
|
||||||
|
'min': 'value_min',
|
||||||
|
'max': 'value_max',
|
||||||
|
'sum': 'num*value_avg' // sum of sums inside the one-hour trend period
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface IDBConnector {
|
||||||
|
getHistory(): any;
|
||||||
|
|
||||||
|
getTrends(): any;
|
||||||
|
|
||||||
|
testDataSource(): any;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for external history database connectors. Subclasses should implement `getHistory()`, `getTrends()` and
|
||||||
|
* `testDataSource()` methods, which describe how to fetch data from source other than Zabbix API.
|
||||||
|
*/
|
||||||
|
export class DBConnector {
|
||||||
|
protected datasourceId: any;
|
||||||
|
private datasourceName: any;
|
||||||
|
protected datasourceTypeId: any;
|
||||||
|
private datasourceTypeName: any;
|
||||||
|
|
||||||
|
constructor(options) {
|
||||||
|
this.datasourceId = options.datasourceId;
|
||||||
|
this.datasourceName = options.datasourceName;
|
||||||
|
this.datasourceTypeId = null;
|
||||||
|
this.datasourceTypeName = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static loadDatasource(dsId, dsName) {
|
||||||
|
if (!dsName && dsId !== undefined) {
|
||||||
|
const ds = _.find(getDataSourceSrv().getList(), { 'id': dsId });
|
||||||
|
if (!ds) {
|
||||||
|
return Promise.reject(`Data Source with ID ${dsId} not found`);
|
||||||
|
}
|
||||||
|
dsName = ds.name;
|
||||||
|
}
|
||||||
|
if (dsName) {
|
||||||
|
return getDataSourceSrv().get(dsName);
|
||||||
|
} else {
|
||||||
|
return Promise.reject(`Data Source name should be specified`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
loadDBDataSource() {
|
||||||
|
return DBConnector.loadDatasource(this.datasourceId, this.datasourceName)
|
||||||
|
.then(ds => {
|
||||||
|
this.datasourceTypeId = ds.meta.id;
|
||||||
|
this.datasourceTypeName = ds.meta.name;
|
||||||
|
if (!this.datasourceName) {
|
||||||
|
this.datasourceName = ds.name;
|
||||||
|
}
|
||||||
|
if (!this.datasourceId) {
|
||||||
|
this.datasourceId = ds.id;
|
||||||
|
}
|
||||||
|
return ds;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
DBConnector,
|
||||||
|
DEFAULT_QUERY_LIMIT,
|
||||||
|
HISTORY_TO_TABLE_MAP,
|
||||||
|
TREND_TO_TABLE_MAP,
|
||||||
|
consolidateByFunc,
|
||||||
|
consolidateByTrendColumns
|
||||||
|
};
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { compactQuery } from '../../../utils';
|
import { compactQuery } from '../../../utils';
|
||||||
import { DBConnector, HISTORY_TO_TABLE_MAP, consolidateByTrendColumns } from '../dbConnector';
|
import { consolidateByTrendColumns, DBConnector, HISTORY_TO_TABLE_MAP } from '../dbConnector';
|
||||||
|
|
||||||
const consolidateByFunc = {
|
const consolidateByFunc = {
|
||||||
'avg': 'MEAN',
|
'avg': 'MEAN',
|
||||||
@@ -11,6 +11,9 @@ const consolidateByFunc = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class InfluxDBConnector extends DBConnector {
|
export class InfluxDBConnector extends DBConnector {
|
||||||
|
private retentionPolicy: any;
|
||||||
|
private influxDS: any;
|
||||||
|
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
this.retentionPolicy = options.retentionPolicy;
|
this.retentionPolicy = options.retentionPolicy;
|
||||||
@@ -26,16 +29,19 @@ export class InfluxDBConnector extends DBConnector {
|
|||||||
testDataSource() {
|
testDataSource() {
|
||||||
return this.influxDS.testDatasource().then(result => {
|
return this.influxDS.testDatasource().then(result => {
|
||||||
if (result.status && result.status === 'error') {
|
if (result.status && result.status === 'error') {
|
||||||
return Promise.reject({ data: {
|
return Promise.reject({
|
||||||
message: `InfluxDB connection error: ${result.message}`
|
data: {
|
||||||
}});
|
message: `InfluxDB connection error: ${result.message}`
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getHistory(items, timeFrom, timeTill, options) {
|
getHistory(items, timeFrom, timeTill, options) {
|
||||||
let { intervalMs, consolidateBy, retentionPolicy } = options;
|
const { intervalMs, retentionPolicy } = options;
|
||||||
|
let { consolidateBy } = options;
|
||||||
const intervalSec = Math.ceil(intervalMs / 1000);
|
const intervalSec = Math.ceil(intervalMs / 1000);
|
||||||
|
|
||||||
const range = { timeFrom, timeTill };
|
const range = { timeFrom, timeTill };
|
||||||
@@ -71,9 +77,12 @@ export class InfluxDBConnector extends DBConnector {
|
|||||||
}
|
}
|
||||||
const aggregation = consolidateByFunc[aggFunction] || aggFunction;
|
const aggregation = consolidateByFunc[aggFunction] || aggFunction;
|
||||||
const where_clause = this.buildWhereClause(itemids);
|
const where_clause = this.buildWhereClause(itemids);
|
||||||
const query = `SELECT ${aggregation}("${value}") FROM ${measurement}
|
const query = `SELECT ${aggregation}("${value}")
|
||||||
WHERE ${where_clause} AND "time" >= ${timeFrom}s AND "time" <= ${timeTill}s
|
FROM ${measurement}
|
||||||
GROUP BY time(${intervalSec}s), "itemid" fill(none)`;
|
WHERE ${where_clause}
|
||||||
|
AND "time" >= ${timeFrom}s
|
||||||
|
AND "time" <= ${timeTill}s
|
||||||
|
GROUP BY time (${intervalSec}s), "itemid" fill(none)`;
|
||||||
return compactQuery(query);
|
return compactQuery(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
/**
|
|
||||||
* MySQL queries
|
|
||||||
*/
|
|
||||||
|
|
||||||
function historyQuery(itemids, table, timeFrom, timeTill, intervalSec, aggFunction) {
|
|
||||||
let query = `
|
|
||||||
SELECT CAST(itemid AS CHAR) AS metric, MIN(clock) AS time_sec, ${aggFunction}(value) AS value
|
|
||||||
FROM ${table}
|
|
||||||
WHERE itemid IN (${itemids})
|
|
||||||
AND clock > ${timeFrom} AND clock < ${timeTill}
|
|
||||||
GROUP BY (clock-${timeFrom}) DIV ${intervalSec}, metric
|
|
||||||
ORDER BY time_sec ASC
|
|
||||||
`;
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
|
|
||||||
function trendsQuery(itemids, table, timeFrom, timeTill, intervalSec, aggFunction, valueColumn) {
|
|
||||||
let query = `
|
|
||||||
SELECT CAST(itemid AS CHAR) AS metric, MIN(clock) AS time_sec, ${aggFunction}(${valueColumn}) AS value
|
|
||||||
FROM ${table}
|
|
||||||
WHERE itemid IN (${itemids})
|
|
||||||
AND clock > ${timeFrom} AND clock < ${timeTill}
|
|
||||||
GROUP BY (clock-${timeFrom}) DIV ${intervalSec}, metric
|
|
||||||
ORDER BY time_sec ASC
|
|
||||||
`;
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TEST_QUERY = `SELECT CAST(itemid AS CHAR) AS metric, clock AS time_sec, value_avg AS value FROM trends_uint LIMIT 1`;
|
|
||||||
|
|
||||||
function testQuery() {
|
|
||||||
return TEST_QUERY;
|
|
||||||
}
|
|
||||||
|
|
||||||
const mysql = {
|
|
||||||
historyQuery,
|
|
||||||
trendsQuery,
|
|
||||||
testQuery
|
|
||||||
};
|
|
||||||
|
|
||||||
export default mysql;
|
|
||||||
44
src/datasource-zabbix/zabbix/connectors/sql/mysql.ts
Normal file
44
src/datasource-zabbix/zabbix/connectors/sql/mysql.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* MySQL queries
|
||||||
|
*/
|
||||||
|
|
||||||
|
function historyQuery(itemids, table, timeFrom, timeTill, intervalSec, aggFunction) {
|
||||||
|
return `
|
||||||
|
SELECT CAST(itemid AS CHAR) AS metric, MIN(clock) AS time_sec, ${aggFunction}(value) AS value
|
||||||
|
FROM ${table}
|
||||||
|
WHERE itemid IN (${itemids})
|
||||||
|
AND clock
|
||||||
|
> ${timeFrom}
|
||||||
|
AND clock
|
||||||
|
< ${timeTill}
|
||||||
|
GROUP BY (clock-${timeFrom}) DIV ${intervalSec}, metric
|
||||||
|
ORDER BY time_sec ASC
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function trendsQuery(itemids, table, timeFrom, timeTill, intervalSec, aggFunction, valueColumn) {
|
||||||
|
return `
|
||||||
|
SELECT CAST(itemid AS CHAR) AS metric, MIN(clock) AS time_sec, ${aggFunction}(${valueColumn}) AS value
|
||||||
|
FROM ${table}
|
||||||
|
WHERE itemid IN (${itemids})
|
||||||
|
AND clock
|
||||||
|
> ${timeFrom}
|
||||||
|
AND clock
|
||||||
|
< ${timeTill}
|
||||||
|
GROUP BY (clock-${timeFrom}) DIV ${intervalSec}, metric
|
||||||
|
ORDER BY time_sec ASC
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function testQuery() {
|
||||||
|
return `SELECT CAST(itemid AS CHAR) AS metric, clock AS time_sec, value_avg AS value
|
||||||
|
FROM trends_uint LIMIT 1`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mysql = {
|
||||||
|
historyQuery,
|
||||||
|
trendsQuery,
|
||||||
|
testQuery
|
||||||
|
};
|
||||||
|
|
||||||
|
export default mysql;
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
/**
|
|
||||||
* Postgres queries
|
|
||||||
*/
|
|
||||||
|
|
||||||
const ITEMID_FORMAT = 'FM99999999999999999999';
|
|
||||||
|
|
||||||
function historyQuery(itemids, table, timeFrom, timeTill, intervalSec, aggFunction) {
|
|
||||||
let time_expression = `clock / ${intervalSec} * ${intervalSec}`;
|
|
||||||
let query = `
|
|
||||||
SELECT to_char(itemid, '${ITEMID_FORMAT}') AS metric, ${time_expression} AS time, ${aggFunction}(value) AS value
|
|
||||||
FROM ${table}
|
|
||||||
WHERE itemid IN (${itemids})
|
|
||||||
AND clock > ${timeFrom} AND clock < ${timeTill}
|
|
||||||
GROUP BY 1, 2
|
|
||||||
ORDER BY time ASC
|
|
||||||
`;
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
|
|
||||||
function trendsQuery(itemids, table, timeFrom, timeTill, intervalSec, aggFunction, valueColumn) {
|
|
||||||
let time_expression = `clock / ${intervalSec} * ${intervalSec}`;
|
|
||||||
let query = `
|
|
||||||
SELECT to_char(itemid, '${ITEMID_FORMAT}') AS metric, ${time_expression} AS time, ${aggFunction}(${valueColumn}) AS value
|
|
||||||
FROM ${table}
|
|
||||||
WHERE itemid IN (${itemids})
|
|
||||||
AND clock > ${timeFrom} AND clock < ${timeTill}
|
|
||||||
GROUP BY 1, 2
|
|
||||||
ORDER BY time ASC
|
|
||||||
`;
|
|
||||||
return query;
|
|
||||||
}
|
|
||||||
|
|
||||||
const TEST_QUERY = `
|
|
||||||
SELECT to_char(itemid, '${ITEMID_FORMAT}') AS metric, clock AS time, value_avg AS value
|
|
||||||
FROM trends_uint LIMIT 1
|
|
||||||
`;
|
|
||||||
|
|
||||||
function testQuery() {
|
|
||||||
return TEST_QUERY;
|
|
||||||
}
|
|
||||||
|
|
||||||
const postgres = {
|
|
||||||
historyQuery,
|
|
||||||
trendsQuery,
|
|
||||||
testQuery
|
|
||||||
};
|
|
||||||
|
|
||||||
export default postgres;
|
|
||||||
52
src/datasource-zabbix/zabbix/connectors/sql/postgres.ts
Normal file
52
src/datasource-zabbix/zabbix/connectors/sql/postgres.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/**
|
||||||
|
* Postgres queries
|
||||||
|
*/
|
||||||
|
|
||||||
|
const ITEMID_FORMAT = 'FM99999999999999999999';
|
||||||
|
|
||||||
|
function historyQuery(itemids, table, timeFrom, timeTill, intervalSec, aggFunction) {
|
||||||
|
const time_expression = `clock / ${intervalSec} * ${intervalSec}`;
|
||||||
|
return `
|
||||||
|
SELECT to_char(itemid, '${ITEMID_FORMAT}') AS metric, ${time_expression} AS time, ${aggFunction}(value) AS value
|
||||||
|
FROM ${table}
|
||||||
|
WHERE itemid IN (${itemids})
|
||||||
|
AND clock
|
||||||
|
> ${timeFrom}
|
||||||
|
AND clock
|
||||||
|
< ${timeTill}
|
||||||
|
GROUP BY 1, 2
|
||||||
|
ORDER BY time ASC
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function trendsQuery(itemids, table, timeFrom, timeTill, intervalSec, aggFunction, valueColumn) {
|
||||||
|
const time_expression = `clock / ${intervalSec} * ${intervalSec}`;
|
||||||
|
return `
|
||||||
|
SELECT to_char(itemid, '${ITEMID_FORMAT}') AS metric, ${time_expression} AS time, ${aggFunction}(${valueColumn}) AS value
|
||||||
|
FROM ${table}
|
||||||
|
WHERE itemid IN (${itemids})
|
||||||
|
AND clock
|
||||||
|
> ${timeFrom}
|
||||||
|
AND clock
|
||||||
|
< ${timeTill}
|
||||||
|
GROUP BY 1, 2
|
||||||
|
ORDER BY time ASC
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TEST_QUERY = `
|
||||||
|
SELECT to_char(itemid, '${ITEMID_FORMAT}') AS metric, clock AS time, value_avg AS value
|
||||||
|
FROM trends_uint LIMIT 1
|
||||||
|
`;
|
||||||
|
|
||||||
|
function testQuery() {
|
||||||
|
return TEST_QUERY;
|
||||||
|
}
|
||||||
|
|
||||||
|
const postgres = {
|
||||||
|
historyQuery,
|
||||||
|
trendsQuery,
|
||||||
|
testQuery
|
||||||
|
};
|
||||||
|
|
||||||
|
export default postgres;
|
||||||
@@ -11,6 +11,9 @@ const supportedDatabases = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export class SQLConnector extends DBConnector {
|
export class SQLConnector extends DBConnector {
|
||||||
|
private limit: number;
|
||||||
|
private sqlDialect: any;
|
||||||
|
|
||||||
constructor(options) {
|
constructor(options) {
|
||||||
super(options);
|
super(options);
|
||||||
|
|
||||||
@@ -35,28 +38,18 @@ export class SQLConnector extends DBConnector {
|
|||||||
* Try to invoke test query for one of Zabbix database tables.
|
* Try to invoke test query for one of Zabbix database tables.
|
||||||
*/
|
*/
|
||||||
testDataSource() {
|
testDataSource() {
|
||||||
let testQuery = this.sqlDialect.testQuery();
|
const testQuery = this.sqlDialect.testQuery();
|
||||||
return this.invokeSQLQuery(testQuery);
|
return this.invokeSQLQuery(testQuery);
|
||||||
}
|
}
|
||||||
|
|
||||||
getHistory(items, timeFrom, timeTill, options) {
|
getHistory(items, timeFrom, timeTill, options) {
|
||||||
let {intervalMs, consolidateBy} = options;
|
const { aggFunction, intervalSec } = getAggFunc(timeFrom, timeTill, options);
|
||||||
let intervalSec = Math.ceil(intervalMs / 1000);
|
|
||||||
|
|
||||||
// The interval must match the time range exactly n times, otherwise
|
|
||||||
// the resulting first and last data points will yield invalid values in the
|
|
||||||
// calculated average value in downsampleSeries - when using consolidateBy(avg)
|
|
||||||
let numOfIntervals = Math.ceil((timeTill - timeFrom) / intervalSec);
|
|
||||||
intervalSec = (timeTill - timeFrom) / numOfIntervals;
|
|
||||||
|
|
||||||
consolidateBy = consolidateBy || 'avg';
|
|
||||||
let aggFunction = dbConnector.consolidateByFunc[consolidateBy];
|
|
||||||
|
|
||||||
// Group items by value type and perform request for each value type
|
// Group items by value type and perform request for each value type
|
||||||
let grouped_items = _.groupBy(items, 'value_type');
|
const grouped_items = _.groupBy(items, 'value_type');
|
||||||
let promises = _.map(grouped_items, (items, value_type) => {
|
const promises = _.map(grouped_items, (items, value_type) => {
|
||||||
let itemids = _.map(items, 'itemid').join(', ');
|
const itemids = _.map(items, 'itemid').join(', ');
|
||||||
let table = HISTORY_TO_TABLE_MAP[value_type];
|
const table = HISTORY_TO_TABLE_MAP[value_type];
|
||||||
let query = this.sqlDialect.historyQuery(itemids, table, timeFrom, timeTill, intervalSec, aggFunction);
|
let query = this.sqlDialect.historyQuery(itemids, table, timeFrom, timeTill, intervalSec, aggFunction);
|
||||||
|
|
||||||
query = compactQuery(query);
|
query = compactQuery(query);
|
||||||
@@ -69,23 +62,14 @@ export class SQLConnector extends DBConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTrends(items, timeFrom, timeTill, options) {
|
getTrends(items, timeFrom, timeTill, options) {
|
||||||
let { intervalMs, consolidateBy } = options;
|
const { consolidateBy } = options;
|
||||||
let intervalSec = Math.ceil(intervalMs / 1000);
|
const { aggFunction, intervalSec } = getAggFunc(timeFrom, timeTill, options);
|
||||||
|
|
||||||
// The interval must match the time range exactly n times, otherwise
|
|
||||||
// the resulting first and last data points will yield invalid values in the
|
|
||||||
// calculated average value in downsampleSeries - when using consolidateBy(avg)
|
|
||||||
let numOfIntervals = Math.ceil((timeTill - timeFrom) / intervalSec);
|
|
||||||
intervalSec = (timeTill - timeFrom) / numOfIntervals;
|
|
||||||
|
|
||||||
consolidateBy = consolidateBy || 'avg';
|
|
||||||
let aggFunction = dbConnector.consolidateByFunc[consolidateBy];
|
|
||||||
|
|
||||||
// Group items by value type and perform request for each value type
|
// Group items by value type and perform request for each value type
|
||||||
let grouped_items = _.groupBy(items, 'value_type');
|
const grouped_items = _.groupBy(items, 'value_type');
|
||||||
let promises = _.map(grouped_items, (items, value_type) => {
|
const promises = _.map(grouped_items, (items, value_type) => {
|
||||||
let itemids = _.map(items, 'itemid').join(', ');
|
const itemids = _.map(items, 'itemid').join(', ');
|
||||||
let table = TREND_TO_TABLE_MAP[value_type];
|
const table = TREND_TO_TABLE_MAP[value_type];
|
||||||
let valueColumn = _.includes(['avg', 'min', 'max', 'sum'], consolidateBy) ? consolidateBy : 'avg';
|
let valueColumn = _.includes(['avg', 'min', 'max', 'sum'], consolidateBy) ? consolidateBy : 'avg';
|
||||||
valueColumn = dbConnector.consolidateByTrendColumns[valueColumn];
|
valueColumn = dbConnector.consolidateByTrendColumns[valueColumn];
|
||||||
let query = this.sqlDialect.trendsQuery(itemids, table, timeFrom, timeTill, intervalSec, aggFunction, valueColumn);
|
let query = this.sqlDialect.trendsQuery(itemids, table, timeFrom, timeTill, intervalSec, aggFunction, valueColumn);
|
||||||
@@ -100,7 +84,7 @@ export class SQLConnector extends DBConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
invokeSQLQuery(query) {
|
invokeSQLQuery(query) {
|
||||||
let queryDef = {
|
const queryDef = {
|
||||||
refId: 'A',
|
refId: 'A',
|
||||||
format: 'time_series',
|
format: 'time_series',
|
||||||
datasourceId: this.datasourceId,
|
datasourceId: this.datasourceId,
|
||||||
@@ -116,7 +100,7 @@ export class SQLConnector extends DBConnector {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
.then(response => {
|
.then(response => {
|
||||||
let results = response.data.results;
|
const results = response.data.results;
|
||||||
if (results['A']) {
|
if (results['A']) {
|
||||||
return results['A'].frames;
|
return results['A'].frames;
|
||||||
} else {
|
} else {
|
||||||
@@ -125,3 +109,19 @@ export class SQLConnector extends DBConnector {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAggFunc(timeFrom, timeTill, options) {
|
||||||
|
const { intervalMs } = options;
|
||||||
|
let { consolidateBy } = options;
|
||||||
|
let intervalSec = Math.ceil(intervalMs / 1000);
|
||||||
|
|
||||||
|
// The interval must match the time range exactly n times, otherwise
|
||||||
|
// the resulting first and last data points will yield invalid values in the
|
||||||
|
// calculated average value in downsampleSeries - when using consolidateBy(avg)
|
||||||
|
const numOfIntervals = Math.ceil((timeTill - timeFrom) / intervalSec);
|
||||||
|
intervalSec = (timeTill - timeFrom) / numOfIntervals;
|
||||||
|
|
||||||
|
consolidateBy = consolidateBy || 'avg';
|
||||||
|
const aggFunction = dbConnector.consolidateByFunc[consolidateBy];
|
||||||
|
return { aggFunction, intervalSec };
|
||||||
|
}
|
||||||
@@ -4,13 +4,12 @@ import semver from 'semver';
|
|||||||
import * as utils from '../utils';
|
import * as utils from '../utils';
|
||||||
import responseHandler from '../responseHandler';
|
import responseHandler from '../responseHandler';
|
||||||
import { CachingProxy } from './proxy/cachingProxy';
|
import { CachingProxy } from './proxy/cachingProxy';
|
||||||
// import { ZabbixNotImplemented } from './connectors/dbConnector';
|
import { DBConnector } from './connectors/dbConnector';
|
||||||
import { DBConnector, handleDBDataSourceResponse } from './connectors/dbConnector';
|
|
||||||
import { ZabbixAPIConnector } from './connectors/zabbix_api/zabbixAPIConnector';
|
import { ZabbixAPIConnector } from './connectors/zabbix_api/zabbixAPIConnector';
|
||||||
import { SQLConnector } from './connectors/sql/sqlConnector';
|
import { SQLConnector } from './connectors/sql/sqlConnector';
|
||||||
import { InfluxDBConnector } from './connectors/influxdb/influxdbConnector';
|
import { InfluxDBConnector } from './connectors/influxdb/influxdbConnector';
|
||||||
import { ZabbixConnector } from './types';
|
import { ZabbixConnector } from './types';
|
||||||
import { joinTriggersWithProblems, joinTriggersWithEvents } from '../problemsHandler';
|
import { joinTriggersWithEvents, joinTriggersWithProblems } from '../problemsHandler';
|
||||||
import { ProblemDTO } from '../types';
|
import { ProblemDTO } from '../types';
|
||||||
|
|
||||||
interface AppsResponse extends Array<any> {
|
interface AppsResponse extends Array<any> {
|
||||||
@@ -274,7 +273,7 @@ export class Zabbix implements ZabbixConnector {
|
|||||||
})
|
})
|
||||||
.then(items => {
|
.then(items => {
|
||||||
if (!options.showDisabledItems) {
|
if (!options.showDisabledItems) {
|
||||||
items = _.filter(items, {'status': '0'});
|
items = _.filter(items, { 'status': '0' });
|
||||||
}
|
}
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
@@ -432,7 +431,7 @@ export class Zabbix implements ZabbixConnector {
|
|||||||
const [timeFrom, timeTo] = timeRange;
|
const [timeFrom, timeTo] = timeRange;
|
||||||
if (this.enableDirectDBConnection) {
|
if (this.enableDirectDBConnection) {
|
||||||
return this.getHistoryDB(items, timeFrom, timeTo, options)
|
return this.getHistoryDB(items, timeFrom, timeTo, options)
|
||||||
.then(history => handleDBDataSourceResponse(history, items));
|
.then(history => responseHandler.dataResponseToTimeSeries(history, items));
|
||||||
} else {
|
} else {
|
||||||
return this.zabbixAPI.getHistory(items, timeFrom, timeTo)
|
return this.zabbixAPI.getHistory(items, timeFrom, timeTo)
|
||||||
.then(history => responseHandler.handleHistory(history, items));
|
.then(history => responseHandler.handleHistory(history, items));
|
||||||
@@ -443,7 +442,7 @@ export class Zabbix implements ZabbixConnector {
|
|||||||
const [timeFrom, timeTo] = timeRange;
|
const [timeFrom, timeTo] = timeRange;
|
||||||
if (this.enableDirectDBConnection) {
|
if (this.enableDirectDBConnection) {
|
||||||
return this.getTrendsDB(items, timeFrom, timeTo, options)
|
return this.getTrendsDB(items, timeFrom, timeTo, options)
|
||||||
.then(history => handleDBDataSourceResponse(history, items));
|
.then(history => responseHandler.dataResponseToTimeSeries(history, items));
|
||||||
} else {
|
} else {
|
||||||
const valueType = options.consolidateBy || options.valueType;
|
const valueType = options.consolidateBy || options.valueType;
|
||||||
return this.zabbixAPI.getTrend(items, timeFrom, timeTo)
|
return this.zabbixAPI.getTrend(items, timeFrom, timeTo)
|
||||||
@@ -473,7 +472,7 @@ export class Zabbix implements ZabbixConnector {
|
|||||||
return this.zabbixAPI.getSLA(itServiceIds, timeRange, options)
|
return this.zabbixAPI.getSLA(itServiceIds, timeRange, options)
|
||||||
.then(slaResponse => {
|
.then(slaResponse => {
|
||||||
return _.map(itServiceIds, serviceid => {
|
return _.map(itServiceIds, serviceid => {
|
||||||
const itservice = _.find(itservices, {'serviceid': serviceid});
|
const itservice = _.find(itservices, { 'serviceid': serviceid });
|
||||||
return responseHandler.handleSLAResponse(itservice, target.slaProperty, slaResponse);
|
return responseHandler.handleSLAResponse(itservice, target.slaProperty, slaResponse);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -489,7 +488,7 @@ export class Zabbix implements ZabbixConnector {
|
|||||||
* @return array with finded element or empty array
|
* @return array with finded element or empty array
|
||||||
*/
|
*/
|
||||||
function findByName(list, name) {
|
function findByName(list, name) {
|
||||||
const finded = _.find(list, {'name': name});
|
const finded = _.find(list, { 'name': name });
|
||||||
if (finded) {
|
if (finded) {
|
||||||
return [finded];
|
return [finded];
|
||||||
} else {
|
} else {
|
||||||
@@ -506,7 +505,7 @@ function findByName(list, name) {
|
|||||||
* @return {[type]} array with finded element or empty array
|
* @return {[type]} array with finded element or empty array
|
||||||
*/
|
*/
|
||||||
function filterByName(list, name) {
|
function filterByName(list, name) {
|
||||||
const finded = _.filter(list, {'name': name});
|
const finded = _.filter(list, { 'name': name });
|
||||||
if (finded) {
|
if (finded) {
|
||||||
return finded;
|
return finded;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
Reference in New Issue
Block a user