Normalise 'le' and 'quantile' labels in queries
Signed-off-by: Ganesh Vernekar <ganesh.vernekar@reddit.com>
This commit is contained in:
parent
11c49151b7
commit
8815b789b8
|
@ -253,6 +253,9 @@ func (c *flagConfig) setFeatureListOptions(logger *slog.Logger) error {
|
||||||
case "promql-duration-expr":
|
case "promql-duration-expr":
|
||||||
parser.ExperimentalDurationExpr = true
|
parser.ExperimentalDurationExpr = true
|
||||||
logger.Info("Experimental duration expression parsing enabled.")
|
logger.Info("Experimental duration expression parsing enabled.")
|
||||||
|
case "promql-normalise-le-quantile-labels":
|
||||||
|
parser.NormaliseLeAndQuantileMatchers = true
|
||||||
|
logger.Info("Experimental normalising of 'le' and 'quantile' labels for PromQL enabled.")
|
||||||
case "native-histograms":
|
case "native-histograms":
|
||||||
c.tsdb.EnableNativeHistograms = true
|
c.tsdb.EnableNativeHistograms = true
|
||||||
c.scrape.EnableNativeHistogramsIngestion = true
|
c.scrape.EnableNativeHistogramsIngestion = true
|
||||||
|
|
|
@ -773,7 +773,7 @@ func normalizeFloatsInLabelValues(t model.MetricType, l, v string) string {
|
||||||
if (t == model.MetricTypeSummary && l == model.QuantileLabel) || (t == model.MetricTypeHistogram && l == model.BucketLabel) {
|
if (t == model.MetricTypeSummary && l == model.QuantileLabel) || (t == model.MetricTypeHistogram && l == model.BucketLabel) {
|
||||||
f, err := strconv.ParseFloat(v, 64)
|
f, err := strconv.ParseFloat(v, 64)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return formatOpenMetricsFloat(f)
|
return FormatOpenMetricsFloat(f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return v
|
return v
|
||||||
|
|
|
@ -34,7 +34,7 @@ import (
|
||||||
"github.com/prometheus/prometheus/schema"
|
"github.com/prometheus/prometheus/schema"
|
||||||
)
|
)
|
||||||
|
|
||||||
// floatFormatBufPool is exclusively used in formatOpenMetricsFloat.
|
// floatFormatBufPool is exclusively used in FormatOpenMetricsFloat.
|
||||||
var floatFormatBufPool = sync.Pool{
|
var floatFormatBufPool = sync.Pool{
|
||||||
New: func() any {
|
New: func() any {
|
||||||
// To contain at most 17 digits and additional syntax for a float64.
|
// To contain at most 17 digits and additional syntax for a float64.
|
||||||
|
@ -632,7 +632,7 @@ func (p *ProtobufParser) getMagicLabel() (bool, string, string) {
|
||||||
qq := p.dec.GetSummary().GetQuantile()
|
qq := p.dec.GetSummary().GetQuantile()
|
||||||
q := qq[p.fieldPos]
|
q := qq[p.fieldPos]
|
||||||
p.fieldsDone = p.fieldPos == len(qq)-1
|
p.fieldsDone = p.fieldPos == len(qq)-1
|
||||||
return true, model.QuantileLabel, formatOpenMetricsFloat(q.GetQuantile())
|
return true, model.QuantileLabel, FormatOpenMetricsFloat(q.GetQuantile())
|
||||||
case dto.MetricType_HISTOGRAM, dto.MetricType_GAUGE_HISTOGRAM:
|
case dto.MetricType_HISTOGRAM, dto.MetricType_GAUGE_HISTOGRAM:
|
||||||
bb := p.dec.GetHistogram().GetBucket()
|
bb := p.dec.GetHistogram().GetBucket()
|
||||||
if p.fieldPos >= len(bb) {
|
if p.fieldPos >= len(bb) {
|
||||||
|
@ -641,15 +641,15 @@ func (p *ProtobufParser) getMagicLabel() (bool, string, string) {
|
||||||
}
|
}
|
||||||
b := bb[p.fieldPos]
|
b := bb[p.fieldPos]
|
||||||
p.fieldsDone = math.IsInf(b.GetUpperBound(), +1)
|
p.fieldsDone = math.IsInf(b.GetUpperBound(), +1)
|
||||||
return true, model.BucketLabel, formatOpenMetricsFloat(b.GetUpperBound())
|
return true, model.BucketLabel, FormatOpenMetricsFloat(b.GetUpperBound())
|
||||||
}
|
}
|
||||||
return false, "", ""
|
return false, "", ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// formatOpenMetricsFloat works like the usual Go string formatting of a float
|
// FormatOpenMetricsFloat works like the usual Go string formatting of a float
|
||||||
// but appends ".0" if the resulting number would otherwise contain neither a
|
// but appends ".0" if the resulting number would otherwise contain neither a
|
||||||
// "." nor an "e".
|
// "." nor an "e".
|
||||||
func formatOpenMetricsFloat(f float64) string {
|
func FormatOpenMetricsFloat(f float64) string {
|
||||||
// A few common cases hardcoded.
|
// A few common cases hardcoded.
|
||||||
switch {
|
switch {
|
||||||
case f == 1:
|
case f == 1:
|
||||||
|
|
|
@ -28,6 +28,7 @@ import (
|
||||||
|
|
||||||
"github.com/prometheus/prometheus/model/histogram"
|
"github.com/prometheus/prometheus/model/histogram"
|
||||||
"github.com/prometheus/prometheus/model/labels"
|
"github.com/prometheus/prometheus/model/labels"
|
||||||
|
"github.com/prometheus/prometheus/model/textparse"
|
||||||
"github.com/prometheus/prometheus/model/timestamp"
|
"github.com/prometheus/prometheus/model/timestamp"
|
||||||
"github.com/prometheus/prometheus/promql/parser/posrange"
|
"github.com/prometheus/prometheus/promql/parser/posrange"
|
||||||
"github.com/prometheus/prometheus/util/strutil"
|
"github.com/prometheus/prometheus/util/strutil"
|
||||||
|
@ -42,6 +43,11 @@ var parserPool = sync.Pool{
|
||||||
// ExperimentalDurationExpr is a flag to enable experimental duration expression parsing.
|
// ExperimentalDurationExpr is a flag to enable experimental duration expression parsing.
|
||||||
var ExperimentalDurationExpr bool
|
var ExperimentalDurationExpr bool
|
||||||
|
|
||||||
|
// NormaliseLeAndQuantileMatchers is a flag to enable experimental conversion of matchers like
|
||||||
|
// le="2" and quantile="2" to le=~"2|2.0" and quantile=~"2|2.0" to match the normalisation
|
||||||
|
// of these labels during scrape time.
|
||||||
|
var NormaliseLeAndQuantileMatchers bool
|
||||||
|
|
||||||
type Parser interface {
|
type Parser interface {
|
||||||
ParseExpr() (Expr, error)
|
ParseExpr() (Expr, error)
|
||||||
Close()
|
Close()
|
||||||
|
@ -839,6 +845,31 @@ func (p *parser) checkAST(node Node) (typ ValueType) {
|
||||||
p.checkAST(n.VectorSelector)
|
p.checkAST(n.VectorSelector)
|
||||||
|
|
||||||
case *VectorSelector:
|
case *VectorSelector:
|
||||||
|
if NormaliseLeAndQuantileMatchers {
|
||||||
|
for i, m := range n.LabelMatchers {
|
||||||
|
// Rewrite the matchers of type le="2" and quantile="2".
|
||||||
|
if m == nil || m.Type != labels.MatchEqual || (m.Name != model.QuantileLabel && m.Name != model.BucketLabel) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
f, err := strconv.ParseFloat(m.Value, 64)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
omFloat := textparse.FormatOpenMetricsFloat(f)
|
||||||
|
if omFloat == m.Value {
|
||||||
|
// The label value is already in the OpenMetric format, example le="2.0".
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// Example: changes the matcher from le="2" to le=~"2|2.0".
|
||||||
|
mat, err := labels.NewMatcher(labels.MatchRegexp, m.Name, fmt.Sprintf("%s|%s", m.Value, omFloat))
|
||||||
|
if err == nil {
|
||||||
|
n.LabelMatchers[i] = mat
|
||||||
|
} else {
|
||||||
|
p.addParseErrf(n.PositionRange(), "rewrite of matcher failed: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if n.Name != "" {
|
if n.Name != "" {
|
||||||
// In this case the last LabelMatcher is checking for the metric name
|
// In this case the last LabelMatcher is checking for the metric name
|
||||||
// set outside the braces. This checks if the name has already been set
|
// set outside the braces. This checks if the name has already been set
|
||||||
|
|
Loading…
Reference in New Issue