Skip to content

Instantly share code, notes, and snippets.

@hoel-zr-o
Created April 24, 2025 21:55
Show Gist options
  • Save hoel-zr-o/f5121d30e985ead6a39c62346b6718ba to your computer and use it in GitHub Desktop.
Save hoel-zr-o/f5121d30e985ead6a39c62346b6718ba to your computer and use it in GitHub Desktop.
PromQL → JSON AST tool
module example.com/alert-rule-experiment
go 1.23.6
require github.com/prometheus/prometheus v0.301.0
require (
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/dennwc/varint v1.0.0 // indirect
github.com/grafana/regexp v0.0.0-20240518133315-a468a5bfb3bc // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/prometheus/client_golang v1.20.5 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.61.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
go.uber.org/atomic v1.11.0 // indirect
golang.org/x/sys v0.28.0 // indirect
golang.org/x/text v0.21.0 // indirect
google.golang.org/protobuf v1.36.0 // indirect
)
package main
import (
"bufio"
"encoding/json"
"fmt"
"os"
"github.com/prometheus/prometheus/promql/parser"
)
func exprToSimpleJSON(expr parser.Expr) any {
switch expr := expr.(type) {
case nil:
return nil
case *parser.ParenExpr:
return map[string]any{
"type": "paren",
"expr": exprToSimpleJSON(expr.Expr),
}
case *parser.SubqueryExpr:
return map[string]any{
"type": "subquery",
"expr": exprToSimpleJSON(expr.Expr),
"range": expr.Range,
"original_offset": expr.OriginalOffset,
"offset": expr.Offset,
"timestamp": expr.Timestamp,
"start_or_end": parser.ItemTypeStr[expr.StartOrEnd],
"step": expr.Step,
}
case *parser.BinaryExpr:
var vectorMatching any
if expr.VectorMatching != nil {
vectorMatching = map[string]any{
"cardinality": expr.VectorMatching.Card.String(),
"matching_labels": expr.VectorMatching.MatchingLabels,
"on": expr.VectorMatching.On,
"include": expr.VectorMatching.Include,
}
}
return map[string]any{
"type": "binary",
"op": parser.ItemTypeStr[expr.Op],
"lhs": exprToSimpleJSON(expr.LHS),
"rhs": exprToSimpleJSON(expr.RHS),
"vector_matching": vectorMatching,
"return_bool": expr.ReturnBool,
}
case *parser.AggregateExpr:
return map[string]any{
"type": "aggregate",
"op": parser.ItemTypeStr[expr.Op],
"expr": exprToSimpleJSON(expr.Expr),
"param": exprToSimpleJSON(expr.Param),
"grouping": expr.Grouping,
"without": expr.Without,
}
case *parser.Call:
args := make([]any, 0, len(expr.Args))
for _, arg := range expr.Args {
args = append(args, exprToSimpleJSON(arg))
}
return map[string]any{
"type": "call",
"func": map[string]any{
"name": expr.Func.Name,
"arg_types": expr.Func.ArgTypes,
"variadic": expr.Func.Variadic,
"return_type": expr.Func.ReturnType,
},
"args": args,
}
case *parser.MatrixSelector:
return map[string]any{
"vector_selector": exprToSimpleJSON(expr.VectorSelector),
"range": expr.Range,
}
case *parser.VectorSelector:
labelMatchers := make([]any, 0, len(expr.LabelMatchers))
for _, labelMatcher := range expr.LabelMatchers {
labelMatchers = append(labelMatchers, map[string]any{
"type": labelMatcher.Type.String(),
"name": labelMatcher.Name,
"value": labelMatcher.Value,
})
}
return map[string]any{
"name": expr.Name,
"original_offset": expr.OriginalOffset,
"offset": expr.Offset,
"timestamp": expr.Timestamp,
"skip_histogram_buckets": expr.SkipHistogramBuckets,
"start_or_end": parser.ItemTypeStr[expr.StartOrEnd],
"label_matchers": labelMatchers,
"bypass_empty_matcher_check": expr.BypassEmptyMatcherCheck,
}
case *parser.StringLiteral:
return expr.Val
case *parser.NumberLiteral:
return expr.Val
default:
panic(fmt.Errorf("unhandled expr type %T", expr))
}
}
func main() {
lines := bufio.NewScanner(os.Stdin)
for lines.Scan() {
query := lines.Text()
expr, err := parser.ParseExpr(query)
if err != nil {
panic(err)
}
j := exprToSimpleJSON(expr)
bytes, err := json.Marshal(j)
if err != nil {
panic(err)
}
fmt.Println(string(bytes))
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment