| 
									
										
										
										
											2019-11-14 17:59:41 +08:00
										 |  |  | package cloudwatch | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2021-07-09 19:43:22 +08:00
										 |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2020-01-17 20:22:43 +08:00
										 |  |  | 	"math" | 
					
						
							| 
									
										
										
										
											2019-11-14 17:59:41 +08:00
										 |  |  | 	"regexp" | 
					
						
							|  |  |  | 	"sort" | 
					
						
							|  |  |  | 	"strconv" | 
					
						
							| 
									
										
										
										
											2020-01-17 20:22:43 +08:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2019-11-14 17:59:41 +08:00
										 |  |  | 	"time" | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	"github.com/aws/aws-sdk-go/aws" | 
					
						
							| 
									
										
										
										
											2021-03-23 23:32:12 +08:00
										 |  |  | 	"github.com/grafana/grafana-plugin-sdk-go/backend" | 
					
						
							| 
									
										
										
										
											2019-11-14 17:59:41 +08:00
										 |  |  | 	"github.com/grafana/grafana/pkg/components/simplejson" | 
					
						
							|  |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-26 04:48:20 +08:00
										 |  |  | // Parses the json queries and returns a requestQuery. The requestQuery has a 1 to 1 mapping to a query editor row
 | 
					
						
							| 
									
										
										
										
											2021-06-10 16:23:17 +08:00
										 |  |  | func (e *cloudWatchExecutor) parseQueries(queries []backend.DataQuery, startTime time.Time, endTime time.Time) (map[string][]*requestQuery, error) { | 
					
						
							| 
									
										
										
										
											2019-11-14 17:59:41 +08:00
										 |  |  | 	requestQueries := make(map[string][]*requestQuery) | 
					
						
							| 
									
										
										
										
											2021-06-10 16:23:17 +08:00
										 |  |  | 	for _, query := range queries { | 
					
						
							| 
									
										
										
										
											2021-03-23 23:32:12 +08:00
										 |  |  | 		model, err := simplejson.NewJson(query.JSON) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return nil, &queryError{err: err, RefID: query.RefID} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		queryType := model.Get("type").MustString() | 
					
						
							| 
									
										
										
										
											2019-11-14 17:59:41 +08:00
										 |  |  | 		if queryType != "timeSeriesQuery" && queryType != "" { | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-03-08 14:02:49 +08:00
										 |  |  | 		refID := query.RefID | 
					
						
							| 
									
										
										
										
											2021-03-23 23:32:12 +08:00
										 |  |  | 		query, err := parseRequestQuery(model, refID, startTime, endTime) | 
					
						
							| 
									
										
										
										
											2019-11-14 17:59:41 +08:00
										 |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2020-05-18 18:25:58 +08:00
										 |  |  | 			return nil, &queryError{err: err, RefID: refID} | 
					
						
							| 
									
										
										
										
											2019-11-14 17:59:41 +08:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2020-05-18 18:25:58 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-11-14 17:59:41 +08:00
										 |  |  | 		if _, exist := requestQueries[query.Region]; !exist { | 
					
						
							|  |  |  | 			requestQueries[query.Region] = make([]*requestQuery, 0) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		requestQueries[query.Region] = append(requestQueries[query.Region], query) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return requestQueries, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-01-17 20:22:43 +08:00
										 |  |  | func parseRequestQuery(model *simplejson.Json, refId string, startTime time.Time, endTime time.Time) (*requestQuery, error) { | 
					
						
							| 
									
										
										
										
											2020-10-06 19:45:58 +08:00
										 |  |  | 	plog.Debug("Parsing request query", "query", model) | 
					
						
							| 
									
										
										
										
											2020-05-18 18:25:58 +08:00
										 |  |  | 	reNumber := regexp.MustCompile(`^\d+$`) | 
					
						
							| 
									
										
										
										
											2019-11-14 17:59:41 +08:00
										 |  |  | 	region, err := model.Get("region").String() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return nil, err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 	namespace, err := model.Get("namespace").String() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2021-07-09 19:43:22 +08:00
										 |  |  | 		return nil, fmt.Errorf("failed to get namespace: %v", err) | 
					
						
							| 
									
										
										
										
											2019-11-14 17:59:41 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	metricName, err := model.Get("metricName").String() | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2021-07-09 19:43:22 +08:00
										 |  |  | 		return nil, fmt.Errorf("failed to get metricName: %v", err) | 
					
						
							| 
									
										
										
										
											2019-11-14 17:59:41 +08:00
										 |  |  | 	} | 
					
						
							|  |  |  | 	dimensions, err := parseDimensions(model) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2021-07-09 19:43:22 +08:00
										 |  |  | 		return nil, fmt.Errorf("failed to parse dimensions: %v", err) | 
					
						
							| 
									
										
										
										
											2019-11-14 17:59:41 +08:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-07-09 19:43:22 +08:00
										 |  |  | 	statistics := parseStatistics(model) | 
					
						
							| 
									
										
										
										
											2019-11-14 17:59:41 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	p := model.Get("period").MustString("") | 
					
						
							|  |  |  | 	var period int | 
					
						
							| 
									
										
										
										
											2020-01-17 20:22:43 +08:00
										 |  |  | 	if strings.ToLower(p) == "auto" || p == "" { | 
					
						
							|  |  |  | 		deltaInSeconds := endTime.Sub(startTime).Seconds() | 
					
						
							| 
									
										
										
										
											2020-01-22 20:43:36 +08:00
										 |  |  | 		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 | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2019-11-14 17:59:41 +08:00
										 |  |  | 	} else { | 
					
						
							| 
									
										
										
										
											2020-05-18 18:25:58 +08:00
										 |  |  | 		if reNumber.Match([]byte(p)) { | 
					
						
							| 
									
										
										
										
											2020-01-17 20:22:43 +08:00
										 |  |  | 			period, err = strconv.Atoi(p) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2021-07-09 19:43:22 +08:00
										 |  |  | 				return nil, fmt.Errorf("failed to parse period as integer: %v", err) | 
					
						
							| 
									
										
										
										
											2020-01-17 20:22:43 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			d, err := time.ParseDuration(p) | 
					
						
							|  |  |  | 			if err != nil { | 
					
						
							| 
									
										
										
										
											2021-07-09 19:43:22 +08:00
										 |  |  | 				return nil, fmt.Errorf("failed to parse period as duration: %v", err) | 
					
						
							| 
									
										
										
										
											2020-01-17 20:22:43 +08:00
										 |  |  | 			} | 
					
						
							|  |  |  | 			period = int(d.Seconds()) | 
					
						
							| 
									
										
										
										
											2019-11-14 17:59:41 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	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{ | 
					
						
							| 
									
										
										
										
											2019-11-20 20:34:44 +08:00
										 |  |  | 		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, | 
					
						
							| 
									
										
										
										
											2019-11-14 17:59:41 +08:00
										 |  |  | 	}, nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-09 19:43:22 +08:00
										 |  |  | func parseStatistics(model *simplejson.Json) []string { | 
					
						
							| 
									
										
										
										
											2019-11-14 17:59:41 +08:00
										 |  |  | 	var statistics []string | 
					
						
							|  |  |  | 	for _, s := range model.Get("statistics").MustArray() { | 
					
						
							|  |  |  | 		statistics = append(statistics, s.(string)) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-07-09 19:43:22 +08:00
										 |  |  | 	return statistics | 
					
						
							| 
									
										
										
										
											2019-11-14 17:59:41 +08:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 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 { | 
					
						
							| 
									
										
										
										
											2021-07-09 19:43:22 +08:00
										 |  |  | 			return nil, errors.New("unknown type as dimension value") | 
					
						
							| 
									
										
										
										
											2019-11-14 17:59:41 +08:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	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 | 
					
						
							|  |  |  | } |