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