From be26c017f64c9cfffbc35a75c607e61031879541 Mon Sep 17 00:00:00 2001 From: Mitsuhiro Tanda Date: Tue, 6 Dec 2016 16:12:30 +0900 Subject: [PATCH] (cloudwatch) percentile support (#6634) * support extended statistics * handle different response format for extended statistics --- pkg/api/cloudwatch/cloudwatch.go | 50 +++++++++------ .../datasource/cloudwatch/datasource.js | 30 ++++++++- .../cloudwatch/query_parameter_ctrl.js | 13 ++-- .../cloudwatch/specs/datasource_specs.ts | 61 +++++++++++++++++++ 4 files changed, 125 insertions(+), 29 deletions(-) diff --git a/pkg/api/cloudwatch/cloudwatch.go b/pkg/api/cloudwatch/cloudwatch.go index 991d336411c..06c4b171d22 100644 --- a/pkg/api/cloudwatch/cloudwatch.go +++ b/pkg/api/cloudwatch/cloudwatch.go @@ -181,25 +181,33 @@ func handleGetMetricStatistics(req *cwRequest, c *middleware.Context) { reqParam := &struct { Parameters struct { - Namespace string `json:"namespace"` - MetricName string `json:"metricName"` - Dimensions []*cloudwatch.Dimension `json:"dimensions"` - Statistics []*string `json:"statistics"` - StartTime int64 `json:"startTime"` - EndTime int64 `json:"endTime"` - Period int64 `json:"period"` + Namespace string `json:"namespace"` + MetricName string `json:"metricName"` + Dimensions []*cloudwatch.Dimension `json:"dimensions"` + Statistics []*string `json:"statistics"` + ExtendedStatistics []*string `json:"extendedStatistics"` + StartTime int64 `json:"startTime"` + EndTime int64 `json:"endTime"` + Period int64 `json:"period"` } `json:"parameters"` }{} json.Unmarshal(req.Body, reqParam) params := &cloudwatch.GetMetricStatisticsInput{ - Namespace: aws.String(reqParam.Parameters.Namespace), - MetricName: aws.String(reqParam.Parameters.MetricName), - Dimensions: reqParam.Parameters.Dimensions, - Statistics: reqParam.Parameters.Statistics, - StartTime: aws.Time(time.Unix(reqParam.Parameters.StartTime, 0)), - EndTime: aws.Time(time.Unix(reqParam.Parameters.EndTime, 0)), - Period: aws.Int64(reqParam.Parameters.Period), + Namespace: aws.String(reqParam.Parameters.Namespace), + MetricName: aws.String(reqParam.Parameters.MetricName), + Dimensions: reqParam.Parameters.Dimensions, + Statistics: reqParam.Parameters.Statistics, + ExtendedStatistics: reqParam.Parameters.ExtendedStatistics, + StartTime: aws.Time(time.Unix(reqParam.Parameters.StartTime, 0)), + EndTime: aws.Time(time.Unix(reqParam.Parameters.EndTime, 0)), + Period: aws.Int64(reqParam.Parameters.Period), + } + if len(reqParam.Parameters.Statistics) != 0 { + params.Statistics = reqParam.Parameters.Statistics + } + if len(reqParam.Parameters.ExtendedStatistics) != 0 { + params.ExtendedStatistics = reqParam.Parameters.ExtendedStatistics } resp, err := svc.GetMetricStatistics(params) @@ -292,11 +300,12 @@ func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) { reqParam := &struct { Parameters struct { - Namespace string `json:"namespace"` - MetricName string `json:"metricName"` - Dimensions []*cloudwatch.Dimension `json:"dimensions"` - Statistic string `json:"statistic"` - Period int64 `json:"period"` + Namespace string `json:"namespace"` + MetricName string `json:"metricName"` + Dimensions []*cloudwatch.Dimension `json:"dimensions"` + Statistic string `json:"statistic"` + ExtendedStatistic string `json:"extendedStatistic"` + Period int64 `json:"period"` } `json:"parameters"` }{} json.Unmarshal(req.Body, reqParam) @@ -312,6 +321,9 @@ func handleDescribeAlarmsForMetric(req *cwRequest, c *middleware.Context) { if reqParam.Parameters.Statistic != "" { params.Statistic = aws.String(reqParam.Parameters.Statistic) } + if reqParam.Parameters.ExtendedStatistic != "" { + params.ExtendedStatistic = aws.String(reqParam.Parameters.ExtendedStatistic) + } resp, err := svc.DescribeAlarmsForMetric(params) if err != nil { diff --git a/public/app/plugins/datasource/cloudwatch/datasource.js b/public/app/plugins/datasource/cloudwatch/datasource.js index 07648779648..acef572367d 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.js +++ b/public/app/plugins/datasource/cloudwatch/datasource.js @@ -16,6 +16,13 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) { this.supportMetrics = true; this.proxyUrl = instanceSettings.url; this.defaultRegion = instanceSettings.jsonData.defaultRegion; + this.standardStatistics = [ + 'Average', + 'Maximum', + 'Minimum', + 'Sum', + 'SampleCount' + ]; var self = this; this.query = function(options) { @@ -98,6 +105,8 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) { }; this.performTimeSeriesQuery = function(query, start, end) { + var statistics = _.filter(query.statistics, function(s) { return _.includes(self.standardStatistics, s); }); + var extendedStatistics = _.reject(query.statistics, function(s) { return _.includes(self.standardStatistics, s); }); return this.awsRequest({ region: query.region, action: 'GetMetricStatistics', @@ -105,7 +114,8 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) { namespace: query.namespace, metricName: query.metricName, dimensions: query.dimensions, - statistics: query.statistics, + statistics: statistics, + extendedStatistics: extendedStatistics, startTime: start, endTime: end, period: query.period @@ -268,10 +278,19 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) { }; 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: statistic, period: period } + parameters: { + namespace: namespace, + metricName: metricName, + dimensions: dimensions, + statistic: s, + extendedStatistic: es, + period: period + } }); }; @@ -338,6 +357,7 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) { var periodMs = options.period * 1000; return _.map(options.statistics, function(stat) { + var extended = !_.includes(self.standardStatistics, stat); var dps = []; var lastTimestamp = null; _.chain(md.Datapoints) @@ -350,7 +370,11 @@ function (angular, _, moment, dateMath, kbn, CloudWatchAnnotationQuery) { dps.push([null, lastTimestamp + periodMs]); } lastTimestamp = timestamp; - dps.push([dp[stat], timestamp]); + if (!extended) { + dps.push([dp[stat], timestamp]); + } else { + dps.push([dp.ExtendedStatistics[stat], timestamp]); + } }) .value(); diff --git a/public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.js b/public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.js index c239291ed8e..f7db203826b 100644 --- a/public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.js +++ b/public/app/plugins/datasource/cloudwatch/query_parameter_ctrl.js @@ -61,14 +61,13 @@ function (angular, _) { }; $scope.getStatSegments = function() { - return $q.when([ + return $q.when(_.flatten([ angular.copy($scope.removeStatSegment), - uiSegmentSrv.getSegmentForValue('Average'), - uiSegmentSrv.getSegmentForValue('Maximum'), - uiSegmentSrv.getSegmentForValue('Minimum'), - uiSegmentSrv.getSegmentForValue('Sum'), - uiSegmentSrv.getSegmentForValue('SampleCount'), - ]); + _.map($scope.datasource.standardStatistics, function(s) { + return uiSegmentSrv.getSegmentForValue(s); + }), + uiSegmentSrv.getSegmentForValue('pNN.NN'), + ])); }; $scope.statSegmentChanged = function(segment, index) { diff --git a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts index 0e7ae5081e6..a834fdb2973 100644 --- a/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts +++ b/public/app/plugins/datasource/cloudwatch/specs/datasource_specs.ts @@ -161,6 +161,67 @@ describe('CloudWatchDatasource', function() { }); }); + describe('When performing CloudWatch query for extended statistics', function() { + var requestParams; + + var query = { + range: { from: 'now-1h', to: 'now' }, + targets: [ + { + region: 'us-east-1', + namespace: 'AWS/ApplicationELB', + metricName: 'TargetResponseTime', + dimensions: { + LoadBalancer: 'lb', + TargetGroup: 'tg' + }, + statistics: ['p90.00'], + period: 300 + } + ] + }; + + var response = { + Datapoints: [ + { + ExtendedStatistics: { + 'p90.00': 1 + }, + Timestamp: 'Wed Dec 31 1969 16:00:00 GMT-0800 (PST)' + }, + { + ExtendedStatistics: { + 'p90.00': 2 + }, + Timestamp: 'Wed Dec 31 1969 16:05:00 GMT-0800 (PST)' + }, + { + ExtendedStatistics: { + 'p90.00': 5 + }, + Timestamp: 'Wed Dec 31 1969 16:15:00 GMT-0800 (PST)' + } + ], + Label: 'TargetResponseTime' + }; + + beforeEach(function() { + ctx.backendSrv.datasourceRequest = function(params) { + requestParams = params; + return ctx.$q.when({data: response}); + }; + }); + + it('should return series list', function(done) { + ctx.ds.query(query).then(function(result) { + expect(result.data[0].target).to.be('TargetResponseTime_p90.00'); + expect(result.data[0].datapoints[0][0]).to.be(response.Datapoints[0].ExtendedStatistics['p90.00']); + done(); + }); + ctx.$rootScope.$apply(); + }); + }); + function describeMetricFindQuery(query, func) { describe('metricFindQuery ' + query, () => { let scenario: any = {};