diff --git a/pkg/tsdb/loki/loki.go b/pkg/tsdb/loki/loki.go index de933cd2d0f..2d55c6fdcb5 100644 --- a/pkg/tsdb/loki/loki.go +++ b/pkg/tsdb/loki/loki.go @@ -182,38 +182,6 @@ func formatLegend(metric model.Metric, query *lokiQuery) string { return string(result) } -func parseQuery(dsInfo *datasourceInfo, queryContext *backend.QueryDataRequest) ([]*lokiQuery, error) { - qs := []*lokiQuery{} - for _, query := range queryContext.Queries { - model := &ResponseModel{} - err := json.Unmarshal(query.JSON, model) - if err != nil { - return nil, err - } - - start := query.TimeRange.From - end := query.TimeRange.To - - var resolution int64 = 1 - if model.Resolution >= 1 && model.Resolution <= 5 || model.Resolution == 10 { - resolution = model.Resolution - } - - step := calculateStep(query.Interval, query.TimeRange.To.Sub(query.TimeRange.From), resolution) - - qs = append(qs, &lokiQuery{ - Expr: model.Expr, - Step: step, - LegendFormat: model.LegendFormat, - Start: start, - End: end, - RefID: query.RefID, - }) - } - - return qs, nil -} - func parseResponse(value *loghttp.QueryResponse, query *lokiQuery) (data.Frames, error) { frames := data.Frames{} diff --git a/pkg/tsdb/loki/loki_test.go b/pkg/tsdb/loki/loki_test.go index ad492adebcb..02797d28a46 100644 --- a/pkg/tsdb/loki/loki_test.go +++ b/pkg/tsdb/loki/loki_test.go @@ -5,7 +5,6 @@ import ( "time" "github.com/google/go-cmp/cmp" - "github.com/grafana/grafana-plugin-sdk-go/backend" "github.com/grafana/grafana-plugin-sdk-go/data" "github.com/grafana/loki/pkg/loghttp" p "github.com/prometheus/common/model" @@ -39,31 +38,6 @@ func TestLoki(t *testing.T) { require.Equal(t, `http_request_total{app="backend", device="mobile"}`, formatLegend(metric, query)) }) - - t.Run("parsing query model", func(t *testing.T) { - queryContext := &backend.QueryDataRequest{ - Queries: []backend.DataQuery{ - { - JSON: []byte(` - { - "expr": "go_goroutines", - "format": "time_series", - "refId": "A" - }`, - ), - TimeRange: backend.TimeRange{ - From: time.Now().Add(-30 * time.Second), - To: time.Now(), - }, - Interval: time.Second * 30, - }, - }, - } - dsInfo := &datasourceInfo{} - models, err := parseQuery(dsInfo, queryContext) - require.NoError(t, err) - require.Equal(t, time.Second*30, models[0].Step) - }) } func TestParseResponse(t *testing.T) { diff --git a/pkg/tsdb/loki/parse_query.go b/pkg/tsdb/loki/parse_query.go new file mode 100644 index 00000000000..60682799018 --- /dev/null +++ b/pkg/tsdb/loki/parse_query.go @@ -0,0 +1,75 @@ +package loki + +import ( + "encoding/json" + "math" + "strconv" + "strings" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/grafana/grafana/pkg/tsdb/intervalv2" +) + +const ( + varInterval = "$__interval" + varIntervalMs = "$__interval_ms" + varRange = "$__range" + varRangeS = "$__range_s" + varRangeMs = "$__range_ms" +) + +func interpolateVariables(expr string, interval time.Duration, timeRange time.Duration) string { + intervalText := intervalv2.FormatDuration(interval) + intervalMsText := strconv.FormatInt(int64(interval/time.Millisecond), 10) + + rangeMs := timeRange.Milliseconds() + rangeSRounded := int64(math.Round(float64(rangeMs) / 1000.0)) + rangeMsText := strconv.FormatInt(rangeMs, 10) + rangeSText := strconv.FormatInt(rangeSRounded, 10) + + expr = strings.ReplaceAll(expr, varIntervalMs, intervalMsText) + expr = strings.ReplaceAll(expr, varInterval, intervalText) + expr = strings.ReplaceAll(expr, varRangeMs, rangeMsText) + expr = strings.ReplaceAll(expr, varRangeS, rangeSText) + expr = strings.ReplaceAll(expr, varRange, rangeSText+"s") + + return expr +} + +func parseQuery(dsInfo *datasourceInfo, queryContext *backend.QueryDataRequest) ([]*lokiQuery, error) { + qs := []*lokiQuery{} + for _, query := range queryContext.Queries { + model := &ResponseModel{} + err := json.Unmarshal(query.JSON, model) + if err != nil { + return nil, err + } + + start := query.TimeRange.From + end := query.TimeRange.To + + var resolution int64 = 1 + if model.Resolution >= 1 && model.Resolution <= 5 || model.Resolution == 10 { + resolution = model.Resolution + } + + interval := query.Interval + timeRange := query.TimeRange.To.Sub(query.TimeRange.From) + + step := calculateStep(interval, timeRange, resolution) + + expr := interpolateVariables(model.Expr, interval, timeRange) + + qs = append(qs, &lokiQuery{ + Expr: expr, + Step: step, + LegendFormat: model.LegendFormat, + Start: start, + End: end, + RefID: query.RefID, + }) + } + + return qs, nil +} diff --git a/pkg/tsdb/loki/parse_query_test.go b/pkg/tsdb/loki/parse_query_test.go new file mode 100644 index 00000000000..2274c6dda0d --- /dev/null +++ b/pkg/tsdb/loki/parse_query_test.go @@ -0,0 +1,54 @@ +package loki + +import ( + "testing" + "time" + + "github.com/grafana/grafana-plugin-sdk-go/backend" + "github.com/stretchr/testify/require" +) + +func TestParseQuery(t *testing.T) { + t.Run("parsing query model", func(t *testing.T) { + queryContext := &backend.QueryDataRequest{ + Queries: []backend.DataQuery{ + { + JSON: []byte(` + { + "expr": "go_goroutines $__interval $__interval_ms $__range $__range_s $__range_ms", + "format": "time_series", + "refId": "A" + }`, + ), + TimeRange: backend.TimeRange{ + From: time.Now().Add(-3000 * time.Second), + To: time.Now(), + }, + Interval: time.Second * 15, + MaxDataPoints: 200, + }, + }, + } + dsInfo := &datasourceInfo{} + models, err := parseQuery(dsInfo, queryContext) + require.NoError(t, err) + require.Equal(t, time.Second*15, models[0].Step) + require.Equal(t, "go_goroutines 15s 15000 3000s 3000 3000000", models[0].Expr) + }) + t.Run("interpolate variables, range between 1s and 0.5s", func(t *testing.T) { + expr := "go_goroutines $__interval $__interval_ms $__range $__range_s $__range_ms" + + interval := time.Millisecond * 50 + timeRange := time.Millisecond * 750 + + require.Equal(t, "go_goroutines 50ms 50 1s 1 750", interpolateVariables(expr, interval, timeRange)) + }) + t.Run("parsing query model, range below 0.5s", func(t *testing.T) { + expr := "go_goroutines $__interval $__interval_ms $__range $__range_s $__range_ms" + + interval := time.Millisecond * 50 + timeRange := time.Millisecond * 250 + + require.Equal(t, "go_goroutines 50ms 50 0s 0 250", interpolateVariables(expr, interval, timeRange)) + }) +}