datasource.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. /* global AWS */
  2. define([
  3. 'angular',
  4. 'lodash',
  5. 'kbn',
  6. 'moment',
  7. './queryCtrl',
  8. 'aws-sdk',
  9. ],
  10. function (angular, _, kbn) {
  11. 'use strict';
  12. var module = angular.module('grafana.services');
  13. module.factory('CloudWatchDatasource', function($q, $http, templateSrv) {
  14. function CloudWatchDatasource(datasource) {
  15. this.type = 'cloudwatch';
  16. this.name = datasource.name;
  17. this.supportMetrics = true;
  18. this.proxyMode = (datasource.jsonData.access === 'proxy');
  19. this.proxyUrl = datasource.url;
  20. this.defaultRegion = datasource.jsonData.defaultRegion;
  21. this.credentials = {
  22. accessKeyId: datasource.jsonData.accessKeyId,
  23. secretAccessKey: datasource.jsonData.secretAccessKey
  24. };
  25. /* jshint -W101 */
  26. this.supportedRegion = [
  27. 'us-east-1', 'us-west-2', 'us-west-1', 'eu-west-1', 'eu-central-1', 'ap-southeast-1', 'ap-southeast-2', 'ap-northeast-1', 'sa-east-1'
  28. ];
  29. this.supportedMetrics = {
  30. 'AWS/AutoScaling': [
  31. 'GroupMinSize', 'GroupMaxSize', 'GroupDesiredCapacity', 'GroupInServiceInstances', 'GroupPendingInstances', 'GroupStandbyInstances', 'GroupTerminatingInstances', 'GroupTotalInstances'
  32. ],
  33. 'AWS/Billing': [
  34. 'EstimatedCharges'
  35. ],
  36. 'AWS/CloudFront': [
  37. 'Requests', 'BytesDownloaded', 'BytesUploaded', 'TotalErrorRate', '4xxErrorRate', '5xxErrorRate'
  38. ],
  39. 'AWS/CloudSearch': [
  40. 'SuccessfulRequests', 'SearchableDocuments', 'IndexUtilization', 'Partitions'
  41. ],
  42. 'AWS/DynamoDB': [
  43. 'ConditionalCheckFailedRequests', 'ConsumedReadCapacityUnits', 'ConsumedWriteCapacityUnits', 'OnlineIndexConsumedWriteCapacity', 'OnlineIndexPercentageProgress', 'OnlineIndexThrottleEvents', 'ProvisionedReadCapacityUnits', 'ProvisionedWriteCapacityUnits', 'ReadThrottleEvents', 'ReturnedItemCount', 'SuccessfulRequestLatency', 'SystemErrors', 'ThrottledRequests', 'UserErrors', 'WriteThrottleEvents'
  44. ],
  45. 'AWS/ElastiCache': [
  46. 'CPUUtilization', 'SwapUsage', 'FreeableMemory', 'NetworkBytesIn', 'NetworkBytesOut',
  47. 'BytesUsedForCacheItems', 'BytesReadIntoMemcached', 'BytesWrittenOutFromMemcached', 'CasBadval', 'CasHits', 'CasMisses', 'CmdFlush', 'CmdGet', 'CmdSet', 'CurrConnections', 'CurrItems', 'DecrHits', 'DecrMisses', 'DeleteHits', 'DeleteMisses', 'Evictions', 'GetHits', 'GetMisses', 'IncrHits', 'IncrMisses', 'Reclaimed',
  48. 'CurrConnections', 'Evictions', 'Reclaimed', 'NewConnections', 'BytesUsedForCache', 'CacheHits', 'CacheMisses', 'ReplicationLag', 'GetTypeCmds', 'SetTypeCmds', 'KeyBasedCmds', 'StringBasedCmds', 'HashBasedCmds', 'ListBasedCmds', 'SetBasedCmds', 'SortedSetBasedCmds', 'CurrItems'
  49. ],
  50. 'AWS/EBS': [
  51. 'VolumeReadBytes', 'VolumeWriteBytes', 'VolumeReadOps', 'VolumeWriteOps', 'VolumeTotalReadTime', 'VolumeTotalWriteTime', 'VolumeIdleTime', 'VolumeQueueLength', 'VolumeThroughputPercentage', 'VolumeConsumedReadWriteOps'
  52. ],
  53. 'AWS/EC2': [
  54. 'CPUCreditUsage', 'CPUCreditBalance', 'CPUUtilization', 'DiskReadOps', 'DiskWriteOps', 'DiskReadBytes', 'DiskWriteBytes', 'NetworkIn', 'NetworkOut', 'StatusCheckFailed', 'StatusCheckFailed_Instance', 'StatusCheckFailed_System'
  55. ],
  56. 'AWS/ELB': [
  57. 'HealthyHostCount', 'UnHealthyHostCount', 'RequestCount', 'Latency', 'HTTPCode_ELB_4XX', 'HTTPCode_ELB_5XX', 'HTTPCode_Backend_2XX', 'HTTPCode_Backend_3XX', 'HTTPCode_Backend_4XX', 'HTTPCode_Backend_5XX', 'BackendConnectionErrors', 'SurgeQueueLength', 'SpilloverCount'
  58. ],
  59. 'AWS/ElasticMapReduce': [
  60. 'CoreNodesPending', 'CoreNodesRunning', 'HBaseBackupFailed', 'HBaseMostRecentBackupDuration', 'HBaseTimeSinceLastSuccessfulBackup', 'HDFSBytesRead', 'HDFSBytesWritten', 'HDFSUtilization', 'IsIdle', 'JobsFailed', 'JobsRunning', 'LiveDataNodes', 'LiveTaskTrackers', 'MapSlotsOpen', 'MissingBlocks', 'ReduceSlotsOpen', 'RemainingMapTasks', 'RemainingMapTasksPerSlot', 'RemainingReduceTasks', 'RunningMapTasks', 'RunningReduceTasks', 'S3BytesRead', 'S3BytesWritten', 'TaskNodesPending', 'TaskNodesRunning', 'TotalLoad'
  61. ],
  62. 'AWS/Kinesis': [
  63. 'PutRecord.Bytes', 'PutRecord.Latency', 'PutRecord.Success', 'PutRecords.Bytes', 'PutRecords.Latency', 'PutRecords.Records', 'PutRecords.Success', 'IncomingBytes', 'IncomingRecords', 'GetRecords.Bytes', 'GetRecords.IteratorAgeMilliseconds', 'GetRecords.Latency', 'GetRecords.Success'
  64. ],
  65. 'AWS/ML': [
  66. 'PredictCount', 'PredictFailureCount'
  67. ],
  68. 'AWS/OpsWorks': [
  69. 'cpu_idle', 'cpu_nice', 'cpu_system', 'cpu_user', 'cpu_waitio', 'load_1', 'load_5', 'load_15', 'memory_buffers', 'memory_cached', 'memory_free', 'memory_swap', 'memory_total', 'memory_used', 'procs'
  70. ],
  71. 'AWS/Redshift': [
  72. 'CPUUtilization', 'DatabaseConnections', 'HealthStatus', 'MaintenanceMode', 'NetworkReceiveThroughput', 'NetworkTransmitThroughput', 'PercentageDiskSpaceUsed', 'ReadIOPS', 'ReadLatency', 'ReadThroughput', 'WriteIOPS', 'WriteLatency', 'WriteThroughput'
  73. ],
  74. 'AWS/RDS': [
  75. 'BinLogDiskUsage', 'CPUUtilization', 'DatabaseConnections', 'DiskQueueDepth', 'FreeableMemory', 'FreeStorageSpace', 'ReplicaLag', 'SwapUsage', 'ReadIOPS', 'WriteIOPS', 'ReadLatency', 'WriteLatency', 'ReadThroughput', 'WriteThroughput', 'NetworkReceiveThroughput', 'NetworkTransmitThroughput'
  76. ],
  77. 'AWS/Route53': [
  78. 'HealthCheckStatus', 'HealthCheckPercentageHealthy'
  79. ],
  80. 'AWS/SNS': [
  81. 'NumberOfMessagesPublished', 'PublishSize', 'NumberOfNotificationsDelivered', 'NumberOfNotificationsFailed'
  82. ],
  83. 'AWS/SQS': [
  84. 'NumberOfMessagesSent', 'SentMessageSize', 'NumberOfMessagesReceived', 'NumberOfEmptyReceives', 'NumberOfMessagesDeleted', 'ApproximateNumberOfMessagesDelayed', 'ApproximateNumberOfMessagesVisible', 'ApproximateNumberOfMessagesNotVisible'
  85. ],
  86. 'AWS/S3': [
  87. 'BucketSizeBytes', 'NumberOfObjects'
  88. ],
  89. 'AWS/SWF': [
  90. 'DecisionTaskScheduleToStartTime', 'DecisionTaskStartToCloseTime', 'DecisionTasksCompleted', 'StartedDecisionTasksTimedOutOnClose', 'WorkflowStartToCloseTime', 'WorkflowsCanceled', 'WorkflowsCompleted', 'WorkflowsContinuedAsNew', 'WorkflowsFailed', 'WorkflowsTerminated', 'WorkflowsTimedOut'
  91. ],
  92. 'AWS/StorageGateway': [
  93. 'CacheHitPercent', 'CachePercentUsed', 'CachePercentDirty', 'CloudBytesDownloaded', 'CloudDownloadLatency', 'CloudBytesUploaded', 'UploadBufferFree', 'UploadBufferPercentUsed', 'UploadBufferUsed', 'QueuedWrites', 'ReadBytes', 'ReadTime', 'TotalCacheSize', 'WriteBytes', 'WriteTime', 'WorkingStorageFree', 'WorkingStoragePercentUsed', 'WorkingStorageUsed', 'CacheHitPercent', 'CachePercentUsed', 'CachePercentDirty', 'ReadBytes', 'ReadTime', 'WriteBytes', 'WriteTime', 'QueuedWrites'
  94. ],
  95. 'AWS/WorkSpaces': [
  96. 'Available', 'Unhealthy', 'ConnectionAttempt', 'ConnectionSuccess', 'ConnectionFailure', 'SessionLaunchTime', 'InSessionLatency', 'SessionDisconnect'
  97. ],
  98. };
  99. this.supportedDimensions = {
  100. 'AWS/AutoScaling': [
  101. 'AutoScalingGroupName'
  102. ],
  103. 'AWS/Billing': [
  104. 'ServiceName', 'LinkedAccount', 'Currency'
  105. ],
  106. 'AWS/CloudFront': [
  107. 'DistributionId', 'Region'
  108. ],
  109. 'AWS/CloudSearch': [
  110. ],
  111. 'AWS/DynamoDB': [
  112. 'TableName', 'GlobalSecondaryIndexName', 'Operation'
  113. ],
  114. 'AWS/ElastiCache': [
  115. 'CacheClusterId', 'CacheNodeId'
  116. ],
  117. 'AWS/EBS': [
  118. 'VolumeId'
  119. ],
  120. 'AWS/EC2': [
  121. 'AutoScalingGroupName', 'ImageId', 'InstanceId', 'InstanceType'
  122. ],
  123. 'AWS/ELB': [
  124. 'LoadBalancerName', 'AvailabilityZone'
  125. ],
  126. 'AWS/ElasticMapReduce': [
  127. 'ClusterId', 'JobId'
  128. ],
  129. 'AWS/Kinesis': [
  130. 'StreamName'
  131. ],
  132. 'AWS/ML': [
  133. 'MLModelId', 'RequestMode'
  134. ],
  135. 'AWS/OpsWorks': [
  136. 'StackId', 'LayerId', 'InstanceId'
  137. ],
  138. 'AWS/Redshift': [
  139. 'NodeID', 'ClusterIdentifier'
  140. ],
  141. 'AWS/RDS': [
  142. 'DBInstanceIdentifier', 'DatabaseClass', 'EngineName'
  143. ],
  144. 'AWS/Route53': [
  145. 'HealthCheckId'
  146. ],
  147. 'AWS/SNS': [
  148. 'Application', 'Platform', 'TopicName'
  149. ],
  150. 'AWS/SQS': [
  151. 'QueueName'
  152. ],
  153. 'AWS/S3': [
  154. 'BucketName', 'StorageType'
  155. ],
  156. 'AWS/SWF': [
  157. 'Domain', 'ActivityTypeName', 'ActivityTypeVersion'
  158. ],
  159. 'AWS/StorageGateway': [
  160. 'GatewayId', 'GatewayName', 'VolumeId'
  161. ],
  162. 'AWS/WorkSpaces': [
  163. 'DirectoryId', 'WorkspaceId'
  164. ],
  165. };
  166. /* jshint +W101 */
  167. }
  168. // Called once per panel (graph)
  169. CloudWatchDatasource.prototype.query = function(options) {
  170. var start = convertToCloudWatchTime(options.range.from);
  171. var end = convertToCloudWatchTime(options.range.to);
  172. var queries = [];
  173. _.each(options.targets, _.bind(function(target) {
  174. if (!target.namespace || !target.metricName || _.isEmpty(target.dimensions) || _.isEmpty(target.statistics)) {
  175. return;
  176. }
  177. var query = {};
  178. query.region = templateSrv.replace(target.region, options.scopedVars);
  179. query.namespace = templateSrv.replace(target.namespace, options.scopedVars);
  180. query.metricName = templateSrv.replace(target.metricName, options.scopedVars);
  181. query.dimensions = _.map(_.keys(target.dimensions), function(key) {
  182. return {
  183. Name: templateSrv.replace(key, options.scopedVars),
  184. Value: templateSrv.replace(target.dimensions[key], options.scopedVars)
  185. };
  186. });
  187. query.statistics = getActivatedStatistics(target.statistics);
  188. query.period = parseInt(target.period, 10);
  189. var range = end - start;
  190. // CloudWatch limit datapoints up to 1440
  191. if (range / query.period >= 1440) {
  192. query.period = Math.floor(range / 1440 / 60) * 60;
  193. }
  194. queries.push(query);
  195. }, this));
  196. // No valid targets, return the empty result to save a round trip.
  197. if (_.isEmpty(queries)) {
  198. var d = $q.defer();
  199. d.resolve({ data: [] });
  200. return d.promise;
  201. }
  202. var allQueryPromise = _.map(queries, _.bind(function(query) {
  203. return this.performTimeSeriesQuery(query, start, end);
  204. }, this));
  205. return $q.all(allQueryPromise)
  206. .then(function(allResponse) {
  207. var result = [];
  208. _.each(allResponse, function(response, index) {
  209. var metrics = transformMetricData(response, options.targets[index]);
  210. _.each(metrics, function(m) {
  211. result.push(m);
  212. });
  213. });
  214. return { data: result };
  215. });
  216. };
  217. CloudWatchDatasource.prototype.performTimeSeriesQuery = function(query, start, end) {
  218. var cloudwatch = this.getCloudWatchClient(query.region);
  219. var params = {
  220. Namespace: query.namespace,
  221. MetricName: query.metricName,
  222. Dimensions: query.dimensions,
  223. Statistics: query.statistics,
  224. StartTime: start,
  225. EndTime: end,
  226. Period: query.period
  227. };
  228. var d = $q.defer();
  229. cloudwatch.getMetricStatistics(params, function(err, data) {
  230. if (err) {
  231. return d.reject(err);
  232. }
  233. return d.resolve(data);
  234. });
  235. return d.promise;
  236. };
  237. CloudWatchDatasource.prototype.performSuggestRegion = function() {
  238. return this.supportedRegion;
  239. };
  240. CloudWatchDatasource.prototype.performSuggestNamespace = function() {
  241. return _.keys(this.supportedMetrics);
  242. };
  243. CloudWatchDatasource.prototype.performSuggestMetrics = function(namespace) {
  244. namespace = templateSrv.replace(namespace);
  245. return this.supportedMetrics[namespace] || [];
  246. };
  247. CloudWatchDatasource.prototype.performSuggestDimensionKeys = function(namespace) {
  248. namespace = templateSrv.replace(namespace);
  249. return this.supportedDimensions[namespace] || [];
  250. };
  251. CloudWatchDatasource.prototype.performSuggestDimensionValues = function(region, namespace, metricName, dimensions) {
  252. region = templateSrv.replace(region);
  253. namespace = templateSrv.replace(namespace);
  254. metricName = templateSrv.replace(metricName);
  255. var cloudwatch = this.getCloudWatchClient(region);
  256. var params = {
  257. Namespace: namespace,
  258. MetricName: metricName
  259. };
  260. if (!_.isEmpty(dimensions)) {
  261. params.Dimensions = _.map(_.keys(dimensions), function(key) {
  262. return {
  263. Name: templateSrv.replace(key),
  264. Value: templateSrv.replace(dimensions[key])
  265. };
  266. });
  267. }
  268. var d = $q.defer();
  269. cloudwatch.listMetrics(params, function(err, data) {
  270. if (err) {
  271. return d.reject(err);
  272. }
  273. var suggestData = _.chain(data.Metrics)
  274. .map(function(metric) {
  275. return metric.Dimensions;
  276. })
  277. .reject(function(metric) {
  278. return _.isEmpty(metric);
  279. })
  280. .value();
  281. return d.resolve(suggestData);
  282. });
  283. return d.promise;
  284. };
  285. CloudWatchDatasource.prototype.getTemplateVariableNames = function() {
  286. var variables = [];
  287. templateSrv.fillVariableValuesForUrl(variables);
  288. return _.map(_.keys(variables), function(k) {
  289. return k.replace(/var-/, '$');
  290. });
  291. };
  292. CloudWatchDatasource.prototype.metricFindQuery = function(query) {
  293. var region;
  294. var namespace;
  295. var metricName;
  296. var transformSuggestData = function(suggestData) {
  297. return _.map(suggestData, function(v) {
  298. return { text: v };
  299. });
  300. };
  301. var d = $q.defer();
  302. var regionQuery = query.match(/^region\(\)/);
  303. if (regionQuery) {
  304. d.resolve(transformSuggestData(this.performSuggestRegion()));
  305. return d.promise;
  306. }
  307. var namespaceQuery = query.match(/^namespace\(\)/);
  308. if (namespaceQuery) {
  309. d.resolve(transformSuggestData(this.performSuggestNamespace()));
  310. return d.promise;
  311. }
  312. var metricNameQuery = query.match(/^metrics\(([^\)]+?)\)/);
  313. if (metricNameQuery) {
  314. namespace = templateSrv.replace(metricNameQuery[1]);
  315. d.resolve(transformSuggestData(this.performSuggestMetrics(namespace)));
  316. return d.promise;
  317. }
  318. var dimensionKeysQuery = query.match(/^dimension_keys\(([^\)]+?)\)/);
  319. if (dimensionKeysQuery) {
  320. namespace = templateSrv.replace(dimensionKeysQuery[1]);
  321. d.resolve(transformSuggestData(this.performSuggestDimensionKeys(namespace)));
  322. return d.promise;
  323. }
  324. var dimensionValuesQuery = query.match(/^dimension_values\(([^,]+?),\s?([^,]+?),\s?([^,]+?)(,\s?([^)]*))?\)/);
  325. if (dimensionValuesQuery) {
  326. region = templateSrv.replace(dimensionValuesQuery[1]);
  327. namespace = templateSrv.replace(dimensionValuesQuery[2]);
  328. metricName = templateSrv.replace(dimensionValuesQuery[3]);
  329. var dimensionPart = templateSrv.replace(dimensionValuesQuery[5]);
  330. var dimensions = {};
  331. if (!_.isEmpty(dimensionPart)) {
  332. _.each(dimensionPart.split(','), function(v) {
  333. var t = v.split('=');
  334. if (t.length !== 2) {
  335. throw new Error('Invalid query format');
  336. }
  337. dimensions[t[0]] = t[1];
  338. });
  339. }
  340. return this.performSuggestDimensionValues(region, namespace, metricName, dimensions)
  341. .then(function(suggestData) {
  342. return _.map(suggestData, function(dimensions) {
  343. var result = _.chain(dimensions)
  344. .sortBy(function(dimension) {
  345. return dimension.Name;
  346. })
  347. .map(function(dimension) {
  348. return dimension.Name + '=' + dimension.Value;
  349. })
  350. .value().join(',');
  351. return { text: result };
  352. });
  353. });
  354. }
  355. return $q.when([]);
  356. };
  357. CloudWatchDatasource.prototype.testDatasource = function() {
  358. /* use billing metrics for test */
  359. var region = 'us-east-1';
  360. var namespace = 'AWS/Billing';
  361. var metricName = 'EstimatedCharges';
  362. var dimensions = {};
  363. return this.performSuggestDimensionValues(region, namespace, metricName, dimensions).then(function () {
  364. return { status: 'success', message: 'Data source is working', title: 'Success' };
  365. });
  366. };
  367. CloudWatchDatasource.prototype.getCloudWatchClient = function(region) {
  368. if (!this.proxyMode) {
  369. return new AWS.CloudWatch({
  370. region: region,
  371. accessKeyId: this.credentials.accessKeyId,
  372. secretAccessKey: this.credentials.secretAccessKey
  373. });
  374. } else {
  375. var self = this;
  376. var generateRequestProxy = function(service, action) {
  377. return function(params, callback) {
  378. var data = {
  379. region: region,
  380. service: service,
  381. action: action,
  382. parameters: params
  383. };
  384. var options = {
  385. method: 'POST',
  386. url: self.proxyUrl,
  387. data: data
  388. };
  389. $http(options).then(function(response) {
  390. callback(null, response.data);
  391. }, function(err) {
  392. callback(err, []);
  393. });
  394. };
  395. };
  396. return {
  397. getMetricStatistics: generateRequestProxy('CloudWatch', 'GetMetricStatistics'),
  398. listMetrics: generateRequestProxy('CloudWatch', 'ListMetrics')
  399. };
  400. }
  401. };
  402. CloudWatchDatasource.prototype.getDefaultRegion = function() {
  403. return this.defaultRegion;
  404. };
  405. function transformMetricData(md, options) {
  406. var result = [];
  407. var dimensionPart = templateSrv.replace(JSON.stringify(options.dimensions));
  408. _.each(getActivatedStatistics(options.statistics), function(s) {
  409. var metricLabel = md.Label + '_' + s + dimensionPart;
  410. var dps = _.map(md.Datapoints, function(value) {
  411. return [value[s], new Date(value.Timestamp).getTime()];
  412. });
  413. dps = _.sortBy(dps, function(dp) { return dp[1]; });
  414. result.push({ target: metricLabel, datapoints: dps });
  415. });
  416. return result;
  417. }
  418. function getActivatedStatistics(statistics) {
  419. var activatedStatistics = [];
  420. _.each(statistics, function(v, k) {
  421. if (v) {
  422. activatedStatistics.push(k);
  423. }
  424. });
  425. return activatedStatistics;
  426. }
  427. function convertToCloudWatchTime(date) {
  428. return Math.round(kbn.parseDate(date).getTime() / 1000);
  429. }
  430. return CloudWatchDatasource;
  431. });
  432. });