Prometheus: Support utf8 metrics and labels in scopes (#99015)

* support utf8 in scopes

* separate the utf8 tests

* remove redundant test after enabling utf8 validation in prometheus
This commit is contained in:
ismail simsek
2025-01-16 18:11:44 +02:00
committed by GitHub
parent c0b3932168
commit e96b5a68c4
3 changed files with 141 additions and 11 deletions
+5
View File
@@ -4,10 +4,15 @@ import (
"fmt"
"strings"
"github.com/prometheus/common/model"
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/promql/parser"
)
func init() {
model.NameValidationScheme = model.UTF8Validation
}
// ApplyFiltersAndGroupBy takes a raw promQL expression, converts the filters into PromQL matchers, and applies these matchers to the parsed expression. It also applies the group by clause to any aggregate expressions in the parsed expression.
func ApplyFiltersAndGroupBy(rawExpr string, scopeFilters, adHocFilters []ScopeFilter, groupBy []string) (string, error) {
expr, err := parser.ParseExpr(rawExpr)
+136 -2
View File
@@ -145,6 +145,37 @@ func TestApplyQueryFiltersAndGroupBy_Filters(t *testing.T) {
}
}
func TestApplyQueryFiltersAndGroupBy_Filters_utf8(t *testing.T) {
tests := []struct {
name string
query string
adhocFilters []ScopeFilter
scopeFilters []ScopeFilter
expected string
expectErr bool
}{
{
name: "No filters with existing utf8 filter",
query: `http_requests_total{"job.name"="prometheus"}`,
expected: `http_requests_total{"job.name"="prometheus"}`,
expectErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expr, err := ApplyFiltersAndGroupBy(tt.query, tt.scopeFilters, tt.adhocFilters, nil)
if tt.expectErr {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, tt.expected, expr, tt.name)
}
})
}
}
func TestApplyQueryFiltersAndGroupBy_GroupBy(t *testing.T) {
tests := []struct {
name string
@@ -197,7 +228,45 @@ func TestApplyQueryFiltersAndGroupBy_GroupBy(t *testing.T) {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, expr, tt.expected)
require.Equal(t, tt.expected, expr)
}
})
}
}
func TestApplyQueryFiltersAndGroupBy_GroupBy_utf8(t *testing.T) {
tests := []struct {
name string
query string
groupBy []string
expected string
expectErr bool
}{
{
name: "GroupBy with no aggregate expression and utf8 metric",
groupBy: []string{"job"},
query: `{"http.requests_total"}`,
expected: `{__name__="http.requests_total"}`,
expectErr: false,
},
{
name: "GroupBy with aggregate expression with existing utf8 group by",
groupBy: []string{"status"},
query: `sum by ("utf8.job") (http_requests_total)`,
expected: `sum by ("utf8.job", status) (http_requests_total)`,
expectErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expr, err := ApplyFiltersAndGroupBy(tt.query, nil, nil, tt.groupBy)
if tt.expectErr {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, tt.expected, expr)
}
})
}
@@ -237,7 +306,72 @@ func TestApplyQueryFiltersAndGroupBy(t *testing.T) {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, expr, tt.expected)
require.Equal(t, tt.expected, expr)
}
})
}
}
func TestApplyQueryFiltersAndGroupBy_utf8(t *testing.T) {
tests := []struct {
name string
query string
adhocFilters []ScopeFilter
scopeFilters []ScopeFilter
groupby []string
expected string
expectErr bool
}{
{
name: "Adhoc filters with more complex expression and utf8 metric name",
query: `sum({"capacity_bytes", job="prometheus"} + {"available_bytes", job="grafana"}) / 1024`,
adhocFilters: []ScopeFilter{
{Key: "job", Value: "alloy", Operator: FilterOperatorEquals},
},
scopeFilters: []ScopeFilter{
{Key: "vol", Value: "/", Operator: FilterOperatorEquals},
},
groupby: []string{"job"},
expected: `sum by (job) ({__name__="capacity_bytes",job="alloy",vol="/"} + {__name__="available_bytes",job="alloy",vol="/"}) / 1024`,
expectErr: false,
},
{
name: "Adhoc filters with more complex expression with utf8 label",
query: `sum(capacity_bytes{job="prometheus", "utf8.label"="value"} + available_bytes{job="grafana"}) / 1024`,
adhocFilters: []ScopeFilter{
{Key: "job", Value: "alloy", Operator: FilterOperatorEquals},
},
scopeFilters: []ScopeFilter{
{Key: "vol", Value: "/", Operator: FilterOperatorEquals},
},
groupby: []string{"job"},
expected: `sum by (job) (capacity_bytes{"utf8.label"="value",job="alloy",vol="/"} + available_bytes{job="alloy",vol="/"}) / 1024`,
expectErr: false,
},
{
name: "Adhoc filters with more complex expression with utf8 metric and label",
query: `sum({"capacity_bytes", job="prometheus", "utf8.label"="value"} + available_bytes{job="grafana"}) / 1024`,
adhocFilters: []ScopeFilter{
{Key: "job", Value: "alloy", Operator: FilterOperatorEquals},
},
scopeFilters: []ScopeFilter{
{Key: "vol", Value: "/", Operator: FilterOperatorEquals},
},
groupby: []string{"job"},
expected: `sum by (job) ({"utf8.label"="value",__name__="capacity_bytes",job="alloy",vol="/"} + available_bytes{job="alloy",vol="/"}) / 1024`,
expectErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
expr, err := ApplyFiltersAndGroupBy(tt.query, tt.scopeFilters, tt.adhocFilters, tt.groupby)
if tt.expectErr {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, tt.expected, expr)
}
})
}
@@ -785,15 +785,6 @@ func TestIntegrationInsertAlertRules(t *testing.T) {
require.ErrorIs(t, err, models.ErrAlertRuleFailedValidation)
require.ErrorContains(t, err, "metric name for recording rule must be a valid utf8 string")
})
t.Run("invalid metric name", func(t *testing.T) {
invalidMetric := "with-dashes"
invalidRule := recordingRulesGen.Generate()
invalidRule.Record.Metric = invalidMetric
_, err := store.InsertAlertRules(context.Background(), []models.AlertRule{invalidRule})
require.ErrorIs(t, err, models.ErrAlertRuleFailedValidation)
require.ErrorContains(t, err, "metric name for recording rule must be a valid Prometheus metric name")
})
})
t.Run("clears fields that should not exist on recording rules", func(t *testing.T) {