diff --git a/pkg/api/cloudwatch/cloudwatch.go b/pkg/api/cloudwatch/cloudwatch.go index 6f8a17ab69d..7b6a189cfde 100644 --- a/pkg/api/cloudwatch/cloudwatch.go +++ b/pkg/api/cloudwatch/cloudwatch.go @@ -11,7 +11,6 @@ import ( "time" "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/awsutil" "github.com/aws/aws-sdk-go/aws/credentials" "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds" "github.com/aws/aws-sdk-go/aws/credentials/endpointcreds" @@ -19,7 +18,6 @@ import ( "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/cloudwatch" "github.com/aws/aws-sdk-go/service/sts" - "github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/middleware" m "github.com/grafana/grafana/pkg/models" ) @@ -73,7 +71,6 @@ func (req *cwRequest) GetDatasourceInfo() *DatasourceInfo { func init() { actionHandlers = map[string]actionHandler{ - "ListMetrics": handleListMetrics, "DescribeAlarms": handleDescribeAlarms, "DescribeAlarmsForMetric": handleDescribeAlarmsForMetric, "DescribeAlarmHistory": handleDescribeAlarmHistory, @@ -216,52 +213,6 @@ func getAwsConfig(req *cwRequest) (*aws.Config, error) { return cfg, nil } -func handleListMetrics(req *cwRequest, c *middleware.Context) { - cfg, err := getAwsConfig(req) - if err != nil { - c.JsonApiErr(500, "Unable to call AWS API", err) - return - } - sess, err := session.NewSession(cfg) - if err != nil { - c.JsonApiErr(500, "Unable to call AWS API", err) - return - } - svc := cloudwatch.New(sess, cfg) - - reqParam := &struct { - Parameters struct { - Namespace string `json:"namespace"` - MetricName string `json:"metricName"` - Dimensions []*cloudwatch.DimensionFilter `json:"dimensions"` - } `json:"parameters"` - }{} - json.Unmarshal(req.Body, reqParam) - - params := &cloudwatch.ListMetricsInput{ - Namespace: aws.String(reqParam.Parameters.Namespace), - MetricName: aws.String(reqParam.Parameters.MetricName), - Dimensions: reqParam.Parameters.Dimensions, - } - - var resp cloudwatch.ListMetricsOutput - err = svc.ListMetricsPages(params, - func(page *cloudwatch.ListMetricsOutput, lastPage bool) bool { - metrics.M_Aws_CloudWatch_ListMetrics.Inc() - metrics, _ := awsutil.ValuesAtPath(page, "Metrics") - for _, metric := range metrics { - resp.Metrics = append(resp.Metrics, metric.(*cloudwatch.Metric)) - } - return !lastPage - }) - if err != nil { - c.JsonApiErr(500, "Unable to call AWS API", err) - return - } - - c.JSON(200, resp) -} - func handleDescribeAlarms(req *cwRequest, c *middleware.Context) { cfg, err := getAwsConfig(req) if err != nil { diff --git a/pkg/tsdb/cloudwatch/metric_find_query.go b/pkg/tsdb/cloudwatch/metric_find_query.go index db20ccd7dc5..2f3abd4b36b 100644 --- a/pkg/tsdb/cloudwatch/metric_find_query.go +++ b/pkg/tsdb/cloudwatch/metric_find_query.go @@ -176,6 +176,9 @@ func (e *CloudWatchExecutor) executeMetricFindQuery(ctx context.Context, queries case "dimension_keys": data, err = e.handleGetDimensions(ctx, parameters, queryContext) break + case "dimension_values": + data, err = e.handleGetDimensionValues(ctx, parameters, queryContext) + break case "ebs_volume_ids": data, err = e.handleGetEbsVolumeIds(ctx, parameters, queryContext) break @@ -328,6 +331,49 @@ func (e *CloudWatchExecutor) handleGetDimensions(ctx context.Context, parameters return result, nil } +func (e *CloudWatchExecutor) handleGetDimensionValues(ctx context.Context, parameters *simplejson.Json, queryContext *tsdb.QueryContext) ([]suggestData, error) { + region := parameters.Get("region").MustString() + namespace := parameters.Get("namespace").MustString() + metricName := parameters.Get("metricName").MustString() + dimensionKey := parameters.Get("dimensionKey").MustString() + dimensionsJson := parameters.Get("dimensionKey").MustMap() + + var dimensions []*cloudwatch.DimensionFilter + for _, d := range dimensionsJson { + if dd, ok := d.(map[string]string); ok { + dimensions = append(dimensions, &cloudwatch.DimensionFilter{ + Name: aws.String(dd["Name"]), + Value: aws.String(dd["Value"]), + }) + } + } + + metrics, err := e.cloudwatchListMetrics(region, namespace, metricName, dimensions) + if err != nil { + return nil, err + } + + result := make([]suggestData, 0) + dupCheck := make(map[string]bool) + for _, metric := range metrics.Metrics { + for _, dim := range metric.Dimensions { + if *dim.Name == dimensionKey { + if _, exists := dupCheck[*dim.Value]; exists { + continue + } + dupCheck[*dim.Value] = true + result = append(result, suggestData{Text: *dim.Value, Value: *dim.Value}) + } + } + } + + sort.Slice(result, func(i, j int) bool { + return result[i].Text < result[j].Text + }) + + return result, nil +} + func (e *CloudWatchExecutor) handleGetEbsVolumeIds(ctx context.Context, parameters *simplejson.Json, queryContext *tsdb.QueryContext) ([]suggestData, error) { region := parameters.Get("region").MustString() instanceId := parameters.Get("instanceId").MustString() @@ -428,6 +474,41 @@ func getAwsConfig(dsInfo *cwapi.DatasourceInfo) (*aws.Config, error) { return cfg, nil } +func (e *CloudWatchExecutor) cloudwatchListMetrics(region string, namespace string, metricName string, dimensions []*cloudwatch.DimensionFilter) (*cloudwatch.ListMetricsOutput, error) { + 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) + + params := &cloudwatch.ListMetricsInput{ + Namespace: aws.String(namespace), + MetricName: aws.String(metricName), + Dimensions: dimensions, + } + + var resp cloudwatch.ListMetricsOutput + err = svc.ListMetricsPages(params, + func(page *cloudwatch.ListMetricsOutput, lastPage bool) bool { + metrics.M_Aws_CloudWatch_ListMetrics.Inc() + metrics, _ := awsutil.ValuesAtPath(page, "Metrics") + for _, metric := range metrics { + resp.Metrics = append(resp.Metrics, metric.(*cloudwatch.Metric)) + } + return !lastPage + }) + if err != nil { + return nil, errors.New("Failed to call cloudwatch:ListMetrics") + } + + return &resp, nil +} + func (e *CloudWatchExecutor) ec2DescribeInstances(region string, filters []*ec2.Filter, instanceIds []*string) (*ec2.DescribeInstancesOutput, error) { dsInfo := e.getDsInfo(region) cfg, err := getAwsConfig(dsInfo) diff --git a/public/app/plugins/datasource/cloudwatch/datasource.js b/public/app/plugins/datasource/cloudwatch/datasource.js index 1f89b88b5ff..31999cf5afe 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.js +++ b/public/app/plugins/datasource/cloudwatch/datasource.js @@ -222,30 +222,28 @@ function (angular, _, moment, dateMath, kbn, templatingVariable, CloudWatchAnnot }; this.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: this.convertDimensionFormat(filterDimensions, {}), - } - }; - - return this.awsRequest(request).then(function(result) { - return _.chain(result.Metrics) - .map('Dimensions') - .flatten() - .filter(function(dimension) { - return dimension !== null && dimension.Name === dimensionKey; - }) - .map('Value') - .uniq() - .sortBy() - .map(function(value) { - return {value: value, text: value}; - }).value(); - }); + var range = timeSrv.timeRange(); + return backendSrv.post('/api/tsdb/query', { + from: range.from, + to: range.to, + queries: [ + { + refId: 'metricFindQuery', + intervalMs: 1, // dummy + maxDataPoints: 1, // dummy + datasourceId: this.instanceSettings.id, + type: 'metricFindQuery', + subtype: 'dimension_values', + parameters: { + region: region, + namespace: templateSrv.replace(namespace), + metricName: templateSrv.replace(metricName), + dimensionKey: templateSrv.replace(dimensionKey), + dimensions: this.convertDimensionFormat(filterDimensions, {}), + } + } + ] + }).then(function (r) { return transformSuggestDataFromTable(r); }); }; this.getEbsVolumeIds = function(region, instanceId) {