Prometheus: (Experimental) Inject label matchers into queries (also change drone to fix ARM rpm build and Update Swagger) (#81396)

- Feature Toggle is `promQLScope`.
 - Query property is:

"scope": {
  "matchers": "{job=~\".*\"}"
 }

Misc:
 - Also updates drone GO version to address ARM bug https://github.com/golang/go/issues/58425
 - Also updates Swagger defs that were causing builds to fail

---------

Co-authored-by: Kevin Minehart <kmineh0151@gmail.com>
This commit is contained in:
Kyle Brandt
2024-01-29 22:22:17 +02:00
committed by GitHub
co-authored by Kevin Minehart
parent c2b64c6739
commit 43d0664340
19 changed files with 187 additions and 52 deletions
+7
View File
@@ -1330,5 +1330,12 @@ var (
Owner: grafanaAlertingSquad,
Created: time.Date(2024, time.January, 22, 12, 0, 0, 0, time.UTC),
},
{
Name: "promQLScope",
Description: "In-development feature that will allow injection of labels into prometheus queries.",
Stage: FeatureStageExperimental,
Owner: grafanaObservabilityMetricsSquad,
Created: time.Date(2024, time.January, 29, 0, 0, 0, 0, time.UTC),
},
}
)
+1
View File
@@ -156,3 +156,4 @@ jitterAlertRules,GA,@grafana/alerting-squad,2024-01-17,false,true,false
jitterAlertRulesWithinGroups,preview,@grafana/alerting-squad,2024-01-17,false,true,false
onPremToCloudMigrations,experimental,@grafana/grafana-operator-experience-squad,2024-01-22,false,false,false
alertingSaveStatePeriodic,privatePreview,@grafana/alerting-squad,2024-01-22,false,false,false
promQLScope,experimental,@grafana/observability-metrics,2024-01-29,false,false,false
1 Name Stage Owner Created requiresDevMode RequiresRestart FrontendOnly
156 jitterAlertRulesWithinGroups preview @grafana/alerting-squad 2024-01-17 false true false
157 onPremToCloudMigrations experimental @grafana/grafana-operator-experience-squad 2024-01-22 false false false
158 alertingSaveStatePeriodic privatePreview @grafana/alerting-squad 2024-01-22 false false false
159 promQLScope experimental @grafana/observability-metrics 2024-01-29 false false false
+4
View File
@@ -634,4 +634,8 @@ const (
// FlagAlertingSaveStatePeriodic
// Writes the state periodically to the database, asynchronous to rule evaluation
FlagAlertingSaveStatePeriodic = "alertingSaveStatePeriodic"
// FlagPromQLScope
// In-development feature that will allow injection of labels into prometheus queries.
FlagPromQLScope = "promQLScope"
)
@@ -97,6 +97,9 @@ type PrometheusDataQuery struct {
// In server side expressions, the refId is used as a variable name to identify results.
// By default, the UI will assign A->Z; however setting meaningful names may be useful.
RefId string `json:"refId"`
Scope *struct {
Matchers string `json:"matchers"`
} `json:"scope,omitempty"`
}
// QueryEditorMode defines model for QueryEditorMode.
+20 -1
View File
@@ -2,12 +2,15 @@ package models
import (
"encoding/json"
"fmt"
"math"
"strconv"
"strings"
"time"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/promql/parser"
"github.com/grafana/grafana/pkg/tsdb/intervalv2"
"github.com/grafana/grafana/pkg/tsdb/prometheus/kinds/dataquery"
@@ -75,9 +78,14 @@ type Query struct {
RangeQuery bool
ExemplarQuery bool
UtcOffsetSec int64
Scope Scope
}
func Parse(query backend.DataQuery, dsScrapeInterval string, intervalCalculator intervalv2.Calculator, fromAlert bool) (*Query, error) {
type Scope struct {
Matchers []*labels.Matcher
}
func Parse(query backend.DataQuery, dsScrapeInterval string, intervalCalculator intervalv2.Calculator, fromAlert bool, enableScope bool) (*Query, error) {
model := &QueryModel{}
if err := json.Unmarshal(query.JSON, model); err != nil {
return nil, err
@@ -99,6 +107,17 @@ func Parse(query backend.DataQuery, dsScrapeInterval string, intervalCalculator
dsScrapeInterval,
timeRange,
)
var matchers []*labels.Matcher
if enableScope && model.Scope != nil && model.Scope.Matchers != "" {
matchers, err = parser.ParseMetricSelector(model.Scope.Matchers)
if err != nil {
return nil, fmt.Errorf("failed to parse metric selector %v in scope", model.Scope.Matchers)
}
expr, err = ApplyQueryScope(expr, matchers)
if err != nil {
return nil, err
}
}
var rangeQuery, instantQuery bool
if model.Instant == nil {
instantQuery = false
+29 -29
View File
@@ -37,7 +37,7 @@ func TestParse(t *testing.T) {
RefID: "A",
}
res, err := models.Parse(q, "15s", intervalCalculator, true)
res, err := models.Parse(q, "15s", intervalCalculator, true, false)
require.NoError(t, err)
require.Equal(t, false, res.ExemplarQuery)
})
@@ -54,7 +54,7 @@ func TestParse(t *testing.T) {
"refId": "A"
}`, timeRange, time.Duration(1)*time.Minute)
res, err := models.Parse(q, "15s", intervalCalculator, false)
res, err := models.Parse(q, "15s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, time.Second*30, res.Step)
})
@@ -72,7 +72,7 @@ func TestParse(t *testing.T) {
"refId": "A"
}`, timeRange, time.Duration(1)*time.Minute)
res, err := models.Parse(q, "15s", intervalCalculator, false)
res, err := models.Parse(q, "15s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, time.Second*15, res.Step)
})
@@ -90,7 +90,7 @@ func TestParse(t *testing.T) {
"refId": "A"
}`, timeRange, time.Duration(1)*time.Minute)
res, err := models.Parse(q, "15s", intervalCalculator, false)
res, err := models.Parse(q, "15s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, time.Minute*20, res.Step)
})
@@ -108,7 +108,7 @@ func TestParse(t *testing.T) {
"refId": "A"
}`, timeRange, time.Duration(1)*time.Minute)
res, err := models.Parse(q, "15s", intervalCalculator, false)
res, err := models.Parse(q, "15s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, time.Minute*2, res.Step)
})
@@ -126,7 +126,7 @@ func TestParse(t *testing.T) {
"refId": "A"
}`, timeRange, time.Duration(1)*time.Minute)
res, err := models.Parse(q, "240s", intervalCalculator, false)
res, err := models.Parse(q, "240s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, time.Minute*4, res.Step)
})
@@ -145,7 +145,7 @@ func TestParse(t *testing.T) {
"refId": "A"
}`, timeRange, time.Duration(1)*time.Minute)
res, err := models.Parse(q, "15s", intervalCalculator, false)
res, err := models.Parse(q, "15s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [2m]})", res.Expr)
require.Equal(t, 120*time.Second, res.Step)
@@ -166,7 +166,7 @@ func TestParse(t *testing.T) {
"refId": "A"
}`, timeRange, time.Duration(1)*time.Minute)
res, err := models.Parse(q, "15s", intervalCalculator, false)
res, err := models.Parse(q, "15s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [2m]})", res.Expr)
})
@@ -185,7 +185,7 @@ func TestParse(t *testing.T) {
"refId": "A"
}`, timeRange, time.Duration(1)*time.Minute)
res, err := models.Parse(q, "15s", intervalCalculator, false)
res, err := models.Parse(q, "15s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [120000]})", res.Expr)
})
@@ -204,7 +204,7 @@ func TestParse(t *testing.T) {
"refId": "A"
}`, timeRange, time.Duration(1)*time.Minute)
res, err := models.Parse(q, "15s", intervalCalculator, false)
res, err := models.Parse(q, "15s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [120000]}) + rate(ALERTS{job=\"test\" [2m]})", res.Expr)
})
@@ -223,7 +223,7 @@ func TestParse(t *testing.T) {
"refId": "A"
}`, timeRange, time.Duration(1)*time.Minute)
res, err := models.Parse(q, "15s", intervalCalculator, false)
res, err := models.Parse(q, "15s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [120000]}) + rate(ALERTS{job=\"test\" [2m]})", res.Expr)
})
@@ -241,7 +241,7 @@ func TestParse(t *testing.T) {
"refId": "A"
}`, timeRange, time.Duration(1)*time.Minute)
res, err := models.Parse(q, "15s", intervalCalculator, false)
res, err := models.Parse(q, "15s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [172800s]})", res.Expr)
})
@@ -259,7 +259,7 @@ func TestParse(t *testing.T) {
"refId": "A"
}`, timeRange, time.Duration(1)*time.Minute)
res, err := models.Parse(q, "15s", intervalCalculator, false)
res, err := models.Parse(q, "15s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [172800]})", res.Expr)
})
@@ -277,7 +277,7 @@ func TestParse(t *testing.T) {
"refId": "A"
}`, timeRange, time.Duration(1)*time.Minute)
res, err := models.Parse(q, "15s", intervalCalculator, false)
res, err := models.Parse(q, "15s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [172800s]})", res.Expr)
})
@@ -295,7 +295,7 @@ func TestParse(t *testing.T) {
"refId": "A"
}`, timeRange, time.Duration(1)*time.Minute)
res, err := models.Parse(q, "15s", intervalCalculator, false)
res, err := models.Parse(q, "15s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [0]})", res.Expr)
})
@@ -313,7 +313,7 @@ func TestParse(t *testing.T) {
"refId": "A"
}`, timeRange, time.Duration(1)*time.Minute)
res, err := models.Parse(q, "15s", intervalCalculator, false)
res, err := models.Parse(q, "15s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [1]})", res.Expr)
})
@@ -331,7 +331,7 @@ func TestParse(t *testing.T) {
"refId": "A"
}`, timeRange, time.Duration(1)*time.Minute)
res, err := models.Parse(q, "15s", intervalCalculator, false)
res, err := models.Parse(q, "15s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [172800000]})", res.Expr)
})
@@ -349,7 +349,7 @@ func TestParse(t *testing.T) {
"refId": "A"
}`, timeRange, time.Duration(1)*time.Minute)
res, err := models.Parse(q, "15s", intervalCalculator, false)
res, err := models.Parse(q, "15s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [20]})", res.Expr)
})
@@ -368,7 +368,7 @@ func TestParse(t *testing.T) {
"refId": "A"
}`, timeRange, time.Duration(1)*time.Minute)
res, err := models.Parse(q, "15s", intervalCalculator, false)
res, err := models.Parse(q, "15s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [20m0s]})", res.Expr)
})
@@ -387,7 +387,7 @@ func TestParse(t *testing.T) {
"refId": "A"
}`, timeRange, 1*time.Minute)
res, err := models.Parse(q, "15s", intervalCalculator, false)
res, err := models.Parse(q, "15s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [1m0s]})", res.Expr)
require.Equal(t, 1*time.Minute, res.Step)
@@ -406,7 +406,7 @@ func TestParse(t *testing.T) {
"refId": "A"
}`, timeRange, 2*time.Minute)
res, err := models.Parse(q, "15s", intervalCalculator, false)
res, err := models.Parse(q, "15s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [135000]})", res.Expr)
})
@@ -424,7 +424,7 @@ func TestParse(t *testing.T) {
"refId": "A"
}`, timeRange, 2*time.Minute)
res, err := models.Parse(q, "15s", intervalCalculator, false)
res, err := models.Parse(q, "15s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [135000]}) + rate(ALERTS{job=\"test\" [2m15s]})", res.Expr)
})
@@ -442,7 +442,7 @@ func TestParse(t *testing.T) {
"refId": "A"
}`, timeRange, 2*time.Minute)
res, err := models.Parse(q, "15s", intervalCalculator, false)
res, err := models.Parse(q, "15s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, "rate(ALERTS{job=\"test\" [135000]}) + rate(ALERTS{job=\"test\" [2m15s]})", res.Expr)
})
@@ -461,7 +461,7 @@ func TestParse(t *testing.T) {
"range": true
}`, timeRange, time.Duration(1)*time.Minute)
res, err := models.Parse(q, "15s", intervalCalculator, false)
res, err := models.Parse(q, "15s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, true, res.RangeQuery)
})
@@ -481,7 +481,7 @@ func TestParse(t *testing.T) {
"instant": true
}`, timeRange, time.Duration(1)*time.Minute)
res, err := models.Parse(q, "15s", intervalCalculator, false)
res, err := models.Parse(q, "15s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, true, res.RangeQuery)
require.Equal(t, true, res.InstantQuery)
@@ -500,7 +500,7 @@ func TestParse(t *testing.T) {
"refId": "A"
}`, timeRange, time.Duration(1)*time.Minute)
res, err := models.Parse(q, "15s", intervalCalculator, false)
res, err := models.Parse(q, "15s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, true, res.RangeQuery)
})
@@ -631,7 +631,7 @@ func TestRateInterval(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
q := mockQuery(tt.args.expr, tt.args.interval, tt.args.intervalMs, tt.args.timeRange)
q.MaxDataPoints = 12384
res, err := models.Parse(q, tt.args.dsScrapeInterval, intervalCalculator, false)
res, err := models.Parse(q, tt.args.dsScrapeInterval, intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, tt.want.Expr, res.Expr)
require.Equal(t, tt.want.Step, res.Step)
@@ -666,7 +666,7 @@ func TestRateInterval(t *testing.T) {
"utcOffsetSec":3600
}`),
}
res, err := models.Parse(query, "30s", intervalCalculator, false)
res, err := models.Parse(query, "30s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, "sum(rate(process_cpu_seconds_total[2m0s]))", res.Expr)
require.Equal(t, 30*time.Second, res.Step)
@@ -701,7 +701,7 @@ func TestRateInterval(t *testing.T) {
"maxDataPoints": 1055
}`),
}
res, err := models.Parse(query, "15s", intervalCalculator, false)
res, err := models.Parse(query, "15s", intervalCalculator, false, false)
require.NoError(t, err)
require.Equal(t, "sum(rate(cache_requests_total[1m0s]))", res.Expr)
require.Equal(t, 15*time.Second, res.Step)
+52
View File
@@ -0,0 +1,52 @@
package models
import (
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/promql/parser"
)
func ApplyQueryScope(rawExpr string, matchers []*labels.Matcher) (string, error) {
expr, err := parser.ParseExpr(rawExpr)
if err != nil {
return "", err
}
matcherNamesToIdx := make(map[string]int, len(matchers))
for i, matcher := range matchers {
if matcher == nil {
continue
}
matcherNamesToIdx[matcher.Name] = i
}
parser.Inspect(expr, func(node parser.Node, nodes []parser.Node) error {
switch v := node.(type) {
case *parser.VectorSelector:
found := make([]bool, len(matchers))
for _, matcher := range v.LabelMatchers {
if matcher == nil || matcher.Name == "__name__" { // const prob
continue
}
if _, ok := matcherNamesToIdx[matcher.Name]; ok {
found[matcherNamesToIdx[matcher.Name]] = true
newM := matchers[matcherNamesToIdx[matcher.Name]]
matcher.Name = newM.Name
matcher.Type = newM.Type
matcher.Value = newM.Value
}
}
for i, f := range found {
if f {
continue
}
v.LabelMatchers = append(v.LabelMatchers, matchers[i])
}
return nil
default:
return nil
}
})
return expr.String(), nil
}
+3 -1
View File
@@ -45,6 +45,7 @@ type QueryData struct {
URL string
TimeInterval string
enableDataplane bool
enableScope bool
exemplarSampler func() exemplar.Sampler
}
@@ -88,6 +89,7 @@ func New(
URL: settings.URL,
enableDataplane: features.IsEnabledGlobally(featuremgmt.FlagPrometheusDataplane),
exemplarSampler: exemplarSampler,
enableScope: features.IsEnabledGlobally(featuremgmt.FlagPromQLScope),
}, nil
}
@@ -98,7 +100,7 @@ func (s *QueryData) Execute(ctx context.Context, req *backend.QueryDataRequest)
}
for _, q := range req.Queries {
query, err := models.Parse(q, s.TimeInterval, s.intervalCalculator, fromAlert)
query, err := models.Parse(q, s.TimeInterval, s.intervalCalculator, fromAlert, s.enableScope)
if err != nil {
return &result, err
}