The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/public/app/plugins/datasource/cloudwatch/datasource.js

370 lines
12 KiB

define([
'angular',
'lodash',
'moment',
'app/core/utils/datemath',
'./query_ctrl',
'./directives',
],
function (angular, _, moment, dateMath) {
'use strict';
var module = angular.module('grafana.services');
module.factory('CloudWatchDatasource', function($q, backendSrv, templateSrv) {
function CloudWatchDatasource(datasource) {
this.type = 'cloudwatch';
this.name = datasource.name;
this.supportMetrics = true;
this.proxyUrl = datasource.url;
this.defaultRegion = datasource.jsonData.defaultRegion;
}
CloudWatchDatasource.prototype.query = function(options) {
var start = convertToCloudWatchTime(options.range.from, false);
var end = convertToCloudWatchTime(options.range.to, true);
var queries = [];
options = angular.copy(options);
_.each(options.targets, _.bind(function(target) {
if (target.hide || !target.namespace || !target.metricName || _.isEmpty(target.statistics)) {
return;
}
var query = {};
query.region = templateSrv.replace(target.region, options.scopedVars);
query.namespace = templateSrv.replace(target.namespace, options.scopedVars);
query.metricName = templateSrv.replace(target.metricName, options.scopedVars);
query.dimensions = convertDimensionFormat(target.dimensions, options.scopedVars);
query.statistics = target.statistics;
var range = end - start;
query.period = parseInt(target.period, 10) || (query.namespace === 'AWS/EC2' ? 300 : 60);
if (range / query.period >= 1440) {
query.period = Math.ceil(range / 1440 / 60) * 60;
}
target.period = query.period;
queries.push(query);
}, this));
// No valid targets, return the empty result to save a round trip.
if (_.isEmpty(queries)) {
var d = $q.defer();
d.resolve({ data: [] });
return d.promise;
}
var allQueryPromise = _.map(queries, function(query) {
return this.performTimeSeriesQuery(query, start, end);
}, this);
return $q.all(allQueryPromise).then(function(allResponse) {
var result = [];
_.each(allResponse, function(response, index) {
var metrics = transformMetricData(response, options.targets[index]);
result = result.concat(metrics);
});
return { data: result };
});
};
CloudWatchDatasource.prototype.performTimeSeriesQuery = function(query, start, end) {
return this.awsRequest({
region: query.region,
action: 'GetMetricStatistics',
parameters: {
namespace: query.namespace,
metricName: query.metricName,
dimensions: query.dimensions,
statistics: query.statistics,
startTime: start,
endTime: end,
period: query.period
}
});
};
CloudWatchDatasource.prototype.getRegions = function() {
return this.awsRequest({action: '__GetRegions'});
};
CloudWatchDatasource.prototype.getNamespaces = function() {
return this.awsRequest({action: '__GetNamespaces'});
};
CloudWatchDatasource.prototype.getMetrics = function(namespace) {
return this.awsRequest({
action: '__GetMetrics',
parameters: {
namespace: templateSrv.replace(namespace)
}
});
};
CloudWatchDatasource.prototype.getDimensionKeys = function(namespace) {
return this.awsRequest({
action: '__GetDimensions',
parameters: {
namespace: templateSrv.replace(namespace)
}
});
};
CloudWatchDatasource.prototype.getDimensionValues = function(region, namespace, metricName, dimensionKey, filterDimensions) {
var request = {
region: templateSrv.replace(region),
action: 'ListMetrics',
parameters: {
namespace: templateSrv.replace(namespace),
metricName: templateSrv.replace(metricName),
dimensions: convertDimensionFormat(filterDimensions, {}),
}
};
return this.awsRequest(request).then(function(result) {
return _.chain(result.Metrics)
.pluck('Dimensions')
.flatten()
.filter(function(dimension) {
return dimension !== null && dimension.Name === dimensionKey;
})
.pluck('Value')
.uniq()
.sortBy()
.map(function(value) {
return {value: value, text: value};
}).value();
});
};
CloudWatchDatasource.prototype.performEC2DescribeInstances = function(region, filters, instanceIds) {
return this.awsRequest({
region: region,
action: 'DescribeInstances',
parameters: { filter: filters, instanceIds: instanceIds }
});
};
CloudWatchDatasource.prototype.metricFindQuery = function(query) {
var region;
var namespace;
var metricName;
var transformSuggestData = function(suggestData) {
return _.map(suggestData, function(v) {
return { text: v };
});
};
var regionQuery = query.match(/^regions\(\)/);
if (regionQuery) {
return this.getRegions();
}
var namespaceQuery = query.match(/^namespaces\(\)/);
if (namespaceQuery) {
return this.getNamespaces();
}
var metricNameQuery = query.match(/^metrics\(([^\)]+?)\)/);
if (metricNameQuery) {
return this.getMetrics(metricNameQuery[1]);
}
var dimensionKeysQuery = query.match(/^dimension_keys\(([^\)]+?)\)/);
if (dimensionKeysQuery) {
return this.getDimensionKeys(dimensionKeysQuery[1]);
}
var dimensionValuesQuery = query.match(/^dimension_values\(([^,]+?),\s?([^,]+?),\s?([^,]+?),\s?([^,]+?)\)/);
if (dimensionValuesQuery) {
region = templateSrv.replace(dimensionValuesQuery[1]);
namespace = templateSrv.replace(dimensionValuesQuery[2]);
metricName = templateSrv.replace(dimensionValuesQuery[3]);
var dimensionKey = templateSrv.replace(dimensionValuesQuery[4]);
return this.getDimensionValues(region, namespace, metricName, dimensionKey, {});
}
var ebsVolumeIdsQuery = query.match(/^ebs_volume_ids\(([^,]+?),\s?([^,]+?)\)/);
if (ebsVolumeIdsQuery) {
region = templateSrv.replace(ebsVolumeIdsQuery[1]);
var instanceId = templateSrv.replace(ebsVolumeIdsQuery[2]);
var instanceIds = [
instanceId
];
return this.performEC2DescribeInstances(region, [], instanceIds).then(function(result) {
var volumeIds = _.map(result.Reservations[0].Instances[0].BlockDeviceMappings, function(mapping) {
return mapping.Ebs.VolumeId;
});
return transformSuggestData(volumeIds);
});
}
return $q.when([]);
};
CloudWatchDatasource.prototype.performDescribeAlarmsForMetric = function(region, namespace, metricName, dimensions, statistic, period) {
return this.awsRequest({
region: region,
action: 'DescribeAlarmsForMetric',
parameters: { namespace: namespace, metricName: metricName, dimensions: dimensions, statistic: statistic, period: period }
});
};
CloudWatchDatasource.prototype.performDescribeAlarmHistory = function(region, alarmName, startDate, endDate) {
return this.awsRequest({
region: region,
action: 'DescribeAlarmHistory',
parameters: { alarmName: alarmName, startDate: startDate, endDate: endDate }
});
};
CloudWatchDatasource.prototype.annotationQuery = function(options) {
var annotation = options.annotation;
var region = templateSrv.replace(annotation.region);
var namespace = templateSrv.replace(annotation.namespace);
var metricName = templateSrv.replace(annotation.metricName);
var dimensions = convertDimensionFormat(annotation.dimensions);
var statistics = _.map(annotation.statistics, function(s) { return templateSrv.replace(s); });
var period = annotation.period || '300';
period = parseInt(period, 10);
if (!region || !namespace || !metricName || _.isEmpty(statistics)) { return $q.when([]); }
var d = $q.defer();
var self = this;
var allQueryPromise = _.map(statistics, function(statistic) {
return self.performDescribeAlarmsForMetric(region, namespace, metricName, dimensions, statistic, period);
});
$q.all(allQueryPromise).then(function(alarms) {
var eventList = [];
var start = convertToCloudWatchTime(options.range.from, false);
var end = convertToCloudWatchTime(options.range.to, true);
_.chain(alarms)
.pluck('MetricAlarms')
.flatten()
.each(function(alarm) {
if (!alarm) {
d.resolve(eventList);
return;
}
self.performDescribeAlarmHistory(region, alarm.AlarmName, start, end).then(function(history) {
_.each(history.AlarmHistoryItems, function(h) {
var event = {
annotation: annotation,
time: Date.parse(h.Timestamp),
title: h.AlarmName,
tags: [h.HistoryItemType],
text: h.HistorySummary
};
eventList.push(event);
});
d.resolve(eventList);
});
});
});
return d.promise;
};
CloudWatchDatasource.prototype.testDatasource = function() {
/* use billing metrics for test */
var region = this.defaultRegion;
var namespace = 'AWS/Billing';
var metricName = 'EstimatedCharges';
var dimensions = {};
return this.getDimensionValues(region, namespace, metricName, 'ServiceName', dimensions).then(function () {
return { status: 'success', message: 'Data source is working', title: 'Success' };
});
};
CloudWatchDatasource.prototype.awsRequest = function(data) {
var options = {
method: 'POST',
url: this.proxyUrl,
data: data
};
return backendSrv.datasourceRequest(options).then(function(result) {
return result.data;
});
};
CloudWatchDatasource.prototype.getDefaultRegion = function() {
return this.defaultRegion;
};
function transformMetricData(md, options) {
var aliasRegex = /\{\{(.+?)\}\}/g;
var aliasPattern = options.alias || '{{metric}}_{{stat}}';
var aliasData = {
region: templateSrv.replace(options.region),
namespace: templateSrv.replace(options.namespace),
metric: templateSrv.replace(options.metricName),
};
_.extend(aliasData, options.dimensions);
var periodMs = options.period * 1000;
return _.map(options.statistics, function(stat) {
var dps = [];
var lastTimestamp = null;
_.chain(md.Datapoints)
.sortBy(function(dp) {
return dp.Timestamp;
})
.each(function(dp) {
var timestamp = new Date(dp.Timestamp).getTime();
if (lastTimestamp && (timestamp - lastTimestamp) > periodMs) {
dps.push([null, lastTimestamp + periodMs]);
}
lastTimestamp = timestamp;
if (options.divideSumByPeriod && stat === 'Sum') {
dp[stat] = dp[stat] / options.period;
}
dps.push([dp[stat], timestamp]);
});
aliasData.stat = stat;
var seriesName = aliasPattern.replace(aliasRegex, function(match, g1) {
if (aliasData[g1]) {
return aliasData[g1];
}
return g1;
});
return {target: seriesName, datapoints: dps};
});
}
function convertToCloudWatchTime(date, roundUp) {
if (_.isString(date)) {
date = dateMath.parse(date, roundUp);
}
return Math.round(date.valueOf() / 1000);
}
function convertDimensionFormat(dimensions, scopedVars) {
return _.map(dimensions, function(value, key) {
return {
Name: templateSrv.replace(key, scopedVars),
Value: templateSrv.replace(value, scopedVars)
};
});
}
return CloudWatchDatasource;
});
});