mirror of https://github.com/grafana/grafana.git
				
				
				
			Cloudwatch: Round up endTime in GetMetricData to next minute (#89341)
* Add cloudWatchRoundUpEndTime feature toggle
This commit is contained in:
		
							parent
							
								
									0aaf820fd9
								
							
						
					
					
						commit
						ba5b33227c
					
				|  | @ -63,6 +63,7 @@ Most [generally available](https://grafana.com/docs/release-life-cycle/#general- | ||||||
| | `alertingQueryOptimization`        | Optimizes eligible queries in order to reduce load on datasources                                                                                                                                                            |                    | | | `alertingQueryOptimization`        | Optimizes eligible queries in order to reduce load on datasources                                                                                                                                                            |                    | | ||||||
| | `cloudWatchNewLabelParsing`        | Updates CloudWatch label parsing to be more accurate                                                                                                                                                                         | Yes                | | | `cloudWatchNewLabelParsing`        | Updates CloudWatch label parsing to be more accurate                                                                                                                                                                         | Yes                | | ||||||
| | `pluginProxyPreserveTrailingSlash` | Preserve plugin proxy trailing slash.                                                                                                                                                                                        |                    | | | `pluginProxyPreserveTrailingSlash` | Preserve plugin proxy trailing slash.                                                                                                                                                                                        |                    | | ||||||
|  | | `cloudWatchRoundUpEndTime`         | Round up end time for metric queries to the next minute to avoid missing data                                                                                                                                                | Yes                | | ||||||
| 
 | 
 | ||||||
| ## Public preview feature toggles | ## Public preview feature toggles | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -198,4 +198,5 @@ export interface FeatureToggles { | ||||||
|   passScopeToDashboardApi?: boolean; |   passScopeToDashboardApi?: boolean; | ||||||
|   alertingApiServer?: boolean; |   alertingApiServer?: boolean; | ||||||
|   dashboardRestoreUI?: boolean; |   dashboardRestoreUI?: boolean; | ||||||
|  |   cloudWatchRoundUpEndTime?: boolean; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -1353,6 +1353,13 @@ var ( | ||||||
| 			Owner:       grafanaFrontendPlatformSquad, | 			Owner:       grafanaFrontendPlatformSquad, | ||||||
| 			Expression:  "false", // enabled by default
 | 			Expression:  "false", // enabled by default
 | ||||||
| 		}, | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Name:        "cloudWatchRoundUpEndTime", | ||||||
|  | 			Description: "Round up end time for metric queries to the next minute to avoid missing data", | ||||||
|  | 			Stage:       FeatureStageGeneralAvailability, | ||||||
|  | 			Owner:       awsDatasourcesSquad, | ||||||
|  | 			Expression:  "true", | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -179,3 +179,4 @@ zanzana,experimental,@grafana/identity-access-team,false,false,false | ||||||
| passScopeToDashboardApi,experimental,@grafana/dashboards-squad,false,false,false | passScopeToDashboardApi,experimental,@grafana/dashboards-squad,false,false,false | ||||||
| alertingApiServer,experimental,@grafana/alerting-squad,false,true,false | alertingApiServer,experimental,@grafana/alerting-squad,false,true,false | ||||||
| dashboardRestoreUI,experimental,@grafana/grafana-frontend-platform,false,false,false | dashboardRestoreUI,experimental,@grafana/grafana-frontend-platform,false,false,false | ||||||
|  | cloudWatchRoundUpEndTime,GA,@grafana/aws-datasources,false,false,false | ||||||
|  |  | ||||||
| 
 | 
|  | @ -726,4 +726,8 @@ const ( | ||||||
| 	// FlagDashboardRestoreUI
 | 	// FlagDashboardRestoreUI
 | ||||||
| 	// Enables the frontend to be able to restore a recently deleted dashboard
 | 	// Enables the frontend to be able to restore a recently deleted dashboard
 | ||||||
| 	FlagDashboardRestoreUI = "dashboardRestoreUI" | 	FlagDashboardRestoreUI = "dashboardRestoreUI" | ||||||
|  | 
 | ||||||
|  | 	// FlagCloudWatchRoundUpEndTime
 | ||||||
|  | 	// Round up end time for metric queries to the next minute to avoid missing data
 | ||||||
|  | 	FlagCloudWatchRoundUpEndTime = "cloudWatchRoundUpEndTime" | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | @ -563,6 +563,18 @@ | ||||||
|         "codeowner": "@grafana/aws-datasources" |         "codeowner": "@grafana/aws-datasources" | ||||||
|       } |       } | ||||||
|     }, |     }, | ||||||
|  |     { | ||||||
|  |       "metadata": { | ||||||
|  |         "name": "cloudWatchRoundUpEndTime", | ||||||
|  |         "resourceVersion": "1719324143210", | ||||||
|  |         "creationTimestamp": "2024-06-25T14:02:23Z" | ||||||
|  |       }, | ||||||
|  |       "spec": { | ||||||
|  |         "description": "Round up end time for metric queries to the next minute to avoid missing data", | ||||||
|  |         "stage": "GA", | ||||||
|  |         "codeowner": "@grafana/aws-datasources" | ||||||
|  |       } | ||||||
|  |     }, | ||||||
|     { |     { | ||||||
|       "metadata": { |       "metadata": { | ||||||
|         "name": "configurableSchedulerTick", |         "name": "configurableSchedulerTick", | ||||||
|  |  | ||||||
|  | @ -10,6 +10,7 @@ const ( | ||||||
| 	FlagCloudWatchCrossAccountQuerying = "cloudWatchCrossAccountQuerying" | 	FlagCloudWatchCrossAccountQuerying = "cloudWatchCrossAccountQuerying" | ||||||
| 	FlagCloudWatchBatchQueries         = "cloudWatchBatchQueries" | 	FlagCloudWatchBatchQueries         = "cloudWatchBatchQueries" | ||||||
| 	FlagCloudWatchNewLabelParsing      = "cloudWatchNewLabelParsing" | 	FlagCloudWatchNewLabelParsing      = "cloudWatchNewLabelParsing" | ||||||
|  | 	FlagCloudWatchRoundUpEndTime       = "cloudWatchRoundUpEndTime" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func IsEnabled(ctx context.Context, feature string) bool { | func IsEnabled(ctx context.Context, feature string) bool { | ||||||
|  |  | ||||||
|  | @ -2,10 +2,12 @@ package cloudwatch | ||||||
| 
 | 
 | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
|  | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/aws/aws-sdk-go/aws" | 	"github.com/aws/aws-sdk-go/aws" | ||||||
| 	"github.com/aws/aws-sdk-go/service/cloudwatch" | 	"github.com/aws/aws-sdk-go/service/cloudwatch" | ||||||
| 	"github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface" | 	"github.com/aws/aws-sdk-go/service/cloudwatch/cloudwatchiface" | ||||||
|  | 	"github.com/grafana/grafana/pkg/tsdb/cloudwatch/features" | ||||||
| 	"github.com/grafana/grafana/pkg/tsdb/cloudwatch/utils" | 	"github.com/grafana/grafana/pkg/tsdb/cloudwatch/utils" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | @ -18,6 +20,11 @@ func (e *cloudWatchExecutor) executeRequest(ctx context.Context, client cloudwat | ||||||
| 		if nextToken != "" { | 		if nextToken != "" { | ||||||
| 			metricDataInput.NextToken = aws.String(nextToken) | 			metricDataInput.NextToken = aws.String(nextToken) | ||||||
| 		} | 		} | ||||||
|  | 		// GetMetricData EndTime is exclusive, so we round up to the next minute to get the last data point
 | ||||||
|  | 		if features.IsEnabled(ctx, features.FlagCloudWatchRoundUpEndTime) { | ||||||
|  | 			*metricDataInput.EndTime = metricDataInput.EndTime.Truncate(time.Minute).Add(time.Minute) | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
| 		resp, err := client.GetMetricDataWithContext(ctx, metricDataInput) | 		resp, err := client.GetMetricDataWithContext(ctx, metricDataInput) | ||||||
| 		if err != nil { | 		if err != nil { | ||||||
| 			return mdo, err | 			return mdo, err | ||||||
|  |  | ||||||
|  | @ -3,18 +3,38 @@ package cloudwatch | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"testing" | 	"testing" | ||||||
|  | 	"time" | ||||||
| 
 | 
 | ||||||
| 	"github.com/aws/aws-sdk-go/aws" | 	"github.com/aws/aws-sdk-go/aws" | ||||||
| 	"github.com/aws/aws-sdk-go/service/cloudwatch" | 	"github.com/aws/aws-sdk-go/service/cloudwatch" | ||||||
|  | 	"github.com/grafana/grafana/pkg/tsdb/cloudwatch/features" | ||||||
| 	"github.com/grafana/grafana/pkg/tsdb/cloudwatch/mocks" | 	"github.com/grafana/grafana/pkg/tsdb/cloudwatch/mocks" | ||||||
| 	"github.com/stretchr/testify/assert" | 	"github.com/stretchr/testify/assert" | ||||||
| 	"github.com/stretchr/testify/mock" | 	"github.com/stretchr/testify/mock" | ||||||
| 	"github.com/stretchr/testify/require" | 	"github.com/stretchr/testify/require" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| func TestGetMetricDataExecutorTest(t *testing.T) { | func TestGetMetricDataExecutorTestRequest(t *testing.T) { | ||||||
|  | 	t.Run("Should round up end time if cloudWatchRoundUpEndTime is enabled", func(t *testing.T) { | ||||||
|  | 		executor := &cloudWatchExecutor{} | ||||||
|  | 		queryEndTime, _ := time.Parse("2006-01-02T15:04:05Z07:00", "2024-05-01T01:45:04Z") | ||||||
|  | 		inputs := &cloudwatch.GetMetricDataInput{EndTime: &queryEndTime, MetricDataQueries: []*cloudwatch.MetricDataQuery{}} | ||||||
|  | 		mockMetricClient := &mocks.MetricsAPI{} | ||||||
|  | 		mockMetricClient.On("GetMetricDataWithContext", mock.Anything, mock.Anything, mock.Anything).Return( | ||||||
|  | 			&cloudwatch.GetMetricDataOutput{ | ||||||
|  | 				MetricDataResults: []*cloudwatch.MetricDataResult{{Values: []*float64{}}}, | ||||||
|  | 			}, nil).Once() | ||||||
|  | 		_, err := executor.executeRequest(contextWithFeaturesEnabled(features.FlagCloudWatchRoundUpEndTime), mockMetricClient, inputs) | ||||||
|  | 		require.NoError(t, err) | ||||||
|  | 		expectedTime, _ := time.Parse("2006-01-02T15:04:05Z07:00", "2024-05-01T01:46:00Z") | ||||||
|  | 		expectedInput := &cloudwatch.GetMetricDataInput{EndTime: &expectedTime, MetricDataQueries: []*cloudwatch.MetricDataQuery{}} | ||||||
|  | 		mockMetricClient.AssertCalled(t, "GetMetricDataWithContext", mock.Anything, expectedInput, mock.Anything) | ||||||
|  | 	}) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestGetMetricDataExecutorTestResponse(t *testing.T) { | ||||||
| 	executor := &cloudWatchExecutor{} | 	executor := &cloudWatchExecutor{} | ||||||
| 	inputs := &cloudwatch.GetMetricDataInput{MetricDataQueries: []*cloudwatch.MetricDataQuery{}} | 	inputs := &cloudwatch.GetMetricDataInput{EndTime: aws.Time(time.Now()), MetricDataQueries: []*cloudwatch.MetricDataQuery{}} | ||||||
| 	mockMetricClient := &mocks.MetricsAPI{} | 	mockMetricClient := &mocks.MetricsAPI{} | ||||||
| 	mockMetricClient.On("GetMetricDataWithContext", mock.Anything, mock.Anything, mock.Anything).Return( | 	mockMetricClient.On("GetMetricDataWithContext", mock.Anything, mock.Anything, mock.Anything).Return( | ||||||
| 		&cloudwatch.GetMetricDataOutput{ | 		&cloudwatch.GetMetricDataOutput{ | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue