mirror of
https://github.com/grafana/grafana.git
synced 2026-08-18 17:15:08 -05:00
CloudWatch: Handle new error codes for MetricInsights (#47033)
* CloudWatch: Handle new error codes for MetricInsights * Changes/test to support case where only the first GetMetricDataOutput returns errors and refactoring * Fix Potential file inclusion via variable * Fix gosec 304 by assigning a new variable * fix goimports issue
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
package cloudwatch
|
||||
|
||||
const (
|
||||
maxMetricsExceeded = "MaxMetricsExceeded"
|
||||
maxQueryTimeRangeExceeded = "MaxQueryTimeRangeExceeded"
|
||||
maxQueryResultsExceeded = "MaxQueryResultsExceeded"
|
||||
maxMatchingResultsExceeded = "MaxMatchingResultsExceeded"
|
||||
)
|
||||
@@ -4,25 +4,29 @@ import "github.com/aws/aws-sdk-go/service/cloudwatch"
|
||||
|
||||
// queryRowResponse represents the GetMetricData response for a query row in the query editor.
|
||||
type queryRowResponse struct {
|
||||
ID string
|
||||
RequestExceededMaxLimit bool
|
||||
PartialData bool
|
||||
Labels []string
|
||||
HasArithmeticError bool
|
||||
ArithmeticErrorMessage string
|
||||
Metrics map[string]*cloudwatch.MetricDataResult
|
||||
StatusCode string
|
||||
ID string
|
||||
ErrorCodes map[string]bool
|
||||
PartialData bool
|
||||
Labels []string
|
||||
HasArithmeticError bool
|
||||
ArithmeticErrorMessage string
|
||||
Metrics map[string]*cloudwatch.MetricDataResult
|
||||
StatusCode string
|
||||
}
|
||||
|
||||
func newQueryRowResponse(id string) queryRowResponse {
|
||||
return queryRowResponse{
|
||||
ID: id,
|
||||
RequestExceededMaxLimit: false,
|
||||
PartialData: false,
|
||||
HasArithmeticError: false,
|
||||
ArithmeticErrorMessage: "",
|
||||
Labels: []string{},
|
||||
Metrics: map[string]*cloudwatch.MetricDataResult{},
|
||||
ID: id,
|
||||
ErrorCodes: map[string]bool{
|
||||
maxMetricsExceeded: false,
|
||||
maxQueryTimeRangeExceeded: false,
|
||||
maxQueryResultsExceeded: false,
|
||||
maxMatchingResultsExceeded: false},
|
||||
PartialData: false,
|
||||
HasArithmeticError: false,
|
||||
ArithmeticErrorMessage: "",
|
||||
Labels: []string{},
|
||||
Metrics: map[string]*cloudwatch.MetricDataResult{},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,11 +47,16 @@ func (e *cloudWatchExecutor) parseResponse(startTime time.Time, endTime time.Tim
|
||||
|
||||
func aggregateResponse(getMetricDataOutputs []*cloudwatch.GetMetricDataOutput) map[string]queryRowResponse {
|
||||
responseByID := make(map[string]queryRowResponse)
|
||||
errorCodes := map[string]bool{
|
||||
maxMetricsExceeded: false,
|
||||
maxQueryTimeRangeExceeded: false,
|
||||
maxQueryResultsExceeded: false,
|
||||
maxMatchingResultsExceeded: false,
|
||||
}
|
||||
for _, gmdo := range getMetricDataOutputs {
|
||||
requestExceededMaxLimit := false
|
||||
for _, message := range gmdo.Messages {
|
||||
if *message.Code == "MaxMetricsExceeded" {
|
||||
requestExceededMaxLimit = true
|
||||
if _, exists := errorCodes[*message.Code]; exists {
|
||||
errorCodes[*message.Code] = true
|
||||
}
|
||||
}
|
||||
for _, r := range gmdo.MetricDataResults {
|
||||
@@ -75,7 +80,11 @@ func aggregateResponse(getMetricDataOutputs []*cloudwatch.GetMetricDataOutput) m
|
||||
response.appendTimeSeries(r)
|
||||
}
|
||||
|
||||
response.RequestExceededMaxLimit = response.RequestExceededMaxLimit || requestExceededMaxLimit
|
||||
for code := range errorCodes {
|
||||
if _, exists := response.ErrorCodes[code]; exists {
|
||||
response.ErrorCodes[code] = errorCodes[code]
|
||||
}
|
||||
}
|
||||
responseByID[id] = response
|
||||
}
|
||||
}
|
||||
@@ -183,11 +192,19 @@ func buildDataFrames(startTime time.Time, endTime time.Time, aggregatedResponse
|
||||
Meta: createMeta(query),
|
||||
}
|
||||
|
||||
if aggregatedResponse.RequestExceededMaxLimit {
|
||||
frame.AppendNotices(data.Notice{
|
||||
Severity: data.NoticeSeverityWarning,
|
||||
Text: "cloudwatch GetMetricData error: Maximum number of allowed metrics exceeded. Your search may have been limited",
|
||||
})
|
||||
warningTextMap := map[string]string{
|
||||
"MaxMetricsExceeded": "Maximum number of allowed metrics exceeded. Your search may have been limited",
|
||||
"MaxQueryTimeRangeExceeded": "Max time window exceeded for query",
|
||||
"MaxQueryResultsExceeded": "Only the first 500 time series can be returned by a query.",
|
||||
"MaxMatchingResultsExceeded": "The query matched more than 10.000 metrics, results might not be accurate.",
|
||||
}
|
||||
for code := range aggregatedResponse.ErrorCodes {
|
||||
if aggregatedResponse.ErrorCodes[code] {
|
||||
frame.AppendNotices(data.Notice{
|
||||
Severity: data.NoticeSeverityWarning,
|
||||
Text: "cloudwatch GetMetricData error: " + warningTextMap[code],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if aggregatedResponse.StatusCode != "Complete" {
|
||||
|
||||
@@ -3,6 +3,7 @@ package cloudwatch
|
||||
import (
|
||||
"encoding/json"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -13,9 +14,10 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func loadGetMetricDataOutputsFromFile() ([]*cloudwatch.GetMetricDataOutput, error) {
|
||||
func loadGetMetricDataOutputsFromFile(filePath string) ([]*cloudwatch.GetMetricDataOutput, error) {
|
||||
var getMetricDataOutputs []*cloudwatch.GetMetricDataOutput
|
||||
jsonBody, err := ioutil.ReadFile("./test-data/multiple-outputs.json")
|
||||
cleanFilePath := filepath.Clean(filePath)
|
||||
jsonBody, err := ioutil.ReadFile(cleanFilePath)
|
||||
if err != nil {
|
||||
return getMetricDataOutputs, err
|
||||
}
|
||||
@@ -27,7 +29,7 @@ func TestCloudWatchResponseParser(t *testing.T) {
|
||||
startTime := time.Now()
|
||||
endTime := startTime.Add(2 * time.Hour)
|
||||
t.Run("when aggregating response", func(t *testing.T) {
|
||||
getMetricDataOutputs, err := loadGetMetricDataOutputsFromFile()
|
||||
getMetricDataOutputs, err := loadGetMetricDataOutputsFromFile("./test-data/multiple-outputs.json")
|
||||
require.NoError(t, err)
|
||||
aggregatedResponse := aggregateResponse(getMetricDataOutputs)
|
||||
t.Run("response for id a", func(t *testing.T) {
|
||||
@@ -43,7 +45,16 @@ func TestCloudWatchResponseParser(t *testing.T) {
|
||||
assert.Equal(t, "Complete", aggregatedResponse[idA].StatusCode)
|
||||
})
|
||||
t.Run("should have exceeded request limit", func(t *testing.T) {
|
||||
assert.True(t, aggregatedResponse[idA].RequestExceededMaxLimit)
|
||||
assert.True(t, aggregatedResponse[idA].ErrorCodes["MaxMetricsExceeded"])
|
||||
})
|
||||
t.Run("should have exceeded query time range", func(t *testing.T) {
|
||||
assert.True(t, aggregatedResponse[idA].ErrorCodes["MaxQueryTimeRangeExceeded"])
|
||||
})
|
||||
t.Run("should have exceeded max query results", func(t *testing.T) {
|
||||
assert.True(t, aggregatedResponse[idA].ErrorCodes["MaxQueryResultsExceeded"])
|
||||
})
|
||||
t.Run("should have exceeded max matching results", func(t *testing.T) {
|
||||
assert.True(t, aggregatedResponse[idA].ErrorCodes["MaxMatchingResultsExceeded"])
|
||||
})
|
||||
})
|
||||
t.Run("response for id b", func(t *testing.T) {
|
||||
@@ -58,6 +69,27 @@ func TestCloudWatchResponseParser(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("when aggregating response and error codes are in first GetMetricDataOutput", func(t *testing.T) {
|
||||
getMetricDataOutputs, err := loadGetMetricDataOutputsFromFile("./test-data/multiple-outputs2.json")
|
||||
require.NoError(t, err)
|
||||
aggregatedResponse := aggregateResponse(getMetricDataOutputs)
|
||||
t.Run("response for id a", func(t *testing.T) {
|
||||
idA := "a"
|
||||
t.Run("should have exceeded request limit", func(t *testing.T) {
|
||||
assert.True(t, aggregatedResponse[idA].ErrorCodes["MaxMetricsExceeded"])
|
||||
})
|
||||
t.Run("should have exceeded query time range", func(t *testing.T) {
|
||||
assert.True(t, aggregatedResponse[idA].ErrorCodes["MaxQueryTimeRangeExceeded"])
|
||||
})
|
||||
t.Run("should have exceeded max query results", func(t *testing.T) {
|
||||
assert.True(t, aggregatedResponse[idA].ErrorCodes["MaxQueryResultsExceeded"])
|
||||
})
|
||||
t.Run("should have exceeded max matching results", func(t *testing.T) {
|
||||
assert.True(t, aggregatedResponse[idA].ErrorCodes["MaxMatchingResultsExceeded"])
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("Expand dimension value using exact match", func(t *testing.T) {
|
||||
timestamp := time.Unix(0, 0)
|
||||
response := &queryRowResponse{
|
||||
|
||||
@@ -52,7 +52,10 @@
|
||||
{
|
||||
"Messages": [
|
||||
{ "Code": "", "Value": null },
|
||||
{ "Code": "MaxMetricsExceeded", "Value": null }
|
||||
{ "Code": "MaxMetricsExceeded", "Value": null },
|
||||
{ "Code": "MaxQueryTimeRangeExceeded", "Value": null },
|
||||
{ "Code": "MaxQueryResultsExceeded", "Value": null },
|
||||
{ "Code": "MaxMatchingResultsExceeded", "Value": null }
|
||||
],
|
||||
"MetricDataResults": [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
[
|
||||
{
|
||||
"Messages": [
|
||||
{ "Code": "", "Value": null },
|
||||
{ "Code": "MaxMetricsExceeded", "Value": null },
|
||||
{ "Code": "MaxQueryTimeRangeExceeded", "Value": null },
|
||||
{ "Code": "MaxQueryResultsExceeded", "Value": null },
|
||||
{ "Code": "MaxMatchingResultsExceeded", "Value": null }
|
||||
],
|
||||
"MetricDataResults": [
|
||||
{
|
||||
"Id": "a",
|
||||
"Label": "label1",
|
||||
"Messages": null,
|
||||
"StatusCode": "Complete",
|
||||
"Timestamps": [
|
||||
"2021-01-15T19:44:00Z",
|
||||
"2021-01-15T19:59:00Z",
|
||||
"2021-01-15T20:14:00Z",
|
||||
"2021-01-15T20:29:00Z",
|
||||
"2021-01-15T20:44:00Z"
|
||||
],
|
||||
"Values": [
|
||||
0.1333395078879982,
|
||||
0.244268469636633,
|
||||
0.15574387947267768,
|
||||
0.14447563659125626,
|
||||
0.15519743138527173
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "a",
|
||||
"Label": "label2",
|
||||
"Messages": null,
|
||||
"StatusCode": "Complete",
|
||||
"Timestamps": [
|
||||
"2021-01-15T19:44:00Z"
|
||||
],
|
||||
"Values": [
|
||||
0.1333395078879982
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "b",
|
||||
"Label": "label2",
|
||||
"Messages": null,
|
||||
"StatusCode": "Complete",
|
||||
"Timestamps": [
|
||||
"2021-01-15T19:44:00Z"
|
||||
],
|
||||
"Values": [
|
||||
0.1333395078879982
|
||||
]
|
||||
}
|
||||
],
|
||||
"NextToken": null
|
||||
},
|
||||
{
|
||||
"Messages": null,
|
||||
"MetricDataResults": [
|
||||
{
|
||||
"Id": "a",
|
||||
"Label": "label1",
|
||||
"Messages": null,
|
||||
"StatusCode": "Complete",
|
||||
"Timestamps": [
|
||||
"2021-01-15T19:44:00Z",
|
||||
"2021-01-15T19:59:00Z",
|
||||
"2021-01-15T20:14:00Z",
|
||||
"2021-01-15T20:29:00Z",
|
||||
"2021-01-15T20:44:00Z"
|
||||
],
|
||||
"Values": [
|
||||
0.1333395078879982,
|
||||
0.244268469636633,
|
||||
0.15574387947267768,
|
||||
0.14447563659125626,
|
||||
0.15519743138527173
|
||||
]
|
||||
},
|
||||
{
|
||||
"Id": "b",
|
||||
"Label": "label2",
|
||||
"Messages": [{
|
||||
"Code": "ArithmeticError",
|
||||
"Value": "One or more data-points have been dropped due to non-numeric values (NaN, -Infinite, +Infinite)"
|
||||
}],
|
||||
"StatusCode": "Partial",
|
||||
"Timestamps": [
|
||||
"2021-01-15T19:44:00Z"
|
||||
],
|
||||
"Values": [
|
||||
0.1333395078879982
|
||||
]
|
||||
}
|
||||
],
|
||||
"NextToken": null
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user