cloudflare/pint

Public

mirrored from https://github.com/cloudflare/pintAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.73.5

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

internal/parser/utils/source.go

1029lines · modecode

1package utils
2
3import (
4 "fmt"
5 "math"
6 "regexp"
7 "slices"
8 "strconv"
9 "strings"
10
11 "github.com/prometheus/prometheus/model/labels"
12 promParser "github.com/prometheus/prometheus/promql/parser"
13 "github.com/prometheus/prometheus/promql/parser/posrange"
14)
15
16var guaranteedLabelsMatches = []labels.MatchType{labels.MatchEqual, labels.MatchRegexp}
17
18type SourceType int
19
20const (
21 UnknownSource SourceType = iota
22 NumberSource
23 StringSource
24 SelectorSource
25 FuncSource
26 AggregateSource
27)
28
29type ExcludedLabel struct {
30 Reason string
31 Fragment posrange.PositionRange
32}
33
34type Join struct {
35 Src Source
36}
37
38// FIXME remove Selector/Call/Aggregation?
39// Use a single parser.Node instead?
40type Source struct {
41 Selector *promParser.VectorSelector // Vector selector used for this source.
42 Call *promParser.Call // Most outer call used inside this source.
43 Aggregation *promParser.AggregateExpr // Most outer aggregation expression used inside this source.
44 ExcludeReason map[string]ExcludedLabel // Reason why a label was excluded
45 Operation string
46 IsDeadReason string
47 Returns promParser.ValueType
48 Joins []Join // Any other sources this source joins with.
49 Unless []Join // Any other sources this source is suppressed by.
50 IncludedLabels []string // Labels that are included by filters, they will be present if exist on source series (by).
51 ExcludedLabels []string // Labels guaranteed to be excluded from the results (without).
52 GuaranteedLabels []string // Labels guaranteed to be present on the results (matchers).
53 Position posrange.PositionRange
54 IsDeadPosition posrange.PositionRange
55 ReturnedNumber float64 // If AlwaysReturns=true this is the number that's returned
56 Type SourceType
57 FixedLabels bool // Labels are fixed and only allowed labels can be present.
58 IsDead bool // True if this source cannot be reached and is dead code.
59 AlwaysReturns bool // True if this source always returns results.
60 KnownReturn bool // True if we always know the return value.
61 IsConditional bool // True if this source is guarded by 'foo > 5' or other condition.
62 IsReturnBool bool // True if this source uses the 'bool' modifier.
63}
64
65func (s Source) Fragment(expr string) string {
66 switch {
67 case s.Type == FuncSource && s.Call != nil:
68 return GetQueryFragment(expr, s.Call.PosRange)
69 case s.Call != nil:
70 return GetQueryFragment(expr, s.Call.PosRange)
71 case s.Type == AggregateSource && s.Aggregation != nil:
72 return GetQueryFragment(expr, s.Aggregation.PosRange)
73 case s.Selector != nil:
74 return GetQueryFragment(expr, s.Selector.PosRange)
75 default:
76 return ""
77 }
78}
79
80func (s Source) CanHaveLabel(name string) bool {
81 if slices.Contains(s.ExcludedLabels, name) {
82 return false
83 }
84 if slices.Contains(s.IncludedLabels, name) {
85 return true
86 }
87 if slices.Contains(s.GuaranteedLabels, name) {
88 return true
89 }
90 return !s.FixedLabels
91}
92
93func (s Source) LabelExcludeReason(name string) ExcludedLabel {
94 if el, ok := s.ExcludeReason[name]; ok {
95 return el
96 }
97 return s.ExcludeReason[""]
98}
99
100type Visitor func(s Source)
101
102func (s Source) WalkSources(fn Visitor) {
103 fn(s)
104 for _, j := range s.Joins {
105 j.Src.WalkSources(fn)
106 }
107 for _, u := range s.Unless {
108 u.Src.WalkSources(fn)
109 }
110}
111
112func LabelsSource(expr string, node promParser.Node) (src []Source) {
113 return walkNode(expr, node)
114}
115
116func walkNode(expr string, node promParser.Node) (src []Source) {
117 var s Source
118 switch n := node.(type) {
119 case *promParser.AggregateExpr:
120 src = append(src, walkAggregation(expr, n)...)
121
122 case *promParser.BinaryExpr:
123 src = append(src, parseBinOps(expr, n)...)
124
125 case *promParser.Call:
126 src = append(src, parseCall(expr, n)...)
127
128 case *promParser.MatrixSelector:
129 for _, s := range walkNode(expr, n.VectorSelector) {
130 s.Returns = promParser.ValueTypeMatrix
131 src = append(src, s)
132 }
133
134 case *promParser.SubqueryExpr:
135 src = append(src, walkNode(expr, n.Expr)...)
136
137 case *promParser.NumberLiteral:
138 s.Type = NumberSource
139 s.Returns = promParser.ValueTypeScalar
140 s.KnownReturn = true
141 s.ReturnedNumber = n.Val
142 s.IncludedLabels = nil
143 s.GuaranteedLabels = nil
144 s.FixedLabels = true
145 s.AlwaysReturns = true
146 s.ExcludeReason = setInMap(
147 s.ExcludeReason,
148 "",
149 ExcludedLabel{
150 Reason: "This query returns a number value with no labels.",
151 Fragment: n.PosRange,
152 },
153 )
154 s.Position = n.PosRange
155 src = append(src, s)
156
157 case *promParser.ParenExpr:
158 src = append(src, walkNode(expr, n.Expr)...)
159
160 case *promParser.StringLiteral:
161 s.Type = StringSource
162 s.Returns = promParser.ValueTypeString
163 s.IncludedLabels = nil
164 s.GuaranteedLabels = nil
165 s.FixedLabels = true
166 s.AlwaysReturns = true
167 s.ExcludeReason = setInMap(
168 s.ExcludeReason,
169 "",
170 ExcludedLabel{
171 Reason: "This query returns a string value with no labels.",
172 Fragment: n.PosRange,
173 },
174 )
175 s.Position = n.PosRange
176 src = append(src, s)
177
178 case *promParser.UnaryExpr:
179 src = append(src, walkNode(expr, n.Expr)...)
180
181 case *promParser.StepInvariantExpr:
182 // Not possible to get this from the parser.
183
184 case *promParser.VectorSelector:
185 s.Type = SelectorSource
186 s.Returns = promParser.ValueTypeVector
187 s.Selector = n
188 s = guaranteeLabel(s, labelsFromSelectors(guaranteedLabelsMatches, n)...)
189 for _, name := range labelsWithEmptyValueSelector(n) {
190 s = excludeLabel(
191 s,
192 fmt.Sprintf("Query uses `{%s=\"\"}` selector which will filter out any time series with the `%s` label set.", name, name),
193 n.PosRange,
194 name,
195 )
196 }
197 s.Position = n.PosRange
198 src = append(src, s)
199
200 default:
201 // unhandled type
202 }
203 return src
204}
205
206func removeFromSlice(sl []string, s ...string) []string {
207 for _, v := range s {
208 idx := slices.Index(sl, v)
209 if idx >= 0 {
210 if len(sl) == 1 {
211 return nil
212 }
213 sl = slices.Delete(sl, idx, idx+1)
214 }
215 }
216 return sl
217}
218
219func appendToSlice(dst []string, values ...string) []string {
220 for _, v := range values {
221 if !slices.Contains(dst, v) {
222 dst = append(dst, v)
223 }
224 }
225 return dst
226}
227
228func includeLabel(s Source, names ...string) Source {
229 s.ExcludedLabels = removeFromSlice(s.ExcludedLabels, names...)
230 for _, name := range names {
231 delete(s.ExcludeReason, name)
232 }
233 s.IncludedLabels = appendToSlice(s.IncludedLabels, names...)
234 return s
235}
236
237// Include labels that were not already excluded.
238func maybeIncludeLabel(s Source, names ...string) Source {
239 for _, name := range names {
240 if !slices.Contains(s.ExcludedLabels, name) {
241 s.IncludedLabels = appendToSlice(s.IncludedLabels, names...)
242 }
243 }
244 return s
245}
246
247func restrictIncludedLabels(s Source, names []string) Source {
248 todo := []string{}
249 for _, name := range s.IncludedLabels {
250 if !slices.Contains(names, name) {
251 todo = append(todo, name)
252 }
253 }
254 s.IncludedLabels = removeFromSlice(s.IncludedLabels, todo...)
255 return s
256}
257
258func guaranteeLabel(s Source, names ...string) Source {
259 s.ExcludedLabels = removeFromSlice(s.ExcludedLabels, names...)
260 for _, name := range names {
261 delete(s.ExcludeReason, name)
262 }
263 s.GuaranteedLabels = appendToSlice(s.GuaranteedLabels, names...)
264 return s
265}
266
267func restrictGuaranteedLabels(s Source, names []string) Source {
268 todo := []string{}
269 for _, name := range s.GuaranteedLabels {
270 if !slices.Contains(names, name) {
271 todo = append(todo, name)
272 }
273 }
274 s.GuaranteedLabels = removeFromSlice(s.GuaranteedLabels, todo...)
275 return s
276}
277
278func excludeLabel(s Source, reason string, fragment posrange.PositionRange, names ...string) Source {
279 s.ExcludedLabels = appendToSlice(s.ExcludedLabels, names...)
280 for _, name := range names {
281 s.ExcludeReason = setInMap(s.ExcludeReason, name, ExcludedLabel{
282 Reason: reason,
283 Fragment: fragment,
284 })
285 }
286 s.IncludedLabels = removeFromSlice(s.IncludedLabels, names...)
287 s.GuaranteedLabels = removeFromSlice(s.GuaranteedLabels, names...)
288 return s
289}
290
291func excludeAllLabels(s Source, reason string, fragment posrange.PositionRange) Source {
292 s.ExcludeReason = setInMap(
293 s.ExcludeReason,
294 "",
295 ExcludedLabel{
296 Reason: reason,
297 Fragment: fragment,
298 },
299 )
300 return s
301}
302
303func setInMap(dst map[string]ExcludedLabel, key string, val ExcludedLabel) map[string]ExcludedLabel {
304 if dst == nil {
305 dst = map[string]ExcludedLabel{}
306 }
307 dst[key] = val
308 return dst
309}
310
311func labelsFromSelectors(matches []labels.MatchType, selector *promParser.VectorSelector) (names []string) {
312 if selector == nil {
313 return nil
314 }
315 // Any label used in positive filters is gurnateed to be present.
316 for _, lm := range selector.LabelMatchers {
317 if lm.Name == labels.MetricName {
318 continue
319 }
320 if !slices.Contains(matches, lm.Type) {
321 continue
322 }
323 names = appendToSlice(names, lm.Name)
324 }
325 return names
326}
327
328func labelsWithEmptyValueSelector(selector *promParser.VectorSelector) (names []string) {
329 for _, lm := range selector.LabelMatchers {
330 if lm.Name == labels.MetricName {
331 continue
332 }
333 if lm.Type == labels.MatchEqual && lm.Value == "" {
334 names = appendToSlice(names, lm.Name)
335 }
336 }
337 return names
338}
339
340func GetQueryFragment(expr string, pos posrange.PositionRange) string {
341 return expr[pos.Start:pos.End]
342}
343
344func walkAggregation(expr string, n *promParser.AggregateExpr) (src []Source) {
345 var s Source
346 switch n.Op {
347 case promParser.SUM:
348 for _, s = range parseAggregation(expr, n) {
349 s.Aggregation = n
350 s.Operation = "sum"
351 s = excludeLabel(s, "Aggregation removes metric name.", n.PosRange, labels.MetricName)
352 src = append(src, s)
353 }
354 case promParser.MIN:
355 for _, s = range parseAggregation(expr, n) {
356 s.Aggregation = n
357 s.Operation = "min"
358 s = excludeLabel(s, "Aggregation removes metric name.", n.PosRange, labels.MetricName)
359 src = append(src, s)
360 }
361 case promParser.MAX:
362 for _, s = range parseAggregation(expr, n) {
363 s.Aggregation = n
364 s.Operation = "max"
365 s = excludeLabel(s, "Aggregation removes metric name.", n.PosRange, labels.MetricName)
366 src = append(src, s)
367 }
368 case promParser.AVG:
369 for _, s = range parseAggregation(expr, n) {
370 s.Aggregation = n
371 s.Operation = "avg"
372 s = excludeLabel(s, "Aggregation removes metric name.", n.PosRange, labels.MetricName)
373 src = append(src, s)
374 }
375 case promParser.GROUP:
376 for _, s = range parseAggregation(expr, n) {
377 s.Aggregation = n
378 s.Operation = "group"
379 s = excludeLabel(s, "Aggregation removes metric name.", n.PosRange, labels.MetricName)
380 src = append(src, s)
381 }
382 case promParser.STDDEV:
383 for _, s = range parseAggregation(expr, n) {
384 s.Aggregation = n
385 s.Operation = "stddev"
386 s = excludeLabel(s, "Aggregation removes metric name.", n.PosRange, labels.MetricName)
387 src = append(src, s)
388 }
389 case promParser.STDVAR:
390 for _, s = range parseAggregation(expr, n) {
391 s.Aggregation = n
392 s.Operation = "stdvar"
393 s = excludeLabel(s, "Aggregation removes metric name.", n.PosRange, labels.MetricName)
394 src = append(src, s)
395 }
396 case promParser.COUNT:
397 for _, s = range parseAggregation(expr, n) {
398 s.Aggregation = n
399 s.Operation = "count"
400 s = excludeLabel(s, "Aggregation removes metric name.", n.PosRange, labels.MetricName)
401 src = append(src, s)
402 }
403 case promParser.COUNT_VALUES:
404 for _, s = range parseAggregation(expr, n) {
405 s.Aggregation = n
406 s.Operation = "count_values"
407 // Param is the label to store the count value in.
408 s = includeLabel(s, n.Param.(*promParser.StringLiteral).Val)
409 s = guaranteeLabel(s, n.Param.(*promParser.StringLiteral).Val)
410 s = excludeLabel(s, "Aggregation removes metric name.", n.PosRange, labels.MetricName)
411 src = append(src, s)
412 }
413 case promParser.QUANTILE:
414 for _, s = range parseAggregation(expr, n) {
415 s.Aggregation = n
416 s.Operation = "quantile"
417 s = excludeLabel(s, "Aggregation removes metric name.", n.PosRange, labels.MetricName)
418 src = append(src, s)
419 }
420 case promParser.TOPK:
421 for _, s = range walkNode(expr, n.Expr) {
422 s.Type = AggregateSource
423 s.Aggregation = n
424 s.Operation = "topk"
425 src = append(src, s)
426 }
427 case promParser.BOTTOMK:
428 for _, s = range walkNode(expr, n.Expr) {
429 s.Type = AggregateSource
430 s.Aggregation = n
431 s.Operation = "bottomk"
432 src = append(src, s)
433 }
434 /*
435 TODO these are experimental and promParser.EnableExperimentalFunctions must be set to true to enable parsing of these.
436 case promParser.LIMITK:
437 s = walkNode(expr, n.Expr)
438 s.Type = AggregateSource
439 s.Operation = "limitk"
440 case promParser.LIMIT_RATIO:
441 s = walkNode(expr, n.Expr)
442 s.Type = AggregateSource
443 s.Operation = "limit_ratio"
444 */
445 }
446 return src
447}
448
449func parseAggregation(expr string, n *promParser.AggregateExpr) (src []Source) {
450 var s Source
451 for _, s = range walkNode(expr, n.Expr) {
452 if n.Without {
453 s = excludeLabel(
454 s,
455 fmt.Sprintf("Query is using aggregation with `without(%s)`, all labels included inside `without(...)` will be removed from the results.",
456 strings.Join(n.Grouping, ", ")),
457 FindPosition(expr, n.PosRange, "without"),
458 n.Grouping...,
459 )
460 } else {
461 if len(n.Grouping) == 0 {
462 s.IncludedLabels = nil
463 s.GuaranteedLabels = nil
464 s = excludeAllLabels(s, "Query is using aggregation that removes all labels.", FindPosition(expr, n.PosRange, "sum"))
465 } else {
466 // Check if source of labels already fixes them.
467 if !s.FixedLabels {
468 s = maybeIncludeLabel(s, n.Grouping...)
469 }
470 s = excludeAllLabels(
471 s,
472 fmt.Sprintf("Query is using aggregation with `by(%s)`, only labels included inside `by(...)` will be present on the results.",
473 strings.Join(n.Grouping, ", ")),
474 FindPosition(expr, n.PosRange, "by"),
475 )
476 s = restrictGuaranteedLabels(s, n.Grouping)
477 s = restrictIncludedLabels(s, n.Grouping)
478 }
479 s.FixedLabels = true
480 }
481 s.Type = AggregateSource
482 s.Returns = promParser.ValueTypeVector
483 src = append(src, s)
484 }
485 return src
486}
487
488func parsePromQLFunc(s Source, expr string, n *promParser.Call) Source {
489 switch n.Func.Name {
490 case "abs", "sgn", "acos", "acosh", "asin", "asinh", "atan", "atanh", "cos", "cosh", "sin", "sinh", "tan", "tanh":
491 // No change to labels.
492 s.Returns = promParser.ValueTypeVector
493 s = guaranteeLabel(s, labelsFromSelectors(guaranteedLabelsMatches, s.Selector)...)
494
495 case "ceil", "floor", "round":
496 // No change to labels.
497 s.Returns = promParser.ValueTypeVector
498 s = guaranteeLabel(s, labelsFromSelectors(guaranteedLabelsMatches, s.Selector)...)
499
500 case "changes", "resets":
501 // No change to labels.
502 s.Returns = promParser.ValueTypeVector
503 s = guaranteeLabel(s, labelsFromSelectors(guaranteedLabelsMatches, s.Selector)...)
504
505 case "clamp", "clamp_max", "clamp_min":
506 // No change to labels.
507 s.Returns = promParser.ValueTypeVector
508 s = guaranteeLabel(s, labelsFromSelectors(guaranteedLabelsMatches, s.Selector)...)
509
510 case "absent", "absent_over_time":
511 s.Returns = promParser.ValueTypeVector
512 s.FixedLabels = true
513 s.IncludedLabels = nil
514 s.GuaranteedLabels = nil
515 for _, name := range labelsFromSelectors([]labels.MatchType{labels.MatchEqual}, s.Selector) {
516 s = includeLabel(s, name)
517 s = guaranteeLabel(s, name)
518 }
519 s = excludeAllLabels(
520 s,
521 fmt.Sprintf(`The [%s()](https://prometheus.io/docs/prometheus/latest/querying/functions/#%s) function is used to check if provided query doesn't match any time series.
522You will only get any results back if the metric selector you pass doesn't match anything.
523Since there are no matching time series there are also no labels. If some time series is missing you cannot read its labels.
524This means that the only labels you can get back from absent call are the ones you pass to it.
525If you're hoping to get instance specific labels this way and alert when some target is down then that won't work, use the `+"`up`"+` metric instead.`,
526 n.Func.Name, n.Func.Name),
527 FindPosition(expr, n.PosRange, n.Func.Name),
528 )
529
530 case "avg_over_time", "count_over_time", "last_over_time", "max_over_time", "min_over_time", "present_over_time", "quantile_over_time", "stddev_over_time", "stdvar_over_time", "sum_over_time":
531 // No change to labels.
532 s.Returns = promParser.ValueTypeVector
533 s = guaranteeLabel(s, labelsFromSelectors(guaranteedLabelsMatches, s.Selector)...)
534
535 case "days_in_month", "day_of_month", "day_of_week", "day_of_year", "hour", "minute", "month", "year":
536 s.Returns = promParser.ValueTypeVector
537 // No labels if we don't pass any arguments.
538 // Otherwise no change to labels.
539 if len(s.Call.Args) == 0 {
540 s.FixedLabels = true
541 s.AlwaysReturns = true
542 s.IncludedLabels = nil
543 s.GuaranteedLabels = nil
544 s = excludeAllLabels(
545 s,
546 fmt.Sprintf("Calling `%s()` with no arguments will return an empty time series with no labels.",
547 n.Func.Name),
548 n.PosRange,
549 )
550 } else {
551 s = guaranteeLabel(s, labelsFromSelectors(guaranteedLabelsMatches, s.Selector)...)
552 }
553
554 case "deg", "rad", "ln", "log10", "log2", "sqrt", "exp":
555 // No change to labels.
556 s.Returns = promParser.ValueTypeVector
557 s = guaranteeLabel(s, labelsFromSelectors(guaranteedLabelsMatches, s.Selector)...)
558
559 case "delta", "idelta", "increase", "deriv", "irate", "rate":
560 // No change to labels.
561 s.Returns = promParser.ValueTypeVector
562 s = guaranteeLabel(s, labelsFromSelectors(guaranteedLabelsMatches, s.Selector)...)
563
564 case "histogram_avg", "histogram_count", "histogram_sum", "histogram_stddev", "histogram_stdvar", "histogram_fraction", "histogram_quantile":
565 // No change to labels.
566 s.Returns = promParser.ValueTypeVector
567 s = guaranteeLabel(s, labelsFromSelectors(guaranteedLabelsMatches, s.Selector)...)
568
569 case "holt_winters", "predict_linear":
570 // No change to labels.
571 s.Returns = promParser.ValueTypeVector
572 s = guaranteeLabel(s, labelsFromSelectors(guaranteedLabelsMatches, s.Selector)...)
573
574 case "label_replace", "label_join":
575 // One label added to the results.
576 s.Returns = promParser.ValueTypeVector
577 s = guaranteeLabel(s, n.Args[1].(*promParser.StringLiteral).Val)
578
579 case "pi":
580 s.Returns = promParser.ValueTypeScalar
581 s.IncludedLabels = nil
582 s.GuaranteedLabels = nil
583 s.FixedLabels = true
584 s.AlwaysReturns = true
585 s = excludeAllLabels(
586 s,
587 fmt.Sprintf("Calling `%s()` will return a scalar value with no labels.", n.Func.Name),
588 n.PosRange,
589 )
590
591 case "scalar":
592 s.Returns = promParser.ValueTypeScalar
593 s.IncludedLabels = nil
594 s.GuaranteedLabels = nil
595 s.FixedLabels = true
596 s.AlwaysReturns = true
597 s = excludeAllLabels(
598 s,
599 fmt.Sprintf("Calling `%s()` will return a scalar value with no labels.", n.Func.Name),
600 FindPosition(expr, n.PositionRange(), n.Func.Name),
601 )
602
603 case "sort", "sort_desc":
604 // No change to labels.
605 s.Returns = promParser.ValueTypeVector
606
607 case "time":
608 s.Returns = promParser.ValueTypeScalar
609 s.IncludedLabels = nil
610 s.GuaranteedLabels = nil
611 s.FixedLabels = true
612 s.AlwaysReturns = true
613 s = excludeAllLabels(
614 s,
615 fmt.Sprintf("Calling `%s()` will return a scalar value with no labels.", n.Func.Name),
616 n.PosRange,
617 )
618
619 case "timestamp":
620 // No change to labels.
621 s.Returns = promParser.ValueTypeVector
622 s = guaranteeLabel(s, labelsFromSelectors(guaranteedLabelsMatches, s.Selector)...)
623
624 case "vector":
625 s.Returns = promParser.ValueTypeVector
626 s.IncludedLabels = nil
627 s.GuaranteedLabels = nil
628 s.FixedLabels = true
629 s.AlwaysReturns = true
630 for _, vs := range walkNode(expr, n.Args[0]) {
631 if vs.KnownReturn {
632 s.ReturnedNumber = vs.ReturnedNumber
633 s.KnownReturn = true
634 }
635 }
636 s = excludeAllLabels(
637 s,
638 fmt.Sprintf("Calling `%s()` will return a vector value with no labels.", n.Func.Name),
639 FindPosition(expr, n.PosRange, n.Func.Name),
640 )
641
642 default:
643 // Unsupported function
644 s.Returns = promParser.ValueTypeNone
645 s.Call = nil
646 }
647 return s
648}
649
650func parseCall(expr string, n *promParser.Call) (src []Source) {
651 var vt promParser.ValueType
652 for i, e := range n.Args {
653 if i >= len(n.Func.ArgTypes) {
654 vt = n.Func.ArgTypes[len(n.Func.ArgTypes)-1]
655 } else {
656 vt = n.Func.ArgTypes[i]
657 }
658
659 switch vt {
660 case promParser.ValueTypeVector, promParser.ValueTypeMatrix:
661 for _, es := range walkNode(expr, e) {
662 es.Type = FuncSource
663 es.Operation = n.Func.Name
664 es.Call = n
665 es.Position = e.PositionRange()
666 src = append(src, parsePromQLFunc(es, expr, n))
667 }
668 case promParser.ValueTypeNone, promParser.ValueTypeScalar, promParser.ValueTypeString:
669 }
670 }
671
672 if len(src) == 0 {
673 var s Source
674 s.Type = FuncSource
675 s.Operation = n.Func.Name
676 s.Call = n
677 s.Position = n.PosRange
678 src = append(src, parsePromQLFunc(s, expr, n))
679 }
680
681 return src
682}
683
684func parseBinOps(expr string, n *promParser.BinaryExpr) (src []Source) {
685 var s Source
686 switch {
687
688 // foo{} + 1
689 // 1 + foo{}
690 // foo{} > 1
691 // 1 > foo{}
692 case n.VectorMatching == nil:
693 lhs := walkNode(expr, n.LHS)
694 rhs := walkNode(expr, n.RHS)
695 for _, ls := range lhs {
696 ls.IsConditional, ls.IsReturnBool = checkConditions(ls, n.Op, n.ReturnBool)
697 for _, rs := range rhs {
698 rs.IsConditional, rs.IsReturnBool = checkConditions(rs, n.Op, n.ReturnBool)
699 var side Source
700 switch {
701 case ls.Returns == promParser.ValueTypeVector, ls.Returns == promParser.ValueTypeMatrix:
702 // Use labels from LHS
703 side = ls
704 case rs.Returns == promParser.ValueTypeVector, rs.Returns == promParser.ValueTypeMatrix:
705 // Use labels from RHS
706 side = rs
707 default:
708 side = ls
709 }
710 if ls.AlwaysReturns && rs.AlwaysReturns && ls.KnownReturn && rs.KnownReturn {
711 // Both sides always return something
712 side.ReturnedNumber, side.IsDead, side.IsDeadReason, side.IsDeadPosition = calculateStaticReturn(
713 expr,
714 ls, rs,
715 n.Op,
716 ls.IsDead,
717 )
718 }
719 src = append(src, side)
720 }
721 }
722
723 // foo{} + bar{}
724 // foo{} + on(...) bar{}
725 // foo{} + ignoring(...) bar{}
726 // foo{} / bar{}
727 case n.VectorMatching.Card == promParser.CardOneToOne:
728 rhs := walkNode(expr, n.RHS)
729 for _, s = range walkNode(expr, n.LHS) {
730 if n.VectorMatching.On {
731 s.FixedLabels = true
732 s = includeLabel(s, n.VectorMatching.MatchingLabels...)
733 s = restrictIncludedLabels(s, n.VectorMatching.MatchingLabels)
734 s = restrictGuaranteedLabels(s, n.VectorMatching.MatchingLabels)
735 s = excludeAllLabels(
736 s,
737 fmt.Sprintf(
738 "Query is using %s vector matching with `on(%s)`, only labels included inside `on(...)` will be present on the results.",
739 n.VectorMatching.Card, strings.Join(n.VectorMatching.MatchingLabels, ", "),
740 ),
741 FindPosition(expr, n.PositionRange(), "on"),
742 )
743 } else {
744 s = excludeLabel(
745 s,
746 fmt.Sprintf(
747 "Query is using %s vector matching with `ignoring(%s)`, all labels included inside `ignoring(...)` will be removed on the results.",
748 n.VectorMatching.Card, strings.Join(n.VectorMatching.MatchingLabels, ", "),
749 ),
750 FindPosition(expr, n.PositionRange(), "ignoring"),
751 n.VectorMatching.MatchingLabels...,
752 )
753 for _, rs := range rhs {
754 rs.IsConditional, rs.IsReturnBool = checkConditions(rs, n.Op, n.ReturnBool)
755 if s.AlwaysReturns && rs.AlwaysReturns && s.KnownReturn && rs.KnownReturn {
756 // Both sides always return something
757 s.ReturnedNumber, s.IsDead, s.IsDeadReason, s.IsDeadPosition = calculateStaticReturn(
758 expr,
759 s, rs,
760 n.Op,
761 s.IsDead,
762 )
763 }
764 }
765 }
766 if s.Operation == "" {
767 s.Operation = n.VectorMatching.Card.String()
768 }
769 for _, rs := range rhs {
770 if ok, s, pos := canJoin(s, rs, n.VectorMatching); !ok {
771 rs.IsDead = true
772 rs.IsDeadReason = s
773 rs.IsDeadPosition = pos
774 }
775 s.Joins = append(s.Joins, Join{
776 Src: rs,
777 })
778 }
779 s.IsConditional, s.IsReturnBool = checkConditions(s, n.Op, n.ReturnBool)
780 src = append(src, s)
781 }
782
783 // foo{} + on(...) group_right(...) bar{}
784 // foo{} + ignoring(...) group_right(...) bar{}
785 case n.VectorMatching.Card == promParser.CardOneToMany:
786 lhs := walkNode(expr, n.LHS)
787 for _, s = range walkNode(expr, n.RHS) {
788 s = includeLabel(s, n.VectorMatching.Include...)
789 // If we have:
790 // foo * on(instance) group_left(a,b) bar{x="y"}
791 // then only group_left() labels will be included.
792 if n.VectorMatching.On {
793 s = includeLabel(s, n.VectorMatching.MatchingLabels...)
794 }
795 if s.Operation == "" {
796 s.Operation = n.VectorMatching.Card.String()
797 }
798 for _, ls := range lhs {
799 if ok, s, pos := canJoin(s, ls, n.VectorMatching); !ok {
800 ls.IsDead = true
801 ls.IsDeadReason = s
802 ls.IsDeadPosition = pos
803 }
804 s.Joins = append(s.Joins, Join{
805 Src: ls,
806 })
807 }
808 s.IsConditional, s.IsReturnBool = checkConditions(s, n.Op, n.ReturnBool)
809 src = append(src, s)
810 }
811
812 // foo{} + on(...) group_left(...) bar{}
813 // foo{} + ignoring(...) group_left(...) bar{}
814 case n.VectorMatching.Card == promParser.CardManyToOne:
815 rhs := walkNode(expr, n.RHS)
816 for _, s = range walkNode(expr, n.LHS) {
817 s = includeLabel(s, n.VectorMatching.Include...)
818 if n.VectorMatching.On {
819 s = includeLabel(s, n.VectorMatching.MatchingLabels...)
820 }
821 if s.Operation == "" {
822 s.Operation = n.VectorMatching.Card.String()
823 }
824 for _, rs := range rhs {
825 if ok, s, pos := canJoin(s, rs, n.VectorMatching); !ok {
826 rs.IsDead = true
827 rs.IsDeadReason = s
828 rs.IsDeadPosition = pos
829 }
830 s.Joins = append(s.Joins, Join{
831 Src: rs,
832 })
833 }
834 s.IsConditional, s.IsReturnBool = checkConditions(s, n.Op, n.ReturnBool)
835 src = append(src, s)
836 }
837
838 // foo{} and on(...) bar{}
839 // foo{} and ignoring(...) bar{}
840 // foo{} unless bar{}
841 case n.VectorMatching.Card == promParser.CardManyToMany:
842 var lhsCanBeEmpty bool // true if any of the LHS query can produce empty results.
843 rhs := walkNode(expr, n.RHS)
844 for _, s = range walkNode(expr, n.LHS) {
845 var rhsConditional bool
846 if n.VectorMatching.On {
847 s = includeLabel(s, n.VectorMatching.MatchingLabels...)
848 }
849 if s.Operation == "" {
850 s.Operation = n.VectorMatching.Card.String()
851 }
852 if !s.AlwaysReturns || s.IsConditional {
853 lhsCanBeEmpty = true
854 }
855 for _, rs := range rhs {
856 isConditional, _ := checkConditions(rs, n.Op, n.ReturnBool)
857 if isConditional {
858 rhsConditional = true
859 }
860 if ok, s, pos := canJoin(s, rs, n.VectorMatching); !ok {
861 rs.IsDead = true
862 rs.IsDeadReason = s
863 rs.IsDeadPosition = pos
864 }
865 switch {
866 case n.Op == promParser.LUNLESS:
867 if n.VectorMatching.On && len(n.VectorMatching.MatchingLabels) == 0 && rs.AlwaysReturns && !rs.IsConditional {
868 s.IsDead = true
869 s.IsDeadReason = "this query will never return anything because the `unless` query always returns something"
870 s.IsDeadPosition = rs.Position
871 }
872 s.Unless = append(s.Unless, Join{
873 Src: rs,
874 })
875 case n.Op != promParser.LOR:
876 s.Joins = append(s.Joins, Join{
877 Src: rs,
878 })
879 }
880 }
881 if n.Op == promParser.LAND && rhsConditional {
882 s.IsConditional = true
883 }
884 src = append(src, s)
885 }
886 if n.Op == promParser.LOR {
887 for _, s = range rhs {
888 if s.Operation == "" {
889 s.Operation = n.VectorMatching.Card.String()
890 }
891 // If LHS can NOT be empty then RHS is dead code.
892 if !lhsCanBeEmpty {
893 s.IsDead = true
894 s.IsDeadReason = "the left hand side always returs something and so the right hand side is never used"
895 s.IsDeadPosition = s.Position
896 }
897 src = append(src, s)
898 }
899 }
900 }
901 return src
902}
903
904func checkConditions(s Source, op promParser.ItemType, isBool bool) (isConditional, isReturnBool bool) {
905 if !s.IsConditional && isBool {
906 isReturnBool = isBool
907 }
908 if s.IsConditional {
909 isConditional = s.IsConditional
910 } else {
911 isConditional = op.IsComparisonOperator()
912 }
913 return isConditional, isReturnBool
914}
915
916func canJoin(ls, rs Source, vm *promParser.VectorMatching) (bool, string, posrange.PositionRange) {
917 var side string
918 if vm.Card == promParser.CardOneToMany {
919 side = "left"
920 } else {
921 side = "right"
922 }
923
924 switch {
925 case vm.On && len(vm.MatchingLabels) == 0: // ls on() unless rs
926 return true, "", posrange.PositionRange{}
927 case vm.On: // ls on(...) unless rs
928 for _, name := range vm.MatchingLabels {
929 if ls.CanHaveLabel(name) && !rs.CanHaveLabel(name) {
930 return false, fmt.Sprintf("The %s hand side will never be matched because it doesn't have the `%s` label from `on(...)`. %s",
931 side, name, rs.LabelExcludeReason(name).Reason), rs.LabelExcludeReason(name).Fragment
932 }
933 }
934 default: // ls unless rs
935 for _, name := range ls.GuaranteedLabels {
936 if ls.CanHaveLabel(name) && !rs.CanHaveLabel(name) {
937 return false, fmt.Sprintf("The %s hand side will never be matched because it doesn't have the `%s` label while the left hand side will. %s",
938 side, name, rs.LabelExcludeReason(name).Reason), rs.LabelExcludeReason(name).Fragment
939 }
940 }
941 }
942 return true, "", posrange.PositionRange{}
943}
944
945func ftos(v float64) string {
946 return strconv.FormatFloat(v, 'f', -1, 64)
947}
948
949func calculateStaticReturn(expr string, ls, rs Source, op promParser.ItemType, isDead bool) (float64, bool, string, posrange.PositionRange) {
950 lf := ls.Fragment(expr)
951 rf := rs.Fragment(expr)
952 var cmpPrefix string
953 if lf != "" && rf != "" {
954 cmpPrefix = fmt.Sprintf("`%s %s %s` always evaluates to", lf, op, rf)
955 } else {
956 cmpPrefix = "this query always evaluates to"
957 }
958 cmpSuffix := "which is not possible, so it will never return anything"
959 switch op {
960 case promParser.EQLC:
961 if ls.ReturnedNumber != rs.ReturnedNumber {
962 return ls.ReturnedNumber,
963 true,
964 fmt.Sprintf("%s `%s == %s` %s", cmpPrefix, ftos(ls.ReturnedNumber), ftos(rs.ReturnedNumber), cmpSuffix),
965 ls.Position
966 }
967 case promParser.NEQ:
968 if ls.ReturnedNumber == rs.ReturnedNumber {
969 return ls.ReturnedNumber,
970 true,
971 fmt.Sprintf("%s `%s != %s` %s", cmpPrefix, ftos(ls.ReturnedNumber), ftos(rs.ReturnedNumber), cmpSuffix),
972 ls.Position
973 }
974 case promParser.LTE:
975 if ls.ReturnedNumber > rs.ReturnedNumber {
976 return ls.ReturnedNumber,
977 true,
978 fmt.Sprintf("%s `%s <= %s` %s", cmpPrefix, ftos(ls.ReturnedNumber), ftos(rs.ReturnedNumber), cmpSuffix),
979 ls.Position
980 }
981 case promParser.LSS:
982 if ls.ReturnedNumber >= rs.ReturnedNumber {
983 return ls.ReturnedNumber,
984 true,
985 fmt.Sprintf("%s `%s < %s` %s", cmpPrefix, ftos(ls.ReturnedNumber), ftos(rs.ReturnedNumber), cmpSuffix),
986 ls.Position
987 }
988 case promParser.GTE:
989 if ls.ReturnedNumber < rs.ReturnedNumber {
990 return ls.ReturnedNumber,
991 true,
992 fmt.Sprintf("%s `%s >= %s` %s", cmpPrefix, ftos(ls.ReturnedNumber), ftos(rs.ReturnedNumber), cmpSuffix),
993 ls.Position
994 }
995 case promParser.GTR:
996 if ls.ReturnedNumber <= rs.ReturnedNumber {
997 return ls.ReturnedNumber,
998 true,
999 fmt.Sprintf("%s `%s > %s` %s", cmpPrefix, ftos(ls.ReturnedNumber), ftos(rs.ReturnedNumber), cmpSuffix),
1000 ls.Position
1001 }
1002 case promParser.ADD:
1003 return ls.ReturnedNumber + rs.ReturnedNumber, isDead, "", ls.IsDeadPosition
1004 case promParser.SUB:
1005 return ls.ReturnedNumber - rs.ReturnedNumber, isDead, "", ls.IsDeadPosition
1006 case promParser.MUL:
1007 return ls.ReturnedNumber * rs.ReturnedNumber, isDead, "", ls.IsDeadPosition
1008 case promParser.DIV:
1009 return ls.ReturnedNumber / rs.ReturnedNumber, isDead, "", ls.IsDeadPosition
1010 case promParser.MOD:
1011 return math.Mod(ls.ReturnedNumber, rs.ReturnedNumber), isDead, "", ls.IsDeadPosition
1012 case promParser.POW:
1013 return math.Pow(ls.ReturnedNumber, rs.ReturnedNumber), isDead, "", ls.IsDeadPosition
1014 }
1015 return ls.ReturnedNumber, isDead, "", ls.IsDeadPosition
1016}
1017
1018// FIXME sum() on ().
1019func FindPosition(expr string, within posrange.PositionRange, fn string) posrange.PositionRange {
1020 re := regexp.MustCompile("(?i)(" + regexp.QuoteMeta(fn) + ")[ \n\t]*\\(")
1021 idx := re.FindStringSubmatchIndex(GetQueryFragment(expr, within))
1022 if idx == nil {
1023 return within
1024 }
1025 return posrange.PositionRange{
1026 Start: within.Start + posrange.Pos(idx[0]),
1027 End: within.Start + posrange.Pos(idx[1]-1),
1028 }
1029}
1030