Mitsuhiro Tanda 8 лет назад
Родитель
Сommit
c140d7aa06

+ 218 - 0
pkg/tsdb/cloudwatch/annotation_query.go

@@ -0,0 +1,218 @@
+package cloudwatch
+
+import (
+	"context"
+	"errors"
+	"time"
+
+	"github.com/aws/aws-sdk-go/aws"
+	"github.com/aws/aws-sdk-go/aws/session"
+	"github.com/aws/aws-sdk-go/service/cloudwatch"
+	"github.com/grafana/grafana/pkg/components/simplejson"
+	"github.com/grafana/grafana/pkg/tsdb"
+)
+
+func (e *CloudWatchExecutor) executeAnnotationQuery(ctx context.Context, queryContext *tsdb.TsdbQuery) (*tsdb.Response, error) {
+	result := &tsdb.Response{
+		Results: make(map[string]*tsdb.QueryResult),
+	}
+	firstQuery := queryContext.Queries[0]
+	queryResult := &tsdb.QueryResult{Meta: simplejson.New(), RefId: firstQuery.RefId}
+
+	parameters := firstQuery.Model
+	usePrefixMatch := parameters.Get("prefixMatching").MustBool()
+	region := parameters.Get("region").MustString("")
+	namespace := parameters.Get("namespace").MustString("")
+	metricName := parameters.Get("metricName").MustString("")
+	dimensions := parameters.Get("dimensions").MustMap()
+	statistics := parameters.Get("statistics").MustStringArray()
+	extendedStatistics := parameters.Get("extendedStatistics").MustStringArray()
+	period := int64(300)
+	if usePrefixMatch {
+		period = int64(parameters.Get("period").MustInt(0))
+	}
+	actionPrefix := parameters.Get("actionPrefix").MustString("")
+	alarmNamePrefix := parameters.Get("alarmNamePrefix").MustString("")
+
+	dsInfo := e.getDsInfo(region)
+	cfg, err := getAwsConfig(dsInfo)
+	if err != nil {
+		return nil, errors.New("Failed to call cloudwatch:ListMetrics")
+	}
+	sess, err := session.NewSession(cfg)
+	if err != nil {
+		return nil, errors.New("Failed to call cloudwatch:ListMetrics")
+	}
+	svc := cloudwatch.New(sess, cfg)
+
+	var alarmNames []*string
+	if usePrefixMatch {
+		params := &cloudwatch.DescribeAlarmsInput{
+			MaxRecords:      aws.Int64(100),
+			ActionPrefix:    aws.String(actionPrefix),
+			AlarmNamePrefix: aws.String(alarmNamePrefix),
+		}
+		resp, err := svc.DescribeAlarms(params)
+		if err != nil {
+			return nil, errors.New("Failed to call cloudwatch:DescribeAlarms")
+		}
+		alarmNames = filterAlarms(resp, namespace, metricName, dimensions, statistics, extendedStatistics, period)
+	} else {
+		if region == "" || namespace == "" || metricName == "" || len(statistics) == 0 {
+			return result, nil
+		}
+
+		var qd []*cloudwatch.Dimension
+		for k, v := range dimensions {
+			if vv, ok := v.(string); ok {
+				qd = append(qd, &cloudwatch.Dimension{
+					Name:  aws.String(k),
+					Value: aws.String(vv),
+				})
+			}
+		}
+		for _, s := range statistics {
+			params := &cloudwatch.DescribeAlarmsForMetricInput{
+				Namespace:  aws.String(namespace),
+				MetricName: aws.String(metricName),
+				Period:     aws.Int64(int64(period)),
+				Dimensions: qd,
+				Statistic:  aws.String(s),
+			}
+			resp, err := svc.DescribeAlarmsForMetric(params)
+			if err != nil {
+				return nil, errors.New("Failed to call cloudwatch:DescribeAlarmsForMetric")
+			}
+			for _, alarm := range resp.MetricAlarms {
+				alarmNames = append(alarmNames, alarm.AlarmName)
+			}
+		}
+		for _, s := range extendedStatistics {
+			params := &cloudwatch.DescribeAlarmsForMetricInput{
+				Namespace:         aws.String(namespace),
+				MetricName:        aws.String(metricName),
+				Period:            aws.Int64(int64(period)),
+				Dimensions:        qd,
+				ExtendedStatistic: aws.String(s),
+			}
+			resp, err := svc.DescribeAlarmsForMetric(params)
+			if err != nil {
+				return nil, errors.New("Failed to call cloudwatch:DescribeAlarmsForMetric")
+			}
+			for _, alarm := range resp.MetricAlarms {
+				alarmNames = append(alarmNames, alarm.AlarmName)
+			}
+		}
+	}
+
+	startTime, err := queryContext.TimeRange.ParseFrom()
+	if err != nil {
+		return nil, err
+	}
+
+	endTime, err := queryContext.TimeRange.ParseTo()
+	if err != nil {
+		return nil, err
+	}
+
+	annotations := make([]map[string]string, 0)
+	for _, alarmName := range alarmNames {
+		params := &cloudwatch.DescribeAlarmHistoryInput{
+			AlarmName: alarmName,
+			StartDate: aws.Time(startTime),
+			EndDate:   aws.Time(endTime),
+		}
+		resp, err := svc.DescribeAlarmHistory(params)
+		if err != nil {
+			return nil, errors.New("Failed to call cloudwatch:DescribeAlarmHistory")
+		}
+		for _, history := range resp.AlarmHistoryItems {
+			annotation := make(map[string]string)
+			annotation["time"] = history.Timestamp.UTC().Format(time.RFC3339)
+			annotation["title"] = *history.AlarmName
+			annotation["tags"] = *history.HistoryItemType
+			annotation["text"] = *history.HistorySummary
+			annotations = append(annotations, annotation)
+		}
+	}
+
+	transformAnnotationToTable(annotations, queryResult)
+	result.Results[firstQuery.RefId] = queryResult
+	return result, err
+}
+
+func transformAnnotationToTable(data []map[string]string, result *tsdb.QueryResult) {
+	table := &tsdb.Table{
+		Columns: make([]tsdb.TableColumn, 4),
+		Rows:    make([]tsdb.RowValues, 0),
+	}
+	table.Columns[0].Text = "time"
+	table.Columns[1].Text = "title"
+	table.Columns[2].Text = "tags"
+	table.Columns[3].Text = "text"
+
+	for _, r := range data {
+		values := make([]interface{}, 4)
+		values[0] = r["time"]
+		values[1] = r["title"]
+		values[2] = r["tags"]
+		values[3] = r["text"]
+		table.Rows = append(table.Rows, values)
+	}
+	result.Tables = append(result.Tables, table)
+	result.Meta.Set("rowCount", len(data))
+}
+
+func filterAlarms(alarms *cloudwatch.DescribeAlarmsOutput, namespace string, metricName string, dimensions map[string]interface{}, statistics []string, extendedStatistics []string, period int64) []*string {
+	alarmNames := make([]*string, 0)
+
+	for _, alarm := range alarms.MetricAlarms {
+		if namespace != "" && *alarm.Namespace != namespace {
+			continue
+		}
+		if metricName != "" && *alarm.MetricName != metricName {
+			continue
+		}
+
+		match := true
+		for _, d := range alarm.Dimensions {
+			if _, ok := dimensions[*d.Name]; !ok {
+				match = false
+			}
+		}
+		if !match {
+			continue
+		}
+		if period != 0 && *alarm.Period != period {
+			continue
+		}
+
+		if len(statistics) != 0 {
+			found := false
+			for _, s := range statistics {
+				if *alarm.Statistic == s {
+					found = true
+				}
+			}
+			if !found {
+				continue
+			}
+		}
+
+		if len(extendedStatistics) != 0 {
+			found := false
+			for _, s := range extendedStatistics {
+				if *alarm.Statistic == s {
+					found = true
+				}
+			}
+			if !found {
+				continue
+			}
+		}
+
+		alarmNames = append(alarmNames, alarm.AlarmName)
+	}
+
+	return alarmNames
+}

+ 3 - 0
pkg/tsdb/cloudwatch/cloudwatch.go

@@ -60,6 +60,9 @@ func (e *CloudWatchExecutor) Query(ctx context.Context, dsInfo *models.DataSourc
 	case "metricFindQuery":
 		result, err = e.executeMetricFindQuery(ctx, queryContext)
 		break
+	case "annotationQuery":
+		result, err = e.executeAnnotationQuery(ctx, queryContext)
+		break
 	case "timeSeriesQuery":
 		fallthrough
 	default:

+ 0 - 2
public/app/plugins/datasource/cloudwatch/annotation_query.d.ts

@@ -1,2 +0,0 @@
-declare var test: any;
-export default test;

+ 0 - 106
public/app/plugins/datasource/cloudwatch/annotation_query.js

@@ -1,106 +0,0 @@
-define([
-  'lodash',
-],
-function (_) {
-  'use strict';
-
-  function CloudWatchAnnotationQuery(datasource, annotation, $q, templateSrv) {
-    this.datasource = datasource;
-    this.annotation = annotation;
-    this.$q = $q;
-    this.templateSrv = templateSrv;
-  }
-
-  CloudWatchAnnotationQuery.prototype.process = function(from, to) {
-    var self = this;
-    var usePrefixMatch = this.annotation.prefixMatching;
-    var region = this.templateSrv.replace(this.annotation.region);
-    var namespace = this.templateSrv.replace(this.annotation.namespace);
-    var metricName = this.templateSrv.replace(this.annotation.metricName);
-    var dimensions = this.datasource.convertDimensionFormat(this.annotation.dimensions);
-    var statistics = _.map(this.annotation.statistics, function(s) { return self.templateSrv.replace(s); });
-    var defaultPeriod = usePrefixMatch ? '' : '300';
-    var period = this.annotation.period || defaultPeriod;
-    period = parseInt(period, 10);
-    var actionPrefix = this.annotation.actionPrefix || '';
-    var alarmNamePrefix = this.annotation.alarmNamePrefix || '';
-
-    var d = this.$q.defer();
-    var allQueryPromise;
-    if (usePrefixMatch) {
-      allQueryPromise = [
-        this.datasource.performDescribeAlarms(region, actionPrefix, alarmNamePrefix, [], '').then(function(alarms) {
-          alarms.MetricAlarms = self.filterAlarms(alarms, namespace, metricName, dimensions, statistics, period);
-          return alarms;
-        })
-      ];
-    } else {
-      if (!region || !namespace || !metricName || _.isEmpty(statistics)) { return this.$q.when([]); }
-
-      allQueryPromise = _.map(statistics, function(statistic) {
-        return self.datasource.performDescribeAlarmsForMetric(region, namespace, metricName, dimensions, statistic, period);
-      });
-    }
-    this.$q.all(allQueryPromise).then(function(alarms) {
-      var eventList = [];
-
-      var start = self.datasource.convertToCloudWatchTime(from, false);
-      var end = self.datasource.convertToCloudWatchTime(to, true);
-      _.chain(alarms)
-      .map('MetricAlarms')
-      .flatten()
-      .each(function(alarm) {
-        if (!alarm) {
-          d.resolve(eventList);
-          return;
-        }
-
-        self.datasource.performDescribeAlarmHistory(region, alarm.AlarmName, start, end).then(function(history) {
-          _.each(history.AlarmHistoryItems, function(h) {
-            var event = {
-              annotation: self.annotation,
-              time: Date.parse(h.Timestamp),
-              title: h.AlarmName,
-              tags: [h.HistoryItemType],
-              text: h.HistorySummary
-            };
-
-            eventList.push(event);
-          });
-
-          d.resolve(eventList);
-        });
-      })
-      .value();
-    });
-
-    return d.promise;
-  };
-
-  CloudWatchAnnotationQuery.prototype.filterAlarms = function(alarms, namespace, metricName, dimensions, statistics, period) {
-    return _.filter(alarms.MetricAlarms, function(alarm) {
-      if (!_.isEmpty(namespace) && alarm.Namespace !== namespace) {
-        return false;
-      }
-      if (!_.isEmpty(metricName) && alarm.MetricName !== metricName) {
-        return false;
-      }
-      var sd = function(d) {
-        return d.Name;
-      };
-      var isSameDimensions = JSON.stringify(_.sortBy(alarm.Dimensions, sd)) === JSON.stringify(_.sortBy(dimensions, sd));
-      if (!_.isEmpty(dimensions) && !isSameDimensions) {
-        return false;
-      }
-      if (!_.isEmpty(statistics) && !_.includes(statistics, alarm.Statistic)) {
-        return false;
-      }
-      if (!_.isNaN(period) && alarm.Period !== period) {
-        return false;
-      }
-      return true;
-    });
-  };
-
-  return CloudWatchAnnotationQuery;
-});

+ 42 - 35
public/app/plugins/datasource/cloudwatch/datasource.js

@@ -5,9 +5,8 @@ define([
   'app/core/utils/datemath',
   'app/core/utils/kbn',
   'app/features/templating/variable',
-  './annotation_query',
 ],
-function (angular, _, moment, dateMath, kbn, templatingVariable, CloudWatchAnnotationQuery) {
+function (angular, _, moment, dateMath, kbn, templatingVariable) {
   'use strict';
 
   /** @ngInject */
@@ -262,44 +261,52 @@ function (angular, _, moment, dateMath, kbn, templatingVariable, CloudWatchAnnot
       return $q.when([]);
     };
 
-    this.performDescribeAlarms = function(region, actionPrefix, alarmNamePrefix, alarmNames, stateValue) {
-      return this.awsRequest({
-        region: region,
-        action: 'DescribeAlarms',
-        parameters: { actionPrefix: actionPrefix, alarmNamePrefix: alarmNamePrefix, alarmNames: alarmNames, stateValue: stateValue }
-      });
-    };
-
-    this.performDescribeAlarmsForMetric = function(region, namespace, metricName, dimensions, statistic, period) {
-      var s = _.includes(self.standardStatistics, statistic) ? statistic : '';
-      var es = _.includes(self.standardStatistics, statistic) ? '' : statistic;
-      return this.awsRequest({
-        region: region,
-        action: 'DescribeAlarmsForMetric',
-        parameters: {
-          namespace: namespace,
-          metricName: metricName,
-          dimensions: dimensions,
-          statistic: s,
-          extendedStatistic: es,
-          period: period
-        }
+    this.annotationQuery = function (options) {
+      var annotation = options.annotation;
+      var defaultPeriod = annotation.prefixMatching ? '' : '300';
+      var period = annotation.period || defaultPeriod;
+      period = parseInt(period, 10);
+      var dimensions = {};
+      _.each(annotation.dimensions, function (value, key) {
+        dimensions[templateSrv.replace(key, options.scopedVars)] = templateSrv.replace(value, options.scopedVars);
       });
-    };
+      var parameters = {
+        prefixMatching: annotation.prefixMatching,
+        region: templateSrv.replace(annotation.region),
+        namespace: templateSrv.replace(annotation.namespace),
+        metricName: templateSrv.replace(annotation.metricName),
+        dimensions: dimensions,
+        statistics: _.map(annotation.statistics, function (s) { return templateSrv.replace(s); }),
+        period: period,
+        actionPrefix: annotation.actionPrefix || '',
+        alarmNamePrefix: annotation.alarmNamePrefix || ''
+      };
 
-    this.performDescribeAlarmHistory = function(region, alarmName, startDate, endDate) {
-      return this.awsRequest({
-        region: region,
-        action: 'DescribeAlarmHistory',
-        parameters: { alarmName: alarmName, startDate: startDate, endDate: endDate }
+      return backendSrv.post('/api/tsdb/query', {
+        from: options.range.from,
+        to: options.range.to,
+        queries: [
+          _.extend({
+            refId: 'annotationQuery',
+            intervalMs: 1, // dummy
+            maxDataPoints: 1, // dummy
+            datasourceId: this.instanceSettings.id,
+            type: 'annotationQuery'
+          }, parameters)
+        ]
+      }).then(function (r) {
+        return _.map(r.results['annotationQuery'].tables[0].rows, function (v) {
+          return {
+            annotation: annotation,
+            time: Date.parse(v[0]),
+            title: v[1],
+            tags: [v[2]],
+            text: v[3]
+          };
+        });
       });
     };
 
-    this.annotationQuery = function(options) {
-      var annotationQuery = new CloudWatchAnnotationQuery(this, options.annotation, $q, templateSrv);
-      return annotationQuery.process(options.range.from, options.range.to);
-    };
-
     this.testDatasource = function() {
       /* use billing metrics for test */
       var region = this.defaultRegion;

+ 0 - 81
public/app/plugins/datasource/cloudwatch/specs/annotation_query_specs.ts

@@ -1,81 +0,0 @@
-import "../datasource";
-import {describe, beforeEach, it, expect, angularMocks} from 'test/lib/common';
-import moment from 'moment';
-import helpers from 'test/specs/helpers';
-import CloudWatchDatasource from "../datasource";
-import CloudWatchAnnotationQuery from '../annotation_query';
-
-describe('CloudWatchAnnotationQuery', function() {
-  var ctx = new helpers.ServiceTestContext();
-  var instanceSettings = {
-    jsonData: {defaultRegion: 'us-east-1', access: 'proxy'},
-  };
-
-  beforeEach(angularMocks.module('grafana.core'));
-  beforeEach(angularMocks.module('grafana.services'));
-  beforeEach(angularMocks.module('grafana.controllers'));
-  beforeEach(ctx.providePhase(['templateSrv', 'backendSrv']));
-
-  beforeEach(angularMocks.inject(function($q, $rootScope, $httpBackend, $injector) {
-    ctx.$q = $q;
-    ctx.$httpBackend =  $httpBackend;
-    ctx.$rootScope = $rootScope;
-    ctx.ds = $injector.instantiate(CloudWatchDatasource, {instanceSettings: instanceSettings});
-  }));
-
-  describe('When performing annotationQuery', function() {
-    var parameter = {
-      annotation: {
-        region: 'us-east-1',
-        namespace: 'AWS/EC2',
-        metricName: 'CPUUtilization',
-        dimensions: {
-          InstanceId: 'i-12345678'
-        },
-        statistics: ['Average'],
-        period: 300
-      },
-      range: {
-        from: moment(1443438674760),
-        to: moment(1443460274760)
-      }
-    };
-    var alarmResponse = {
-      MetricAlarms: [
-        {
-          AlarmName: 'test_alarm_name'
-        }
-      ]
-    };
-    var historyResponse = {
-      AlarmHistoryItems: [
-        {
-          Timestamp: '2015-01-01T00:00:00.000Z',
-          HistoryItemType: 'StateUpdate',
-          AlarmName: 'test_alarm_name',
-          HistoryData: '{}',
-          HistorySummary: 'test_history_summary'
-        }
-      ]
-    };
-    beforeEach(function() {
-      ctx.backendSrv.datasourceRequest = function(params) {
-        switch (params.data.action) {
-        case 'DescribeAlarmsForMetric':
-          return ctx.$q.when({data: alarmResponse});
-        case 'DescribeAlarmHistory':
-          return ctx.$q.when({data: historyResponse});
-        }
-      };
-    });
-    it('should return annotation list', function(done) {
-      var annotationQuery = new CloudWatchAnnotationQuery(ctx.ds, parameter.annotation, ctx.$q, ctx.templateSrv);
-      annotationQuery.process(parameter.range.from, parameter.range.to).then(function(result) {
-        expect(result[0].title).to.be('test_alarm_name');
-        expect(result[0].text).to.be('test_history_summary');
-        done();
-      });
-      ctx.$rootScope.$apply();
-    });
-  });
-});