Skip to content

Commit

Permalink
allow using group_left(*) and group_right(*) as a special case for co…
Browse files Browse the repository at this point in the history
…pying all the labels from `one` side of `many-to-one` operations

Also allow specifying an optional prefix to add to copied label names
from the other side of binary operator via `group_left(...) prefix "..."` syntax.
The labels specified inside `on(...)` aren't copied.

For example, the following query copies all the labels from kube_namepsace_labels metric to kube_pod_info,
while adding "ns_" prefix to the copied labels:

  kube_pod_info * on(namespace) group_left(*) prefix "ns_" kube_namespace_labels

This allows solving the following questions:

- https://stackoverflow.com/questions/76661818/how-to-add-namespace-labels-to-pod-labels-in-prometheus
- https://stackoverflow.com/questions/76653997/how-can-i-make-a-new-copy-of-kube-namespace-labels-metric-with-a-different-name
  • Loading branch information
valyala committed Jul 17, 2023
1 parent d948e2f commit eba05b9
Show file tree
Hide file tree
Showing 2 changed files with 86 additions and 21 deletions.
89 changes: 68 additions & 21 deletions parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ func (p *parser) parseWithArgExpr() (*withArgExpr, error) {
}
if p.lex.Token == "(" {
// Parse func args.
args, err := p.parseIdentList()
args, err := p.parseIdentList(false)
if err != nil {
return nil, fmt.Errorf(`withArgExpr: cannot parse args for %q: %s`, wa.Name, err)
}
Expand Down Expand Up @@ -365,16 +365,26 @@ func (p *parser) parseExpr() (Expr, error) {
}
}
if isBinaryOpGroupModifier(p.lex.Token) {
if err := p.parseModifierExpr(&be.GroupModifier); err != nil {
if err := p.parseModifierExpr(&be.GroupModifier, false); err != nil {
return nil, err
}
if isBinaryOpJoinModifier(p.lex.Token) {
if isBinaryOpLogicalSet(be.Op) {
return nil, fmt.Errorf(`modifier %q cannot be applied to %q`, p.lex.Token, be.Op)
}
if err := p.parseModifierExpr(&be.JoinModifier); err != nil {
if err := p.parseModifierExpr(&be.JoinModifier, true); err != nil {
return nil, err
}
if strings.ToLower(p.lex.Token) == "prefix" {
if err := p.lex.Next(); err != nil {
return nil, fmt.Errorf("cannot read prefix for %s: %w", be.JoinModifier.AppendString(nil), err)
}
se, err := p.parseStringExpr()
if err != nil {
return nil, fmt.Errorf("cannot parse prefix for %s: %w", be.JoinModifier.AppendString(nil), err)
}
be.JoinModifierPrefix = se
}
}
}
e2, err := p.parseSingleExpr()
Expand Down Expand Up @@ -608,7 +618,7 @@ funcPrefixLabel:
if !isAggrFuncModifier(p.lex.Token) {
return nil, fmt.Errorf(`AggrFuncExpr: unexpected token %q; want aggregate func modifier`, p.lex.Token)
}
if err := p.parseModifierExpr(&ae.Modifier); err != nil {
if err := p.parseModifierExpr(&ae.Modifier, false); err != nil {
return nil, err
}
}
Expand All @@ -623,7 +633,7 @@ funcArgsLabel:

// Verify whether func suffix exists.
if ae.Modifier.Op == "" && isAggrFuncModifier(p.lex.Token) {
if err := p.parseModifierExpr(&ae.Modifier); err != nil {
if err := p.parseModifierExpr(&ae.Modifier, false); err != nil {
return nil, err
}
}
Expand Down Expand Up @@ -665,6 +675,18 @@ func expandWithExpr(was []*withArgExpr, e Expr) (Expr, error) {
if err != nil {
return nil, err
}
var joinModifierPrefix *StringExpr
if t.JoinModifierPrefix != nil {
jmp, err := expandWithExpr(was, t.JoinModifierPrefix)
if err != nil {
return nil, err
}
se, ok := jmp.(*StringExpr)
if !ok {
return nil, fmt.Errorf("unexpected prefix for %s; want quoted string; got %s", t.JoinModifier.AppendString(nil), jmp.AppendString(nil))
}
joinModifierPrefix = se
}
if t.Op == "+" {
lse, lok := left.(*StringExpr)
rse, rok := right.(*StringExpr)
Expand All @@ -680,6 +702,7 @@ func expandWithExpr(was []*withArgExpr, e Expr) (Expr, error) {
be.Right = right
be.GroupModifier.Args = groupModifierArgs
be.JoinModifier.Args = joinModifierArgs
be.JoinModifierPrefix = joinModifierPrefix
pe := parensExpr{&be}
return &pe, nil
case *FuncExpr:
Expand Down Expand Up @@ -1084,7 +1107,7 @@ func isKeepMetricNames(token string) bool {
return token == "keep_metric_names"
}

func (p *parser) parseModifierExpr(me *ModifierExpr) error {
func (p *parser) parseModifierExpr(me *ModifierExpr, allowStar bool) error {
if !isIdentPrefix(p.lex.Token) {
return fmt.Errorf(`ModifierExpr: unexpected token %q; want "ident"`, p.lex.Token)
}
Expand All @@ -1098,25 +1121,40 @@ func (p *parser) parseModifierExpr(me *ModifierExpr) error {
// join modifier may miss ident list.
return nil
}
args, err := p.parseIdentList()
args, err := p.parseIdentList(allowStar)
if err != nil {
return err
return fmt.Errorf("ModifierExpr: %w", err)
}
me.Args = args
return nil
}

func (p *parser) parseIdentList() ([]string, error) {
func (p *parser) parseIdentList(allowStar bool) ([]string, error) {
if p.lex.Token != "(" {
return nil, fmt.Errorf(`identList: unexpected token %q; want "("`, p.lex.Token)
}
var idents []string
for {
if err := p.lex.Next(); err != nil {
return nil, err
}
if allowStar && p.lex.Token == "*" {
if err := p.lex.Next(); err != nil {
return nil, err
}
if p.lex.Token != ")" {
return nil, fmt.Errorf(`identList: unexpected token %q after "*"; want ")"`, p.lex.Token)
}
if err := p.lex.Next(); err != nil {
return nil, err
}
return []string{"*"}, nil
}
var idents []string
for {
if p.lex.Token == ")" {
goto closeParensLabel
if err := p.lex.Next(); err != nil {
return nil, err
}
return idents, nil
}
if !isIdentPrefix(p.lex.Token) {
return nil, fmt.Errorf(`identList: unexpected token %q; want "ident"`, p.lex.Token)
Expand All @@ -1127,19 +1165,15 @@ func (p *parser) parseIdentList() ([]string, error) {
}
switch p.lex.Token {
case ",":
continue
if err := p.lex.Next(); err != nil {
return nil, err
}
case ")":
goto closeParensLabel
continue
default:
return nil, fmt.Errorf(`identList: unexpected token %q; want ",", ")"`, p.lex.Token)
}
}

closeParensLabel:
if err := p.lex.Next(); err != nil {
return nil, err
}
return idents, nil
}

func (p *parser) parseArgListExpr() ([]Expr, error) {
Expand Down Expand Up @@ -1628,6 +1662,11 @@ type BinaryOpExpr struct {
// JoinModifier contains modifier such as "group_left" or "group_right".
JoinModifier ModifierExpr

// JoinModifierPrefix is an optional prefix to add to labels specified inside group_left() or group_right() lists.
//
// The syntax is `group_left(foo,bar) prefix "abc"`
JoinModifierPrefix *StringExpr

// If KeepMetricNames is set to true, then the operation should keep metric names.
KeepMetricNames bool

Expand Down Expand Up @@ -1668,6 +1707,10 @@ func (be *BinaryOpExpr) appendStringNoKeepMetricNames(dst []byte) []byte {
if be.JoinModifier.Op != "" {
dst = append(dst, ' ')
dst = be.JoinModifier.AppendString(dst)
if prefix := be.JoinModifierPrefix; prefix != nil {
dst = append(dst, " prefix "...)
dst = prefix.AppendString(dst)
}
}
dst = append(dst, ' ')
if be.needRightParens() {
Expand Down Expand Up @@ -1739,7 +1782,11 @@ func (me *ModifierExpr) AppendString(dst []byte) []byte {
dst = append(dst, me.Op...)
dst = append(dst, '(')
for i, arg := range me.Args {
dst = appendEscapedIdent(dst, arg)
if arg == "*" {
dst = append(dst, '*')
} else {
dst = appendEscapedIdent(dst, arg)
}
if i+1 < len(me.Args) {
dst = append(dst, ',')
}
Expand Down
18 changes: 18 additions & 0 deletions parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,12 @@ func TestParseSuccess(t *testing.T) {
same(`m1 + on(foo,bar) group_right(x,y) m2`)
another(`m1 + on (foo, bar,) group_right (x, y,) m2`, `m1 + on(foo,bar) group_right(x,y) m2`)
same(`m1 ==bool on(foo,bar) group_right(x,y) m2`)
same(`a + on() group_left(*) b`)
same(`a + on() group_right(*) b`)
same(`a + on() group_left(*) prefix "foo" b`)
another(`a + oN() gROUp_rigHt(*) PREfix "bar" b`, `a + on() group_right(*) prefix "bar" b`)
same(`a + on(a) group_left(x,y) prefix "foo" b`)
same(`a + on(a,b) group_right(z) prefix "bar" b`)
another(`5 - 1 + 3 * 2 ^ 2 ^ 3 - 2 OR Metric {Bar= "Baz", aaa!="bb",cc=~"dd" ,zz !~"ff" } `,
`770 or Metric{Bar="Baz",aaa!="bb",cc=~"dd",zz!~"ff"}`)
same(`"foo" + bar()`)
Expand Down Expand Up @@ -476,6 +482,12 @@ func TestParseSuccess(t *testing.T) {
another(`with (f(x,y) = a + on (x,y) group_left (y,bar) b) f((foo),())`, `a + on(foo) group_left(bar) b`)
another(`with (f(x,y) = a + on (x,y) group_left (y,bar) b) f((foo,xx),())`, `a + on(foo,xx) group_left(bar) b`)

// withExpr for group_left() / group_right() prefix
another(`with (f(x) = a+on() group_left() prefix x b) f("foo")`, `a + on() group_left() prefix "foo" b`)
another(`with (f(x) = a+on() group_left() prefix x+"bar" b) f("foo")`, `a + on() group_left() prefix "foobar" b`)
another(`with (f(x) = a+on() group_left() prefix "bar"+x b) f("foo")`, `a + on() group_left() prefix "barfoo" b`)
another(`with (f(x,y) = a+on() group_left() prefix y+x b) f("foo","bar")`, `a + on() group_left() prefix "barfoo" b`)

// Verify nested with exprs
another(`with (f(x) = (with(x=y) x) + x) f(z)`, `y + z`)
another(`with (x=foo) f(a, with (y=x) y)`, `f(a, foo)`)
Expand Down Expand Up @@ -719,6 +731,10 @@ func TestParseError(t *testing.T) {
f(`foo == bool $$`)
f(`"foo" + bar`)
f(`(foo + `)
f(`a + on(*) b`) // star cannot be used inside on()
f(`a + ignoring(*) b`) // star cannot be used inside ignoring()
f(`a + on() group_left(*,x) b`) // star cannot be mixed with other labels inside group_left()
f(`a + on() group_right(x,*) b`) // star cannot be mixed with other labels inside group_right()

// invalid parensExpr
f(`(`)
Expand Down Expand Up @@ -799,6 +815,8 @@ func TestParseError(t *testing.T) {
f(`sum by (x) (y) by (z)`)
f(`sum(m) by (1)`)
f(`sum(m) keep_metric_names`) // keep_metric_names cannot be used for aggregate functions
f(`sum(m) by(*)`) // star cannot be used in by()
f(`sum(m) without(*)`) // star cannot be used in without()

// invalid withExpr
f(`with $`)
Expand Down

0 comments on commit eba05b9

Please sign in to comment.