datasource.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. define([
  2. 'angular',
  3. 'lodash',
  4. 'moment',
  5. 'app/core/utils/datemath',
  6. 'app/core/utils/kbn',
  7. 'app/features/templating/variable',
  8. ],
  9. function (angular, _, moment, dateMath, kbn, templatingVariable) {
  10. 'use strict';
  11. /** @ngInject */
  12. function CloudWatchDatasource(instanceSettings, $q, backendSrv, templateSrv, timeSrv) {
  13. this.type = 'cloudwatch';
  14. this.name = instanceSettings.name;
  15. this.supportMetrics = true;
  16. this.proxyUrl = instanceSettings.url;
  17. this.defaultRegion = instanceSettings.jsonData.defaultRegion;
  18. this.instanceSettings = instanceSettings;
  19. this.standardStatistics = [
  20. 'Average',
  21. 'Maximum',
  22. 'Minimum',
  23. 'Sum',
  24. 'SampleCount'
  25. ];
  26. var self = this;
  27. this.query = function(options) {
  28. options = angular.copy(options);
  29. options.targets = this.expandTemplateVariable(options.targets, options.scopedVars, templateSrv);
  30. var queries = _.filter(options.targets, function (item) {
  31. return item.hide !== true &&
  32. !!item.region &&
  33. !!item.namespace &&
  34. !!item.metricName &&
  35. !_.isEmpty(item.statistics);
  36. }).map(function (item) {
  37. item.region = templateSrv.replace(item.region, options.scopedVars);
  38. item.namespace = templateSrv.replace(item.namespace, options.scopedVars);
  39. item.metricName = templateSrv.replace(item.metricName, options.scopedVars);
  40. var dimensions = {};
  41. _.each(item.dimensions, function (value, key) {
  42. dimensions[templateSrv.replace(key, options.scopedVars)] = templateSrv.replace(value, options.scopedVars);
  43. });
  44. item.dimensions = dimensions;
  45. item.period = self.getPeriod(item, options);
  46. return _.extend({
  47. refId: item.refId,
  48. intervalMs: options.intervalMs,
  49. maxDataPoints: options.maxDataPoints,
  50. datasourceId: self.instanceSettings.id,
  51. type: 'timeSeriesQuery',
  52. }, item);
  53. });
  54. // No valid targets, return the empty result to save a round trip.
  55. if (_.isEmpty(queries)) {
  56. var d = $q.defer();
  57. d.resolve({ data: [] });
  58. return d.promise;
  59. }
  60. var request = {
  61. from: options.rangeRaw.from,
  62. to: options.rangeRaw.to,
  63. queries: queries
  64. };
  65. return this.performTimeSeriesQuery(request);
  66. };
  67. this.getPeriod = function(target, options, now) {
  68. var start = this.convertToCloudWatchTime(options.range.from, false);
  69. var end = this.convertToCloudWatchTime(options.range.to, true);
  70. now = Math.round((now || Date.now()) / 1000);
  71. var period;
  72. var range = end - start;
  73. var hourSec = 60 * 60;
  74. var daySec = hourSec * 24;
  75. var periodUnit = 60;
  76. if (!target.period) {
  77. if (now - start <= (daySec * 15)) { // until 15 days ago
  78. if (target.namespace === 'AWS/EC2') {
  79. periodUnit = period = 300;
  80. } else {
  81. periodUnit = period = 60;
  82. }
  83. } else if (now - start <= (daySec * 63)) { // until 63 days ago
  84. periodUnit = period = 60 * 5;
  85. } else if (now - start <= (daySec * 455)) { // until 455 days ago
  86. periodUnit = period = 60 * 60;
  87. } else { // over 455 days, should return error, but try to long period
  88. periodUnit = period = 60 * 60;
  89. }
  90. } else {
  91. if (/^\d+$/.test(target.period)) {
  92. period = parseInt(target.period, 10);
  93. } else {
  94. period = kbn.interval_to_seconds(templateSrv.replace(target.period, options.scopedVars));
  95. }
  96. }
  97. if (period < 1) {
  98. period = 1;
  99. }
  100. if (range / period >= 1440) {
  101. period = Math.ceil(range / 1440 / periodUnit) * periodUnit;
  102. }
  103. return period;
  104. };
  105. this.performTimeSeriesQuery = function(request) {
  106. return backendSrv.post('/api/tsdb/query', request).then(function (res) {
  107. var data = [];
  108. if (res.results) {
  109. _.forEach(res.results, function (queryRes) {
  110. _.forEach(queryRes.series, function (series) {
  111. data.push({target: series.name, datapoints: series.points});
  112. });
  113. });
  114. }
  115. return {data: data};
  116. });
  117. };
  118. function transformSuggestDataFromTable(suggestData) {
  119. return _.map(suggestData.results['metricFindQuery'].tables[0].rows, function (v) {
  120. return {
  121. text: v[0],
  122. value: v[1]
  123. };
  124. });
  125. }
  126. this.doMetricQueryRequest = function (subtype, parameters) {
  127. var range = timeSrv.timeRange();
  128. return backendSrv.post('/api/tsdb/query', {
  129. from: range.from,
  130. to: range.to,
  131. queries: [
  132. _.extend({
  133. refId: 'metricFindQuery',
  134. intervalMs: 1, // dummy
  135. maxDataPoints: 1, // dummy
  136. datasourceId: this.instanceSettings.id,
  137. type: 'metricFindQuery',
  138. subtype: subtype
  139. }, parameters)
  140. ]
  141. }).then(function (r) { return transformSuggestDataFromTable(r); });
  142. };
  143. this.getRegions = function () {
  144. return this.doMetricQueryRequest('regions', null);
  145. };
  146. this.getNamespaces = function() {
  147. return this.doMetricQueryRequest('namespaces', null);
  148. };
  149. this.getMetrics = function (namespace, region) {
  150. return this.doMetricQueryRequest('metrics', {
  151. region: templateSrv.replace(region),
  152. namespace: templateSrv.replace(namespace)
  153. });
  154. };
  155. this.getDimensionKeys = function(namespace, region) {
  156. return this.doMetricQueryRequest('dimension_keys', {
  157. region: templateSrv.replace(region),
  158. namespace: templateSrv.replace(namespace)
  159. });
  160. };
  161. this.getDimensionValues = function(region, namespace, metricName, dimensionKey, filterDimensions) {
  162. return this.doMetricQueryRequest('dimension_values', {
  163. region: templateSrv.replace(region),
  164. namespace: templateSrv.replace(namespace),
  165. metricName: templateSrv.replace(metricName),
  166. dimensionKey: templateSrv.replace(dimensionKey),
  167. dimensions: this.convertDimensionFormat(filterDimensions, {}),
  168. });
  169. };
  170. this.getEbsVolumeIds = function(region, instanceId) {
  171. return this.doMetricQueryRequest('ebs_volume_ids', {
  172. region: templateSrv.replace(region),
  173. instanceId: templateSrv.replace(instanceId)
  174. });
  175. };
  176. this.getEc2InstanceAttribute = function(region, attributeName, filters) {
  177. return this.doMetricQueryRequest('ec2_instance_attribute', {
  178. region: templateSrv.replace(region),
  179. attributeName: templateSrv.replace(attributeName),
  180. filters: filters
  181. });
  182. };
  183. this.metricFindQuery = function(query) {
  184. var region;
  185. var namespace;
  186. var metricName;
  187. var regionQuery = query.match(/^regions\(\)/);
  188. if (regionQuery) {
  189. return this.getRegions();
  190. }
  191. var namespaceQuery = query.match(/^namespaces\(\)/);
  192. if (namespaceQuery) {
  193. return this.getNamespaces();
  194. }
  195. var metricNameQuery = query.match(/^metrics\(([^\)]+?)(,\s?([^,]+?))?\)/);
  196. if (metricNameQuery) {
  197. namespace = metricNameQuery[1];
  198. region = metricNameQuery[3];
  199. return this.getMetrics(namespace, region);
  200. }
  201. var dimensionKeysQuery = query.match(/^dimension_keys\(([^\)]+?)(,\s?([^,]+?))?\)/);
  202. if (dimensionKeysQuery) {
  203. namespace = dimensionKeysQuery[1];
  204. region = dimensionKeysQuery[3];
  205. return this.getDimensionKeys(namespace, region);
  206. }
  207. var dimensionValuesQuery = query.match(/^dimension_values\(([^,]+?),\s?([^,]+?),\s?([^,]+?),\s?([^,]+?)\)/);
  208. if (dimensionValuesQuery) {
  209. region = dimensionValuesQuery[1];
  210. namespace = dimensionValuesQuery[2];
  211. metricName = dimensionValuesQuery[3];
  212. var dimensionKey = dimensionValuesQuery[4];
  213. return this.getDimensionValues(region, namespace, metricName, dimensionKey, {});
  214. }
  215. var ebsVolumeIdsQuery = query.match(/^ebs_volume_ids\(([^,]+?),\s?([^,]+?)\)/);
  216. if (ebsVolumeIdsQuery) {
  217. region = ebsVolumeIdsQuery[1];
  218. var instanceId = ebsVolumeIdsQuery[2];
  219. return this.getEbsVolumeIds(region, instanceId);
  220. }
  221. var ec2InstanceAttributeQuery = query.match(/^ec2_instance_attribute\(([^,]+?),\s?([^,]+?),\s?(.+?)\)/);
  222. if (ec2InstanceAttributeQuery) {
  223. region = ec2InstanceAttributeQuery[1];
  224. var targetAttributeName = ec2InstanceAttributeQuery[2];
  225. var filterJson = JSON.parse(templateSrv.replace(ec2InstanceAttributeQuery[3]));
  226. return this.getEc2InstanceAttribute(region, targetAttributeName, filterJson);
  227. }
  228. return $q.when([]);
  229. };
  230. this.annotationQuery = function (options) {
  231. var annotation = options.annotation;
  232. var defaultPeriod = annotation.prefixMatching ? '' : '300';
  233. var period = annotation.period || defaultPeriod;
  234. period = parseInt(period, 10);
  235. var dimensions = {};
  236. _.each(annotation.dimensions, function (value, key) {
  237. dimensions[templateSrv.replace(key, options.scopedVars)] = templateSrv.replace(value, options.scopedVars);
  238. });
  239. var parameters = {
  240. prefixMatching: annotation.prefixMatching,
  241. region: templateSrv.replace(annotation.region),
  242. namespace: templateSrv.replace(annotation.namespace),
  243. metricName: templateSrv.replace(annotation.metricName),
  244. dimensions: dimensions,
  245. statistics: _.map(annotation.statistics, function (s) { return templateSrv.replace(s); }),
  246. period: period,
  247. actionPrefix: annotation.actionPrefix || '',
  248. alarmNamePrefix: annotation.alarmNamePrefix || ''
  249. };
  250. return backendSrv.post('/api/tsdb/query', {
  251. from: options.range.from,
  252. to: options.range.to,
  253. queries: [
  254. _.extend({
  255. refId: 'annotationQuery',
  256. intervalMs: 1, // dummy
  257. maxDataPoints: 1, // dummy
  258. datasourceId: this.instanceSettings.id,
  259. type: 'annotationQuery'
  260. }, parameters)
  261. ]
  262. }).then(function (r) {
  263. return _.map(r.results['annotationQuery'].tables[0].rows, function (v) {
  264. return {
  265. annotation: annotation,
  266. time: Date.parse(v[0]),
  267. title: v[1],
  268. tags: [v[2]],
  269. text: v[3]
  270. };
  271. });
  272. });
  273. };
  274. this.testDatasource = function() {
  275. /* use billing metrics for test */
  276. var region = this.defaultRegion;
  277. var namespace = 'AWS/Billing';
  278. var metricName = 'EstimatedCharges';
  279. var dimensions = {};
  280. return this.getDimensionValues(region, namespace, metricName, 'ServiceName', dimensions).then(function () {
  281. return { status: 'success', message: 'Data source is working' };
  282. });
  283. };
  284. this.awsRequest = function(data) {
  285. var options = {
  286. method: 'POST',
  287. url: this.proxyUrl,
  288. data: data
  289. };
  290. return backendSrv.datasourceRequest(options).then(function(result) {
  291. return result.data;
  292. });
  293. };
  294. this.getDefaultRegion = function() {
  295. return this.defaultRegion;
  296. };
  297. this.getExpandedVariables = function(target, dimensionKey, variable, templateSrv) {
  298. /* if the all checkbox is marked we should add all values to the targets */
  299. var allSelected = _.find(variable.options, {'selected': true, 'text': 'All'});
  300. return _.chain(variable.options)
  301. .filter(function(v) {
  302. if (allSelected) {
  303. return v.text !== 'All';
  304. } else {
  305. return v.selected;
  306. }
  307. })
  308. .map(function(v) {
  309. var t = angular.copy(target);
  310. var scopedVar = {};
  311. scopedVar[variable.name] = v;
  312. t.dimensions[dimensionKey] = templateSrv.replace(t.dimensions[dimensionKey], scopedVar);
  313. return t;
  314. }).value();
  315. };
  316. this.expandTemplateVariable = function(targets, scopedVars, templateSrv) {
  317. var self = this;
  318. return _.chain(targets)
  319. .map(function(target) {
  320. var dimensionKey = _.findKey(target.dimensions, function(v) {
  321. return templateSrv.variableExists(v) && !_.has(scopedVars, templateSrv.getVariableName(v));
  322. });
  323. if (dimensionKey) {
  324. var multiVariable = _.find(templateSrv.variables, function(variable) {
  325. return templatingVariable.containsVariable(target.dimensions[dimensionKey], variable.name) && variable.multi;
  326. });
  327. var variable = _.find(templateSrv.variables, function(variable) {
  328. return templatingVariable.containsVariable(target.dimensions[dimensionKey], variable.name);
  329. });
  330. return self.getExpandedVariables(target, dimensionKey, multiVariable || variable, templateSrv);
  331. } else {
  332. return [target];
  333. }
  334. }).flatten().value();
  335. };
  336. this.convertToCloudWatchTime = function(date, roundUp) {
  337. if (_.isString(date)) {
  338. date = dateMath.parse(date, roundUp);
  339. }
  340. return Math.round(date.valueOf() / 1000);
  341. };
  342. this.convertDimensionFormat = function(dimensions, scopedVars) {
  343. return _.map(dimensions, function(value, key) {
  344. return {
  345. Name: templateSrv.replace(key, scopedVars),
  346. Value: templateSrv.replace(value, scopedVars)
  347. };
  348. });
  349. };
  350. }
  351. return CloudWatchDatasource;
  352. });