grafana/pkg/tsdb/cloudwatch/request_parser.go
Erik Sundell 685c9043a8
CloudWatch: Auto period snap to next higher period (#21659)
* Snap to next higher period instead of closest

* Adjust period calc
2020-01-22 13:43:36 +01:00

168 lines
4.4 KiB
Go

package cloudwatch
import (
"errors"
"math"
"regexp"
"sort"
"strconv"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/tsdb"
)
// Parses the json queries and returns a requestQuery. The requstQuery has a 1 to 1 mapping to a query editor row
func (e *CloudWatchExecutor) parseQueries(queryContext *tsdb.TsdbQuery, startTime time.Time, endTime time.Time) (map[string][]*requestQuery, error) {
requestQueries := make(map[string][]*requestQuery)
for i, model := range queryContext.Queries {
queryType := model.Model.Get("type").MustString()
if queryType != "timeSeriesQuery" && queryType != "" {
continue
}
RefID := queryContext.Queries[i].RefId
query, err := parseRequestQuery(queryContext.Queries[i].Model, RefID, startTime, endTime)
if err != nil {
return nil, &queryError{err, RefID}
}
if _, exist := requestQueries[query.Region]; !exist {
requestQueries[query.Region] = make([]*requestQuery, 0)
}
requestQueries[query.Region] = append(requestQueries[query.Region], query)
}
return requestQueries, nil
}
func parseRequestQuery(model *simplejson.Json, refId string, startTime time.Time, endTime time.Time) (*requestQuery, error) {
region, err := model.Get("region").String()
if err != nil {
return nil, err
}
namespace, err := model.Get("namespace").String()
if err != nil {
return nil, err
}
metricName, err := model.Get("metricName").String()
if err != nil {
return nil, err
}
dimensions, err := parseDimensions(model)
if err != nil {
return nil, err
}
statistics, err := parseStatistics(model)
if err != nil {
return nil, err
}
p := model.Get("period").MustString("")
var period int
if strings.ToLower(p) == "auto" || p == "" {
deltaInSeconds := endTime.Sub(startTime).Seconds()
periods := []int{60, 300, 900, 3600, 21600, 86400}
datapoints := int(math.Ceil(deltaInSeconds / 2000))
period = periods[len(periods)-1]
for _, value := range periods {
if datapoints <= value {
period = value
break
}
}
} else {
if regexp.MustCompile(`^\d+$`).Match([]byte(p)) {
period, err = strconv.Atoi(p)
if err != nil {
return nil, err
}
} else {
d, err := time.ParseDuration(p)
if err != nil {
return nil, err
}
period = int(d.Seconds())
}
}
id := model.Get("id").MustString("")
expression := model.Get("expression").MustString("")
alias := model.Get("alias").MustString()
returnData := !model.Get("hide").MustBool(false)
queryType := model.Get("type").MustString()
if queryType == "" {
// If no type is provided we assume we are called by alerting service, which requires to return data!
// Note, this is sort of a hack, but the official Grafana interfaces do not carry the information
// who (which service) called the TsdbQueryEndpoint.Query(...) function.
returnData = true
}
matchExact := model.Get("matchExact").MustBool(true)
return &requestQuery{
RefId: refId,
Region: region,
Namespace: namespace,
MetricName: metricName,
Dimensions: dimensions,
Statistics: aws.StringSlice(statistics),
Period: period,
Alias: alias,
Id: id,
Expression: expression,
ReturnData: returnData,
MatchExact: matchExact,
}, nil
}
func parseStatistics(model *simplejson.Json) ([]string, error) {
var statistics []string
for _, s := range model.Get("statistics").MustArray() {
statistics = append(statistics, s.(string))
}
return statistics, nil
}
func parseDimensions(model *simplejson.Json) (map[string][]string, error) {
parsedDimensions := make(map[string][]string)
for k, v := range model.Get("dimensions").MustMap() {
// This is for backwards compatibility. Before 6.5 dimensions values were stored as strings and not arrays
if value, ok := v.(string); ok {
parsedDimensions[k] = []string{value}
} else if values, ok := v.([]interface{}); ok {
for _, value := range values {
parsedDimensions[k] = append(parsedDimensions[k], value.(string))
}
} else {
return nil, errors.New("failed to parse dimensions")
}
}
sortedDimensions := sortDimensions(parsedDimensions)
return sortedDimensions, nil
}
func sortDimensions(dimensions map[string][]string) map[string][]string {
sortedDimensions := make(map[string][]string)
var keys []string
for k := range dimensions {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
sortedDimensions[k] = dimensions[k]
}
return sortedDimensions
}