@@ -627,23 +627,30 @@ func (ng *Engine) cumulativeSubqueryOffset(path []parser.Node) time.Duration {
627
627
func (ng * Engine ) findMinTime (s * parser.EvalStmt ) time.Time {
628
628
var maxOffset time.Duration
629
629
parser .Inspect (s .Expr , func (node parser.Node , path []parser.Node ) error {
630
+ var nodeOffset time.Duration
630
631
subqOffset := ng .cumulativeSubqueryOffset (path )
631
632
switch n := node .(type ) {
632
633
case * parser.VectorSelector :
633
- if maxOffset < ng .lookbackDelta + subqOffset {
634
- maxOffset = ng .lookbackDelta + subqOffset
635
- }
636
- if n .Offset + ng .lookbackDelta + subqOffset > maxOffset {
637
- maxOffset = n .Offset + ng .lookbackDelta + subqOffset
634
+ nodeOffset += ng .lookbackDelta + subqOffset
635
+ if n .Offset > 0 {
636
+ nodeOffset += n .Offset
638
637
}
639
638
case * parser.MatrixSelector :
640
- if maxOffset < n .Range + subqOffset {
641
- maxOffset = n .Range + subqOffset
639
+ nodeOffset += n .Range + subqOffset
640
+ if m := n .VectorSelector .(* parser.VectorSelector ).Offset ; m > 0 {
641
+ nodeOffset += m
642
642
}
643
- if m := n .VectorSelector .(* parser.VectorSelector ).Offset + n .Range + subqOffset ; m > maxOffset {
644
- maxOffset = m
643
+ // Include an extra lookbackDelta iff this is the argument to an
644
+ // extended range function. Extended ranges include one extra
645
+ // point, this is how far back we need to look for it.
646
+ f , ok := parser .Functions [extractFuncFromPath (path )]
647
+ if ok && f .ExtRange {
648
+ nodeOffset += ng .lookbackDelta
645
649
}
646
650
}
651
+ if maxOffset < nodeOffset {
652
+ maxOffset = nodeOffset
653
+ }
647
654
return nil
648
655
})
649
656
return s .Start .Add (- maxOffset )
@@ -678,18 +685,26 @@ func (ng *Engine) populateSeries(ctx context.Context, querier storage.Querier, s
678
685
679
686
switch n := node .(type ) {
680
687
case * parser.VectorSelector :
688
+ hints .Func = extractFuncFromPath (path )
689
+ hints .By , hints .Grouping = extractGroupsFromPath (path )
690
+
681
691
if evalRange == 0 {
682
692
hints .Start = hints .Start - durationMilliseconds (ng .lookbackDelta )
683
693
} else {
684
694
hints .Range = durationMilliseconds (evalRange )
685
695
// For all matrix queries we want to ensure that we have (end-start) + range selected
686
696
// this way we have `range` data before the start time
687
697
hints .Start = hints .Start - durationMilliseconds (evalRange )
698
+ // Include an extra lookbackDelta iff this is the argument to an
699
+ // extended range function. Extended ranges include one extra
700
+ // point, this is how far back we need to look for it.
701
+ f , ok := parser .Functions [hints .Func ]
702
+ if ok && f .ExtRange {
703
+ hints .Start = hints .Start - durationMilliseconds (ng .lookbackDelta )
704
+ }
688
705
evalRange = 0
689
706
}
690
707
691
- hints .Func = extractFuncFromPath (path )
692
- hints .By , hints .Grouping = extractGroupsFromPath (path )
693
708
if n .Offset > 0 {
694
709
offsetMilliseconds := durationMilliseconds (n .Offset )
695
710
hints .Start = hints .Start - offsetMilliseconds
@@ -1108,7 +1123,15 @@ func (ev *evaluator) eval(expr parser.Expr) parser.Value {
1108
1123
mat := make (Matrix , 0 , len (selVS .Series )) // Output matrix.
1109
1124
offset := durationMilliseconds (selVS .Offset )
1110
1125
selRange := durationMilliseconds (sel .Range )
1126
+ bufferRange := selRange
1111
1127
stepRange := selRange
1128
+ // Include an extra lookbackDelta iff this is an extended
1129
+ // range function. Extended ranges include one extra point,
1130
+ // this is how far back we need to look for it.
1131
+ if e .Func .ExtRange {
1132
+ bufferRange += durationMilliseconds (ev .lookbackDelta )
1133
+ stepRange += durationMilliseconds (ev .lookbackDelta )
1134
+ }
1112
1135
if stepRange > ev .interval {
1113
1136
stepRange = ev .interval
1114
1137
}
@@ -1118,7 +1141,7 @@ func (ev *evaluator) eval(expr parser.Expr) parser.Value {
1118
1141
inArgs [matrixArgIndex ] = inMatrix
1119
1142
enh := & EvalNodeHelper {out : make (Vector , 0 , 1 )}
1120
1143
// Process all the calls for one time series at a time.
1121
- it := storage .NewBuffer (selRange )
1144
+ it := storage .NewBuffer (bufferRange )
1122
1145
for i , s := range selVS .Series {
1123
1146
ev .currentSamples -= len (points )
1124
1147
points = points [:0 ]
@@ -1144,7 +1167,7 @@ func (ev *evaluator) eval(expr parser.Expr) parser.Value {
1144
1167
maxt := ts - offset
1145
1168
mint := maxt - selRange
1146
1169
// Evaluate the matrix selector for this series for this step.
1147
- points = ev .matrixIterSlice (it , mint , maxt , points )
1170
+ points = ev .matrixIterSlice (it , mint , maxt , e . Func . ExtRange , points )
1148
1171
if len (points ) == 0 {
1149
1172
continue
1150
1173
}
@@ -1453,7 +1476,7 @@ func (ev *evaluator) matrixSelector(node *parser.MatrixSelector) Matrix {
1453
1476
Metric : series [i ].Labels (),
1454
1477
}
1455
1478
1456
- ss .Points = ev .matrixIterSlice (it , mint , maxt , getPointSlice (16 ))
1479
+ ss .Points = ev .matrixIterSlice (it , mint , maxt , false , getPointSlice (16 ))
1457
1480
1458
1481
if len (ss .Points ) > 0 {
1459
1482
matrix = append (matrix , ss )
@@ -1469,24 +1492,39 @@ func (ev *evaluator) matrixSelector(node *parser.MatrixSelector) Matrix {
1469
1492
//
1470
1493
// As an optimization, the matrix vector may already contain points of the same
1471
1494
// time series from the evaluation of an earlier step (with lower mint and maxt
1472
- // values). Any such points falling before mint are discarded; points that fall
1473
- // into the [mint, maxt] range are retained; only points with later timestamps
1474
- // are populated from the iterator.
1475
- func (ev * evaluator ) matrixIterSlice (it * storage.BufferedSeriesIterator , mint , maxt int64 , out []Point ) []Point {
1476
- if len (out ) > 0 && out [len (out )- 1 ].T >= mint {
1495
+ // values). Any such points falling before mint (except the last, when extRange
1496
+ // is true) are discarded; points that fall into the [mint, maxt] range are
1497
+ // retained; only points with later timestamps are populated from the iterator.
1498
+ func (ev * evaluator ) matrixIterSlice (it * storage.BufferedSeriesIterator , mint , maxt int64 , extRange bool , out []Point ) []Point {
1499
+ extMint := mint - durationMilliseconds (ev .lookbackDelta )
1500
+ if len (out ) > 0 && (out [len (out )- 1 ].T >= mint || (extRange && out [len (out )- 1 ].T >= extMint )) {
1477
1501
// There is an overlap between previous and current ranges, retain common
1478
1502
// points. In most such cases:
1479
1503
// (a) the overlap is significantly larger than the eval step; and/or
1480
1504
// (b) the number of samples is relatively small.
1481
1505
// so a linear search will be as fast as a binary search.
1482
1506
var drop int
1483
- for drop = 0 ; out [drop ].T < mint ; drop ++ {
1507
+ if ! extRange {
1508
+ for drop = 0 ; out [drop ].T < mint ; drop ++ {
1509
+ }
1510
+ // Only append points with timestamps after the last timestamp we have.
1511
+ mint = out [len (out )- 1 ].T + 1
1512
+ } else {
1513
+ // This is an argument to an extended range function, first go past mint.
1514
+ for drop = 0 ; drop < len (out ) && out [drop ].T <= mint ; drop ++ {
1515
+ }
1516
+ // Then, go back one sample if within lookbackDelta of mint.
1517
+ if drop > 0 && out [drop - 1 ].T >= extMint {
1518
+ drop --
1519
+ }
1520
+ if out [len (out )- 1 ].T >= mint {
1521
+ // Only append points with timestamps after the last timestamp we have.
1522
+ mint = out [len (out )- 1 ].T + 1
1523
+ }
1484
1524
}
1485
1525
ev .currentSamples -= drop
1486
1526
copy (out , out [drop :])
1487
1527
out = out [:len (out )- drop ]
1488
- // Only append points with timestamps after the last timestamp we have.
1489
- mint = out [len (out )- 1 ].T + 1
1490
1528
} else {
1491
1529
ev .currentSamples -= len (out )
1492
1530
out = out [:0 ]
@@ -1500,18 +1538,35 @@ func (ev *evaluator) matrixIterSlice(it *storage.BufferedSeriesIterator, mint, m
1500
1538
}
1501
1539
1502
1540
buf := it .Buffer ()
1541
+ appendedPointBeforeMint := len (out ) > 0
1503
1542
for buf .Next () {
1504
1543
t , v := buf .At ()
1505
1544
if value .IsStaleNaN (v ) {
1506
1545
continue
1507
1546
}
1508
- // Values in the buffer are guaranteed to be smaller than maxt.
1509
- if t >= mint {
1510
- if ev .currentSamples >= ev .maxSamples {
1511
- ev .error (ErrTooManySamples (env ))
1547
+ if ! extRange {
1548
+ // Values in the buffer are guaranteed to be smaller than maxt.
1549
+ if t >= mint {
1550
+ if ev .currentSamples >= ev .maxSamples {
1551
+ ev .error (ErrTooManySamples (env ))
1552
+ }
1553
+ out = append (out , Point {T : t , V : v })
1554
+ ev .currentSamples ++
1555
+ }
1556
+ } else {
1557
+ // This is the argument to an extended range function: if any point
1558
+ // exists at or before range start, add it and then keep replacing
1559
+ // it with later points while not yet (strictly) inside the range.
1560
+ if t > mint || ! appendedPointBeforeMint {
1561
+ if ev .currentSamples >= ev .maxSamples {
1562
+ ev .error (ErrTooManySamples (env ))
1563
+ }
1564
+ out = append (out , Point {T : t , V : v })
1565
+ ev .currentSamples ++
1566
+ appendedPointBeforeMint = true
1567
+ } else {
1568
+ out [len (out )- 1 ] = Point {T : t , V : v }
1512
1569
}
1513
- out = append (out , Point {T : t , V : v })
1514
- ev .currentSamples ++
1515
1570
}
1516
1571
}
1517
1572
// The seeked sample might also be in the range.
0 commit comments