monasca-grafana-datasource/datasource.js

480 lines
15 KiB
JavaScript

define([
'angular',
'lodash',
'moment',
'app/plugins/sdk',
'app/core/utils/datemath',
'app/core/utils/kbn',
'./query_ctrl',
],
function (angular, _, moment, sdk, dateMath, kbn) {
'use strict';
var self;
function MonascaDatasource(instanceSettings, $q, backendSrv, templateSrv) {
this.url = instanceSettings.url;
this.url_version = '/v2.0'
this.name = instanceSettings.name;
if (instanceSettings.jsonData) {
this.token = instanceSettings.jsonData.token;
switch ( instanceSettings.jsonData.authMode ) {
case "Keystone":
this.keystoneAuth = true;
this.useHorizonProxy = false;
break;
case "Horizon":
this.keystoneAuth = false;
this.useHorizonProxy = true;
this.token = null;
break;
case "Token":
this.keystoneAuth = false;
this.useHorizonProxy = false;
break;
}
} else {
this.token = null;
this.keystoneAuth = null;
this.useHorizonProxy = false;
}
if ( this.useHorizonProxy == true ) {
this.url_version = '';
}
this.q = $q;
this.backendSrv = backendSrv;
this.templateSrv = templateSrv;
self = this;
}
MonascaDatasource.prototype.timeRange = function() {
var timeRange = angular.element('grafana-app').injector().get('timeSrv').timeRange();
return {
start_time: this.translateTime(timeRange.from),
end_time: this.translateTime(timeRange.to),
};
};
MonascaDatasource.prototype.query = function(options) {
var datasource = this;
var from = this.translateTime(options.range.from);
var to = this.translateTime(options.range.to);
var targets_list = [];
for (var i = 0; i < options.targets.length; i++) {
var target = options.targets[i];
if (target.error || target.hide || !target.metric) {
continue;
}
var query = this.buildDataQuery(options.targets[i], from, to);
query = self.templateSrv.replace(query, options.scopedVars);
var query_list = this.expandTemplatedQueries(query);
// Pre query aliasing
// Used when querying with group_by, where some dimensions are not returned
query_list = query_list.map(datasource.autoAlias);
targets_list.push(query_list);
}
var targets_promise = self.q.all(targets_list).then(function(results) {
return _.flatten(results);
});
var promises = self.q.resolve(targets_promise).then(function(targets) {
return targets.map(function (target) {
target = datasource.convertPeriod(target);
return datasource._limitedMonascaRequest(target, {}, true).then(datasource.convertDataPoints).catch(function(err) {throw err;});
});
}).catch(function(err) {throw err;});
return self.q.resolve(promises).then(function(promises) {
return self.q.all(promises).then(function(results) {
var sorted_results = results.map(function (results) {
return results.sort(function (a, b) {
return a.target.localeCompare(b.target);
});
});
return { data: _.flatten(sorted_results).filter(function(result) { return !_.isEmpty(result);}) };
});
});
};
MonascaDatasource.prototype.metricsQuery = function(params) {
var url = this.url_version + '/metrics';
return this._limitedMonascaRequest(url, params, true).catch(function(err) {throw err;});
};
MonascaDatasource.prototype.namesQuery = function() {
var datasource = this;
var url = this.url_version + '/metrics/names';
return this._limitedMonascaRequest(url, {}, false).then(function(data) {
return datasource.convertDataList(data, 'name');
}).catch(function(err) {throw err;});
};
MonascaDatasource.prototype.dimensionNamesQuery = function(params) {
var datasource = this;
var url = this.url_version + '/metrics/dimensions/names';
params = Object.assign(params, this.timeRange())
return this._limitedMonascaRequest(url, params, false).then(function(data) {
return datasource.convertDataList(data, 'dimension_name');
}).catch(function(err) {throw err;});
};
MonascaDatasource.prototype.dimensionValuesQuery = function(params) {
var datasource = this;
var url = this.url_version + '/metrics/dimensions/names/values';
params = Object.assign(params, this.timeRange())
return this._limitedMonascaRequest(url, params, false).then(function(data) {
return datasource.convertDataList(data, 'dimension_value');
}).catch(function(err) {throw err;});
};
MonascaDatasource.prototype.convertDataList = function(data, key) {
if ( JSON.stringify(data.data) === '{}' ) { return {}; }
var values = data.data.elements.map(function(element) {
return element[key];
});
return values;
};
MonascaDatasource.prototype.buildDataQuery = function(options, from, to) {
var params = {};
params.name = options.metric;
params.start_time = from;
if (to) {
params.end_time = to;
}
if (options.dimensions) {
var dimensions = [];
var group_by = [];
for (var i = 0; i < options.dimensions.length; i++) {
var key = options.dimensions[i].key;
var value = options.dimensions[i].value;
if (value == '$all' || value =='*') {
group_by.push(key);
}
else {
dimensions.push(key + ':' + value);
}
}
var dimensions_string = dimensions.join(',');
var group_by_string = group_by.join(',');
if (dimensions_string){
params.dimensions = dimensions;
}
if (group_by_string){
params.group_by = group_by;
}
}
if (options.group) {
params.group_by = '*';
}
else {
params.merge_metrics = 'true';
}
if (options.alias) {
params.alias = options.alias;
}
var path;
if (options.aggregator && options.aggregator != 'none') {
params.statistics = options.aggregator;
params.period = options.period;
path = this.url_version + '/metrics/statistics?';
}
else {
path = this.url_version + '/metrics/measurements?';
}
path += Object.keys(params).map(function(key) {
return key + '=' + params[key];
}).join('&');
return path;
};
MonascaDatasource.prototype.expandTemplatedQueries = function(query) {
var templated_vars = query.match(/{[^}]*}/g);
if (!templated_vars) {
return [query];
}
var expandedQueries = [];
var to_replace = templated_vars[0];
var var_options = to_replace.substring(1, to_replace.length - 1);
var_options = var_options.split(',');
for (var i = 0; i < var_options.length; i++) {
var new_query = query.split(to_replace).join(var_options[i]);
expandedQueries = expandedQueries.concat(this.expandTemplatedQueries(new_query));
}
return expandedQueries;
};
MonascaDatasource.prototype.autoAlias = function(query, dimensions, return_target) {
function keysSortedByLengthDesc(obj) {
var keys = [];
for (var key in obj) {
keys.push(key);
}
function byLength(a, b) {return b.length - a.length;}
return keys.sort(byLength);
}
if (typeof(dimensions) !== "object") {
var dimensions_string = query.match(/dimensions=([^&]*)/);
if (dimensions_string) {
var dimensions_list = dimensions_string[1].split(',');
dimensions = {};
for (var j = 0; j < dimensions_list.length; j++) {
var dim = dimensions_list[j].split(':');
dimensions[dim[0]] = dim[1];
}
}
}
var target = null;
var alias_string = query.match(/alias=[^&]*/);
if (alias_string) {
var alias = alias_string[0];
var keys = keysSortedByLengthDesc(dimensions);
for (var k in keys) {
alias = alias.replace(new RegExp("@"+keys[k], 'g'), dimensions[keys[k]]);
}
query = query.replace(alias_string[0], alias);
target = alias.match(/alias=(.*)/)[1];
}
if (return_target === true) {
return target;
}
return query;
};
MonascaDatasource.prototype.convertDataPoints = function(data) {
var url = data.config.url;
var results = [];
for (var i = 0; i < data.data.elements.length; i++)
{
var element = data.data.elements[i];
var target = element.name;
// Post query aliasing
// Used when querying with group_by flag where dimensions are not specified in initial query
var alias = self.autoAlias(data.config.url, element.dimensions, true);
if (alias) {
target = alias;
}
var raw_datapoints;
var aggregator = 'value';
if ('measurements' in element) {
raw_datapoints = element.measurements;
}
else {
raw_datapoints = element.statistics;
if (element.columns.indexOf(aggregator) < 0) {
aggregator = url.match(/statistics=[^&]*/);
aggregator = aggregator[0].substring('statistics='.length);
}
}
var datapoints = [];
var timeCol = element.columns.indexOf('timestamp');
var dataCol = element.columns.indexOf(aggregator);
var metaCol = element.columns.indexOf('value_meta');
for (var j = 0; j < raw_datapoints.length; j++) {
var datapoint = raw_datapoints[j];
var time = new Date(datapoint[timeCol]);
var point = datapoint[dataCol];
var newpoint = [point, time.getTime()];
if (metaCol >= 0) {
newpoint.push(datapoint[metaCol]);
}
datapoints.push(newpoint);
}
var convertedData = { 'target': target, 'datapoints': datapoints };
results.push(convertedData);
}
return results;
};
// For use with specified or api enforced limits.
// Pages through data until all data is retrieved.
MonascaDatasource.prototype._limitedMonascaRequest = function(path, params, aggregate) {
var datasource = this;
var deferred = self.q.defer();
var data = null;
var element_list = [];
function aggregateResults() {
var elements = {};
for (var i = 0; i < element_list.length; i++) {
var element = element_list[i];
if (element.id in elements){
if (element.measurements){
elements[element.id].measurements = elements[element.id].measurements.concat(element.measurements);
}
if (element.statistics){
elements[element.id].measurements = elements[element.id].statistics.concat(element.statistics);
}
}
else{
elements[element.id] = element;
}
}
data.data.elements = Object.keys(elements).map(function(key) {
return elements[key];
});
}
function requestAll(multi_page){
datasource._monascaRequest(path, params)
.then(function(d) {
data = d;
element_list = element_list.concat(d.data.elements);
if(d.data.links) {
for (var i = 0; i < d.data.links.length; i++) {
if (d.data.links[i].rel == 'next'){
var next = decodeURIComponent(d.data.links[i].href);
var offset = next.match(/offset=([^&]*)/);
params.offset = offset[1];
requestAll(true);
return;
}
}
}
if (multi_page){
if (aggregate){
aggregateResults();
}
else {
data.data.elements = element_list;
}
}
deferred.resolve(data);
}).catch(function(err) {deferred.reject(err);});
}
requestAll(false);
return deferred.promise;
};
MonascaDatasource.prototype._monascaRequest = function(path, params) {
var headers = {
'Content-Type': 'application/json',
'X-Auth-Token': this.token
};
var options = {
method: 'GET',
url: this.url + path,
params: params,
headers: headers,
withCredentials: true,
};
return this.backendSrv.datasourceRequest(options).catch(function(err) {
if (err.status !== 0 || err.status >= 300) {
var monasca_response;
if (err.data) {
if (err.data.message){
monasca_response = err.data.message;
} else{
var err_name = Object.keys(err.data)[0];
monasca_response = err.data[err_name].message;
}
}
if (monasca_response) {
throw { message: 'Monasca Error Response: ' + monasca_response };
} else {
throw { message: 'Monasca Error Status: ' + err.status };
}
}
});
};
// Called by grafana to retrieve possible query template options
MonascaDatasource.prototype.metricFindQuery = function(query) {
return this.dimensionValuesQuery({'dimension_name': query}).then(function(data) {
return _.map(data, function(value) {
return {text: value};
});
});
};
// Called by grafana to retrieve data for dispalying annotations
MonascaDatasource.prototype.annotationQuery = function(options) {
var dimensions = [];
if (options.annotation.dimensions) {
options.annotation.dimensions.split(',').forEach(function(x){
var dim = x.split('=');
dimensions.push({'key': dim[0], 'value': dim[1]});
});
}
options.targets = [{ 'metric': options.annotation.metric, 'dimensions': dimensions}];
return this.query(options).then(function(result) {
var list = [];
for (var i = 0; i < result.data.length; i++) {
var target = result.data[i];
for (var y = 0; y < target.datapoints.length; y++) {
var datapoint = target.datapoints[y];
if (!datapoint[0] && !options.annotation.shownull) { continue; }
var event = {
annotation: options.annotation,
time: datapoint[1],
title: target.target
};
if (datapoint.length > 2 && options.annotation.showmeta){ // value_meta exists
event.text = datapoint[2].detail;
}
list.push(event);
}
}
return list;
});
};
MonascaDatasource.prototype.listTemplates = function() {
var template_list = [];
for (var i = 0; i < self.templateSrv.variables.length; i++) {
template_list.push('$'+self.templateSrv.variables[i].name);
}
return template_list;
};
// Called by grafana to test the datasource
MonascaDatasource.prototype.testDatasource = function() {
return this.namesQuery().then(function () {
return { status: 'success', message: 'Data source is working', title: 'Success' };
});
};
MonascaDatasource.prototype.translateTime = function(date) {
if (date === 'now') {
return null;
}
return moment.utc(dateMath.parse(date).valueOf()).toISOString();
};
MonascaDatasource.prototype.convertPeriod = function(target) {
var regex = target.match(/period=[^&]*/);
if (regex) {
var period = regex[0].substring('period='.length);
var matches = period.match(kbn.interval_regex);
if (matches) {
period = kbn.intervalToSeconds(period);
target = target.replace(regex, 'period='+period);
}
}
return target;
};
MonascaDatasource.prototype.isInt = function(str) {
var n = ~~Number(str);
return String(n) === str && n >= 0;
};
return MonascaDatasource;
});