cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.71.5

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/parser/utils/source.go

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