Skip to content

Commit

Permalink
Prototype info PromQL function
Browse files Browse the repository at this point in the history
Signed-off-by: Arve Knudsen <arve.knudsen@gmail.com>
  • Loading branch information
aknuds1 committed Mar 13, 2024
1 parent 2b3d9c0 commit 5b1a4dd
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 1 deletion.
5 changes: 5 additions & 0 deletions docs/querying/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -421,6 +421,11 @@ by the number of seconds under the specified time range window, and should be
used primarily for human readability. Use `rate` in recording rules so that
increases are tracked consistently on a per-second basis.

## `info()`

For each time series in `v`, `info(v instant-vector, [label-selector string])` finds all info metrics with corresponding
identifying labels, and adds the union of their data labels to the time series, that gets returned.

## `irate()`

`irate(v range-vector)` calculates the per-second instant rate of increase of
Expand Down
11 changes: 10 additions & 1 deletion promql/engine.go
Original file line number Diff line number Diff line change
Expand Up @@ -706,6 +706,7 @@ func (ng *Engine) execEvalStmt(ctx context.Context, query *query, s *parser.Eval
lookbackDelta: s.LookbackDelta,
samplesStats: query.sampleStats,
noStepSubqueryIntervalFn: ng.noStepSubqueryIntervalFn,
querier: querier,
}
query.sampleStats.InitStepTracking(start, start, 1)

Expand Down Expand Up @@ -763,6 +764,7 @@ func (ng *Engine) execEvalStmt(ctx context.Context, query *query, s *parser.Eval
lookbackDelta: s.LookbackDelta,
samplesStats: query.sampleStats,
noStepSubqueryIntervalFn: ng.noStepSubqueryIntervalFn,
querier: querier,
}
query.sampleStats.InitStepTracking(evaluator.startTimestamp, evaluator.endTimestamp, evaluator.interval)
val, warnings, err := evaluator.Eval(s.Expr)
Expand Down Expand Up @@ -1016,6 +1018,8 @@ type evaluator struct {
lookbackDelta time.Duration
samplesStats *stats.QuerySamples
noStepSubqueryIntervalFn func(rangeMillis int64) int64

querier storage.Querier
}

// errorf causes a panic with the input formatted into an error.
Expand Down Expand Up @@ -1089,6 +1093,8 @@ type EvalNodeHelper struct {
rightSigs map[string]Sample
matchedSigs map[string]map[uint64]struct{}
resultMetric map[string]labels.Labels

Querier storage.Querier
}

func (enh *EvalNodeHelper) resetBuilder(lbls labels.Labels) {
Expand Down Expand Up @@ -1138,7 +1144,7 @@ func (ev *evaluator) rangeEval(prepSeries func(labels.Labels, *EvalSeriesHelper)
biggestLen = len(matrixes[i])
}
}
enh := &EvalNodeHelper{Out: make(Vector, 0, biggestLen)}
enh := &EvalNodeHelper{Out: make(Vector, 0, biggestLen), Querier: ev.querier}
type seriesAndTimestamp struct {
Series
ts int64
Expand Down Expand Up @@ -1167,6 +1173,9 @@ func (ev *evaluator) rangeEval(prepSeries func(labels.Labels, *EvalSeriesHelper)
}
}

// For each timestamp, iterate over the expressions, and for each series in each expression's matrix,
// append a corresponding sample to the expression's input vector.
// Then, call funcCall.
for ts := ev.startTimestamp; ts <= ev.endTimestamp; ts += ev.interval {
if err := contextDone(ev.ctx, "expression evaluation"); err != nil {
ev.error(err)
Expand Down
123 changes: 123 additions & 0 deletions promql/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
package promql

import (
"context"
"fmt"
"math"
"slices"
Expand All @@ -30,6 +31,7 @@ import (
"github.com/prometheus/prometheus/model/labels"
"github.com/prometheus/prometheus/promql/parser"
"github.com/prometheus/prometheus/promql/parser/posrange"
"github.com/prometheus/prometheus/storage"
"github.com/prometheus/prometheus/util/annotations"
)

Expand Down Expand Up @@ -1486,6 +1488,126 @@ func funcYear(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper)
}), nil
}

// === info(vector parser.ValueTypeVector, [ls label-selector]) (Vector, Annotations) ===
func funcInfo(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) (Vector, annotations.Annotations) {
vector := vals[0].(Vector)
var dataLabelMatchers []*labels.Matcher
if len(args) > 1 {
// TODO: Introduce a dedicated LabelSelector type
labelSelector := args[1].(*parser.VectorSelector)
dataLabelMatchers = labelSelector.LabelMatchers
}

if len(vector) == 0 {
return enh.Out, nil
}

if enh.Dmn == nil {
enh.Dmn = make(map[uint64]labels.Labels, len(enh.Out))
}

ts := vector[0].T
hints := &storage.SelectHints{
Start: ts,
End: ts,
Func: "info",
}
selectors := []*labels.Matcher{
labels.MustNewMatcher(labels.MatchRegexp, "__name__", ".+_info"),
}
selectors = append(selectors, dataLabelMatchers...)

it := enh.Querier.Select(context.TODO(), false, hints, selectors...)
var infoSeries []storage.Series
for it.Next() {
infoSeries = append(infoSeries, it.At())
}
if it.Err() != nil {
var annots annotations.Annotations
annots.Add(fmt.Errorf("querying for info metrics: %w", it.Err()))
return nil, annots
}

for _, s := range vector {
// Hash of sample's label set (the label set identifies the metric)
h := s.Metric.Hash()
if ls, ok := enh.Dmn[h]; ok {
// We've encountered this label set/metric before and can re-use the previous result
enh.Out = append(enh.Out, Sample{
Metric: ls,
F: s.F,
H: s.H,
})
continue
}

lb := labels.NewBuilder(s.Metric)

// Find info series with matching labels, these are identifying labels
addedDataLabels := map[string]struct{}{}
for _, series := range infoSeries {
infoLbls := series.Labels()

identifyingLabels := map[string]string{}
dataLabels := map[string]string{}
infoLbls.Range(func(l labels.Label) {
if l.Name == "__name__" {
return
}

if s.Metric.Has(l.Name) {
identifyingLabels[l.Name] = l.Value
return
}

if len(dataLabelMatchers) > 0 {
// Keep only data labels among dataLabelMatchers
found := false
for _, m := range dataLabelMatchers {
if m.Name == l.Name {
found = true
break
}
}
if !found {
return
}
}
dataLabels[l.Name] = l.Value
})
if len(identifyingLabels) == 0 {
continue
}

name := infoLbls.Get("__name__")
fmt.Printf("Identifying labels for info metric %s: %#v, data labels: %#v\n", name, identifyingLabels, dataLabels)
for n, v := range dataLabels {
if lb.Get(n) == "" {
lb.Set(n, v)
}

addedDataLabels[n] = struct{}{}
}
}

// If info metric data label matchers are specified, we should only include series where
// info metric data labels are found
if len(dataLabelMatchers) > 0 && len(addedDataLabels) == 0 {
continue
}

ls := lb.Labels()
enh.Dmn[h] = ls
enh.Out = append(enh.Out, Sample{
Metric: ls,
F: s.F,
H: s.H,
})
}

return enh.Out, nil
}

// FunctionCalls is a list of all functions supported by PromQL, including their types.
var FunctionCalls = map[string]FunctionCall{
"abs": funcAbs,
Expand Down Expand Up @@ -1526,6 +1648,7 @@ var FunctionCalls = map[string]FunctionCall{
"hour": funcHour,
"idelta": funcIdelta,
"increase": funcIncrease,
"info": funcInfo,
"irate": funcIrate,
"label_replace": funcLabelReplace,
"label_join": funcLabelJoin,
Expand Down
7 changes: 7 additions & 0 deletions promql/parser/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,13 @@ var Functions = map[string]*Function{
ArgTypes: []ValueType{ValueTypeMatrix},
ReturnType: ValueTypeVector,
},
"info": {
Name: "info",
ArgTypes: []ValueType{ValueTypeVector, ValueTypeVector},
ReturnType: ValueTypeVector,
Experimental: true,
Variadic: 1,
},
"irate": {
Name: "irate",
ArgTypes: []ValueType{ValueTypeMatrix},
Expand Down
6 changes: 6 additions & 0 deletions web/ui/module/codemirror-promql/src/complete/promql.terms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,12 @@ export const functionIdentifierTerms = [
info: 'Calculate the increase in value over a range of time (for counters)',
type: 'function',
},
{
label: 'info',
detail: 'function',
info: 'Add data labels from corresponding info metrics',
type: 'function',
},
{
label: 'irate',
detail: 'function',
Expand Down
7 changes: 7 additions & 0 deletions web/ui/module/codemirror-promql/src/types/function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ import {
Hour,
Idelta,
Increase,
Info,
Irate,
LabelJoin,
LabelReplace,
Expand Down Expand Up @@ -336,6 +337,12 @@ const promqlFunctions: { [key: number]: PromQLFunction } = {
variadic: 0,
returnType: ValueType.vector,
},
[Info]: {
name: 'info',
argTypes: [ValueType.vector, ValueType.vector],
variadic: 0,
returnType: ValueType.vector,
},
[Irate]: {
name: 'irate',
argTypes: [ValueType.matrix],
Expand Down
2 changes: 2 additions & 0 deletions web/ui/module/lezer-promql/src/promql.grammar
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ FunctionIdentifier {
Hour |
Idelta |
Increase |
Info |
Irate |
LabelReplace |
LabelJoin |
Expand Down Expand Up @@ -376,6 +377,7 @@ NumberLiteral {
Hour { condFn<"hour"> }
Idelta { condFn<"idelta"> }
Increase { condFn<"increase"> }
Info { condFn<"info"> }
Irate { condFn<"irate"> }
LabelReplace { condFn<"label_replace"> }
LabelJoin { condFn<"label_join"> }
Expand Down

0 comments on commit 5b1a4dd

Please sign in to comment.