@ -19,7 +19,6 @@ import (
"regexp"
"sort"
"strconv"
"time"
"github.com/prometheus/common/model"
@ -44,28 +43,25 @@ func funcTime(ev *evaluator, args Expressions) model.Value {
}
}
// === delta(matrix model.ValMatrix) Vector ===
func funcDelta ( ev * evaluator , args Expressions ) model . Value {
// This function still takes a 2nd argument for use by rate() and increase().
isCounter := len ( args ) >= 2 && ev . evalInt ( args [ 1 ] ) > 0
// extrapolatedRate is a utility function for rate/increase/delta.
// It calculates the rate (allowing for counter resets if isCounter is true),
// extrapolates if the first/last sample is close to the boundary, and returns
// the result as either per-second (if isRate is true) or overall.
func extrapolatedRate ( ev * evaluator , arg Expr , isCounter bool , isRate bool ) model . Value {
ms := arg . ( * MatrixSelector )
rangeStart := ev . Timestamp . Add ( - ms . Range - ms . Offset )
rangeEnd := ev . Timestamp . Add ( - ms . Offset )
resultVector := vector { }
// If we treat these metrics as counters, we need to fetch all values
// in the interval to find breaks in the timeseries' monotonicity.
// I.e. if a counter resets, we want to ignore that reset.
var matrixValue matrix
if isCounter {
matrixValue = ev . evalMatrix ( args [ 0 ] )
} else {
matrixValue = ev . evalMatrixBounds ( args [ 0 ] )
}
matrixValue := ev . evalMatrix ( ms )
for _ , samples := range matrixValue {
// No sense in trying to compute a delta without at least two points. Drop
// No sense in trying to compute a rate without at least two points. Drop
// this vector element.
if len ( samples . Values ) < 2 {
continue
}
var (
counterCorrection model . SampleValue
lastValue model . SampleValue
@ -79,22 +75,48 @@ func funcDelta(ev *evaluator, args Expressions) model.Value {
}
resultValue := lastValue - samples . Values [ 0 ] . Value + counterCorrection
targetInterval := args [ 0 ] . ( * MatrixSelector ) . Range
sampledInterval := samples . Values [ len ( samples . Values ) - 1 ] . Timestamp . Sub ( samples . Values [ 0 ] . Timestamp )
if sampledInterval == 0 {
// Only found one sample. Cannot compute a rate from this.
continue
// Duration between first/last samples and boundary of range.
durationToStart := samples . Values [ 0 ] . Timestamp . Sub ( rangeStart ) . Seconds ( )
durationToEnd := rangeEnd . Sub ( samples . Values [ len ( samples . Values ) - 1 ] . Timestamp ) . Seconds ( )
sampledInterval := samples . Values [ len ( samples . Values ) - 1 ] . Timestamp . Sub ( samples . Values [ 0 ] . Timestamp ) . Seconds ( )
averageDurationBetweenSamples := sampledInterval / float64 ( len ( samples . Values ) - 1 )
if isCounter && resultValue > 0 && samples . Values [ 0 ] . Value >= 0 {
// Counters cannot be negative. If we have any slope at
// all (i.e. resultValue 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.
durationToZero := sampledInterval * float64 ( samples . Values [ 0 ] . Value / resultValue )
if durationToZero < durationToStart {
durationToStart = durationToZero
}
}
// 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
}
resultValue = resultValue * model . SampleValue ( extrapolateToInterval / sampledInterval )
if isRate {
resultValue = resultValue / model . SampleValue ( ms . Range . Seconds ( ) )
}
// Correct for differences in target vs. actual delta interval.
//
// Above, we didn't actually calculate the delta for the specified target
// interval, but for an interval between the first and last found samples
// under the target interval, which will usually have less time between
// them. Depending on how many samples are found under a target interval,
// the delta results are distorted and temporal aliasing occurs (ugly
// bumps). This effect is corrected for below.
intervalCorrection := model . SampleValue ( targetInterval ) / model . SampleValue ( sampledInterval )
resultValue *= intervalCorrection
resultSample := & sample {
Metric : samples . Metric ,
@ -107,25 +129,19 @@ func funcDelta(ev *evaluator, args Expressions) model.Value {
return resultVector
}
// === delta(matrix model.ValMatrix) Vector ===
func funcDelta ( ev * evaluator , args Expressions ) model . Value {
return extrapolatedRate ( ev , args [ 0 ] , false , false )
}
// === rate(node model.ValMatrix) Vector ===
func funcRate ( ev * evaluator , args Expressions ) model . Value {
args = append ( args , & NumberLiteral { 1 } )
vector := funcDelta ( ev , args ) . ( vector )
// TODO: could be other type of model.ValMatrix in the future (right now, only
// MatrixSelector exists). Find a better way of getting the duration of a
// matrix, such as looking at the samples themselves.
interval := args [ 0 ] . ( * MatrixSelector ) . Range
for i := range vector {
vector [ i ] . Value /= model . SampleValue ( interval / time . Second )
}
return vector
return extrapolatedRate ( ev , args [ 0 ] , true , true )
}
// === increase(node model.ValMatrix) Vector ===
func funcIncrease ( ev * evaluator , args Expressions ) model . Value {
args = append ( args , & NumberLiteral { 1 } )
return funcDelta ( ev , args ) . ( vector )
return extrapolatedRate ( ev , args [ 0 ] , true , false )
}
// === irate(node model.ValMatrix) Vector ===