Skip to content

Commit

Permalink
Merge pull request #13725 from prometheus/beorn7/promql2
Browse files Browse the repository at this point in the history
promql: Fix limiting of extrapolation to negative values
  • Loading branch information
beorn7 committed Mar 7, 2024
2 parents e705d83 + 7f912db commit b0c0961
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 25 deletions.
33 changes: 16 additions & 17 deletions promql/functions.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,37 +128,36 @@ func extrapolatedRate(vals []parser.Value, args parser.Expressions, enh *EvalNod
sampledInterval := float64(lastT-firstT) / 1000
averageDurationBetweenSamples := sampledInterval / float64(numSamplesMinusOne)

// TODO(beorn7): Do this for histograms, too.
// If the first/last samples are close to the boundaries of the range,
// extrapolate the result. This is as we expect that another sample
// will exist given the spacing between samples we've seen thus far,
// with an allowance for noise.
extrapolationThreshold := averageDurationBetweenSamples * 1.1
extrapolateToInterval := sampledInterval

if durationToStart >= extrapolationThreshold {
durationToStart = averageDurationBetweenSamples / 2
}
if isCounter && resultFloat > 0 && len(samples.Floats) > 0 && samples.Floats[0].F >= 0 {
// Counters cannot be negative. If we have any slope at all
// (i.e. resultFloat went up), we can extrapolate the zero point
// of the counter. If the duration to the zero point is shorter
// than the durationToStart, we take the zero point as the start
// of the series, thereby avoiding extrapolation to negative
// counter values.
// TODO(beorn7): Do this for histograms, too.
durationToZero := sampledInterval * (samples.Floats[0].F / resultFloat)
if durationToZero < durationToStart {
durationToStart = durationToZero
}
}
extrapolateToInterval += durationToStart

// If the first/last samples are close to the boundaries of the range,
// extrapolate the result. This is as we expect that another sample
// will exist given the spacing between samples we've seen thus far,
// with an allowance for noise.
extrapolationThreshold := averageDurationBetweenSamples * 1.1
extrapolateToInterval := sampledInterval

if durationToStart < extrapolationThreshold {
extrapolateToInterval += durationToStart
} else {
extrapolateToInterval += averageDurationBetweenSamples / 2
}
if durationToEnd < extrapolationThreshold {
extrapolateToInterval += durationToEnd
} else {
extrapolateToInterval += averageDurationBetweenSamples / 2
if durationToEnd >= extrapolationThreshold {
durationToEnd = averageDurationBetweenSamples / 2
}
extrapolateToInterval += durationToEnd

factor := extrapolateToInterval / sampledInterval
if isRate {
factor /= ms.Range.Seconds()
Expand Down
31 changes: 23 additions & 8 deletions promql/testdata/functions.test
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,28 @@ clear
load 5m
http_requests{path="/foo"} 0+10x10
http_requests{path="/bar"} 0+10x5 0+10x5
http_requests{path="/dings"} 10+10x10
http_requests{path="/bumms"} 1+10x10

# Tests for increase().
eval instant at 50m increase(http_requests[50m])
{path="/foo"} 100
{path="/bar"} 90

{path="/foo"} 100
{path="/bar"} 90
{path="/dings"} 100
{path="/bumms"} 100

# "foo" and "bar" are already at value 0 at t=0, so no extrapolation
# happens. "dings" has value 10 at t=0 and would reach 0 at t=-5m. The
# normal extrapolation by half a sample interval only goes to
# t=-2m30s, so that's not yet reaching a negative value and therefore
# chosen. However, "bumms" has value 1 at t=0 and would reach 0 at
# t=-30s. Here the extrapolation to t=-2m30s would reach a negative
# value, and therefore the extrapolation happens only by 30s.
eval instant at 50m increase(http_requests[100m])
{path="/foo"} 100
{path="/bar"} 90
{path="/foo"} 100
{path="/bar"} 90
{path="/dings"} 105
{path="/bumms"} 101

clear

Expand Down Expand Up @@ -133,13 +146,15 @@ load 4m
testcounter_zero_cutoff{start="4m"} 240+240x10
testcounter_zero_cutoff{start="5m"} 300+240x10

# Zero cutoff for left-side extrapolation.
# Zero cutoff for left-side extrapolation happens until we
# reach half a sampling interval (2m). Beyond that, we only
# extrapolate by half a sampling interval.
eval instant at 10m rate(testcounter_zero_cutoff[20m])
{start="0m"} 0.5
{start="1m"} 0.55
{start="2m"} 0.6
{start="3m"} 0.65
{start="4m"} 0.7
{start="3m"} 0.6
{start="4m"} 0.6
{start="5m"} 0.6

# Normal half-interval cutoff for left-side extrapolation.
Expand Down

0 comments on commit b0c0961

Please sign in to comment.