mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
CloudWatch: Batch different time ranges separately (#98230)
This commit is contained in:
parent
5429512779
commit
f3a553fb9b
@ -62,6 +62,8 @@ type sqlExpression struct {
|
|||||||
|
|
||||||
type CloudWatchQuery struct {
|
type CloudWatchQuery struct {
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
|
StartTime time.Time
|
||||||
|
EndTime time.Time
|
||||||
RefId string
|
RefId string
|
||||||
Region string
|
Region string
|
||||||
Id string
|
Id string
|
||||||
@ -251,6 +253,8 @@ func ParseMetricDataQueries(dataQueries []backend.DataQuery, startTime time.Time
|
|||||||
for refId, mdq := range metricDataQueries {
|
for refId, mdq := range metricDataQueries {
|
||||||
cwQuery := &CloudWatchQuery{
|
cwQuery := &CloudWatchQuery{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
StartTime: startTime,
|
||||||
|
EndTime: endTime,
|
||||||
RefId: refId,
|
RefId: refId,
|
||||||
Id: utils.Depointerizer(mdq.Id),
|
Id: utils.Depointerizer(mdq.Id),
|
||||||
Region: utils.Depointerizer(mdq.Region),
|
Region: utils.Depointerizer(mdq.Region),
|
||||||
|
@ -272,6 +272,10 @@ func TestRequestParser(t *testing.T) {
|
|||||||
QueryType: "timeSeriesQuery",
|
QueryType: "timeSeriesQuery",
|
||||||
Interval: 0,
|
Interval: 0,
|
||||||
RefID: "A",
|
RefID: "A",
|
||||||
|
TimeRange: backend.TimeRange{
|
||||||
|
From: time.Now(),
|
||||||
|
To: time.Now(),
|
||||||
|
},
|
||||||
JSON: json.RawMessage(`{
|
JSON: json.RawMessage(`{
|
||||||
"region":"us-east-1",
|
"region":"us-east-1",
|
||||||
"namespace":"ec2",
|
"namespace":"ec2",
|
||||||
@ -303,6 +307,10 @@ func TestRequestParser(t *testing.T) {
|
|||||||
QueryType: "timeSeriesQuery",
|
QueryType: "timeSeriesQuery",
|
||||||
Interval: 0,
|
Interval: 0,
|
||||||
RefID: "A",
|
RefID: "A",
|
||||||
|
TimeRange: backend.TimeRange{
|
||||||
|
From: time.Now(),
|
||||||
|
To: time.Now(),
|
||||||
|
},
|
||||||
JSON: json.RawMessage(`{
|
JSON: json.RawMessage(`{
|
||||||
"region":"us-east-1",
|
"region":"us-east-1",
|
||||||
"namespace":"ec2",
|
"namespace":"ec2",
|
||||||
|
@ -18,7 +18,7 @@ import (
|
|||||||
// matches a dynamic label
|
// matches a dynamic label
|
||||||
var dynamicLabel = regexp.MustCompile(`\$\{.+\}`)
|
var dynamicLabel = regexp.MustCompile(`\$\{.+\}`)
|
||||||
|
|
||||||
func (e *cloudWatchExecutor) parseResponse(ctx context.Context, startTime time.Time, endTime time.Time, metricDataOutputs []*cloudwatch.GetMetricDataOutput,
|
func (e *cloudWatchExecutor) parseResponse(ctx context.Context, metricDataOutputs []*cloudwatch.GetMetricDataOutput,
|
||||||
queries []*models.CloudWatchQuery) ([]*responseWrapper, error) {
|
queries []*models.CloudWatchQuery) ([]*responseWrapper, error) {
|
||||||
aggregatedResponse := aggregateResponse(metricDataOutputs)
|
aggregatedResponse := aggregateResponse(metricDataOutputs)
|
||||||
queriesById := map[string]*models.CloudWatchQuery{}
|
queriesById := map[string]*models.CloudWatchQuery{}
|
||||||
@ -40,7 +40,7 @@ func (e *cloudWatchExecutor) parseResponse(ctx context.Context, startTime time.T
|
|||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
dataRes.Frames, err = buildDataFrames(ctx, startTime, endTime, response, queryRow)
|
dataRes.Frames, err = buildDataFrames(ctx, response, queryRow)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -164,7 +164,7 @@ func getLabels(cloudwatchLabel string, query *models.CloudWatchQuery, addSeriesL
|
|||||||
return labels
|
return labels
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildDataFrames(ctx context.Context, startTime time.Time, endTime time.Time, aggregatedResponse models.QueryRowResponse,
|
func buildDataFrames(ctx context.Context, aggregatedResponse models.QueryRowResponse,
|
||||||
query *models.CloudWatchQuery) (data.Frames, error) {
|
query *models.CloudWatchQuery) (data.Frames, error) {
|
||||||
frames := data.Frames{}
|
frames := data.Frames{}
|
||||||
hasStaticLabel := query.Label != "" && !dynamicLabel.MatchString(query.Label)
|
hasStaticLabel := query.Label != "" && !dynamicLabel.MatchString(query.Label)
|
||||||
@ -172,7 +172,7 @@ func buildDataFrames(ctx context.Context, startTime time.Time, endTime time.Time
|
|||||||
for _, metric := range aggregatedResponse.Metrics {
|
for _, metric := range aggregatedResponse.Metrics {
|
||||||
label := *metric.Label
|
label := *metric.Label
|
||||||
|
|
||||||
deepLink, err := query.BuildDeepLink(startTime, endTime)
|
deepLink, err := query.BuildDeepLink(query.StartTime, query.EndTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -188,6 +188,8 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
query := &models.CloudWatchQuery{
|
query := &models.CloudWatchQuery{
|
||||||
|
StartTime: startTime,
|
||||||
|
EndTime: endTime,
|
||||||
RefId: "refId1",
|
RefId: "refId1",
|
||||||
Region: "us-east-1",
|
Region: "us-east-1",
|
||||||
Namespace: "AWS/ApplicationELB",
|
Namespace: "AWS/ApplicationELB",
|
||||||
@ -202,7 +204,7 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) {
|
|||||||
MetricEditorMode: models.MetricEditorModeBuilder,
|
MetricEditorMode: models.MetricEditorModeBuilder,
|
||||||
MatchExact: true,
|
MatchExact: true,
|
||||||
}
|
}
|
||||||
frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), startTime, endTime, *response, query)
|
frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), *response, query)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
frame1 := frames[0]
|
frame1 := frames[0]
|
||||||
@ -256,6 +258,8 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
query := &models.CloudWatchQuery{
|
query := &models.CloudWatchQuery{
|
||||||
|
StartTime: startTime,
|
||||||
|
EndTime: endTime,
|
||||||
RefId: "refId1",
|
RefId: "refId1",
|
||||||
Region: "us-east-1",
|
Region: "us-east-1",
|
||||||
Namespace: "AWS/ApplicationELB",
|
Namespace: "AWS/ApplicationELB",
|
||||||
@ -270,7 +274,7 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) {
|
|||||||
MetricQueryType: models.MetricQueryTypeSearch,
|
MetricQueryType: models.MetricQueryTypeSearch,
|
||||||
MetricEditorMode: models.MetricEditorModeBuilder,
|
MetricEditorMode: models.MetricEditorModeBuilder,
|
||||||
}
|
}
|
||||||
frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), startTime, endTime, *response, query)
|
frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), *response, query)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, "some label lb3", frames[0].Name)
|
assert.Equal(t, "some label lb3", frames[0].Name)
|
||||||
@ -305,6 +309,8 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
query := &models.CloudWatchQuery{
|
query := &models.CloudWatchQuery{
|
||||||
|
StartTime: startTime,
|
||||||
|
EndTime: endTime,
|
||||||
RefId: "refId1",
|
RefId: "refId1",
|
||||||
Region: "us-east-1",
|
Region: "us-east-1",
|
||||||
Namespace: "AWS/ApplicationELB",
|
Namespace: "AWS/ApplicationELB",
|
||||||
@ -317,7 +323,7 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) {
|
|||||||
MetricQueryType: models.MetricQueryTypeSearch,
|
MetricQueryType: models.MetricQueryTypeSearch,
|
||||||
MetricEditorMode: models.MetricEditorModeBuilder,
|
MetricEditorMode: models.MetricEditorModeBuilder,
|
||||||
}
|
}
|
||||||
frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), startTime, endTime, *response, query)
|
frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), *response, query)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Len(t, frames, 2)
|
assert.Len(t, frames, 2)
|
||||||
@ -347,6 +353,8 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
query := &models.CloudWatchQuery{
|
query := &models.CloudWatchQuery{
|
||||||
|
StartTime: startTime,
|
||||||
|
EndTime: endTime,
|
||||||
RefId: "refId1",
|
RefId: "refId1",
|
||||||
Region: "us-east-1",
|
Region: "us-east-1",
|
||||||
Namespace: "AWS/ApplicationELB",
|
Namespace: "AWS/ApplicationELB",
|
||||||
@ -361,7 +369,7 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) {
|
|||||||
MetricQueryType: models.MetricQueryTypeSearch,
|
MetricQueryType: models.MetricQueryTypeSearch,
|
||||||
MetricEditorMode: models.MetricEditorModeBuilder,
|
MetricEditorMode: models.MetricEditorModeBuilder,
|
||||||
}
|
}
|
||||||
frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), startTime, endTime, *response, query)
|
frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), *response, query)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Len(t, frames, 2)
|
assert.Len(t, frames, 2)
|
||||||
@ -393,6 +401,8 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
query := &models.CloudWatchQuery{
|
query := &models.CloudWatchQuery{
|
||||||
|
StartTime: startTime,
|
||||||
|
EndTime: endTime,
|
||||||
RefId: "refId1",
|
RefId: "refId1",
|
||||||
Region: "us-east-1",
|
Region: "us-east-1",
|
||||||
Namespace: "AWS/ApplicationELB",
|
Namespace: "AWS/ApplicationELB",
|
||||||
@ -407,7 +417,7 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) {
|
|||||||
MetricQueryType: models.MetricQueryTypeSearch,
|
MetricQueryType: models.MetricQueryTypeSearch,
|
||||||
MetricEditorMode: models.MetricEditorModeBuilder,
|
MetricEditorMode: models.MetricEditorModeBuilder,
|
||||||
}
|
}
|
||||||
frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), startTime, endTime, *response, query)
|
frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), *response, query)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, "some label", frames[0].Name)
|
assert.Equal(t, "some label", frames[0].Name)
|
||||||
@ -433,6 +443,8 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
query := &models.CloudWatchQuery{
|
query := &models.CloudWatchQuery{
|
||||||
|
StartTime: startTime,
|
||||||
|
EndTime: endTime,
|
||||||
RefId: "refId1",
|
RefId: "refId1",
|
||||||
Region: "us-east-1",
|
Region: "us-east-1",
|
||||||
Namespace: "AWS/ApplicationELB",
|
Namespace: "AWS/ApplicationELB",
|
||||||
@ -448,7 +460,7 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) {
|
|||||||
MetricEditorMode: models.MetricEditorModeBuilder,
|
MetricEditorMode: models.MetricEditorModeBuilder,
|
||||||
Label: "set ${AVG} label",
|
Label: "set ${AVG} label",
|
||||||
}
|
}
|
||||||
frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), startTime, endTime, *response, query)
|
frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), *response, query)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, "some label", frames[0].Name)
|
assert.Equal(t, "some label", frames[0].Name)
|
||||||
@ -474,6 +486,8 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
query := &models.CloudWatchQuery{
|
query := &models.CloudWatchQuery{
|
||||||
|
StartTime: startTime,
|
||||||
|
EndTime: endTime,
|
||||||
RefId: "refId1",
|
RefId: "refId1",
|
||||||
Region: "us-east-1",
|
Region: "us-east-1",
|
||||||
Namespace: "AWS/ApplicationELB",
|
Namespace: "AWS/ApplicationELB",
|
||||||
@ -489,7 +503,7 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) {
|
|||||||
MetricEditorMode: models.MetricEditorModeBuilder,
|
MetricEditorMode: models.MetricEditorModeBuilder,
|
||||||
Label: "actual",
|
Label: "actual",
|
||||||
}
|
}
|
||||||
frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), startTime, endTime, *response, query)
|
frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), *response, query)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, "actual", frames[0].Name)
|
assert.Equal(t, "actual", frames[0].Name)
|
||||||
@ -515,6 +529,8 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
query := &models.CloudWatchQuery{
|
query := &models.CloudWatchQuery{
|
||||||
|
StartTime: startTime,
|
||||||
|
EndTime: endTime,
|
||||||
RefId: "refId1",
|
RefId: "refId1",
|
||||||
Region: "us-east-1",
|
Region: "us-east-1",
|
||||||
Namespace: "",
|
Namespace: "",
|
||||||
@ -527,7 +543,7 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) {
|
|||||||
MetricEditorMode: models.MetricEditorModeRaw,
|
MetricEditorMode: models.MetricEditorModeRaw,
|
||||||
Label: "actual",
|
Label: "actual",
|
||||||
}
|
}
|
||||||
frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), startTime, endTime, *response, query)
|
frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), *response, query)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, "actual", frames[0].Name)
|
assert.Equal(t, "actual", frames[0].Name)
|
||||||
@ -560,6 +576,8 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
query := &models.CloudWatchQuery{
|
query := &models.CloudWatchQuery{
|
||||||
|
StartTime: startTime,
|
||||||
|
EndTime: endTime,
|
||||||
RefId: "refId1",
|
RefId: "refId1",
|
||||||
Region: "us-east-1",
|
Region: "us-east-1",
|
||||||
Statistic: "Average",
|
Statistic: "Average",
|
||||||
@ -569,7 +587,7 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) {
|
|||||||
Dimensions: map[string][]string{"Service": {"EC2", "Elastic Loading Balancing"}, "Resource": {"vCPU", "ApplicationLoadBalancersPerRegion"}},
|
Dimensions: map[string][]string{"Service": {"EC2", "Elastic Loading Balancing"}, "Resource": {"vCPU", "ApplicationLoadBalancersPerRegion"}},
|
||||||
SqlExpression: "SELECT AVG(ResourceCount) FROM SCHEMA(\"AWS/Usage\", Class, Resource, Service, Type) GROUP BY Service, Resource",
|
SqlExpression: "SELECT AVG(ResourceCount) FROM SCHEMA(\"AWS/Usage\", Class, Resource, Service, Type) GROUP BY Service, Resource",
|
||||||
}
|
}
|
||||||
frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), startTime, endTime, *response, query)
|
frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), *response, query)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, "EC2 vCPU", frames[0].Name)
|
assert.Equal(t, "EC2 vCPU", frames[0].Name)
|
||||||
@ -597,6 +615,8 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
query := &models.CloudWatchQuery{
|
query := &models.CloudWatchQuery{
|
||||||
|
StartTime: startTime,
|
||||||
|
EndTime: endTime,
|
||||||
RefId: "refId1",
|
RefId: "refId1",
|
||||||
Region: "us-east-1",
|
Region: "us-east-1",
|
||||||
Statistic: "Average",
|
Statistic: "Average",
|
||||||
@ -605,7 +625,7 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) {
|
|||||||
MetricEditorMode: models.MetricEditorModeBuilder,
|
MetricEditorMode: models.MetricEditorModeBuilder,
|
||||||
SqlExpression: "SELECT AVG(ResourceCount) FROM SCHEMA(\"AWS/Usage\", Class, Resource, Service, Type)",
|
SqlExpression: "SELECT AVG(ResourceCount) FROM SCHEMA(\"AWS/Usage\", Class, Resource, Service, Type)",
|
||||||
}
|
}
|
||||||
frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), startTime, endTime, *response, query)
|
frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), *response, query)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, "cloudwatch-default-label", frames[0].Name)
|
assert.Equal(t, "cloudwatch-default-label", frames[0].Name)
|
||||||
@ -629,6 +649,8 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
query := &models.CloudWatchQuery{
|
query := &models.CloudWatchQuery{
|
||||||
|
StartTime: startTime,
|
||||||
|
EndTime: endTime,
|
||||||
RefId: "refId1",
|
RefId: "refId1",
|
||||||
Region: "us-east-1",
|
Region: "us-east-1",
|
||||||
Namespace: "AWS/ApplicationELB",
|
Namespace: "AWS/ApplicationELB",
|
||||||
@ -642,7 +664,7 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) {
|
|||||||
MetricQueryType: models.MetricQueryTypeSearch,
|
MetricQueryType: models.MetricQueryTypeSearch,
|
||||||
MetricEditorMode: models.MetricEditorModeRaw,
|
MetricEditorMode: models.MetricEditorModeRaw,
|
||||||
}
|
}
|
||||||
frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), startTime, endTime, *response, query)
|
frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), *response, query)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.Equal(t, "some label", frames[0].Name)
|
assert.Equal(t, "some label", frames[0].Name)
|
||||||
@ -673,6 +695,8 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
query := &models.CloudWatchQuery{
|
query := &models.CloudWatchQuery{
|
||||||
|
StartTime: startTime,
|
||||||
|
EndTime: endTime,
|
||||||
RefId: "refId1",
|
RefId: "refId1",
|
||||||
Region: "us-east-1",
|
Region: "us-east-1",
|
||||||
Namespace: "AWS/ApplicationELB",
|
Namespace: "AWS/ApplicationELB",
|
||||||
@ -686,7 +710,7 @@ func Test_buildDataFrames_parse_label_to_name_and_labels(t *testing.T) {
|
|||||||
MetricQueryType: models.MetricQueryTypeSearch,
|
MetricQueryType: models.MetricQueryTypeSearch,
|
||||||
MetricEditorMode: models.MetricEditorModeBuilder,
|
MetricEditorMode: models.MetricEditorModeBuilder,
|
||||||
}
|
}
|
||||||
frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), startTime, endTime, *response, query)
|
frames, err := buildDataFrames(contextWithFeaturesEnabled(features.FlagCloudWatchNewLabelParsing), *response, query)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
frame := frames[0]
|
frame := frames[0]
|
||||||
|
@ -25,12 +25,6 @@ func (e *cloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, req *ba
|
|||||||
if len(req.Queries) == 0 {
|
if len(req.Queries) == 0 {
|
||||||
return nil, backend.DownstreamError(fmt.Errorf("request contains no queries"))
|
return nil, backend.DownstreamError(fmt.Errorf("request contains no queries"))
|
||||||
}
|
}
|
||||||
// startTime and endTime are always the same for all queries
|
|
||||||
startTime := req.Queries[0].TimeRange.From
|
|
||||||
endTime := req.Queries[0].TimeRange.To
|
|
||||||
if !startTime.Before(endTime) {
|
|
||||||
return nil, backend.DownstreamError(fmt.Errorf("invalid time range: start time must be before end time"))
|
|
||||||
}
|
|
||||||
|
|
||||||
instance, err := e.getInstance(ctx, req.PluginContext)
|
instance, err := e.getInstance(ctx, req.PluginContext)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -38,34 +32,45 @@ func (e *cloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, req *ba
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
requestQueries, err := models.ParseMetricDataQueries(req.Queries, startTime, endTime, instance.Settings.Region, e.logger.FromContext(ctx),
|
timeBatches := utils.BatchDataQueriesByTimeRange(req.Queries)
|
||||||
features.IsEnabled(ctx, features.FlagCloudWatchCrossAccountQuerying))
|
requestQueriesByTimeAndRegion := make(map[string][]*models.CloudWatchQuery)
|
||||||
if err != nil {
|
for i, timeBatch := range timeBatches {
|
||||||
return nil, err
|
startTime := timeBatch[0].TimeRange.From
|
||||||
}
|
endTime := timeBatch[0].TimeRange.To
|
||||||
|
if !startTime.Before(endTime) {
|
||||||
if len(requestQueries) == 0 {
|
return nil, backend.DownstreamError(fmt.Errorf("invalid time range: start time must be before end time"))
|
||||||
return backend.NewQueryDataResponse(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
requestQueriesByRegion := make(map[string][]*models.CloudWatchQuery)
|
|
||||||
for _, query := range requestQueries {
|
|
||||||
if _, exist := requestQueriesByRegion[query.Region]; !exist {
|
|
||||||
requestQueriesByRegion[query.Region] = []*models.CloudWatchQuery{}
|
|
||||||
}
|
}
|
||||||
requestQueriesByRegion[query.Region] = append(requestQueriesByRegion[query.Region], query)
|
requestQueries, err := models.ParseMetricDataQueries(timeBatch, startTime, endTime, instance.Settings.Region, e.logger.FromContext(ctx),
|
||||||
|
features.IsEnabled(ctx, features.FlagCloudWatchCrossAccountQuerying))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, query := range requestQueries {
|
||||||
|
key := fmt.Sprintf("%d %s", i, query.Region)
|
||||||
|
if _, exist := requestQueriesByTimeAndRegion[key]; !exist {
|
||||||
|
requestQueriesByTimeAndRegion[key] = []*models.CloudWatchQuery{}
|
||||||
|
}
|
||||||
|
requestQueriesByTimeAndRegion[key] = append(requestQueriesByTimeAndRegion[key], query)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(requestQueriesByTimeAndRegion) == 0 {
|
||||||
|
return backend.NewQueryDataResponse(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
resultChan := make(chan *responseWrapper, len(req.Queries))
|
resultChan := make(chan *responseWrapper, len(req.Queries))
|
||||||
eg, ectx := errgroup.WithContext(ctx)
|
eg, ectx := errgroup.WithContext(ctx)
|
||||||
for r, regionQueries := range requestQueriesByRegion {
|
for _, timeAndRegionQueries := range requestQueriesByTimeAndRegion {
|
||||||
region := r
|
batches := [][]*models.CloudWatchQuery{timeAndRegionQueries}
|
||||||
|
|
||||||
batches := [][]*models.CloudWatchQuery{regionQueries}
|
|
||||||
if features.IsEnabled(ctx, features.FlagCloudWatchBatchQueries) {
|
if features.IsEnabled(ctx, features.FlagCloudWatchBatchQueries) {
|
||||||
batches = getMetricQueryBatches(regionQueries, e.logger.FromContext(ctx))
|
batches = getMetricQueryBatches(timeAndRegionQueries, e.logger.FromContext(ctx))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// region, startTime, and endTime are the same for the set of queries
|
||||||
|
region := timeAndRegionQueries[0].Region
|
||||||
|
startTime := timeAndRegionQueries[0].StartTime
|
||||||
|
endTime := timeAndRegionQueries[0].EndTime
|
||||||
|
|
||||||
for _, batch := range batches {
|
for _, batch := range batches {
|
||||||
requestQueries := batch
|
requestQueries := batch
|
||||||
eg.Go(func() error {
|
eg.Go(func() error {
|
||||||
@ -113,7 +118,7 @@ func (e *cloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, req *ba
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := e.parseResponse(ctx, startTime, endTime, mdo, requestQueries)
|
res, err := e.parseResponse(ctx, mdo, requestQueries)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -130,7 +135,7 @@ func (e *cloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, req *ba
|
|||||||
if err := eg.Wait(); err != nil {
|
if err := eg.Wait(); err != nil {
|
||||||
dataResponse := backend.ErrorResponseWithErrorSource(fmt.Errorf("metric request error: %w", err))
|
dataResponse := backend.ErrorResponseWithErrorSource(fmt.Errorf("metric request error: %w", err))
|
||||||
resultChan <- &responseWrapper{
|
resultChan <- &responseWrapper{
|
||||||
RefId: getQueryRefIdFromErrorString(err.Error(), requestQueries),
|
RefId: getQueryRefIdFromErrorString(err.Error(), requestQueriesByTimeAndRegion),
|
||||||
DataResponse: &dataResponse,
|
DataResponse: &dataResponse,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -143,14 +148,16 @@ func (e *cloudWatchExecutor) executeTimeSeriesQuery(ctx context.Context, req *ba
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getQueryRefIdFromErrorString(err string, queries []*models.CloudWatchQuery) string {
|
func getQueryRefIdFromErrorString(err string, queriesByRegion map[string][]*models.CloudWatchQuery) string {
|
||||||
// error can be in format "Error in expression 'test': Invalid syntax"
|
// error can be in format "Error in expression 'test': Invalid syntax"
|
||||||
// so we can find the query id or ref id between the quotations
|
// so we can find the query id or ref id between the quotations
|
||||||
erroredRefId := ""
|
erroredRefId := ""
|
||||||
|
|
||||||
for _, query := range queries {
|
for _, queries := range queriesByRegion {
|
||||||
if regexp.MustCompile(`'`+query.RefId+`':`).MatchString(err) || regexp.MustCompile(`'`+query.Id+`':`).MatchString(err) {
|
for _, query := range queries {
|
||||||
erroredRefId = query.RefId
|
if regexp.MustCompile(`'`+query.RefId+`':`).MatchString(err) || regexp.MustCompile(`'`+query.Id+`':`).MatchString(err) {
|
||||||
|
erroredRefId = query.RefId
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// if errorRefId is empty, it means the error concerns all queries (error metric limit exceeded, for example)
|
// if errorRefId is empty, it means the error concerns all queries (error metric limit exceeded, for example)
|
||||||
|
@ -118,18 +118,26 @@ func TestTimeSeriesQuery(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("End time before start time should result in error", func(t *testing.T) {
|
t.Run("End time before start time should result in error", func(t *testing.T) {
|
||||||
_, err := executor.executeTimeSeriesQuery(context.Background(), &backend.QueryDataRequest{Queries: []backend.DataQuery{{TimeRange: backend.TimeRange{
|
_, err := executor.executeTimeSeriesQuery(context.Background(), &backend.QueryDataRequest{
|
||||||
From: now.Add(time.Hour * -1),
|
PluginContext: backend.PluginContext{
|
||||||
To: now.Add(time.Hour * -2),
|
DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{},
|
||||||
}}}})
|
},
|
||||||
|
Queries: []backend.DataQuery{{TimeRange: backend.TimeRange{
|
||||||
|
From: now.Add(time.Hour * -1),
|
||||||
|
To: now.Add(time.Hour * -2),
|
||||||
|
}}}})
|
||||||
assert.EqualError(t, err, "invalid time range: start time must be before end time")
|
assert.EqualError(t, err, "invalid time range: start time must be before end time")
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("End time equals start time should result in error", func(t *testing.T) {
|
t.Run("End time equals start time should result in error", func(t *testing.T) {
|
||||||
_, err := executor.executeTimeSeriesQuery(context.Background(), &backend.QueryDataRequest{Queries: []backend.DataQuery{{TimeRange: backend.TimeRange{
|
_, err := executor.executeTimeSeriesQuery(context.Background(), &backend.QueryDataRequest{
|
||||||
From: now.Add(time.Hour * -1),
|
PluginContext: backend.PluginContext{
|
||||||
To: now.Add(time.Hour * -1),
|
DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{},
|
||||||
}}}})
|
},
|
||||||
|
Queries: []backend.DataQuery{{TimeRange: backend.TimeRange{
|
||||||
|
From: now.Add(time.Hour * -1),
|
||||||
|
To: now.Add(time.Hour * -1),
|
||||||
|
}}}})
|
||||||
assert.EqualError(t, err, "invalid time range: start time must be before end time")
|
assert.EqualError(t, err, "invalid time range: start time must be before end time")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -271,6 +279,72 @@ func Test_executeTimeSeriesQuery_getCWClient_is_called_once_per_region_and_GetMe
|
|||||||
mockMetricClient.AssertNumberOfCalls(t, "GetMetricDataWithContext", 2)
|
mockMetricClient.AssertNumberOfCalls(t, "GetMetricDataWithContext", 2)
|
||||||
// GetMetricData is asserted to have been called 2 times, presumably once for each group of regions (2 regions total)
|
// GetMetricData is asserted to have been called 2 times, presumably once for each group of regions (2 regions total)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("3 queries with 2 time ranges calls GetSessionWithAuthSettings 2 times and calls GetMetricDataWithContext 2 times", func(t *testing.T) {
|
||||||
|
sessionCache := &mockSessionCache{}
|
||||||
|
sessionCache.On("GetSessionWithAuthSettings", mock.MatchedBy(
|
||||||
|
func(config awsds.GetSessionConfig) bool {
|
||||||
|
return config.Settings.Region == "us-east-2"
|
||||||
|
})).
|
||||||
|
Return(&session.Session{Config: &aws.Config{}}, nil).Times(2)
|
||||||
|
|
||||||
|
im := datasource.NewInstanceManager(func(ctx context.Context, s backend.DataSourceInstanceSettings) (instancemgmt.Instance, error) {
|
||||||
|
return DataSource{Settings: models.CloudWatchSettings{}, sessions: sessionCache}, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
mockMetricClient = mocks.MetricsAPI{}
|
||||||
|
mockMetricClient.On("GetMetricDataWithContext", mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
|
||||||
|
|
||||||
|
executor := newExecutor(im, log.NewNullLogger())
|
||||||
|
_, err := executor.QueryData(context.Background(), &backend.QueryDataRequest{
|
||||||
|
PluginContext: backend.PluginContext{
|
||||||
|
DataSourceInstanceSettings: &backend.DataSourceInstanceSettings{},
|
||||||
|
},
|
||||||
|
Queries: []backend.DataQuery{
|
||||||
|
{
|
||||||
|
RefID: "A",
|
||||||
|
TimeRange: backend.TimeRange{From: time.Now().Add(time.Hour * -2), To: time.Now()},
|
||||||
|
JSON: json.RawMessage(`{
|
||||||
|
"type": "timeSeriesQuery",
|
||||||
|
"namespace": "AWS/EC2",
|
||||||
|
"metricName": "NetworkOut",
|
||||||
|
"region": "us-east-2",
|
||||||
|
"statistic": "Maximum",
|
||||||
|
"period": "300"
|
||||||
|
}`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RefID: "A2",
|
||||||
|
TimeRange: backend.TimeRange{From: time.Now().Add(time.Hour * -2), To: time.Now()},
|
||||||
|
JSON: json.RawMessage(`{
|
||||||
|
"type": "timeSeriesQuery",
|
||||||
|
"namespace": "AWS/EC2",
|
||||||
|
"metricName": "NetworkOut",
|
||||||
|
"region": "us-east-2",
|
||||||
|
"statistic": "Maximum",
|
||||||
|
"period": "300"
|
||||||
|
}`),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RefID: "B",
|
||||||
|
TimeRange: backend.TimeRange{From: time.Now().Add(time.Hour * -2), To: time.Now().Add(time.Hour * -1)},
|
||||||
|
JSON: json.RawMessage(`{
|
||||||
|
"type": "timeSeriesQuery",
|
||||||
|
"namespace": "AWS/EC2",
|
||||||
|
"metricName": "NetworkIn",
|
||||||
|
"region": "us-east-2",
|
||||||
|
"statistic": "Maximum",
|
||||||
|
"period": "300"
|
||||||
|
}`),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
sessionCache.AssertExpectations(t) // method is defined to return twice (once for each batch)
|
||||||
|
mockMetricClient.AssertNumberOfCalls(t, "GetMetricDataWithContext", 2)
|
||||||
|
// GetMetricData is asserted to have been called 2 times, presumably once for each time range (2 time ranges total)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
type queryDimensions struct {
|
type queryDimensions struct {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||||
"github.com/prometheus/client_golang/prometheus"
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
"github.com/prometheus/client_golang/prometheus/promauto"
|
"github.com/prometheus/client_golang/prometheus/promauto"
|
||||||
)
|
)
|
||||||
@ -20,3 +21,19 @@ var QueriesTotalCounter = promauto.NewCounterVec(
|
|||||||
},
|
},
|
||||||
[]string{"query_type"},
|
[]string{"query_type"},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// BatchDataQueriesByTimeRange separates the passed in queries into batches based on time ranges
|
||||||
|
func BatchDataQueriesByTimeRange(queries []backend.DataQuery) [][]backend.DataQuery {
|
||||||
|
timeToBatch := make(map[backend.TimeRange][]backend.DataQuery)
|
||||||
|
|
||||||
|
for _, query := range queries {
|
||||||
|
key := backend.TimeRange{From: query.TimeRange.From.UTC(), To: query.TimeRange.To.UTC()}
|
||||||
|
timeToBatch[key] = append(timeToBatch[key], query)
|
||||||
|
}
|
||||||
|
|
||||||
|
finalBatches := [][]backend.DataQuery{}
|
||||||
|
for _, batch := range timeToBatch {
|
||||||
|
finalBatches = append(finalBatches, batch)
|
||||||
|
}
|
||||||
|
return finalBatches
|
||||||
|
}
|
||||||
|
47
pkg/tsdb/cloudwatch/utils/metrics_test.go
Normal file
47
pkg/tsdb/cloudwatch/utils/metrics_test.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBatchDataQueriesByTimeRange(t *testing.T) {
|
||||||
|
start := time.Date(2024, time.November, 29, 0, 42, 34, 0, time.UTC)
|
||||||
|
FiveMin := time.Date(2024, time.November, 29, 0, 47, 34, 0, time.UTC)
|
||||||
|
TenMin := time.Date(2024, time.November, 29, 0, 52, 34, 0, time.UTC)
|
||||||
|
loc := time.FixedZone("UTC+1", 1*60*60)
|
||||||
|
FiveMinDifferentZone := time.Date(2024, time.November, 29, 1, 47, 34, 0, loc)
|
||||||
|
testQueries := []backend.DataQuery{
|
||||||
|
{
|
||||||
|
RefID: "A",
|
||||||
|
TimeRange: backend.TimeRange{From: start, To: FiveMin},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RefID: "B",
|
||||||
|
TimeRange: backend.TimeRange{From: start, To: TenMin},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
RefID: "C",
|
||||||
|
TimeRange: backend.TimeRange{From: start, To: FiveMinDifferentZone},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
result := BatchDataQueriesByTimeRange(testQueries)
|
||||||
|
require.Equal(t, 2, len(result))
|
||||||
|
var FiveMinQueries = result[0]
|
||||||
|
var TenMinQueries = result[1]
|
||||||
|
// Since BatchDataQueriesByTimeRange uses a map, we don't known the return indices for the batches
|
||||||
|
if len(result[0]) == 1 {
|
||||||
|
TenMinQueries = result[0]
|
||||||
|
FiveMinQueries = result[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, 2, len(FiveMinQueries))
|
||||||
|
require.Equal(t, "A", FiveMinQueries[0].RefID)
|
||||||
|
require.Equal(t, "C", FiveMinQueries[1].RefID)
|
||||||
|
|
||||||
|
require.Equal(t, 1, len(TenMinQueries))
|
||||||
|
require.Equal(t, "B", TenMinQueries[0].RefID)
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user