mirror of https://github.com/grafana/grafana
parent
8f3b060946
commit
c140d7aa06
@ -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 |
||||
} |
@ -1,2 +0,0 @@ |
||||
declare var test: any; |
||||
export default test; |
@ -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; |
||||
}); |
@ -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(); |
||||
}); |
||||
}); |
||||
}); |
Loading…
Reference in new issue