Public beta — not for production use. Data may be wiped at any time. Questions? Contact us.
A
Axiom
/Marketplace/christiangeorgelucas/promql-tools
Sign in

christiangeorgelucas/promql-tools

v0.1.1Go

The promql-tools package provides deterministic, offline parsing and structural analysis of PromQL query strings and Prometheus rule files. It enables users to validate, pretty-print, and inspect PromQL queries and rule files without requiring access to a Prometheus server, ensuring robust error handling and detailed insights into query structure.

Published by Christian George Lucas· MIT

promqlprometheusquery-analysisyamlparsingvalidationalertingrecording-rules

Use cases

  • Validate PromQL queries for syntax errors before deployment
  • Extract metric names and label matchers from complex queries
  • Pretty-print PromQL queries for better readability
  • Analyze alerting and recording rules in Prometheus rule files
  • Perform structural audits of Prometheus rule files

Nodes (15)

DetectResultTypeunary
PromQLQuery·ResultTypeInfo

The DetectResultType node analyzes a PromQL query and determines its result type, which can be an instant vector, range vector, scalar, or string, based on the parsed abstract syntax tree (AST). This functionality is essential for understanding the output of PromQL queries in monitoring and alerting systems.

View source
package nodes

import (
	"context"

	"christiangeorgelucas/promql-tools/axiom"
	gen "christiangeorgelucas/promql-tools/gen"
	"christiangeorgelucas/promql-tools/internal/pq"
)

// DetectResultType infers a PromQL query's result type — instant vector,
// range vector, scalar, or string — from its parsed AST.
func DetectResultType(ctx context.Context, ax axiom.Context, input *gen.PromQLQuery) (*gen.ResultTypeInfo, error) {
	a := pq.Analyze(input.GetQuery())
	out := &gen.ResultTypeInfo{Valid: a.Valid, Error: toGenError(a.Error)}
	if a.Valid {
		out.ResultType = toGenResultType(a.ResultType)
		out.TypeString = a.ResultType
	}
	return out, nil
}
ExtractAggregationsunary
PromQLQuery·Aggregations

The ExtractAggregations node analyzes a PromQL query to identify and return all aggregation expressions, such as sum, avg, and count_values, along with their associated grouping labels. It also indicates whether the query includes a grouping clause or not.

View source
package nodes

import (
	"context"

	"christiangeorgelucas/promql-tools/axiom"
	gen "christiangeorgelucas/promql-tools/gen"
	"christiangeorgelucas/promql-tools/internal/pq"
)

// ExtractAggregations returns every aggregation expression in a PromQL
// query (sum, avg, topk, count_values, ...) with its by/without grouping
// labels. has_grouping distinguishes "no by/without clause at all" from an
// explicit, empty `by ()`.
func ExtractAggregations(ctx context.Context, ax axiom.Context, input *gen.PromQLQuery) (*gen.Aggregations, error) {
	a := pq.Analyze(input.GetQuery())
	return &gen.Aggregations{Valid: a.Valid, Error: toGenError(a.Error), Aggregations: toGenAggregations(a.Aggregations)}, nil
}
ExtractFunctionsunary
PromQLQuery·FunctionCalls

The ExtractFunctions node analyzes a PromQL query to identify and return every function call present, including the count of arguments for each call, in the order they appear. It specifically focuses on function calls like rate, sum, and histogram_quantile, while aggregations are handled by a separate node.

View source
package nodes

import (
	"context"

	"christiangeorgelucas/promql-tools/axiom"
	gen "christiangeorgelucas/promql-tools/gen"
	"christiangeorgelucas/promql-tools/internal/pq"
)

// ExtractFunctions returns every function call in a PromQL query (rate,
// sum, histogram_quantile, etc.) with each call's argument count, in the
// order they appear.
func ExtractFunctions(ctx context.Context, ax axiom.Context, input *gen.PromQLQuery) (*gen.FunctionCalls, error) {
	a := pq.Analyze(input.GetQuery())
	return &gen.FunctionCalls{Valid: a.Valid, Error: toGenError(a.Error), Functions: toGenFunctions(a.Functions)}, nil
}
ExtractLabelMatchersunary
PromQLQuery·LabelMatchers

The ExtractLabelMatchers node analyzes a PromQL query and extracts all label matchers used in vector selectors, including the label name, operator, value, and the metric name associated with each selector. This allows users to identify the specific criteria used to filter metrics in the query.

View source
package nodes

import (
	"context"

	"christiangeorgelucas/promql-tools/axiom"
	gen "christiangeorgelucas/promql-tools/gen"
	"christiangeorgelucas/promql-tools/internal/pq"
)

// ExtractLabelMatchers returns every label matcher used across every vector
// selector in a PromQL query — label name, operator (=, !=, =~, !~), value,
// and the metric name of the selector it came from. A matcher that merely
// spells out the selector's own literal metric name (the implicit
// `__name__="x"` behind `x{...}`) is not repeated here — see
// ExtractMetricNames for that.
func ExtractLabelMatchers(ctx context.Context, ax axiom.Context, input *gen.PromQLQuery) (*gen.LabelMatchers, error) {
	a := pq.Analyze(input.GetQuery())
	return &gen.LabelMatchers{Valid: a.Valid, Error: toGenError(a.Error), Matchers: toGenMatchers(a.Matchers)}, nil
}
ExtractMetricNamesunary
PromQLQuery·MetricNames

The ExtractMetricNames node processes a PromQL query to identify and return all unique metric names referenced within it, including both standard metric selectors and explicit matchers, ensuring the results are presented in the order of their first occurrence.

View source
package nodes

import (
	"context"

	"christiangeorgelucas/promql-tools/axiom"
	gen "christiangeorgelucas/promql-tools/gen"
	"christiangeorgelucas/promql-tools/internal/pq"
)

// ExtractMetricNames returns every metric name referenced in a PromQL
// query — both `metric_name{...}` selectors and an explicit
// `{__name__="metric_name"}` matcher — deduplicated, in first-occurrence
// order.
func ExtractMetricNames(ctx context.Context, ax axiom.Context, input *gen.PromQLQuery) (*gen.MetricNames, error) {
	a := pq.Analyze(input.GetQuery())
	return &gen.MetricNames{Valid: a.Valid, Error: toGenError(a.Error), Names: a.Metrics}, nil
}
ExtractRangesunary
PromQLQuery·RangeDurations

The ExtractRanges node analyzes a PromQL query to identify and extract all range-vector and subquery durations, returning them in both canonical text format and as numerical seconds.

View source
package nodes

import (
	"context"

	"christiangeorgelucas/promql-tools/axiom"
	gen "christiangeorgelucas/promql-tools/gen"
	"christiangeorgelucas/promql-tools/internal/pq"
)

// ExtractRanges returns every range-vector and subquery duration in a
// PromQL query — the `[5m]` in `rate(x[5m])`, or the `[30m:1m]` range+step
// pair in a subquery — as both canonical duration text and seconds.
func ExtractRanges(ctx context.Context, ax axiom.Context, input *gen.PromQLQuery) (*gen.RangeDurations, error) {
	a := pq.Analyze(input.GetQuery())
	return &gen.RangeDurations{Valid: a.Valid, Error: toGenError(a.Error), Ranges: toGenRanges(a.Ranges)}, nil
}
ExtractRuleExpressionsunary
RuleFile·RuleExpressions

The ExtractRuleExpressions node processes a RuleFile to extract each rule's PromQL expression, associating it with its group, rule name, and alerting or recording type, while also validating the expression's syntax for further analysis.

View source
package nodes

import (
	"context"

	"christiangeorgelucas/promql-tools/axiom"
	gen "christiangeorgelucas/promql-tools/gen"
	"christiangeorgelucas/promql-tools/internal/pq"
)

// ExtractRuleExpressions returns every rule's PromQL `expr`, tagged with
// its owning group, rule name, and alerting/recording kind, plus whether
// that expression itself parses — so each one can be fed independently
// into ParseQuery/ExtractMetricNames/etc. for per-expression analysis.
func ExtractRuleExpressions(ctx context.Context, ax axiom.Context, input *gen.RuleFile) (*gen.RuleExpressions, error) {
	r := pq.ParseRuleFile(input.GetYaml())
	out := &gen.RuleExpressions{Valid: r.Valid, Error: toGenError(r.Error)}
	if !r.Valid {
		return out, nil
	}
	for _, g := range r.Groups {
		for _, rule := range g.Rules {
			name := rule.Alert
			if !rule.IsAlerting {
				name = rule.Record
			}
			exprValid, exprErr := pq.ValidateExpr(rule.Expr)
			out.Expressions = append(out.Expressions, &gen.RuleExpression{
				Group:      g.Name,
				RuleName:   name,
				IsAlerting: rule.IsAlerting,
				Expr:       rule.Expr,
				ExprValid:  exprValid,
				ExprError:  toGenError(exprErr),
			})
		}
	}
	return out, nil
}
ExtractRuleFileInventoryunary
RuleFile·RuleFileInventory

The ExtractRuleFileInventory node analyzes a given rule file to extract and union all metric names and label matchers referenced in the rules' expressions, ensuring they are deduplicated and sorted for auditing dependencies and coverage.

View source
package nodes

import (
	"context"
	"sort"

	"christiangeorgelucas/promql-tools/axiom"
	gen "christiangeorgelucas/promql-tools/gen"
	"christiangeorgelucas/promql-tools/internal/pq"
)

// ExtractRuleFileInventory unions every metric name and label matcher
// referenced across every rule's expr in a rule file, deduplicated and
// sorted, for a dependency/coverage audit ("which metrics does this alert
// file depend on").
func ExtractRuleFileInventory(ctx context.Context, ax axiom.Context, input *gen.RuleFile) (*gen.RuleFileInventory, error) {
	r := pq.ParseRuleFile(input.GetYaml())
	out := &gen.RuleFileInventory{Valid: r.Valid, Error: toGenError(r.Error)}
	if !r.Valid {
		return out, nil
	}

	metricSet := map[string]struct{}{}
	type matcherKey struct{ label, op, value, ctx string }
	matcherSet := map[matcherKey]*gen.LabelMatcher{}

	for _, g := range r.Groups {
		for _, rule := range g.Rules {
			if rule.Expr == "" {
				continue
			}
			a := pq.Analyze(rule.Expr)
			if !a.Valid {
				continue
			}
			for _, m := range a.Metrics {
				metricSet[m] = struct{}{}
			}
			for _, m := range a.Matchers {
				k := matcherKey{m.Label, m.Op, m.Value, m.MetricContext}
				if _, ok := matcherSet[k]; !ok {
					matcherSet[k] = toGenMatcher(m)
				}
			}
		}
	}

	for m := range metricSet {
		out.MetricNames = append(out.MetricNames, m)
	}
	sort.Strings(out.MetricNames)

	for _, m := range matcherSet {
		out.LabelMatchers = append(out.LabelMatchers, m)
	}
	sort.Slice(out.LabelMatchers, func(i, j int) bool {
		a, b := out.LabelMatchers[i], out.LabelMatchers[j]
		if a.MetricContext != b.MetricContext {
			return a.MetricContext < b.MetricContext
		}
		if a.Label != b.Label {
			return a.Label < b.Label
		}
		if a.Op != b.Op {
			return a.Op < b.Op
		}
		return a.Value < b.Value
	})

	return out, nil
}
FormatQueryunary
PromQLQuery·FormattedQuery

The FormatQuery node takes a PromQL query as input and pretty-prints it into its canonical form using the Prometheus parser's Prettify function, ensuring stable whitespace and quoting while maintaining semantic equivalence to the original query.

View source
package nodes

import (
	"context"

	"christiangeorgelucas/promql-tools/axiom"
	gen "christiangeorgelucas/promql-tools/gen"
	"christiangeorgelucas/promql-tools/internal/pq"
)

// FormatQuery pretty-prints a PromQL query into its canonical form
// (github.com/prometheus/prometheus/promql/parser's own Prettify) — stable
// whitespace and quoting, semantically identical to the input.
func FormatQuery(ctx context.Context, ax axiom.Context, input *gen.PromQLQuery) (*gen.FormattedQuery, error) {
	a := pq.Analyze(input.GetQuery())
	return &gen.FormattedQuery{Valid: a.Valid, Error: toGenError(a.Error), Formatted: a.Formatted}, nil
}
ListAlertingRulesunary
RuleFile·AlertingRules

The ListAlertingRules node extracts and returns all alerting rules defined in a rule file, including details such as alert name, expression, duration, and associated labels and annotations, organized by their respective groups.

View source
package nodes

import (
	"context"

	"christiangeorgelucas/promql-tools/axiom"
	gen "christiangeorgelucas/promql-tools/gen"
	"christiangeorgelucas/promql-tools/internal/pq"
)

// ListAlertingRules returns every alerting rule (`alert:` set) across every
// group in a rule file: alert name, expr, for-duration, keep_firing_for,
// labels, and annotations, each tagged with its owning group.
func ListAlertingRules(ctx context.Context, ax axiom.Context, input *gen.RuleFile) (*gen.AlertingRules, error) {
	r := pq.ParseRuleFile(input.GetYaml())
	out := &gen.AlertingRules{Valid: r.Valid, Error: toGenError(r.Error)}
	if !r.Valid {
		return out, nil
	}
	for _, g := range r.Groups {
		for _, rule := range g.Rules {
			if !rule.IsAlerting {
				continue
			}
			out.Rules = append(out.Rules, &gen.AlertingRule{
				Group:         g.Name,
				Alert:         rule.Alert,
				Expr:          rule.Expr,
				ForDuration:   rule.ForDuration,
				KeepFiringFor: rule.KeepFiringFor,
				Labels:        rule.Labels,
				Annotations:   rule.Annotations,
			})
		}
	}
	return out, nil
}
ListRecordingRulesunary
RuleFile·RecordingRules

The ListRecordingRules node extracts and returns all recording rules from a given rule file, including their names, expressions, and associated labels, organized by their respective groups.

View source
package nodes

import (
	"context"

	"christiangeorgelucas/promql-tools/axiom"
	gen "christiangeorgelucas/promql-tools/gen"
	"christiangeorgelucas/promql-tools/internal/pq"
)

// ListRecordingRules returns every recording rule (`record:` set) across
// every group in a rule file: record name, expr, and labels, each tagged
// with its owning group.
func ListRecordingRules(ctx context.Context, ax axiom.Context, input *gen.RuleFile) (*gen.RecordingRules, error) {
	r := pq.ParseRuleFile(input.GetYaml())
	out := &gen.RecordingRules{Valid: r.Valid, Error: toGenError(r.Error)}
	if !r.Valid {
		return out, nil
	}
	for _, g := range r.Groups {
		for _, rule := range g.Rules {
			if rule.IsAlerting || rule.Record == "" {
				continue
			}
			out.Rules = append(out.Rules, &gen.RecordingRule{
				Group:  g.Name,
				Record: rule.Record,
				Expr:   rule.Expr,
				Labels: rule.Labels,
			})
		}
	}
	return out, nil
}
ParseQueryunary
PromQLQuery·ParsedQuery

The ParseQuery node analyzes a PromQL query, providing a detailed structural breakdown that includes its validity, a formatted representation, inferred result type, and various components such as metric names and label matchers. In case of syntax errors, it returns an error message along with the location of the error in the query.

View source
package nodes

import (
	"context"

	"christiangeorgelucas/promql-tools/axiom"
	gen "christiangeorgelucas/promql-tools/gen"
	"christiangeorgelucas/promql-tools/internal/pq"
)

// ParseQuery parses a PromQL query into its full structural analysis:
// validity, a canonical pretty-printed form, the inferred result type, and
// every metric name, label matcher, function call, aggregation, and
// range/subquery duration found in the expression. On a syntax error, valid
// is false and error carries the message plus 1-based line/column.
func ParseQuery(ctx context.Context, ax axiom.Context, input *gen.PromQLQuery) (*gen.ParsedQuery, error) {
	a := pq.Analyze(input.GetQuery())
	out := &gen.ParsedQuery{
		Query: input.GetQuery(),
		Valid: a.Valid,
		Error: toGenError(a.Error),
	}
	if !a.Valid {
		return out, nil
	}
	out.Formatted = a.Formatted
	out.ResultType = toGenResultType(a.ResultType)
	out.MetricNames = a.Metrics
	out.LabelMatchers = toGenMatchers(a.Matchers)
	out.Functions = toGenFunctions(a.Functions)
	out.Aggregations = toGenAggregations(a.Aggregations)
	out.Ranges = toGenRanges(a.Ranges)
	return out, nil
}
ParseRuleFileunary
RuleFile·ParsedRuleFile

The ParseRuleFile node processes a Prometheus rule file in YAML format, extracting its groups and rules while validating each rule's PromQL expression. It identifies fatal decoding errors and reports rule-level issues without discarding valid groups and rules.

View source
package nodes

import (
	"context"

	"christiangeorgelucas/promql-tools/axiom"
	gen "christiangeorgelucas/promql-tools/gen"
	"christiangeorgelucas/promql-tools/internal/pq"
)

// ParseRuleFile parses a Prometheus rule file (the YAML behind
// `rule_files:` / a PrometheusRule resource's `spec.groups`) into its
// groups and rules. Every rule's `expr` is itself PromQL-validated. A
// document that fails to decode at all (bad YAML, an unknown top-level
// field, or a missing `groups:` key) is reported as the fatal top-level
// error; a document that decodes but has a rule-level problem (a missing
// `expr`, neither/both of alert+record set, a bad duration) still returns
// every group and rule, with each problem listed in rule_errors.
func ParseRuleFile(ctx context.Context, ax axiom.Context, input *gen.RuleFile) (*gen.ParsedRuleFile, error) {
	r := pq.ParseRuleFile(input.GetYaml())
	out := &gen.ParsedRuleFile{Valid: r.Valid, Error: toGenError(r.Error)}
	if !r.Valid {
		return out, nil
	}
	out.Groups = toGenGroups(r.Groups)
	out.RuleErrors = toGenErrors(r.RuleErrors)
	return out, nil
}
ValidateQueryunary
PromQLQuery·ValidationResult

The ValidateQuery node checks the syntactical correctness of a PromQL query and returns a validation result indicating whether the query is well-formed. If the query is not valid, it provides the location of the parse error in terms of line and column numbers.

View source
package nodes

import (
	"context"

	"christiangeorgelucas/promql-tools/axiom"
	gen "christiangeorgelucas/promql-tools/gen"
	"christiangeorgelucas/promql-tools/internal/pq"
)

// ValidateQuery reports only whether a PromQL query is syntactically
// well-formed, with the parse error's 1-based line/column when it is not.
// Cheaper than ParseQuery when the caller does not need the full AST
// breakdown.
func ValidateQuery(ctx context.Context, ax axiom.Context, input *gen.PromQLQuery) (*gen.ValidationResult, error) {
	valid, err := pq.ValidateExpr(input.GetQuery())
	return &gen.ValidationResult{Valid: valid, Error: toGenError(err)}, nil
}
ValidateRuleFileunary
RuleFile·RuleFileValidation

The ValidateRuleFile node checks the structure of a rule file, ensuring it decodes correctly, that each group has a unique name, and that every rule adheres to the required specifications. It returns a comprehensive list of any issues found within the file.

View source
package nodes

import (
	"context"

	"christiangeorgelucas/promql-tools/axiom"
	gen "christiangeorgelucas/promql-tools/gen"
	"christiangeorgelucas/promql-tools/internal/pq"
)

// ValidateRuleFile checks a rule file's structure: the document decodes,
// every group has a name (and no two groups share one), and every rule has
// exactly one of alert/record, a valid expr, and (for alerting rules) valid
// for/keep_firing_for durations and label names. Returns every problem
// found, not just the first.
func ValidateRuleFile(ctx context.Context, ax axiom.Context, input *gen.RuleFile) (*gen.RuleFileValidation, error) {
	r := pq.ParseRuleFile(input.GetYaml())
	if !r.Valid {
		return &gen.RuleFileValidation{
			Valid:  false,
			Errors: []*gen.ParseError{toGenError(r.Error)},
		}, nil
	}

	ruleCount := 0
	for _, g := range r.Groups {
		ruleCount += len(g.Rules)
	}

	return &gen.RuleFileValidation{
		Valid:      len(r.RuleErrors) == 0,
		Errors:     toGenErrors(r.RuleErrors),
		GroupCount: int32(len(r.Groups)),
		RuleCount:  int32(ruleCount),
	}, nil
}

Messages (27)

Download .proto
Aggregation
op:string
grouping_labels:[]string
without:bool
has_grouping:bool
Aggregations
valid:bool
error:ParseError
aggregations:[]Aggregation
AlertingRule
group:string
alert:string
expr:string
for_duration:string
keep_firing_for:string
AlertingRules
valid:bool
error:ParseError
rules:[]AlertingRule
FormattedQuery
valid:bool
error:ParseError
formatted:string
FunctionCall
name:string
arg_count:int32
FunctionCalls
valid:bool
error:ParseError
functions:[]FunctionCall
LabelMatcher
label:string
op:string
value:string
LabelMatchers
valid:bool
error:ParseError
matchers:[]LabelMatcher
MetricNames
valid:bool
error:ParseError
names:[]string
ParsedQuery
query:string
valid:bool
error:ParseError
formatted:string
result_type:ResultType
metric_names:[]string
label_matchers:[]LabelMatcher
functions:[]FunctionCall
aggregations:[]Aggregation
ranges:[]RangeDuration
ParsedRuleFile
valid:bool
error:ParseError
groups:[]RuleGroup

A collection of groups extracted from the rule file, each containing associated rules.

rule_errors:[]ParseError

A list of errors related to specific rules, detailing issues such as missing expressions or invalid durations.

ParseError
message:string
code:string
line:int32
column:int32
PromQLQuery
query:string
RangeDuration
duration:string
duration_seconds:double
is_subquery:bool
step:string
RangeDurations
valid:bool
error:ParseError
ranges:[]RangeDuration
RecordingRule
group:string
record:string
expr:string
RecordingRules
valid:bool
error:ParseError
rules:[]RecordingRule
ResultTypeInfo
valid:bool
error:ParseError
result_type:ResultType
type_string:string
Rule
alert:string
record:string
expr:string
for_duration:string
keep_firing_for:string
is_alerting:bool
RuleExpression
group:string
rule_name:string
is_alerting:bool
expr:string
expr_valid:bool
expr_error:ParseError
RuleExpressions
valid:bool
error:ParseError
expressions:[]RuleExpression
RuleFile
yaml:string
RuleFileInventory
valid:bool
error:ParseError
metric_names:[]string
label_matchers:[]LabelMatcher
RuleFileValidation
valid:bool
errors:[]ParseError
group_count:int32
rule_count:int32
RuleGroup
name:string
interval:string
rules:[]Rule
ValidationResult
valid:bool
error:ParseError

Use as a tool in any AI agent

christiangeorgelucas/promql-tools is callable as a tool from any MCP client (Claude, Cursor, or your own agent) via Axiom's hosted MCP server — no install, no download. Add the server, then your agent can search and invoke it, or pin it as a typed tool.

claude mcp add --transport http axiom https://api.axiomide.com/mcp --header "Authorization: Bearer YOUR_AXIOM_API_KEY"

Replace YOUR_AXIOM_API_KEY with a key you create in Console → API Keys.

MCP server setup & other clients →

Use this package

Sign in to import christiangeorgelucas/promql-tools into the graph editor — drop it into a flow and hit run. Or call it directly via the API Reference with your API key.

Sign in to useOpen editor

Stats

Error rate (1h)0.0%
Back to marketplace