Skip to content

Commit

Permalink
Patch up LogQL rules to match new stability level, and variable subst…
Browse files Browse the repository at this point in the history
…itution
  • Loading branch information
rgeyer committed Nov 6, 2024
1 parent d8b1885 commit 81dcd35
Show file tree
Hide file tree
Showing 7 changed files with 36 additions and 55 deletions.
1 change: 1 addition & 0 deletions lint/rule_target_logql.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ func NewTargetLogQLRule() *TargetRuleFunc {
return &TargetRuleFunc{
name: "target-logql-rule",
description: "Checks that each target uses a valid LogQL query.",
stability: ruleStabilityExperimental,
fn: func(d Dashboard, p Panel, t Target) TargetRuleResults {
r := TargetRuleResults{}

Expand Down
11 changes: 6 additions & 5 deletions lint/rule_target_logql_auto.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,10 @@ func parseLogQL(expr string, variables []Template) (syntax.Expr, error) {
}

func NewTargetLogQLAutoRule() *TargetRuleFunc {
autoDuration, err := time.ParseDuration(globalVariables["__auto"].(string))
if err != nil {
panic(err)
}

return &TargetRuleFunc{
name: "target-logql-auto-rule",
description: "Checks that each Loki target uses $__auto for range vectors when appropriate.",
stability: ruleStabilityExperimental,
fn: func(d Dashboard, p Panel, t Target) TargetRuleResults {
r := TargetRuleResults{}

Expand All @@ -51,6 +47,11 @@ func NewTargetLogQLAutoRule() *TargetRuleFunc {
return r
}

autoDuration, err := time.ParseDuration(placeholderByVariable["$__auto"].value)
if err != nil {
panic(err)
}

parsedExpr, err := parseLogQL(t.Expr, d.Templating.List)
if err != nil {
r.AddError(d, p, t, fmt.Sprintf("Invalid LogQL query: %v", err))
Expand Down
2 changes: 1 addition & 1 deletion lint/rule_target_promql.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func panelHasQueries(p Panel) bool {
// replacing eg [$__rate_interval] with [5m] so queries parse correctly.
// We also replace various other Grafana global variables.
func parsePromQL(expr string, variables []Template) (parser.Expr, error) {
expr, err := expandVariables(expr, variables)
expr, err := expandPromQlVariables(expr, variables)
if err != nil {
return nil, fmt.Errorf("could not expand variables: %w", err)
}
Expand Down
4 changes: 2 additions & 2 deletions lint/rules.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,8 +186,6 @@ func NewRuleSet(experimental bool, ruleSettings ConfigurationRuleSettings) RuleS
NewPanelTitleDescriptionRule(),
NewPanelUnitsRule(),
NewPanelNoTargetsRule(),
NewTargetLogQLRule(),
NewTargetLogQLAutoRule(),
NewTargetPromQLRule(),
NewTargetRateIntervalRule(),
NewTargetJobRule(),
Expand All @@ -198,6 +196,8 @@ func NewRuleSet(experimental bool, ruleSettings ConfigurationRuleSettings) RuleS
// Add experimental rules here
if experimental {
rules = append(rules,
NewTargetLogQLRule(),
NewTargetLogQLAutoRule(),
NewTargetRequiredMatchersRule(ruleSettings.TargetRequiredMatchersRule),
NewTemplateRequiredVariablesRule(ruleSettings.TemplateRequiredVariablesRule, ruleSettings.TargetRequiredMatchersRule),
)
Expand Down
2 changes: 1 addition & 1 deletion lint/rules_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func TestFixableRules(t *testing.T) {
assert.NoError(t, err)

rule := lint.NewDashboardRuleFunc(
"test-fixable-rule", "Test fixable rule",
"test-fixable-rule", "Test fixable rule", "stable",
func(d lint.Dashboard) lint.DashboardRuleResults {
rr := lint.DashboardRuleResults{}
rr.AddFixableError(d, "fixing first issue", func(d *lint.Dashboard) {
Expand Down
69 changes: 24 additions & 45 deletions lint/variables.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
)

const (
auto = "__auto"
rateInterval = "__rate_interval"
interval = "__interval"
intervalMs = "__interval_ms"
Expand Down Expand Up @@ -38,6 +39,9 @@ const (
valTypeString valType = iota
valTypeTimeRange
valTypeEpoch
// This is effectively a valTypeTimeRange, but it will always have 's' appended to it to identify that the duration unit is seconds.
// Initially used only for logql which is more strict about requiring the duration unit for range vectors, while promql implies 's' as the unit of duration if not specified.
valTypeDuration
)

type valType int
Expand Down Expand Up @@ -123,6 +127,10 @@ var globalVariables = []*placeholder{
variable: timeFilter2,
valType: valTypeString, // not really a string, but currently we do only support prometheus queries, and this would not be a valid prometheus query...
},
{
variable: auto,
valType: valTypeDuration,
},
}

// var supportedFormatOptions = []string{"csv", "distributed", "doublequote", "glob", "json", "lucene", "percentencode", "pipe", "raw", "regex", "singlequote", "sqlstring", "text", "queryparam"}
Expand All @@ -135,7 +143,9 @@ var variableRegexp = regexp.MustCompile(
}, "|"),
)

func expandVariables(expr string, variables []Template) (string, error) {
// Initializes the global variables if not already initialized.
// Creates placeholders for every variable found in the expression, returning the expression with placeholders
func expandVariables(expr string, variables []Template) string {
// initialize global variables if not already initialized
if !globalVariablesInit {
for _, v := range globalVariables {
Expand Down Expand Up @@ -174,6 +184,11 @@ func expandVariables(expr string, variables []Template) (string, error) {
}

expr = variableRegexp.ReplaceAllStringFunc(expr, RegexpExpandVariables)
return expr
}

func expandPromQlVariables(expr string, variables []Template) (string, error) {
expr = expandVariables(expr, variables)

// Check if the expression can be parsed
_, err := parser.ParseExpr(expr)
Expand Down Expand Up @@ -318,6 +333,11 @@ func createPlaceholder(variable string, valType valType) string {
timeRange := magicTimeRange + counter
value = strconv.Itoa(timeRange)
}
if valType == valTypeDuration {
// Using magicTimeRange as a seed for the placeholder
duration := magicTimeRange + counter
value = strconv.Itoa(duration) + "s"
}
if valType == valTypeEpoch {
// Using magicEpoch as a seed for the placeholder
epoch := magicEpoch + float64(counter)
Expand Down Expand Up @@ -397,48 +417,7 @@ func getTemplateVariableValue(v Template) string {
}

func expandLogQLVariables(expr string, variables []Template) (string, error) {
lines := strings.Split(expr, "\n")
for i, line := range lines {
parts := strings.Split(line, "\"")
for j, part := range parts {
if j%2 == 1 {
// Inside a double quote string, just add it
continue
}

// Accumulator to store the processed submatches
var subparts []string
// Cursor indicates where we are in the part being processed
cursor := 0
for _, v := range variableRegexp.FindAllStringSubmatchIndex(part, -1) {
// Add all until match starts
subparts = append(subparts, part[cursor:v[0]])
// Iterate on all the subgroups and find the one that matched
for k := 2; k < len(v); k += 2 {
if v[k] < 0 {
continue
}
// Replace the match with sample value
val, err := variableSampleValue(part[v[k]:v[k+1]], variables)
if err != nil {
return "", err
}
// If the variable is within square brackets, remove the '$' prefix
if strings.HasPrefix(part[v[0]-1:v[0]], "[") && strings.HasSuffix(part[v[1]:v[1]+1], "]") {
val = strings.TrimPrefix(val, "$")
}
subparts = append(subparts, val)
}
// Move the start cursor at the end of the current match
cursor = v[1]
}
// Add rest of the string
subparts = append(subparts, part[cursor:])
// Merge all back into the parts
parts[j] = strings.Join(subparts, "")
}
lines[i] = strings.Join(parts, "\"")
}
result := strings.Join(lines, "\n")
return result, nil
expr = expandVariables(expr, variables)
// TODO: Use the logql parsing to validate this is parsable logql.
return expr, nil
}
2 changes: 1 addition & 1 deletion lint/variables_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ func TestVariableExpansion(t *testing.T) {
result: "sum (rate(cpu{}[11277982]))",
},
} {
s, err := expandVariables(tc.expr, tc.variables)
s, err := expandPromQlVariables(tc.expr, tc.variables)
require.Equal(t, tc.err, err)
require.Equal(t, tc.result, s, tc.desc)
}
Expand Down

0 comments on commit 81dcd35

Please sign in to comment.