2019-11-14 03:59:41 -06:00
|
|
|
package cloudwatch
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2020-01-17 08:27:03 -06:00
|
|
|
"sort"
|
2019-11-14 03:59:41 -06:00
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/aws/aws-sdk-go/service/cloudwatch"
|
|
|
|
"github.com/grafana/grafana/pkg/components/null"
|
|
|
|
"github.com/grafana/grafana/pkg/tsdb"
|
|
|
|
)
|
|
|
|
|
2020-07-14 01:23:23 -05:00
|
|
|
func (e *cloudWatchExecutor) parseResponse(metricDataOutputs []*cloudwatch.GetMetricDataOutput, queries map[string]*cloudWatchQuery) ([]*cloudwatchResponse, error) {
|
2019-11-14 03:59:41 -06:00
|
|
|
mdr := make(map[string]map[string]*cloudwatch.MetricDataResult)
|
|
|
|
for _, mdo := range metricDataOutputs {
|
|
|
|
requestExceededMaxLimit := false
|
|
|
|
for _, message := range mdo.Messages {
|
|
|
|
if *message.Code == "MaxMetricsExceeded" {
|
|
|
|
requestExceededMaxLimit = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, r := range mdo.MetricDataResults {
|
|
|
|
if _, exists := mdr[*r.Id]; !exists {
|
|
|
|
mdr[*r.Id] = make(map[string]*cloudwatch.MetricDataResult)
|
|
|
|
mdr[*r.Id][*r.Label] = r
|
|
|
|
} else if _, exists := mdr[*r.Id][*r.Label]; !exists {
|
|
|
|
mdr[*r.Id][*r.Label] = r
|
|
|
|
} else {
|
|
|
|
mdr[*r.Id][*r.Label].Timestamps = append(mdr[*r.Id][*r.Label].Timestamps, r.Timestamps...)
|
|
|
|
mdr[*r.Id][*r.Label].Values = append(mdr[*r.Id][*r.Label].Values, r.Values...)
|
2019-11-19 06:36:32 -06:00
|
|
|
if *r.StatusCode == "Complete" {
|
|
|
|
mdr[*r.Id][*r.Label].StatusCode = r.StatusCode
|
|
|
|
}
|
2019-11-14 03:59:41 -06:00
|
|
|
}
|
|
|
|
queries[*r.Id].RequestExceededMaxLimit = requestExceededMaxLimit
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
cloudWatchResponses := make([]*cloudwatchResponse, 0)
|
|
|
|
for id, lr := range mdr {
|
2020-01-17 04:18:28 -06:00
|
|
|
series, partialData, err := parseGetMetricDataTimeSeries(lr, queries[id])
|
2019-11-14 03:59:41 -06:00
|
|
|
if err != nil {
|
2020-05-18 05:25:58 -05:00
|
|
|
return nil, err
|
2019-11-14 03:59:41 -06:00
|
|
|
}
|
|
|
|
|
2020-05-18 05:25:58 -05:00
|
|
|
response := &cloudwatchResponse{
|
|
|
|
series: series,
|
|
|
|
Period: queries[id].Period,
|
|
|
|
Expression: queries[id].UsedExpression,
|
|
|
|
RefId: queries[id].RefId,
|
|
|
|
Id: queries[id].Id,
|
|
|
|
RequestExceededMaxLimit: queries[id].RequestExceededMaxLimit,
|
|
|
|
PartialData: partialData,
|
|
|
|
}
|
2019-11-14 03:59:41 -06:00
|
|
|
cloudWatchResponses = append(cloudWatchResponses, response)
|
|
|
|
}
|
|
|
|
|
|
|
|
return cloudWatchResponses, nil
|
|
|
|
}
|
|
|
|
|
2020-01-17 04:18:28 -06:00
|
|
|
func parseGetMetricDataTimeSeries(metricDataResults map[string]*cloudwatch.MetricDataResult, query *cloudWatchQuery) (*tsdb.TimeSeriesSlice, bool, error) {
|
2020-01-17 09:30:42 -06:00
|
|
|
metricDataResultLabels := make([]string, 0)
|
|
|
|
for k := range metricDataResults {
|
|
|
|
metricDataResultLabels = append(metricDataResultLabels, k)
|
|
|
|
}
|
|
|
|
sort.Strings(metricDataResultLabels)
|
|
|
|
|
2020-05-18 05:25:58 -05:00
|
|
|
partialData := false
|
|
|
|
result := tsdb.TimeSeriesSlice{}
|
2020-01-17 09:30:42 -06:00
|
|
|
for _, label := range metricDataResultLabels {
|
|
|
|
metricDataResult := metricDataResults[label]
|
2019-11-14 03:59:41 -06:00
|
|
|
if *metricDataResult.StatusCode != "Complete" {
|
2020-01-17 04:18:28 -06:00
|
|
|
partialData = true
|
2019-11-14 03:59:41 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, message := range metricDataResult.Messages {
|
|
|
|
if *message.Code == "ArithmeticError" {
|
2020-05-18 05:25:58 -05:00
|
|
|
return nil, false, fmt.Errorf("ArithmeticError in query %q: %s", query.RefId, *message.Value)
|
2019-11-14 03:59:41 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-10 15:14:58 -05:00
|
|
|
// In case a multi-valued dimension is used and the cloudwatch query yields no values, create one empty time series for each dimension value.
|
|
|
|
// Use that dimension value to expand the alias field
|
|
|
|
if len(metricDataResult.Values) == 0 && query.isMultiValuedDimensionExpression() {
|
|
|
|
series := 0
|
|
|
|
multiValuedDimension := ""
|
|
|
|
for key, values := range query.Dimensions {
|
|
|
|
if len(values) > series {
|
|
|
|
series = len(values)
|
|
|
|
multiValuedDimension = key
|
|
|
|
}
|
|
|
|
}
|
2019-11-14 03:59:41 -06:00
|
|
|
|
2020-03-10 15:14:58 -05:00
|
|
|
for _, value := range query.Dimensions[multiValuedDimension] {
|
|
|
|
emptySeries := tsdb.TimeSeries{
|
|
|
|
Tags: map[string]string{multiValuedDimension: value},
|
|
|
|
Points: make([]tsdb.TimePoint, 0),
|
|
|
|
}
|
|
|
|
for key, values := range query.Dimensions {
|
|
|
|
if key != multiValuedDimension && len(values) > 0 {
|
|
|
|
emptySeries.Tags[key] = values[0]
|
|
|
|
}
|
|
|
|
}
|
2020-01-17 08:27:03 -06:00
|
|
|
|
2020-03-10 15:14:58 -05:00
|
|
|
emptySeries.Name = formatAlias(query, query.Stats, emptySeries.Tags, label)
|
|
|
|
result = append(result, &emptySeries)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
keys := make([]string, 0)
|
|
|
|
for k := range query.Dimensions {
|
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
sort.Strings(keys)
|
|
|
|
|
|
|
|
series := tsdb.TimeSeries{
|
|
|
|
Tags: make(map[string]string),
|
|
|
|
Points: make([]tsdb.TimePoint, 0),
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, key := range keys {
|
|
|
|
values := query.Dimensions[key]
|
|
|
|
if len(values) == 1 && values[0] != "*" {
|
|
|
|
series.Tags[key] = values[0]
|
|
|
|
} else {
|
|
|
|
for _, value := range values {
|
|
|
|
if value == label || value == "*" {
|
|
|
|
series.Tags[key] = label
|
|
|
|
} else if strings.Contains(label, value) {
|
|
|
|
series.Tags[key] = value
|
|
|
|
}
|
2019-11-14 03:59:41 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-03-10 15:14:58 -05:00
|
|
|
series.Name = formatAlias(query, query.Stats, series.Tags, label)
|
2019-11-14 03:59:41 -06:00
|
|
|
|
2020-03-10 15:14:58 -05:00
|
|
|
for j, t := range metricDataResult.Timestamps {
|
|
|
|
if j > 0 {
|
|
|
|
expectedTimestamp := metricDataResult.Timestamps[j-1].Add(time.Duration(query.Period) * time.Second)
|
|
|
|
if expectedTimestamp.Before(*t) {
|
|
|
|
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFromPtr(nil), float64(expectedTimestamp.Unix()*1000)))
|
|
|
|
}
|
2019-11-14 03:59:41 -06:00
|
|
|
}
|
2020-06-29 07:08:32 -05:00
|
|
|
series.Points = append(series.Points, tsdb.NewTimePoint(null.FloatFrom(*metricDataResult.Values[j]),
|
|
|
|
float64(t.Unix())*1000))
|
2019-11-14 03:59:41 -06:00
|
|
|
}
|
2020-03-10 15:14:58 -05:00
|
|
|
result = append(result, &series)
|
2019-11-14 03:59:41 -06:00
|
|
|
}
|
|
|
|
}
|
2020-01-17 04:18:28 -06:00
|
|
|
return &result, partialData, nil
|
2019-11-14 03:59:41 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
func formatAlias(query *cloudWatchQuery, stat string, dimensions map[string]string, label string) string {
|
|
|
|
region := query.Region
|
|
|
|
namespace := query.Namespace
|
|
|
|
metricName := query.MetricName
|
|
|
|
period := strconv.Itoa(query.Period)
|
|
|
|
|
|
|
|
if query.isUserDefinedSearchExpression() {
|
|
|
|
pIndex := strings.LastIndex(query.Expression, ",")
|
|
|
|
period = strings.Trim(query.Expression[pIndex+1:], " )")
|
|
|
|
sIndex := strings.LastIndex(query.Expression[:pIndex], ",")
|
|
|
|
stat = strings.Trim(query.Expression[sIndex+1:pIndex], " '")
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(query.Alias) == 0 && query.isMathExpression() {
|
|
|
|
return query.Id
|
|
|
|
}
|
2020-03-10 15:14:58 -05:00
|
|
|
if len(query.Alias) == 0 && query.isInferredSearchExpression() && !query.isMultiValuedDimensionExpression() {
|
2019-11-14 03:59:41 -06:00
|
|
|
return label
|
|
|
|
}
|
|
|
|
|
2020-05-18 05:25:58 -05:00
|
|
|
data := map[string]string{
|
|
|
|
"region": region,
|
|
|
|
"namespace": namespace,
|
|
|
|
"metric": metricName,
|
|
|
|
"stat": stat,
|
|
|
|
"period": period,
|
|
|
|
}
|
2019-11-14 03:59:41 -06:00
|
|
|
if len(label) != 0 {
|
|
|
|
data["label"] = label
|
|
|
|
}
|
|
|
|
for k, v := range dimensions {
|
|
|
|
data[k] = v
|
|
|
|
}
|
|
|
|
|
|
|
|
result := aliasFormat.ReplaceAllFunc([]byte(query.Alias), func(in []byte) []byte {
|
|
|
|
labelName := strings.Replace(string(in), "{{", "", 1)
|
|
|
|
labelName = strings.Replace(labelName, "}}", "", 1)
|
|
|
|
labelName = strings.TrimSpace(labelName)
|
|
|
|
if val, exists := data[labelName]; exists {
|
|
|
|
return []byte(val)
|
|
|
|
}
|
|
|
|
|
|
|
|
return in
|
|
|
|
})
|
|
|
|
|
|
|
|
if string(result) == "" {
|
|
|
|
return metricName + "_" + stat
|
|
|
|
}
|
|
|
|
|
|
|
|
return string(result)
|
|
|
|
}
|