From 1ac0c03a1d01fb1531d64fbadbbb1699bc4bdb00 Mon Sep 17 00:00:00 2001 From: Andres Martinez Gotor Date: Wed, 2 Nov 2022 12:14:30 +0100 Subject: [PATCH] [v9.2.x] Google Cloud Monitoring: Set frame interval to draw null values (#57914) --- pkg/tsdb/cloudmonitoring/cloudmonitoring.go | 11 +++++++++- .../cloudmonitoring/time_series_filter.go | 2 +- .../time_series_filter_test.go | 15 +++++++++++++ pkg/tsdb/cloudmonitoring/time_series_query.go | 21 +++++++++--------- .../cloudmonitoring/time_series_query_test.go | 22 +++++++++++++++++++ pkg/tsdb/cloudmonitoring/utils.go | 21 ++++++++++++++++++ 6 files changed, 79 insertions(+), 13 deletions(-) diff --git a/pkg/tsdb/cloudmonitoring/cloudmonitoring.go b/pkg/tsdb/cloudmonitoring/cloudmonitoring.go index d6d4277f9c5..5098a66b6ce 100644 --- a/pkg/tsdb/cloudmonitoring/cloudmonitoring.go +++ b/pkg/tsdb/cloudmonitoring/cloudmonitoring.go @@ -663,7 +663,7 @@ func unmarshalResponse(res *http.Response) (cloudMonitoringResponse, error) { return data, nil } -func addConfigData(frames data.Frames, dl string, unit string) data.Frames { +func addConfigData(frames data.Frames, dl string, unit string, period string) data.Frames { for i := range frames { if frames[i].Fields[1].Config == nil { frames[i].Fields[1].Config = &data.FieldConfig{} @@ -681,6 +681,15 @@ func addConfigData(frames data.Frames, dl string, unit string) data.Frames { frames[i].Fields[1].Config.Unit = val } } + if frames[i].Fields[0].Config == nil { + frames[i].Fields[0].Config = &data.FieldConfig{} + } + if period != "" { + err := addInterval(period, frames[i].Fields[0]) + if err != nil { + slog.Error("Failed to add interval", "error", err) + } + } } return frames } diff --git a/pkg/tsdb/cloudmonitoring/time_series_filter.go b/pkg/tsdb/cloudmonitoring/time_series_filter.go index 6677638f623..021a293c49d 100644 --- a/pkg/tsdb/cloudmonitoring/time_series_filter.go +++ b/pkg/tsdb/cloudmonitoring/time_series_filter.go @@ -211,7 +211,7 @@ func (timeSeriesFilter *cloudMonitoringTimeSeriesFilter) parseResponse(queryRes } if len(response.TimeSeries) > 0 { dl := timeSeriesFilter.buildDeepLink() - frames = addConfigData(frames, dl, response.Unit) + frames = addConfigData(frames, dl, response.Unit, timeSeriesFilter.Params.Get("aggregation.alignmentPeriod")) } queryRes.Frames = frames diff --git a/pkg/tsdb/cloudmonitoring/time_series_filter_test.go b/pkg/tsdb/cloudmonitoring/time_series_filter_test.go index 81b3f877570..20aa99c02c9 100644 --- a/pkg/tsdb/cloudmonitoring/time_series_filter_test.go +++ b/pkg/tsdb/cloudmonitoring/time_series_filter_test.go @@ -389,6 +389,21 @@ func TestTimeSeriesFilter(t *testing.T) { assert.Equal(t, "114250375703598695", labels["resource.label.instance_id"]) }) + t.Run("includes time interval", func(t *testing.T) { + data, err := loadTestFile("./test-data/5-series-response-meta-data.json") + require.NoError(t, err) + assert.Equal(t, 3, len(data.TimeSeries)) + res := &backend.DataResponse{} + query := &cloudMonitoringTimeSeriesFilter{Params: url.Values{ + "aggregation.alignmentPeriod": []string{"+60s"}, + }} + err = query.parseResponse(res, data, "") + require.NoError(t, err) + frames := res.Frames + timeField := frames[0].Fields[0] + assert.Equal(t, float64(60*1000), timeField.Config.Interval) + }) + t.Run("parseResponse successfully parses metadata for distribution valueType", func(t *testing.T) { t.Run("exponential bounds", func(t *testing.T) { data, err := loadTestFile("./test-data/3-series-response-distribution-exponential.json") diff --git a/pkg/tsdb/cloudmonitoring/time_series_query.go b/pkg/tsdb/cloudmonitoring/time_series_query.go index f7c1edf0a5d..1f5af1d4501 100644 --- a/pkg/tsdb/cloudmonitoring/time_series_query.go +++ b/pkg/tsdb/cloudmonitoring/time_series_query.go @@ -20,22 +20,21 @@ import ( "github.com/grafana/grafana/pkg/tsdb/intervalv2" ) -func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) appendGraphPeriod(req *backend.QueryDataRequest) string { +func (timeSeriesQuery *cloudMonitoringTimeSeriesQuery) appendGraphPeriod(req *backend.QueryDataRequest) string { // GraphPeriod needs to be explicitly disabled. // If not set, the default behavior is to set an automatic value if timeSeriesQuery.GraphPeriod != "disabled" { - graphPeriod := timeSeriesQuery.GraphPeriod - if graphPeriod == "auto" || graphPeriod == "" { + if timeSeriesQuery.GraphPeriod == "auto" || timeSeriesQuery.GraphPeriod == "" { intervalCalculator := intervalv2.NewCalculator(intervalv2.CalculatorOptions{}) interval := intervalCalculator.Calculate(req.Queries[0].TimeRange, time.Duration(timeSeriesQuery.IntervalMS/1000)*time.Second, req.Queries[0].MaxDataPoints) - graphPeriod = interval.Text + timeSeriesQuery.GraphPeriod = interval.Text } - return fmt.Sprintf(" | graph_period %s", graphPeriod) + return fmt.Sprintf(" | graph_period %s", timeSeriesQuery.GraphPeriod) } return "" } -func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) run(ctx context.Context, req *backend.QueryDataRequest, +func (timeSeriesQuery *cloudMonitoringTimeSeriesQuery) run(ctx context.Context, req *backend.QueryDataRequest, s *Service, dsInfo datasourceInfo, tracer tracing.Tracer) (*backend.DataResponse, cloudMonitoringResponse, string, error) { dr := &backend.DataResponse{} projectName := timeSeriesQuery.ProjectName @@ -93,7 +92,7 @@ func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) run(ctx context.Context, r return dr, d, timeSeriesQuery.Query, nil } -func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) parseResponse(queryRes *backend.DataResponse, +func (timeSeriesQuery *cloudMonitoringTimeSeriesQuery) parseResponse(queryRes *backend.DataResponse, response cloudMonitoringResponse, executedQueryString string) error { frames := data.Frames{} @@ -256,7 +255,7 @@ func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) parseResponse(queryRes *ba } if len(response.TimeSeriesData) > 0 { dl := timeSeriesQuery.buildDeepLink() - frames = addConfigData(frames, dl, response.Unit) + frames = addConfigData(frames, dl, response.Unit, timeSeriesQuery.GraphPeriod) } queryRes.Frames = frames @@ -264,7 +263,7 @@ func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) parseResponse(queryRes *ba return nil } -func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) parseToAnnotations(queryRes *backend.DataResponse, +func (timeSeriesQuery *cloudMonitoringTimeSeriesQuery) parseToAnnotations(queryRes *backend.DataResponse, data cloudMonitoringResponse, title, text string) error { annotations := make([]*annotationEvent, 0) @@ -316,7 +315,7 @@ func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) parseToAnnotations(queryRe return nil } -func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) buildDeepLink() string { +func (timeSeriesQuery *cloudMonitoringTimeSeriesQuery) buildDeepLink() string { u, err := url.Parse("https://console.cloud.google.com/monitoring/metrics-explorer") if err != nil { slog.Error("Failed to generate deep link: unable to parse metrics explorer URL", "projectName", timeSeriesQuery.ProjectName, "query", timeSeriesQuery.RefID) @@ -371,6 +370,6 @@ func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) buildDeepLink() string { return accountChooserURL.String() } -func (timeSeriesQuery cloudMonitoringTimeSeriesQuery) getRefID() string { +func (timeSeriesQuery *cloudMonitoringTimeSeriesQuery) getRefID() string { return timeSeriesQuery.RefID } diff --git a/pkg/tsdb/cloudmonitoring/time_series_query_test.go b/pkg/tsdb/cloudmonitoring/time_series_query_test.go index df56700724a..2e983dcaa3d 100644 --- a/pkg/tsdb/cloudmonitoring/time_series_query_test.go +++ b/pkg/tsdb/cloudmonitoring/time_series_query_test.go @@ -102,6 +102,28 @@ func TestTimeSeriesQuery(t *testing.T) { assert.Equal(t, "6724404429462225363", labels["resource.label.instance_id"]) }) + t.Run("includes time interval", func(t *testing.T) { + data, err := loadTestFile("./test-data/7-series-response-mql.json") + require.NoError(t, err) + + fromStart := time.Date(2018, 3, 15, 13, 0, 0, 0, time.UTC).In(time.Local) + res := &backend.DataResponse{} + query := &cloudMonitoringTimeSeriesQuery{ + ProjectName: "test-proj", + Query: "test-query", + timeRange: backend.TimeRange{ + From: fromStart, + To: fromStart.Add(34 * time.Minute), + }, + GraphPeriod: "60s", + } + err = query.parseResponse(res, data, "") + require.NoError(t, err) + frames := res.Frames + timeField := frames[0].Fields[0] + assert.Equal(t, float64(60*1000), timeField.Config.Interval) + }) + t.Run("appends graph_period to the query", func(t *testing.T) { query := &cloudMonitoringTimeSeriesQuery{} assert.Equal(t, query.appendGraphPeriod(&backend.QueryDataRequest{Queries: []backend.DataQuery{{}}}), " | graph_period 1ms") diff --git a/pkg/tsdb/cloudmonitoring/utils.go b/pkg/tsdb/cloudmonitoring/utils.go index 91b2b6bacd1..bcaa7550f94 100644 --- a/pkg/tsdb/cloudmonitoring/utils.go +++ b/pkg/tsdb/cloudmonitoring/utils.go @@ -2,6 +2,9 @@ package cloudmonitoring import ( "strings" + + "github.com/grafana/grafana-plugin-sdk-go/data" + "github.com/grafana/grafana/pkg/tsdb/intervalv2" ) func reverse(s string) string { @@ -24,3 +27,21 @@ func containsLabel(labels []string, newLabel string) bool { } return false } + +func addInterval(period string, field *data.Field) error { + period = strings.TrimPrefix(period, "+") + p, err := intervalv2.ParseIntervalStringToTimeDuration(period) + if err != nil { + return err + } + if err == nil { + if field.Config != nil { + field.Config.Interval = float64(p.Milliseconds()) + } else { + field.SetConfig(&data.FieldConfig{ + Interval: float64(p.Milliseconds()), + }) + } + } + return nil +}