From 1e297952143d83c05244986b0b9c7150f45741ab Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Tue, 4 Apr 2023 09:09:56 -0700 Subject: [PATCH 01/29] add logging; total rewrite of extendedRate (tests don't pass) --- go.mod | 1 + promql/functions.go | 131 +++++++++++++++++++++++++++++++++----------- 2 files changed, 99 insertions(+), 33 deletions(-) diff --git a/go.mod b/go.mod index cf9e9dbb3d0..3fb30fbc0c2 100644 --- a/go.mod +++ b/go.mod @@ -54,6 +54,7 @@ require ( github.com/prometheus/exporter-toolkit v0.7.1 github.com/scaleway/scaleway-sdk-go v1.0.0-beta.9 github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 + github.com/sirupsen/logrus v1.8.1 // indirect github.com/stretchr/testify v1.7.1 go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.31.0 go.opentelemetry.io/otel v1.6.1 diff --git a/promql/functions.go b/promql/functions.go index 6c47e59e836..037508eaf12 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -14,7 +14,9 @@ package promql import ( + "bytes" "fmt" + "log" "math" "os" "sort" @@ -132,8 +134,36 @@ func extrapolatedRate(vals []parser.Value, args parser.Expressions, enh *EvalNod }) } +// Infers the scrape interval in msec by taking the median interval among the first 25. +// By using the median, we avoid the highs (late or missed scrape) and the lows +// (where a late scrape caused the next to be too close). +func inferScrapeInterval(points []Point) int64 { + if len(points) == 0 { + panic(errors.Errorf("no points to work on")) + } + + // Compute up to 25 time intervals and store them in intervalArray. + var intervalArray [25]int64 + var i int + for i = 0; i < (len(points)-1) && i < len(intervalArray); i++ { + intervalArray[i] = points[i+1].T - points[i].T + } + + // Slice and sort the intervals (may be fewer than 25, if points was shorter than 26). + intervals := intervalArray[:i] + sort.Slice(intervals, func(i0, i1 int) bool { return intervals[i0] < intervals[i1] }) + + // Return the median interval. + return intervals[len(intervals)/2] +} + +const scrapeIntervalMarginPercent float64 = 0.40 +const elide_samples_after int = 10 + //** This function was apparently copied from extrapolatedRate. -//** Each Point has its own msec timestamp stored in T. And the EvalNodeHelper has the evaluation timestamp in Ts. +//** Each Point has its own msec timestamp stored in T. And the EvalNodeHelper has the evaluation msec timestamp in Ts. +//** Note the caller of this method has arranged to have at most 1 extra data point at the front before the range, +//** taken from the lookbackInterval. // extendedRate is a utility function for xrate/xincrease/xdelta. // It calculates the rate (allowing for counter resets if isCounter is true), @@ -143,61 +173,96 @@ func extendedRate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHel ms := args[0].(*parser.MatrixSelector) vs := ms.VectorSelector.(*parser.VectorSelector) - samples := vals[0].(Matrix)[0] rangeStart := enh.Ts - durationMilliseconds(ms.Range+vs.Offset) rangeEnd := enh.Ts - durationMilliseconds(vs.Offset) + samples := vals[0].(Matrix)[0] points := samples.Points + //** Cannot compute rate with 0 or 1 data points. if len(points) < 2 { return enh.Out } - sampledRange := float64(points[len(points)-1].T - points[0].T) - averageInterval := sampledRange / float64(len(points)-1) //** msec; if any samples are missing, this will be higher than the scrape interval - - firstPoint := 0 - // If the point before the range is too far from rangeStart, drop it. - if float64(rangeStart-points[0].T) > averageInterval { //** There's 0 slop here, so we could drop the point even if it was scraped 1 msec early - //** Repeating the above check for 0 or 1 data points, with +1. I'm pretty sure these checks could be done just once. - if len(points) < 3 { + + scrapeInterval := float64(inferScrapeInterval(points)) //** milliseconds + scrapeIntervalMargin := int64(scrapeInterval * float64(scrapeIntervalMarginPercent)) + + log.Printf("extendedRate: isCounter: %t, isRate: %t, scrapeInterval: %.1f", isCounter, isRate, scrapeInterval/1000.0) + log.Printf("extendedRate: enh range: %.3f..%.3f (offset %.3f)", float64(rangeStart)/1000.0, float64(rangeEnd)/1000.0, float64(vs.Offset)/1000.0) + + buffer := new(bytes.Buffer) + for i, point := range points { + if i == elide_samples_after && len(points)-1 > elide_samples_after { + fmt.Fprintf(buffer, "...") + } else if i > elide_samples_after && len(points)-i > elide_samples_after { + continue + } else { + if i > 0 { + fmt.Fprintf(buffer, ", ") + } + fmt.Fprintf(buffer, "[%d,%.1f]", point.T/1000, point.V) + } + } + log.Println("extendedRate: samples:", buffer.String()) + + firstPoint := 0 //** Assume point 0 is in range +/-. + + //** Handle the more common case where point 0 is actually before range +/-. + if points[0].T < rangeStart-scrapeIntervalMargin { + firstPoint = 1 //** Skip point 0 + + //** Cannot compute rate with 0 or 1 data points. + if len(points) < 2 { return enh.Out } - firstPoint = 1 - sampledRange = float64(points[len(points)-1].T - points[1].T) //** repeating above code " - averageInterval = sampledRange / float64(len(points)-2) //** repeating above code " } - counterCorrection := float64(0.0) - lastValue := float64(0.0) + resultValue := points[len(points)-1].V - points[firstPoint].V if isCounter { //** isCounter means we were called from rate or increase or delta... which means you can't use those for gauges? //** Here, we can handle the initial start from "null" (no previous data point) //** if first point is near rangeStart, that means there was no earlier data point, which means we probably just started. - //** counterCorrection = points[firstPoint].V - //** (unless maybe the counterCorrection would be huge compared to the remaining deltas...in which case it might be a missed scrape) - for i := firstPoint; i < len(points); i++ { - sample := points[i] - if sample.V < lastValue { //** Handle when the counter steps backwards due to process restart - counterCorrection += lastValue //** Accumulate the sum of backward steps + //** counterWrapCorrection = points[0].V + //** (unless maybe the counterWrapCorrection would be huge compared to the remaining deltas...in which case it might be a missed scrape) + lastValue := float64(0.0) + if firstPoint == 0 { //** We have no preceding point, so we assume pod just started. + resultValue += points[0].V //** The 0 Fix! Reverse the subtraction above. + } else if firstPoint == 1 { + lastValue = points[0].V //** We have a preceding point, so start the lastValue there. + } + + for _, point := range points[firstPoint:] { + step := point.V - lastValue + if step < 0 { //** Counter stepped backwards, which must be due to process restart + resultValue += -step //** Accumulate the sum of backward steps } - lastValue = sample.V + lastValue = point.V } } - resultValue := points[len(points)-1].V - points[firstPoint].V + counterCorrection //** last.V - first.V + backward correction - - // Duration between last sample and boundary of range. - durationToEnd := float64(rangeEnd - points[len(points)-1].T) - // If the points cover the whole range (i.e. they start just before the - // range start and end just before the range end) adjust the value from - // the sampled range to the requested range. - if points[firstPoint].T <= rangeStart && durationToEnd < averageInterval { - adjustToRange := float64(durationMilliseconds(ms.Range)) - resultValue = resultValue * (adjustToRange / sampledRange) + // If the first or last sampled point is outside the range boundaries +/-, assume we are missing a sample at + // that edge and extrapolate from the sampled range to the requested range. + extrapolate := false + sampledStart := points[firstPoint].T + sampledEnd := points[len(points)-1].T + if int64(math.Abs(float64(rangeStart-sampledStart))) < scrapeIntervalMargin { + sampledStart = rangeStart //** snap + } else { + extrapolate = true + } + if int64(math.Abs(float64(rangeEnd-sampledEnd))) < scrapeIntervalMargin { + sampledEnd = rangeEnd //** snap + } else { + extrapolate = true + } + if extrapolate { + sampledRange := sampledEnd - sampledStart + requestedRange := durationMilliseconds(ms.Range) + resultValue *= (float64(requestedRange) / float64(sampledRange)) } if isRate { - resultValue = resultValue / ms.Range.Seconds() + resultValue /= ms.Range.Seconds() } return append(enh.Out, Sample{ From 166b442ecf48e5101a27c2a4f0fb7307e7b73497 Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Tue, 4 Apr 2023 09:28:04 -0700 Subject: [PATCH 02/29] undo total rewrite of extendedRate (tests pass) --- promql/functions.go | 72 ++++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 43 deletions(-) diff --git a/promql/functions.go b/promql/functions.go index 037508eaf12..d253a0e0784 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -184,10 +184,10 @@ func extendedRate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHel return enh.Out } - scrapeInterval := float64(inferScrapeInterval(points)) //** milliseconds - scrapeIntervalMargin := int64(scrapeInterval * float64(scrapeIntervalMarginPercent)) + scrapeInterval := inferScrapeInterval(points) //** milliseconds + scrapeIntervalMargin := int64(float64(scrapeInterval) * float64(scrapeIntervalMarginPercent)) - log.Printf("extendedRate: isCounter: %t, isRate: %t, scrapeInterval: %.1f", isCounter, isRate, scrapeInterval/1000.0) + log.Printf("extendedRate: isCounter: %t, isRate: %t, scrapeInterval: %.1f", isCounter, isRate, float64(scrapeInterval)/1000.0) log.Printf("extendedRate: enh range: %.3f..%.3f (offset %.3f)", float64(rangeStart)/1000.0, float64(rangeEnd)/1000.0, float64(vs.Offset)/1000.0) buffer := new(bytes.Buffer) @@ -205,11 +205,10 @@ func extendedRate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHel } log.Println("extendedRate: samples:", buffer.String()) - firstPoint := 0 //** Assume point 0 is in range +/-. - - //** Handle the more common case where point 0 is actually before range +/-. - if points[0].T < rangeStart-scrapeIntervalMargin { - firstPoint = 1 //** Skip point 0 + //** If the point before the range is too far before rangeStart, skip over it. + firstPoint := 0 + if (rangeStart-points[0].T) > scrapeInterval + scrapeIntervalMargin { + firstPoint = 1 //** Cannot compute rate with 0 or 1 data points. if len(points) < 2 { @@ -217,48 +216,35 @@ func extendedRate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHel } } - resultValue := points[len(points)-1].V - points[firstPoint].V + + counterCorrection := float64(0.0) + lastValue := float64(0.0) if isCounter { //** isCounter means we were called from rate or increase or delta... which means you can't use those for gauges? //** Here, we can handle the initial start from "null" (no previous data point) //** if first point is near rangeStart, that means there was no earlier data point, which means we probably just started. - //** counterWrapCorrection = points[0].V - //** (unless maybe the counterWrapCorrection would be huge compared to the remaining deltas...in which case it might be a missed scrape) - lastValue := float64(0.0) - if firstPoint == 0 { //** We have no preceding point, so we assume pod just started. - resultValue += points[0].V //** The 0 Fix! Reverse the subtraction above. - } else if firstPoint == 1 { - lastValue = points[0].V //** We have a preceding point, so start the lastValue there. - } - - for _, point := range points[firstPoint:] { - step := point.V - lastValue - if step < 0 { //** Counter stepped backwards, which must be due to process restart - resultValue += -step //** Accumulate the sum of backward steps + //** counterCorrection = points[firstPoint].V + //** (unless maybe the counterCorrection would be huge compared to the remaining deltas...in which case it might be a missed scrape) + for i := firstPoint; i < len(points); i++ { + sample := points[i] + if sample.V < lastValue { //** Handle when the counter steps backwards due to process restart + counterCorrection += lastValue //** Accumulate the sum of backward steps } - lastValue = point.V + lastValue = sample.V } } - - // If the first or last sampled point is outside the range boundaries +/-, assume we are missing a sample at - // that edge and extrapolate from the sampled range to the requested range. - extrapolate := false - sampledStart := points[firstPoint].T - sampledEnd := points[len(points)-1].T - if int64(math.Abs(float64(rangeStart-sampledStart))) < scrapeIntervalMargin { - sampledStart = rangeStart //** snap - } else { - extrapolate = true - } - if int64(math.Abs(float64(rangeEnd-sampledEnd))) < scrapeIntervalMargin { - sampledEnd = rangeEnd //** snap - } else { - extrapolate = true - } - if extrapolate { - sampledRange := sampledEnd - sampledStart - requestedRange := durationMilliseconds(ms.Range) - resultValue *= (float64(requestedRange) / float64(sampledRange)) + resultValue := points[len(points)-1].V - points[firstPoint].V + counterCorrection //** last.V - first.V + backward correction + + // Duration between last sample and boundary of range. + durationToEnd := rangeEnd - points[len(points)-1].T + + // If the points cover the whole range (i.e. they start just before the + // range start and end just before the range end) adjust the value from + // the sampled range to the requested range. + if points[firstPoint].T <= rangeStart && durationToEnd < scrapeInterval + scrapeIntervalMargin { + sampledRange := float64(points[len(points)-1].T - points[firstPoint].T) + adjustToRange := float64(durationMilliseconds(ms.Range)) + resultValue *= (adjustToRange / sampledRange) } if isRate { From 43f94a303ec62fe7aa2b598ffa6bac83f42ff5e6 Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Mon, 10 Apr 2023 10:26:08 -0700 Subject: [PATCH 03/29] 0 counter fix on startup (commented out) --- promql/functions.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/promql/functions.go b/promql/functions.go index d253a0e0784..91b276085b1 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -216,15 +216,17 @@ func extendedRate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHel } } - counterCorrection := float64(0.0) - lastValue := float64(0.0) - if isCounter { //** isCounter means we were called from rate or increase or delta... which means you can't use those for gauges? + if isCounter { //** isCounter means we were called from xrate or xincrease or xdelta... which means you can't use those for gauges. //** Here, we can handle the initial start from "null" (no previous data point) //** if first point is near rangeStart, that means there was no earlier data point, which means we probably just started. //** counterCorrection = points[firstPoint].V //** (unless maybe the counterCorrection would be huge compared to the remaining deltas...in which case it might be a missed scrape) + lastValue := float64(0.0) + if firstPoint == 0 { //** We have no preceding point, so we assume pod just started. + //counterCorrection += points[0].V //** The 0 Fix: count this first step. + } for i := firstPoint; i < len(points); i++ { sample := points[i] if sample.V < lastValue { //** Handle when the counter steps backwards due to process restart @@ -243,8 +245,8 @@ func extendedRate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHel // the sampled range to the requested range. if points[firstPoint].T <= rangeStart && durationToEnd < scrapeInterval + scrapeIntervalMargin { sampledRange := float64(points[len(points)-1].T - points[firstPoint].T) - adjustToRange := float64(durationMilliseconds(ms.Range)) - resultValue *= (adjustToRange / sampledRange) + requestedRange := float64(durationMilliseconds(ms.Range)) + resultValue *= (requestedRange / sampledRange) } if isRate { From 762f5f2bf396538b75a3466264c973ddd8ff3d2f Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Mon, 10 Apr 2023 10:37:05 -0700 Subject: [PATCH 04/29] add xrate0 and xincrease0 --- promql/functions.go | 104 +++++++++++++++++++++++++++++++++++++ promql/parser/functions.go | 12 +++++ 2 files changed, 116 insertions(+) diff --git a/promql/functions.go b/promql/functions.go index 91b276085b1..8f809f6595b 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -258,6 +258,98 @@ func extendedRate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHel }) } +// extendedRate0 is a utility function for xrate0/xincrease0. +// It calculates the rate (allowing for counter resets and including the startup value from 0), +// taking into account the last sample before the range start, and returns +// the result as either per-second (if isRate is true) or overall. +func extendedRate0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper, isRate bool) Vector { + ms := args[0].(*parser.MatrixSelector) + vs := ms.VectorSelector.(*parser.VectorSelector) + + rangeStart := enh.Ts - durationMilliseconds(ms.Range+vs.Offset) + rangeEnd := enh.Ts - durationMilliseconds(vs.Offset) + + samples := vals[0].(Matrix)[0] + points := samples.Points + + //** Cannot compute rate with 0 or 1 data points. + if len(points) < 2 { + return enh.Out + } + + scrapeInterval := inferScrapeInterval(points) //** milliseconds + scrapeIntervalMargin := int64(float64(scrapeInterval) * float64(scrapeIntervalMarginPercent)) + + log.Printf("extendedRate: isRate: %t, scrapeInterval: %.1f", isRate, float64(scrapeInterval)/1000.0) + log.Printf("extendedRate: enh range: %.3f..%.3f (offset %.3f)", float64(rangeStart)/1000.0, float64(rangeEnd)/1000.0, float64(vs.Offset)/1000.0) + + buffer := new(bytes.Buffer) + for i, point := range points { + if i == elide_samples_after && len(points)-1 > elide_samples_after { + fmt.Fprintf(buffer, "...") + } else if i > elide_samples_after && len(points)-i > elide_samples_after { + continue + } else { + if i > 0 { + fmt.Fprintf(buffer, ", ") + } + fmt.Fprintf(buffer, "[%d,%.1f]", point.T/1000, point.V) + } + } + log.Println("extendedRate: samples:", buffer.String()) + + //** If the point before the range is too far before rangeStart, skip over it. + firstPoint := 0 + if (rangeStart-points[0].T) > scrapeInterval + scrapeIntervalMargin { + firstPoint = 1 + + //** Cannot compute rate with 0 or 1 data points. + if len(points) < 2 { + return enh.Out + } + } + + counterCorrection := float64(0.0) + + //** Here, we can handle the initial start from "null" (no previous data point) + //** if first point is near rangeStart, that means there was no earlier data point, which means we probably just started. + //** counterCorrection = points[firstPoint].V + //** (unless maybe the counterCorrection would be huge compared to the remaining deltas...in which case it might be a missed scrape) + lastValue := float64(0.0) + if firstPoint == 0 { //** We have no preceding point, so we assume pod just started. + //counterCorrection += points[0].V //** The 0 Fix: count this first step. + } + for i := firstPoint; i < len(points); i++ { + sample := points[i] + if sample.V < lastValue { //** Handle when the counter steps backwards due to process restart + counterCorrection += lastValue //** Accumulate the sum of backward steps + } + lastValue = sample.V + } + + resultValue := points[len(points)-1].V - points[firstPoint].V + counterCorrection //** last.V - first.V + backward correction + + // Duration between last sample and boundary of range. + durationToEnd := rangeEnd - points[len(points)-1].T + + // If the points cover the whole range (i.e. they start just before the + // range start and end just before the range end) adjust the value from + // the sampled range to the requested range. + if points[firstPoint].T <= rangeStart && durationToEnd < scrapeInterval + scrapeIntervalMargin { + sampledRange := float64(points[len(points)-1].T - points[firstPoint].T) + requestedRange := float64(durationMilliseconds(ms.Range)) + resultValue *= (requestedRange / sampledRange) + } + + if isRate { + resultValue /= ms.Range.Seconds() + } + + return append(enh.Out, Sample{ + Point: Point{V: resultValue}, + }) +} + // === delta(Matrix parser.ValueTypeMatrix) Vector === func funcDelta(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector { return extrapolatedRate(vals, args, enh, false, false) @@ -283,11 +375,21 @@ func funcXrate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper return extendedRate(vals, args, enh, true, true) } +// === xrate0(node parser.ValueTypeMatrix) Vector === +func funcXrate0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector { + return extendedRate0(vals, args, enh, true) +} + // === xincrease(node parser.ValueTypeMatrix) Vector === func funcXincrease(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector { return extendedRate(vals, args, enh, true, false) } +// === xincrease0(node parser.ValueTypeMatrix) Vector === +func funcXincrease0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector { + return extendedRate0(vals, args, enh, false) +} + // === irate(node parser.ValueTypeMatrix) Vector === func funcIrate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector { return instantValue(vals, enh.Out, true) @@ -1278,7 +1380,9 @@ var FunctionCalls = map[string]FunctionCall{ "vector": funcVector, "xdelta": funcXdelta, "xincrease": funcXincrease, + "xincrease0": funcXincrease0, "xrate": funcXrate, + "xrate0": funcXrate0, "year": funcYear, } diff --git a/promql/parser/functions.go b/promql/parser/functions.go index d346d5c7b9f..5006fdc0afb 100644 --- a/promql/parser/functions.go +++ b/promql/parser/functions.go @@ -370,12 +370,24 @@ var Functions = map[string]*Function{ ReturnType: ValueTypeVector, ExtRange: true, }, + "xincrease0": { + Name: "xincrease0", + ArgTypes: []ValueType{ValueTypeMatrix}, + ReturnType: ValueTypeVector, + ExtRange: true, + }, "xrate": { Name: "xrate", ArgTypes: []ValueType{ValueTypeMatrix}, ReturnType: ValueTypeVector, ExtRange: true, }, + "xrate0": { + Name: "xrate0", + ArgTypes: []ValueType{ValueTypeMatrix}, + ReturnType: ValueTypeVector, + ExtRange: true, + }, "year": { Name: "year", ArgTypes: []ValueType{ValueTypeVector}, From 4b0cdd1dfc773bedef6c662f3e1856f75f01cd87 Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Mon, 10 Apr 2023 11:49:18 -0700 Subject: [PATCH 05/29] refactor extendedRate0 --- promql/functions.go | 98 ++++++++++++++++++++++----------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/promql/functions.go b/promql/functions.go index 8f809f6595b..0b34ca6a955 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -272,7 +272,7 @@ func extendedRate0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe samples := vals[0].(Matrix)[0] points := samples.Points - //** Cannot compute rate with 0 or 1 data points. + // Cannot compute rate with 0 or 1 data points. if len(points) < 2 { return enh.Out } @@ -280,65 +280,65 @@ func extendedRate0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe scrapeInterval := inferScrapeInterval(points) //** milliseconds scrapeIntervalMargin := int64(float64(scrapeInterval) * float64(scrapeIntervalMarginPercent)) - log.Printf("extendedRate: isRate: %t, scrapeInterval: %.1f", isRate, float64(scrapeInterval)/1000.0) - log.Printf("extendedRate: enh range: %.3f..%.3f (offset %.3f)", float64(rangeStart)/1000.0, float64(rangeEnd)/1000.0, float64(vs.Offset)/1000.0) + firstPoint := 1 // Assume normal case: point 0 is before the range +/-, so we can use it to measure increase. + lastValue := points[0].V // Prime the lastValue with the preceding value (before the range +/-). - buffer := new(bytes.Buffer) - for i, point := range points { - if i == elide_samples_after && len(points)-1 > elide_samples_after { - fmt.Fprintf(buffer, "...") - } else if i > elide_samples_after && len(points)-i > elide_samples_after { - continue - } else { - if i > 0 { - fmt.Fprintf(buffer, ", ") - } - fmt.Fprintf(buffer, "[%d,%.1f]", point.T/1000, point.V) - } + // The 0 Fix: Check for fresh pod start case where we have no point before the range +/-. + // Treat this as if we had a 0 just before the range. + if points[0].T >= rangeStart-scrapeIntervalMargin { + firstPoint = 0 + lastValue = 0.0 } - log.Println("extendedRate: samples:", buffer.String()) - //** If the point before the range is too far before rangeStart, skip over it. - firstPoint := 0 - if (rangeStart-points[0].T) > scrapeInterval + scrapeIntervalMargin { - firstPoint = 1 + // Debug logging + log.Printf("extendedRate0: isRate: %t, firstPoint: %d, scrapeInterval: %.1f", isRate, firstPoint, float64(scrapeInterval)/1000.0) + log.Printf("extendedRate0: enh range: %.3f..%.3f (offset %.3f)", float64(rangeStart)/1000.0, float64(rangeEnd)/1000.0, float64(vs.Offset)/1000.0) - //** Cannot compute rate with 0 or 1 data points. - if len(points) < 2 { - return enh.Out + { + buffer := new(bytes.Buffer) + for i, point := range points { + if i == elide_samples_after && len(points)-1 > elide_samples_after { + fmt.Fprintf(buffer, "...") + } else if i > elide_samples_after && len(points)-i > elide_samples_after { + continue + } else { + if i > 0 { + fmt.Fprintf(buffer, ", ") + } + fmt.Fprintf(buffer, "[%d,%.1f]", point.T/1000, point.V) + } } + log.Println("extendedRate0: samples:", buffer.String()) } - counterCorrection := float64(0.0) + // Simplest result: the difference beteween last point's value and lastValue. + resultValue := points[len(points)-1].V - lastValue - //** Here, we can handle the initial start from "null" (no previous data point) - //** if first point is near rangeStart, that means there was no earlier data point, which means we probably just started. - //** counterCorrection = points[firstPoint].V - //** (unless maybe the counterCorrection would be huge compared to the remaining deltas...in which case it might be a missed scrape) - lastValue := float64(0.0) - if firstPoint == 0 { //** We have no preceding point, so we assume pod just started. - //counterCorrection += points[0].V //** The 0 Fix: count this first step. - } - for i := firstPoint; i < len(points); i++ { - sample := points[i] - if sample.V < lastValue { //** Handle when the counter steps backwards due to process restart - counterCorrection += lastValue //** Accumulate the sum of backward steps + // Correct for any case where a process restart caused the increase to turn negative. + for _, point := range points[firstPoint:] { + step := point.V - lastValue + if step < 0 { + resultValue += -step } - lastValue = sample.V + lastValue = point.V } - resultValue := points[len(points)-1].V - points[firstPoint].V + counterCorrection //** last.V - first.V + backward correction - - // Duration between last sample and boundary of range. - durationToEnd := rangeEnd - points[len(points)-1].T - - // If the points cover the whole range (i.e. they start just before the - // range start and end just before the range end) adjust the value from - // the sampled range to the requested range. - if points[firstPoint].T <= rangeStart && durationToEnd < scrapeInterval + scrapeIntervalMargin { - sampledRange := float64(points[len(points)-1].T - points[firstPoint].T) - requestedRange := float64(durationMilliseconds(ms.Range)) - resultValue *= (requestedRange / sampledRange) + // If the first or last sampled point is outside the range boundaries +/-, assume we are missing a sample at + // that edge and extrapolate from the sampled range up to the requested range. + { + sampledStart := points[firstPoint].T + sampledEnd := points[len(points)-1].T + if int64(math.Abs(float64(rangeStart-sampledStart))) < scrapeIntervalMargin { + sampledStart = rangeStart // Close enough, so snap to start + } + if int64(math.Abs(float64(rangeEnd-sampledEnd))) < scrapeIntervalMargin { + sampledEnd = rangeEnd // Close enough, so snap to end + } + if sampledStart != rangeStart || sampledEnd != rangeEnd { + sampledRange := sampledEnd - sampledStart + requestedRange := durationMilliseconds(ms.Range) + resultValue *= (float64(requestedRange) / float64(sampledRange)) + } } if isRate { From 98caa6be0d131dbcfa79eced53797ae6dba40dc8 Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Mon, 10 Apr 2023 11:58:58 -0700 Subject: [PATCH 06/29] restore extendedRate to original+comments --- promql/functions.go | 63 ++++++++++++--------------------------------- 1 file changed, 17 insertions(+), 46 deletions(-) diff --git a/promql/functions.go b/promql/functions.go index 0b34ca6a955..4d5c0acca48 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -161,9 +161,7 @@ const scrapeIntervalMarginPercent float64 = 0.40 const elide_samples_after int = 10 //** This function was apparently copied from extrapolatedRate. -//** Each Point has its own msec timestamp stored in T. And the EvalNodeHelper has the evaluation msec timestamp in Ts. -//** Note the caller of this method has arranged to have at most 1 extra data point at the front before the range, -//** taken from the lookbackInterval. +//** Each Point has its own msec timestamp stored in T. And the EvalNodeHelper has the evaluation timestamp in Ts. // extendedRate is a utility function for xrate/xincrease/xdelta. // It calculates the rate (allowing for counter resets if isCounter is true), @@ -173,60 +171,34 @@ func extendedRate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHel ms := args[0].(*parser.MatrixSelector) vs := ms.VectorSelector.(*parser.VectorSelector) + samples := vals[0].(Matrix)[0] rangeStart := enh.Ts - durationMilliseconds(ms.Range+vs.Offset) rangeEnd := enh.Ts - durationMilliseconds(vs.Offset) - samples := vals[0].(Matrix)[0] points := samples.Points - //** Cannot compute rate with 0 or 1 data points. if len(points) < 2 { return enh.Out } + sampledRange := float64(points[len(points)-1].T - points[0].T) + averageInterval := sampledRange / float64(len(points)-1) //** msec; if any samples are missing, this will be higher than the scrape interval - scrapeInterval := inferScrapeInterval(points) //** milliseconds - scrapeIntervalMargin := int64(float64(scrapeInterval) * float64(scrapeIntervalMarginPercent)) - - log.Printf("extendedRate: isCounter: %t, isRate: %t, scrapeInterval: %.1f", isCounter, isRate, float64(scrapeInterval)/1000.0) - log.Printf("extendedRate: enh range: %.3f..%.3f (offset %.3f)", float64(rangeStart)/1000.0, float64(rangeEnd)/1000.0, float64(vs.Offset)/1000.0) - - buffer := new(bytes.Buffer) - for i, point := range points { - if i == elide_samples_after && len(points)-1 > elide_samples_after { - fmt.Fprintf(buffer, "...") - } else if i > elide_samples_after && len(points)-i > elide_samples_after { - continue - } else { - if i > 0 { - fmt.Fprintf(buffer, ", ") - } - fmt.Fprintf(buffer, "[%d,%.1f]", point.T/1000, point.V) - } - } - log.Println("extendedRate: samples:", buffer.String()) - - //** If the point before the range is too far before rangeStart, skip over it. firstPoint := 0 - if (rangeStart-points[0].T) > scrapeInterval + scrapeIntervalMargin { - firstPoint = 1 - - //** Cannot compute rate with 0 or 1 data points. - if len(points) < 2 { + // If the point before the range is too far from rangeStart, drop it. + if float64(rangeStart-points[0].T) > averageInterval { //** There's 0 slop here, so we could drop the point even if it was scraped 1 msec early + //** Repeating the above check for 0 or 1 data points, with +1. + if len(points) < 3 { //** This looks like a bug to me. 2 points is enough to measure an increase. -Colin return enh.Out } + firstPoint = 1 + sampledRange = float64(points[len(points)-1].T - points[1].T) //** repeating above code + averageInterval = sampledRange / float64(len(points)-2) //** repeating above code } counterCorrection := float64(0.0) + lastValue := float64(0.0) - if isCounter { //** isCounter means we were called from xrate or xincrease or xdelta... which means you can't use those for gauges. - //** Here, we can handle the initial start from "null" (no previous data point) - //** if first point is near rangeStart, that means there was no earlier data point, which means we probably just started. - //** counterCorrection = points[firstPoint].V - //** (unless maybe the counterCorrection would be huge compared to the remaining deltas...in which case it might be a missed scrape) - lastValue := float64(0.0) - if firstPoint == 0 { //** We have no preceding point, so we assume pod just started. - //counterCorrection += points[0].V //** The 0 Fix: count this first step. - } + if isCounter { //** called from rate or increase (not delta) for i := firstPoint; i < len(points); i++ { sample := points[i] if sample.V < lastValue { //** Handle when the counter steps backwards due to process restart @@ -238,15 +210,14 @@ func extendedRate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHel resultValue := points[len(points)-1].V - points[firstPoint].V + counterCorrection //** last.V - first.V + backward correction // Duration between last sample and boundary of range. - durationToEnd := rangeEnd - points[len(points)-1].T + durationToEnd := float64(rangeEnd - points[len(points)-1].T) // If the points cover the whole range (i.e. they start just before the // range start and end just before the range end) adjust the value from // the sampled range to the requested range. - if points[firstPoint].T <= rangeStart && durationToEnd < scrapeInterval + scrapeIntervalMargin { - sampledRange := float64(points[len(points)-1].T - points[firstPoint].T) - requestedRange := float64(durationMilliseconds(ms.Range)) - resultValue *= (requestedRange / sampledRange) + if points[firstPoint].T <= rangeStart && durationToEnd < averageInterval { + adjustToRange := float64(durationMilliseconds(ms.Range)) + resultValue *= (adjustToRange / sampledRange) } if isRate { From 88042616665530094c28710deb272a6e431e0bbf Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Mon, 10 Apr 2023 12:53:51 -0700 Subject: [PATCH 07/29] copy xrate, xincrease tests to cover xrate0, xincrease0 --- promql/testdata/functions.test | 89 ++++++++++++++++++++++++++++++++-- 1 file changed, 84 insertions(+), 5 deletions(-) diff --git a/promql/testdata/functions.test b/promql/testdata/functions.test index 8b59eb42f24..a7818a7b403 100644 --- a/promql/testdata/functions.test +++ b/promql/testdata/functions.test @@ -1,4 +1,4 @@ -# Comparison of rate vs xrate. +# Comparison of rate vs xrate vs xrate0. load 5s http_requests{path="/foo"} 1 1 1 2 2 2 2 2 3 3 3 @@ -18,6 +18,10 @@ eval instant at 25s xrate(http_requests[50s]) {path="/foo"} .02 {path="/bar"} .1 +eval instant at 25s xrate0(http_requests[50s]) + {path="/foo"} .08 + {path="/bar"} .24 + # 2. Eval 1 second earlier compared to (1). # * path="/foo" rate should be same or fractionally higher ("shorter" sample, same actual increase); # * path="/bar" rate should be same or fractionally lower (80% the increase, 80/96% range covered by sample). @@ -30,6 +34,10 @@ eval instant at 24s xrate(http_requests[50s]) {path="/foo"} .02 {path="/bar"} .08 +eval instant at 24s xrate0(http_requests[50s]) + {path="/foo"} .1 + {path="/bar"} .25 + # 3. Eval 1 second later compared to (1). # * path="/foo" rate should be same or fractionally lower ("longer" sample, same actual increase). # * path="/bar" rate should be same or fractionally lower ("longer" sample, same actual increase). @@ -42,6 +50,10 @@ eval instant at 26s xrate(http_requests[50s]) {path="/foo"} .02 {path="/bar"} .1 +eval instant at 26s xrate0(http_requests[50s]) + {path="/foo"} .07692307692307693 + {path="/bar"} 0.23076923076923075 + # # Timeseries starts before range, ends within range. @@ -56,6 +68,10 @@ eval instant at 75s xrate(http_requests[50s]) {path="/foo"} .02 {path="/bar"} .1 +eval instant at 75s xrate0(http_requests[50s]) + {path="/foo"} .12 + {path="/bar"} .44 + # 5. Eval 1s earlier compared to (4). # * path="/foo" rate should be same or fractionally lower ("longer" sample, same actual increase). # * path="/bar" rate should be same or fractionally lower ("longer" sample, same actual increase). @@ -69,6 +85,10 @@ eval instant at 74s xrate(http_requests[50s]) {path="/foo"} .02 {path="/bar"} .12 +eval instant at 74s xrate0(http_requests[50s]) + {path="/foo"} .038461538461538464 + {path="/bar"} .23076923076923075 + # 6. Eval 1s later compared to (4). Rate/increase (should be) fractionally smaller. # * path="/foo" rate should be same or fractionally higher ("shorter" sample, same actual increase); # * path="/bar" rate should be same or fractionally lower (80% the increase, 80/96% range covered by sample). @@ -81,6 +101,10 @@ eval instant at 76s xrate(http_requests[50s]) {path="/foo"} .02 {path="/bar"} .1 +eval instant at 76s xrate0(http_requests[50s]) + {path="/foo"} .125 + {path="/bar"} .45833333333333337 + # # Evaluation of 10 second rate every 10 seconds, not aligned with collection. # @@ -110,23 +134,43 @@ eval instant at 9s xrate(http_requests[10s]) {path="/foo"} 0 {path="/bar"} 0.1 +eval instant at 9s xrate0(http_requests[10s]) + {path="/foo"} 0.16666666666666669 + {path="/bar"} 0.33333333333333337 + eval instant at 19s xrate(http_requests[10s]) {path="/foo"} 0.1 {path="/bar"} 0.2 +eval instant at 19s xrate0(http_requests[10s]) + {path="/foo"} 0.16666666666666669 + {path="/bar"} 0.33333333333333337 + eval instant at 29s xrate(http_requests[10s]) {path="/foo"} 0 {path="/bar"} 0.2 +eval instant at 29s xrate0(http_requests[10s]) + {path="/foo"} 0 + {path="/bar"} 0.33333333333333337 + eval instant at 39s xrate(http_requests[10s]) {path="/foo"} 0 {path="/bar"} 0.2 +eval instant at 39s xrate0(http_requests[10s]) + {path="/foo"} 0 + {path="/bar"} 0.33333333333333337 + # XXX Sees the increase in path="/foo" between timestamps 35 and 40. eval instant at 49s xrate(http_requests[10s]) {path="/foo"} .1 {path="/bar"} 0.2 +eval instant at 49s xrate0(http_requests[10s]) + {path="/foo"} 0.16666666666666669 + {path="/bar"} 0.33333333333333337 + clear @@ -202,7 +246,7 @@ eval instant at 15m changes(x[15m]) clear -# Tests for increase()/xincrease()/xrate(). +# Tests for increase()/xincrease()/xincrease0()//xrate()/xrate0(). load 5s http_requests{path="/foo"} 0+10x10 http_requests{path="/bar"} 0+10x5 0+10x4 @@ -216,43 +260,75 @@ eval instant at 50s increase(http_requests[100s]) {path="/foo"} 100 {path="/bar"} 90 -# Tests for xincrease(). +# Tests for xincrease()/xincrease0(). eval instant at 50s xincrease(http_requests[50s]) {path="/foo"} 100 {path="/bar"} 90 +eval instant at 50s xincrease0(http_requests[50s]) + {path="/foo"} 100 + {path="/bar"} 90 + eval instant at 50s xincrease(http_requests[5s]) {path="/foo"} 10 {path="/bar"} 10 +eval instant at 50s xincrease0(http_requests[5s]) + {path="/foo"} 100 + {path="/bar"} 40 + eval instant at 50s xincrease(http_requests[3s]) {path="/foo"} 6 {path="/bar"} 6 +eval instant at 50s xincrease0(http_requests[3s]) + {path="/foo"} 60 + {path="/bar"} 24 + eval instant at 49s xincrease(http_requests[3s]) -# Tests for xrate(). +eval instant at 49s xincrease0(http_requests[3s]) + +# Tests for xrate()/xrate0(). eval instant at 50s xrate(http_requests[50s]) {path="/foo"} 2 {path="/bar"} 1.8 +eval instant at 50s xrate0(http_requests[50s]) + {path="/foo"} 2 + {path="/bar"} 1.8 + eval instant at 50s xrate(http_requests[100s]) {path="/foo"} 1 {path="/bar"} 0.9 +eval instant at 50s xrate0(http_requests[100s]) + {path="/foo"} 2 + {path="/bar"} 1.8 + eval instant at 50s xrate(http_requests[5s]) {path="/foo"} 2 {path="/bar"} 2 +eval instant at 50s xrate0(http_requests[5s]) + {path="/foo"} 20 + {path="/bar"} 8 + eval instant at 50s xrate(http_requests[3s]) {path="/foo"} 2 {path="/bar"} 2 +eval instant at 50s xrate0(http_requests[3s]) + {path="/foo"} 20 + {path="/bar"} 8 + eval instant at 49s xrate(http_requests[3s]) +eval instant at 49s xrate0(http_requests[3s]) + clear -# Test for increase()/xincrease with counter reset. +# Test for increase()/xincrease/xincrease0 with counter reset. # When the counter is reset, it always starts at 0. # So the sequence 3 2 (decreasing counter = reset) is interpreted the same as 3 0 1 2. # Prometheus assumes it missed the intermediate values 0 and 1. @@ -265,6 +341,9 @@ eval instant at 30m increase(http_requests[30m]) eval instant at 30m xincrease(http_requests[30m]) {path="/foo"} 7 +eval instant at 30m xincrease0(http_requests[30m]) + {path="/foo"} 5 + clear # Tests for rate(). From f0e3a33c54c65efe8152b53d513d547457dddf73 Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Mon, 10 Apr 2023 13:39:47 -0700 Subject: [PATCH 08/29] gofmt and lint --- promql/functions.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/promql/functions.go b/promql/functions.go index 4d5c0acca48..53b77eb82a5 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -158,7 +158,7 @@ func inferScrapeInterval(points []Point) int64 { } const scrapeIntervalMarginPercent float64 = 0.40 -const elide_samples_after int = 10 +const elideSamplesAfter int = 10 //** This function was apparently copied from extrapolatedRate. //** Each Point has its own msec timestamp stored in T. And the EvalNodeHelper has the evaluation timestamp in Ts. @@ -251,7 +251,7 @@ func extendedRate0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe scrapeInterval := inferScrapeInterval(points) //** milliseconds scrapeIntervalMargin := int64(float64(scrapeInterval) * float64(scrapeIntervalMarginPercent)) - firstPoint := 1 // Assume normal case: point 0 is before the range +/-, so we can use it to measure increase. + firstPoint := 1 // Assume normal case: point 0 is before the range +/-, so we can use it to measure increase. lastValue := points[0].V // Prime the lastValue with the preceding value (before the range +/-). // The 0 Fix: Check for fresh pod start case where we have no point before the range +/-. @@ -268,9 +268,9 @@ func extendedRate0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe { buffer := new(bytes.Buffer) for i, point := range points { - if i == elide_samples_after && len(points)-1 > elide_samples_after { + if i == elideSamplesAfter && len(points)-1 > elideSamplesAfter { fmt.Fprintf(buffer, "...") - } else if i > elide_samples_after && len(points)-i > elide_samples_after { + } else if i > elideSamplesAfter && len(points)-i > elideSamplesAfter { continue } else { if i > 0 { From 5e734faa9c5537ee2c2e41fcb9369a102aeb9e5c Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Mon, 10 Apr 2023 14:06:58 -0700 Subject: [PATCH 09/29] move extendedRate up to simplify diff --- promql/functions.go | 52 ++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/promql/functions.go b/promql/functions.go index 53b77eb82a5..dfbe9258eab 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -134,32 +134,6 @@ func extrapolatedRate(vals []parser.Value, args parser.Expressions, enh *EvalNod }) } -// Infers the scrape interval in msec by taking the median interval among the first 25. -// By using the median, we avoid the highs (late or missed scrape) and the lows -// (where a late scrape caused the next to be too close). -func inferScrapeInterval(points []Point) int64 { - if len(points) == 0 { - panic(errors.Errorf("no points to work on")) - } - - // Compute up to 25 time intervals and store them in intervalArray. - var intervalArray [25]int64 - var i int - for i = 0; i < (len(points)-1) && i < len(intervalArray); i++ { - intervalArray[i] = points[i+1].T - points[i].T - } - - // Slice and sort the intervals (may be fewer than 25, if points was shorter than 26). - intervals := intervalArray[:i] - sort.Slice(intervals, func(i0, i1 int) bool { return intervals[i0] < intervals[i1] }) - - // Return the median interval. - return intervals[len(intervals)/2] -} - -const scrapeIntervalMarginPercent float64 = 0.40 -const elideSamplesAfter int = 10 - //** This function was apparently copied from extrapolatedRate. //** Each Point has its own msec timestamp stored in T. And the EvalNodeHelper has the evaluation timestamp in Ts. @@ -229,6 +203,32 @@ func extendedRate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHel }) } +// Infers the scrape interval in msec by taking the median interval among the first 25. +// By using the median, we avoid the highs (late or missed scrape) and the lows +// (where a late scrape caused the next to be too close). +func inferScrapeInterval(points []Point) int64 { + if len(points) == 0 { + panic(errors.Errorf("no points to work on")) + } + + // Compute up to 25 time intervals and store them in intervalArray. + var intervalArray [25]int64 + var i int + for i = 0; i < (len(points)-1) && i < len(intervalArray); i++ { + intervalArray[i] = points[i+1].T - points[i].T + } + + // Slice and sort the intervals (may be fewer than 25, if points was shorter than 26). + intervals := intervalArray[:i] + sort.Slice(intervals, func(i0, i1 int) bool { return intervals[i0] < intervals[i1] }) + + // Return the median interval. + return intervals[len(intervals)/2] +} + +const scrapeIntervalMarginPercent float64 = 0.40 +const elideSamplesAfter int = 10 + // extendedRate0 is a utility function for xrate0/xincrease0. // It calculates the rate (allowing for counter resets and including the startup value from 0), // taking into account the last sample before the range start, and returns From d0c6d45bb9a8a660769ae680e9b09bb2ad236430 Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Mon, 10 Apr 2023 22:37:23 -0700 Subject: [PATCH 10/29] add Msec suffix to locals; add extra logging --- promql/functions.go | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/promql/functions.go b/promql/functions.go index dfbe9258eab..05351a09156 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -237,8 +237,8 @@ func extendedRate0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe ms := args[0].(*parser.MatrixSelector) vs := ms.VectorSelector.(*parser.VectorSelector) - rangeStart := enh.Ts - durationMilliseconds(ms.Range+vs.Offset) - rangeEnd := enh.Ts - durationMilliseconds(vs.Offset) + rangeStartMsec := enh.Ts - durationMilliseconds(ms.Range+vs.Offset) + rangeEndMsec := enh.Ts - durationMilliseconds(vs.Offset) samples := vals[0].(Matrix)[0] points := samples.Points @@ -248,22 +248,25 @@ func extendedRate0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe return enh.Out } - scrapeInterval := inferScrapeInterval(points) //** milliseconds - scrapeIntervalMargin := int64(float64(scrapeInterval) * float64(scrapeIntervalMarginPercent)) + scrapeIntervalMsec := inferScrapeInterval(points) //** milliseconds + scrapeIntervalMarginMsec := int64(float64(scrapeIntervalMsec) * float64(scrapeIntervalMarginPercent)) firstPoint := 1 // Assume normal case: point 0 is before the range +/-, so we can use it to measure increase. lastValue := points[0].V // Prime the lastValue with the preceding value (before the range +/-). // The 0 Fix: Check for fresh pod start case where we have no point before the range +/-. // Treat this as if we had a 0 just before the range. - if points[0].T >= rangeStart-scrapeIntervalMargin { + if points[0].T >= rangeStartMsec-scrapeIntervalMarginMsec { firstPoint = 0 lastValue = 0.0 } + // Simplest result: the difference beteween last point's value and lastValue. + resultValue := points[len(points)-1].V - lastValue + // Debug logging - log.Printf("extendedRate0: isRate: %t, firstPoint: %d, scrapeInterval: %.1f", isRate, firstPoint, float64(scrapeInterval)/1000.0) - log.Printf("extendedRate0: enh range: %.3f..%.3f (offset %.3f)", float64(rangeStart)/1000.0, float64(rangeEnd)/1000.0, float64(vs.Offset)/1000.0) + log.Printf("extendedRate0: isRate: %t, firstPoint: %d, scrapeIntervalMsec: %.1f", isRate, firstPoint, float64(scrapeIntervalMsec)/1000.0) + log.Printf("extendedRate0: enh range: %.3f..%.3f (offset %.3f); starting result: %.1f", float64(rangeStartMsec)/1000.0, float64(rangeEndMsec)/1000.0, float64(vs.Offset)/1000.0, resultValue) { buffer := new(bytes.Buffer) @@ -282,14 +285,12 @@ func extendedRate0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe log.Println("extendedRate0: samples:", buffer.String()) } - // Simplest result: the difference beteween last point's value and lastValue. - resultValue := points[len(points)-1].V - lastValue - // Correct for any case where a process restart caused the increase to turn negative. for _, point := range points[firstPoint:] { step := point.V - lastValue if step < 0 { resultValue += -step + log.Printf("extendedRate0: correcting for step: %.1f", step) } lastValue = point.V } @@ -297,18 +298,19 @@ func extendedRate0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe // If the first or last sampled point is outside the range boundaries +/-, assume we are missing a sample at // that edge and extrapolate from the sampled range up to the requested range. { - sampledStart := points[firstPoint].T - sampledEnd := points[len(points)-1].T - if int64(math.Abs(float64(rangeStart-sampledStart))) < scrapeIntervalMargin { - sampledStart = rangeStart // Close enough, so snap to start + sampledStartMsec := points[firstPoint].T + sampledEndMsec := points[len(points)-1].T + if int64(math.Abs(float64(rangeStartMsec-sampledStartMsec))) < scrapeIntervalMarginMsec { + sampledStartMsec = rangeStartMsec // Close enough, so snap to start } - if int64(math.Abs(float64(rangeEnd-sampledEnd))) < scrapeIntervalMargin { - sampledEnd = rangeEnd // Close enough, so snap to end + if int64(math.Abs(float64(rangeEndMsec-sampledEndMsec))) < scrapeIntervalMarginMsec { + sampledEndMsec = rangeEndMsec // Close enough, so snap to end } - if sampledStart != rangeStart || sampledEnd != rangeEnd { - sampledRange := sampledEnd - sampledStart - requestedRange := durationMilliseconds(ms.Range) - resultValue *= (float64(requestedRange) / float64(sampledRange)) + if sampledStartMsec != rangeStartMsec || sampledEndMsec != rangeEndMsec { + sampledRangeMsec := sampledEndMsec - sampledStartMsec + requestedRangeMsec := durationMilliseconds(ms.Range) + log.Printf("extendedRate0: scaling result: %.1f by %d / %d", resultValue, requestedRangeMsec, sampledRangeMsec) + resultValue *= (float64(requestedRangeMsec) / float64(sampledRangeMsec)) } } @@ -316,6 +318,8 @@ func extendedRate0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe resultValue /= ms.Range.Seconds() } + log.Printf("extendedRate0: returning result: %.1f", resultValue) + return append(enh.Out, Sample{ Point: Point{V: resultValue}, }) From 7f1efda748cfbe7b18ff46995a5d46d0790b5fbc Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Mon, 10 Apr 2023 23:21:01 -0700 Subject: [PATCH 11/29] fix extendedRate0 starting resultValue --- promql/functions.go | 4 ++-- promql/testdata/functions.test | 40 +++++++++++++++++----------------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/promql/functions.go b/promql/functions.go index 05351a09156..775065fbf22 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -261,8 +261,8 @@ func extendedRate0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe lastValue = 0.0 } - // Simplest result: the difference beteween last point's value and lastValue. - resultValue := points[len(points)-1].V - lastValue + // Simplest result: the difference beteween first and last values. + resultValue := points[len(points)-1].V - points[0].V // Debug logging log.Printf("extendedRate0: isRate: %t, firstPoint: %d, scrapeIntervalMsec: %.1f", isRate, firstPoint, float64(scrapeIntervalMsec)/1000.0) diff --git a/promql/testdata/functions.test b/promql/testdata/functions.test index a7818a7b403..9410725e0cf 100644 --- a/promql/testdata/functions.test +++ b/promql/testdata/functions.test @@ -19,8 +19,8 @@ eval instant at 25s xrate(http_requests[50s]) {path="/bar"} .1 eval instant at 25s xrate0(http_requests[50s]) - {path="/foo"} .08 - {path="/bar"} .24 + {path="/foo"} .04 + {path="/bar"} .2 # 2. Eval 1 second earlier compared to (1). # * path="/foo" rate should be same or fractionally higher ("shorter" sample, same actual increase); @@ -35,8 +35,8 @@ eval instant at 24s xrate(http_requests[50s]) {path="/bar"} .08 eval instant at 24s xrate0(http_requests[50s]) - {path="/foo"} .1 - {path="/bar"} .25 + {path="/foo"} .05 + {path="/bar"} .2 # 3. Eval 1 second later compared to (1). # * path="/foo" rate should be same or fractionally lower ("longer" sample, same actual increase). @@ -51,8 +51,8 @@ eval instant at 26s xrate(http_requests[50s]) {path="/bar"} .1 eval instant at 26s xrate0(http_requests[50s]) - {path="/foo"} .07692307692307693 - {path="/bar"} 0.23076923076923075 + {path="/foo"} 0.038461538461538464 + {path="/bar"} 0.1923076923076923 # @@ -69,8 +69,8 @@ eval instant at 75s xrate(http_requests[50s]) {path="/bar"} .1 eval instant at 75s xrate0(http_requests[50s]) - {path="/foo"} .12 - {path="/bar"} .44 + {path="/foo"} .04 + {path="/bar"} .2 # 5. Eval 1s earlier compared to (4). # * path="/foo" rate should be same or fractionally lower ("longer" sample, same actual increase). @@ -102,8 +102,8 @@ eval instant at 76s xrate(http_requests[50s]) {path="/bar"} .1 eval instant at 76s xrate0(http_requests[50s]) - {path="/foo"} .125 - {path="/bar"} .45833333333333337 + {path="/foo"} 0.04166666666666667 + {path="/bar"} 0.20833333333333337 # # Evaluation of 10 second rate every 10 seconds, not aligned with collection. @@ -135,8 +135,8 @@ eval instant at 9s xrate(http_requests[10s]) {path="/bar"} 0.1 eval instant at 9s xrate0(http_requests[10s]) - {path="/foo"} 0.16666666666666669 - {path="/bar"} 0.33333333333333337 + {path="/foo"} 0 + {path="/bar"} 0.16666666666666669 eval instant at 19s xrate(http_requests[10s]) {path="/foo"} 0.1 @@ -274,16 +274,16 @@ eval instant at 50s xincrease(http_requests[5s]) {path="/bar"} 10 eval instant at 50s xincrease0(http_requests[5s]) - {path="/foo"} 100 - {path="/bar"} 40 + {path="/foo"} 10 + {path="/bar"} 10 eval instant at 50s xincrease(http_requests[3s]) {path="/foo"} 6 {path="/bar"} 6 eval instant at 50s xincrease0(http_requests[3s]) - {path="/foo"} 60 - {path="/bar"} 24 + {path="/foo"} 6 + {path="/bar"} 6 eval instant at 49s xincrease(http_requests[3s]) @@ -311,16 +311,16 @@ eval instant at 50s xrate(http_requests[5s]) {path="/bar"} 2 eval instant at 50s xrate0(http_requests[5s]) - {path="/foo"} 20 - {path="/bar"} 8 + {path="/foo"} 2 + {path="/bar"} 2 eval instant at 50s xrate(http_requests[3s]) {path="/foo"} 2 {path="/bar"} 2 eval instant at 50s xrate0(http_requests[3s]) - {path="/foo"} 20 - {path="/bar"} 8 + {path="/foo"} 2 + {path="/bar"} 2 eval instant at 49s xrate(http_requests[3s]) From 37bd4ada560877e854a6f2bbc54c6f2aecff3293 Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Tue, 11 Apr 2023 09:00:12 -0700 Subject: [PATCH 12/29] drop margin % and use scrapeIntervalMsec instead --- promql/functions.go | 12 +++++------- promql/testdata/functions.test | 26 +++++++++++++------------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/promql/functions.go b/promql/functions.go index 775065fbf22..a3202ba96c2 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -226,7 +226,6 @@ func inferScrapeInterval(points []Point) int64 { return intervals[len(intervals)/2] } -const scrapeIntervalMarginPercent float64 = 0.40 const elideSamplesAfter int = 10 // extendedRate0 is a utility function for xrate0/xincrease0. @@ -249,14 +248,13 @@ func extendedRate0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe } scrapeIntervalMsec := inferScrapeInterval(points) //** milliseconds - scrapeIntervalMarginMsec := int64(float64(scrapeIntervalMsec) * float64(scrapeIntervalMarginPercent)) firstPoint := 1 // Assume normal case: point 0 is before the range +/-, so we can use it to measure increase. lastValue := points[0].V // Prime the lastValue with the preceding value (before the range +/-). - // The 0 Fix: Check for fresh pod start case where we have no point before the range +/-. - // Treat this as if we had a 0 just before the range. - if points[0].T >= rangeStartMsec-scrapeIntervalMarginMsec { + // The 0 Fix: Check for fresh pod start case where we have no point before the range. + // Treat this as if we had a 0 to start. + if points[0].T >= rangeStartMsec { firstPoint = 0 lastValue = 0.0 } @@ -300,10 +298,10 @@ func extendedRate0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe { sampledStartMsec := points[firstPoint].T sampledEndMsec := points[len(points)-1].T - if int64(math.Abs(float64(rangeStartMsec-sampledStartMsec))) < scrapeIntervalMarginMsec { + if int64(math.Abs(float64(rangeStartMsec-sampledStartMsec))) < scrapeIntervalMsec { sampledStartMsec = rangeStartMsec // Close enough, so snap to start } - if int64(math.Abs(float64(rangeEndMsec-sampledEndMsec))) < scrapeIntervalMarginMsec { + if int64(math.Abs(float64(rangeEndMsec-sampledEndMsec))) < scrapeIntervalMsec { sampledEndMsec = rangeEndMsec // Close enough, so snap to end } if sampledStartMsec != rangeStartMsec || sampledEndMsec != rangeEndMsec { diff --git a/promql/testdata/functions.test b/promql/testdata/functions.test index 9410725e0cf..202457fd008 100644 --- a/promql/testdata/functions.test +++ b/promql/testdata/functions.test @@ -35,8 +35,8 @@ eval instant at 24s xrate(http_requests[50s]) {path="/bar"} .08 eval instant at 24s xrate0(http_requests[50s]) - {path="/foo"} .05 - {path="/bar"} .2 + {path="/foo"} 0.04166666666666667 + {path="/bar"} 0.16666666666666669 # 3. Eval 1 second later compared to (1). # * path="/foo" rate should be same or fractionally lower ("longer" sample, same actual increase). @@ -136,15 +136,15 @@ eval instant at 9s xrate(http_requests[10s]) eval instant at 9s xrate0(http_requests[10s]) {path="/foo"} 0 - {path="/bar"} 0.16666666666666669 + {path="/bar"} 0.1 eval instant at 19s xrate(http_requests[10s]) {path="/foo"} 0.1 {path="/bar"} 0.2 eval instant at 19s xrate0(http_requests[10s]) - {path="/foo"} 0.16666666666666669 - {path="/bar"} 0.33333333333333337 + {path="/foo"} 0.1 + {path="/bar"} 0.2 eval instant at 29s xrate(http_requests[10s]) {path="/foo"} 0 @@ -152,7 +152,7 @@ eval instant at 29s xrate(http_requests[10s]) eval instant at 29s xrate0(http_requests[10s]) {path="/foo"} 0 - {path="/bar"} 0.33333333333333337 + {path="/bar"} 0.2 eval instant at 39s xrate(http_requests[10s]) {path="/foo"} 0 @@ -160,7 +160,7 @@ eval instant at 39s xrate(http_requests[10s]) eval instant at 39s xrate0(http_requests[10s]) {path="/foo"} 0 - {path="/bar"} 0.33333333333333337 + {path="/bar"} 0.2 # XXX Sees the increase in path="/foo" between timestamps 35 and 40. eval instant at 49s xrate(http_requests[10s]) @@ -168,8 +168,8 @@ eval instant at 49s xrate(http_requests[10s]) {path="/bar"} 0.2 eval instant at 49s xrate0(http_requests[10s]) - {path="/foo"} 0.16666666666666669 - {path="/bar"} 0.33333333333333337 + {path="/foo"} 0.1 + {path="/bar"} 0.2 clear @@ -282,8 +282,8 @@ eval instant at 50s xincrease(http_requests[3s]) {path="/bar"} 6 eval instant at 50s xincrease0(http_requests[3s]) - {path="/foo"} 6 - {path="/bar"} 6 + {path="/foo"} 10 + {path="/bar"} 10 eval instant at 49s xincrease(http_requests[3s]) @@ -319,8 +319,8 @@ eval instant at 50s xrate(http_requests[3s]) {path="/bar"} 2 eval instant at 50s xrate0(http_requests[3s]) - {path="/foo"} 2 - {path="/bar"} 2 + {path="/foo"} 3.3333333333333335 + {path="/bar"} 3.3333333333333335 eval instant at 49s xrate(http_requests[3s]) From 296d345a2794e22b04753204a0fd97986b637e47 Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Mon, 17 Apr 2023 12:37:47 -0700 Subject: [PATCH 13/29] fix bug in 0 Fix; add Msec var suffixes --- promql/functions.go | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/promql/functions.go b/promql/functions.go index a3202ba96c2..4d9b9e199c2 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -161,7 +161,7 @@ func extendedRate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHel // If the point before the range is too far from rangeStart, drop it. if float64(rangeStart-points[0].T) > averageInterval { //** There's 0 slop here, so we could drop the point even if it was scraped 1 msec early //** Repeating the above check for 0 or 1 data points, with +1. - if len(points) < 3 { //** This looks like a bug to me. 2 points is enough to measure an increase. -Colin + if len(points) < 3 { return enh.Out } firstPoint = 1 @@ -249,22 +249,35 @@ func extendedRate0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe scrapeIntervalMsec := inferScrapeInterval(points) //** milliseconds - firstPoint := 1 // Assume normal case: point 0 is before the range +/-, so we can use it to measure increase. - lastValue := points[0].V // Prime the lastValue with the preceding value (before the range +/-). + firstPoint := 0 + var lastValue float64 - // The 0 Fix: Check for fresh pod start case where we have no point before the range. + // The 0 Fix: Check for fresh pod start case where we have no point before the range, + // meaning the caller failed to find any before the range in the lookback interval. // Treat this as if we had a 0 to start. - if points[0].T >= rangeStartMsec { - firstPoint = 0 - lastValue = 0.0 + firstPointToRangeStartMsec := rangeStartMsec - points[0].T + if firstPointToRangeStartMsec < 0 { + lastValue = 0.0 // include points[0].V in resultValue + } else { + // If the point before the range is too far from rangeStart, drop it. + if firstPointToRangeStartMsec > scrapeIntervalMsec { //** There's 0 slop here, so we could drop the point even if it was scraped 1 msec early + firstPoint = 1 + } + lastValue = points[firstPoint].V + } + + // Cannot compute rate with 0 or 1 data points. + if len(points)-firstPoint < 2 { + return enh.Out } // Simplest result: the difference beteween first and last values. - resultValue := points[len(points)-1].V - points[0].V + resultValue := points[len(points)-1].V - lastValue // Debug logging log.Printf("extendedRate0: isRate: %t, firstPoint: %d, scrapeIntervalMsec: %.1f", isRate, firstPoint, float64(scrapeIntervalMsec)/1000.0) - log.Printf("extendedRate0: enh range: %.3f..%.3f (offset %.3f); starting result: %.1f", float64(rangeStartMsec)/1000.0, float64(rangeEndMsec)/1000.0, float64(vs.Offset)/1000.0, resultValue) + log.Printf("extendedRate0: enh range: %.3f...%.3f (offset %.3f); starting result: %.1f; lastValue: %.1f", + float64(rangeStartMsec)/1000.0, float64(rangeEndMsec)/1000.0, float64(vs.Offset)/1000.0, resultValue, lastValue) { buffer := new(bytes.Buffer) @@ -277,7 +290,7 @@ func extendedRate0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe if i > 0 { fmt.Fprintf(buffer, ", ") } - fmt.Fprintf(buffer, "[%d,%.1f]", point.T/1000, point.V) + fmt.Fprintf(buffer, "[%.3f,%.1f]", float64(point.T)/1000.0, point.V) } } log.Println("extendedRate0: samples:", buffer.String()) @@ -298,7 +311,8 @@ func extendedRate0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe { sampledStartMsec := points[firstPoint].T sampledEndMsec := points[len(points)-1].T - if int64(math.Abs(float64(rangeStartMsec-sampledStartMsec))) < scrapeIntervalMsec { + if int64(math.Abs(float64(rangeStartMsec-sampledStartMsec))) < scrapeIntervalMsec || + firstPointToRangeStartMsec < 0 { sampledStartMsec = rangeStartMsec // Close enough, so snap to start } if int64(math.Abs(float64(rangeEndMsec-sampledEndMsec))) < scrapeIntervalMsec { @@ -307,7 +321,7 @@ func extendedRate0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe if sampledStartMsec != rangeStartMsec || sampledEndMsec != rangeEndMsec { sampledRangeMsec := sampledEndMsec - sampledStartMsec requestedRangeMsec := durationMilliseconds(ms.Range) - log.Printf("extendedRate0: scaling result: %.1f by %d / %d", resultValue, requestedRangeMsec, sampledRangeMsec) + log.Printf("extendedRate0: sampled{Start,End}tMsec: %d:%d scaling result: %.1f by %d / %d", sampledStartMsec, sampledEndMsec, resultValue, requestedRangeMsec, sampledRangeMsec) resultValue *= (float64(requestedRangeMsec) / float64(sampledRangeMsec)) } } From e97cd45c89cb1c4b3b3e4a60246fb8a1b8aaea57 Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Mon, 17 Apr 2023 15:04:27 -0700 Subject: [PATCH 14/29] +1000, +2000 for first set of test data --- promql/testdata/functions.test | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/promql/testdata/functions.test b/promql/testdata/functions.test index 202457fd008..3298b2efa4e 100644 --- a/promql/testdata/functions.test +++ b/promql/testdata/functions.test @@ -1,8 +1,8 @@ # Comparison of rate vs xrate vs xrate0. load 5s - http_requests{path="/foo"} 1 1 1 2 2 2 2 2 3 3 3 - http_requests{path="/bar"} 1 2 3 4 5 6 7 8 9 10 11 + http_requests{path="/foo"} 1001 1001 1001 1002 1002 1002 1002 1002 1003 1003 1003 + http_requests{path="/bar"} 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 # @@ -12,15 +12,15 @@ load 5s # 1. Reference eval, aligned with collection. eval instant at 25s rate(http_requests[50s]) {path="/foo"} .022 - {path="/bar"} .12 + {path="/bar"} .11 eval instant at 25s xrate(http_requests[50s]) {path="/foo"} .02 {path="/bar"} .1 eval instant at 25s xrate0(http_requests[50s]) - {path="/foo"} .04 - {path="/bar"} .2 + {path="/foo"} 20.04 + {path="/bar"} 40.12 # 2. Eval 1 second earlier compared to (1). # * path="/foo" rate should be same or fractionally higher ("shorter" sample, same actual increase); @@ -28,15 +28,15 @@ eval instant at 25s xrate0(http_requests[50s]) # XXX Seeing ~20% jump for path="/foo" eval instant at 24s rate(http_requests[50s]) {path="/foo"} .0265 - {path="/bar"} .116 + {path="/bar"} .106 eval instant at 24s xrate(http_requests[50s]) {path="/foo"} .02 {path="/bar"} .08 eval instant at 24s xrate0(http_requests[50s]) - {path="/foo"} 0.04166666666666667 - {path="/bar"} 0.16666666666666669 + {path="/foo"} 20.04 + {path="/bar"} 40.1 # 3. Eval 1 second later compared to (1). # * path="/foo" rate should be same or fractionally lower ("longer" sample, same actual increase). @@ -44,15 +44,15 @@ eval instant at 24s xrate0(http_requests[50s]) # XXX Higher instead of lower for both. eval instant at 26s rate(http_requests[50s]) {path="/foo"} .0228 - {path="/bar"} .124 + {path="/bar"} .11399999999999999 eval instant at 26s xrate(http_requests[50s]) {path="/foo"} .02 {path="/bar"} .1 eval instant at 26s xrate0(http_requests[50s]) - {path="/foo"} 0.038461538461538464 - {path="/bar"} 0.1923076923076923 + {path="/foo"} 20.04 + {path="/bar"} 40.12 # @@ -135,8 +135,8 @@ eval instant at 9s xrate(http_requests[10s]) {path="/bar"} 0.1 eval instant at 9s xrate0(http_requests[10s]) - {path="/foo"} 0 - {path="/bar"} 0.1 + {path="/foo"} 100.1 + {path="/bar"} 200.2 eval instant at 19s xrate(http_requests[10s]) {path="/foo"} 0.1 @@ -303,8 +303,8 @@ eval instant at 50s xrate(http_requests[100s]) {path="/bar"} 0.9 eval instant at 50s xrate0(http_requests[100s]) - {path="/foo"} 2 - {path="/bar"} 1.8 + {path="/foo"} 1 + {path="/bar"} 0.9 eval instant at 50s xrate(http_requests[5s]) {path="/foo"} 2 From 2c874af02984f8eb648725bdda73c41952e74ad5 Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Mon, 17 Apr 2023 16:45:06 -0700 Subject: [PATCH 15/29] add explicit presample, edge, and no presample test cases --- promql/testdata/functions.test | 35 ++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/promql/testdata/functions.test b/promql/testdata/functions.test index 3298b2efa4e..895ac12e710 100644 --- a/promql/testdata/functions.test +++ b/promql/testdata/functions.test @@ -4,6 +4,41 @@ load 5s http_requests{path="/foo"} 1001 1001 1001 1002 1002 1002 1002 1002 1003 1003 1003 http_requests{path="/bar"} 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 +# With presample +eval instant at 50s rate(http_requests[40s]) + {path="/foo"} 0.05 + {path="/bar"} 0.2 + +eval instant at 50s xrate(http_requests[40s]) + {path="/foo"} 0.05 + {path="/bar"} 0.2 + +eval instant at 50s xincrease(http_requests[40s]) + {path="/foo"} 2 + {path="/bar"} 8 + +eval instant at 50s xincrease0(http_requests[40s]) + {path="/foo"} 2 + {path="/bar"} 8 + +# Edge case--treat like presample +eval instant at 25s xincrease(http_requests[25s]) + {path="/foo"} 1 + {path="/bar"} 5 + +eval instant at 25s xincrease0(http_requests[25s]) + {path="/foo"} 1 + {path="/bar"} 5 + +# Without presample - 0 Fix for fresh pod start +eval instant at 20s xincrease(http_requests[25s]) + {path="/foo"} 1 + {path="/bar"} 4 + +eval instant at 20s xincrease0(http_requests[25s]) + {path="/foo"} 1002 + {path="/bar"} 2005 + # # Timeseries starts inside range, (presumably) goes on after range end. From c8260bc4856918ec755c6d1f26ed362a3e323096 Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Fri, 21 Apr 2023 16:36:46 -0700 Subject: [PATCH 16/29] add yincrease, yrate --- promql/functions.go | 81 ++++++++++++++++++++++++++++++++++++++ promql/parser/functions.go | 12 ++++++ 2 files changed, 93 insertions(+) diff --git a/promql/functions.go b/promql/functions.go index 4d9b9e199c2..279ed411e69 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -337,6 +337,51 @@ func extendedRate0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe }) } +// yIncrease is a utility function for yrate/yincrease. +// It calculates the increase of the range (allowing for counter resets and including the startup value from 0.0), +// taking into account the last sample before the range start. +func yIncrease(points []Point, rangeStartMsec, rangeEndMsec int64) float64 { + // Debug logging + log.Printf("yincrease: range: %.3f...%.3f", float64(rangeStartMsec)/1000.0, float64(rangeEndMsec)/1000.0) + + { + buffer := new(bytes.Buffer) + for i, point := range points { + if i == elideSamplesAfter && len(points)-1 > elideSamplesAfter { + fmt.Fprintf(buffer, "...") + } else if i > elideSamplesAfter && len(points)-i > elideSamplesAfter { + continue + } else { + if i > 0 { + fmt.Fprintf(buffer, ", ") + } + fmt.Fprintf(buffer, "[%.3f,%.1f]", float64(point.T)/1000.0, point.V) + } + } + log.Println("yincrease: samples:", buffer.String()) + } + + lastBeforeRange := float64(0.0) // This provides the 0 fix for a fresh start of a pod. + lastInRange := float64(0.0) + + // The points are in time order, so we can just walk the list once and remember the last values + // seen "before" and "in" range. If there are no values in range, we use the last value before range. + for _, point := range points { + if point.T < rangeStartMsec { + lastBeforeRange = point.V + } + if point.T < rangeEndMsec { + lastInRange = point.V + } + } + + result := lastInRange - lastBeforeRange + + log.Printf("yincrease: returning result: %.1f", result) + + return result +} + // === delta(Matrix parser.ValueTypeMatrix) Vector === func funcDelta(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector { return extrapolatedRate(vals, args, enh, false, false) @@ -367,6 +412,31 @@ func funcXrate0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelpe return extendedRate0(vals, args, enh, true) } +// Extracts points, rangeStartMsec, rangeEndMsec, rangeSeconds from common params. +// Note: the range is [rangeStartMsec, rangeEndMsec). That is, every sample in range is +// rangeStartMsec <= sample.T < rangeEndMsec +func rangeFromSelectors(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) ([]Point, int64, int64, float64) { + ms := args[0].(*parser.MatrixSelector) + vs := ms.VectorSelector.(*parser.VectorSelector) + + rangeStartMsec := enh.Ts - durationMilliseconds(ms.Range+vs.Offset) + rangeEndMsec := enh.Ts - durationMilliseconds(vs.Offset) + + points := vals[0].(Matrix)[0].Points + + // TODO: Is rangeSeconds == (rangeEndMsec - rangeStartMsec)/1000.0? If so, let's drop it. -Colin + return points, rangeStartMsec, rangeEndMsec, ms.Range.Seconds() +} + +// === yrate(node parser.ValueTypeMatrix) Vector === +func funcYrate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector { + points, rangeStartMsec, rangeEndMsec, rangeSeconds := rangeFromSelectors(vals, args, enh) + + result := yIncrease(points, rangeStartMsec, rangeEndMsec) / rangeSeconds + + return append(enh.Out, Sample{Point: Point{V: result}}) +} + // === xincrease(node parser.ValueTypeMatrix) Vector === func funcXincrease(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector { return extendedRate(vals, args, enh, true, false) @@ -377,6 +447,15 @@ func funcXincrease0(vals []parser.Value, args parser.Expressions, enh *EvalNodeH return extendedRate0(vals, args, enh, false) } +// === yincrease(node parser.ValueTypeMatrix) Vector === +func funcYincrease(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector { + points, rangeStartMsec, rangeEndMsec, _ := rangeFromSelectors(vals, args, enh) + + result := yIncrease(points, rangeStartMsec, rangeEndMsec) + + return append(enh.Out, Sample{Point: Point{V: result}}) +} + // === irate(node parser.ValueTypeMatrix) Vector === func funcIrate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector { return instantValue(vals, enh.Out, true) @@ -1371,6 +1450,8 @@ var FunctionCalls = map[string]FunctionCall{ "xrate": funcXrate, "xrate0": funcXrate0, "year": funcYear, + "yincrease": funcYincrease, + "yrate": funcYrate, } // AtModifierUnsafeFunctions are the functions whose result diff --git a/promql/parser/functions.go b/promql/parser/functions.go index 5006fdc0afb..feeab564953 100644 --- a/promql/parser/functions.go +++ b/promql/parser/functions.go @@ -394,6 +394,18 @@ var Functions = map[string]*Function{ Variadic: 1, ReturnType: ValueTypeVector, }, + "yincrease": { + Name: "yincrease", + ArgTypes: []ValueType{ValueTypeMatrix}, + ReturnType: ValueTypeVector, + ExtRange: true, + }, + "yrate": { + Name: "yrate", + ArgTypes: []ValueType{ValueTypeMatrix}, + ReturnType: ValueTypeVector, + ExtRange: true, + }, } // getFunction returns a predefined Function object for the given name. From fd3b4bedf03b15f7ce614a77567c61dab4770ba7 Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Fri, 21 Apr 2023 22:27:13 -0700 Subject: [PATCH 17/29] extract debugSampleString; add support for inRangeRestartSkew --- promql/functions.go | 88 ++++++++++++++++++++++++--------------------- 1 file changed, 48 insertions(+), 40 deletions(-) diff --git a/promql/functions.go b/promql/functions.go index 279ed411e69..61500ddb26c 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -337,33 +337,37 @@ func extendedRate0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe }) } +func debugSampleString(points []Point) string { + buffer := new(bytes.Buffer) + for i, point := range points { + if i == elideSamplesAfter && len(points)-1 > elideSamplesAfter { + fmt.Fprintf(buffer, "...") + } else if i > elideSamplesAfter && len(points)-i > elideSamplesAfter { + continue + } else { + if i > 0 { + fmt.Fprintf(buffer, ", ") + } + fmt.Fprintf(buffer, "[%.3f,%.1f]", float64(point.T)/1000.0, point.V) + } + } + return buffer.String() +} + // yIncrease is a utility function for yrate/yincrease. // It calculates the increase of the range (allowing for counter resets and including the startup value from 0.0), // taking into account the last sample before the range start. func yIncrease(points []Point, rangeStartMsec, rangeEndMsec int64) float64 { // Debug logging log.Printf("yincrease: range: %.3f...%.3f", float64(rangeStartMsec)/1000.0, float64(rangeEndMsec)/1000.0) - - { - buffer := new(bytes.Buffer) - for i, point := range points { - if i == elideSamplesAfter && len(points)-1 > elideSamplesAfter { - fmt.Fprintf(buffer, "...") - } else if i > elideSamplesAfter && len(points)-i > elideSamplesAfter { - continue - } else { - if i > 0 { - fmt.Fprintf(buffer, ", ") - } - fmt.Fprintf(buffer, "[%.3f,%.1f]", float64(point.T)/1000.0, point.V) - } - } - log.Println("yincrease: samples:", buffer.String()) - } + log.Println("yincrease: samples: ", debugSampleString(points)) lastBeforeRange := float64(0.0) // This provides the 0 fix for a fresh start of a pod. lastInRange := float64(0.0) + lastValue := float64(0.0) + inRangeRestartSkew := float64(0.0) + // The points are in time order, so we can just walk the list once and remember the last values // seen "before" and "in" range. If there are no values in range, we use the last value before range. for _, point := range points { @@ -372,10 +376,14 @@ func yIncrease(points []Point, rangeStartMsec, rangeEndMsec int64) float64 { } if point.T < rangeEndMsec { lastInRange = point.V + if point.T >= rangeStartMsec && point.V < lastValue { // Counter can only go backwards on a restart. + inRangeRestartSkew += point.V + } } + lastValue = point.V } - result := lastInRange - lastBeforeRange + result := lastInRange - lastBeforeRange + inRangeRestartSkew log.Printf("yincrease: returning result: %.1f", result) @@ -412,8 +420,18 @@ func funcXrate0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelpe return extendedRate0(vals, args, enh, true) } +// === xincrease(node parser.ValueTypeMatrix) Vector === +func funcXincrease(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector { + return extendedRate(vals, args, enh, true, false) +} + +// === xincrease0(node parser.ValueTypeMatrix) Vector === +func funcXincrease0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector { + return extendedRate0(vals, args, enh, false) +} + // Extracts points, rangeStartMsec, rangeEndMsec, rangeSeconds from common params. -// Note: the range is [rangeStartMsec, rangeEndMsec). That is, every sample in range is +// Note: the range is [rangeStartMsec, rangeEndMsec). That is, every sample in range has the property: // rangeStartMsec <= sample.T < rangeEndMsec func rangeFromSelectors(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) ([]Point, int64, int64, float64) { ms := args[0].(*parser.MatrixSelector) @@ -424,36 +442,26 @@ func rangeFromSelectors(vals []parser.Value, args parser.Expressions, enh *EvalN points := vals[0].(Matrix)[0].Points - // TODO: Is rangeSeconds == (rangeEndMsec - rangeStartMsec)/1000.0? If so, let's drop it. -Colin + // TODO: Is rangeSeconds == (rangeEndMsec - rangeStartMsec)/1000.0? If so, let's drop the separate return value. -Colin return points, rangeStartMsec, rangeEndMsec, ms.Range.Seconds() } -// === yrate(node parser.ValueTypeMatrix) Vector === -func funcYrate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector { - points, rangeStartMsec, rangeEndMsec, rangeSeconds := rangeFromSelectors(vals, args, enh) - - result := yIncrease(points, rangeStartMsec, rangeEndMsec) / rangeSeconds - - return append(enh.Out, Sample{Point: Point{V: result}}) -} +// === yincrease(node parser.ValueTypeMatrix) Vector === +func funcYincrease(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector { + points, rangeStartMsec, rangeEndMsec, _ := rangeFromSelectors(vals, args, enh) -// === xincrease(node parser.ValueTypeMatrix) Vector === -func funcXincrease(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector { - return extendedRate(vals, args, enh, true, false) -} + value := yIncrease(points, rangeStartMsec, rangeEndMsec) -// === xincrease0(node parser.ValueTypeMatrix) Vector === -func funcXincrease0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector { - return extendedRate0(vals, args, enh, false) + return append(enh.Out, Sample{Point: Point{V: value}}) } -// === yincrease(node parser.ValueTypeMatrix) Vector === -func funcYincrease(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector { - points, rangeStartMsec, rangeEndMsec, _ := rangeFromSelectors(vals, args, enh) +// === yrate(node parser.ValueTypeMatrix) Vector === +func funcYrate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector { + points, rangeStartMsec, rangeEndMsec, rangeSeconds := rangeFromSelectors(vals, args, enh) - result := yIncrease(points, rangeStartMsec, rangeEndMsec) + value := yIncrease(points, rangeStartMsec, rangeEndMsec) / rangeSeconds - return append(enh.Out, Sample{Point: Point{V: result}}) + return append(enh.Out, Sample{Point: Point{V: value}}) } // === irate(node parser.ValueTypeMatrix) Vector === From 76f6dc2aaed9ffff3646769008a490b16756a661 Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Fri, 21 Apr 2023 22:36:02 -0700 Subject: [PATCH 18/29] drop xrate0 and xincrease0 along with extendedRate0 --- promql/functions.go | 121 ----------------------------- promql/parser/functions.go | 12 --- promql/testdata/functions.test | 134 ++++++++++++++++----------------- 3 files changed, 67 insertions(+), 200 deletions(-) diff --git a/promql/functions.go b/promql/functions.go index 61500ddb26c..c3b5f84ec9c 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -228,115 +228,6 @@ func inferScrapeInterval(points []Point) int64 { const elideSamplesAfter int = 10 -// extendedRate0 is a utility function for xrate0/xincrease0. -// It calculates the rate (allowing for counter resets and including the startup value from 0), -// taking into account the last sample before the range start, and returns -// the result as either per-second (if isRate is true) or overall. -func extendedRate0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper, isRate bool) Vector { - ms := args[0].(*parser.MatrixSelector) - vs := ms.VectorSelector.(*parser.VectorSelector) - - rangeStartMsec := enh.Ts - durationMilliseconds(ms.Range+vs.Offset) - rangeEndMsec := enh.Ts - durationMilliseconds(vs.Offset) - - samples := vals[0].(Matrix)[0] - points := samples.Points - - // Cannot compute rate with 0 or 1 data points. - if len(points) < 2 { - return enh.Out - } - - scrapeIntervalMsec := inferScrapeInterval(points) //** milliseconds - - firstPoint := 0 - var lastValue float64 - - // The 0 Fix: Check for fresh pod start case where we have no point before the range, - // meaning the caller failed to find any before the range in the lookback interval. - // Treat this as if we had a 0 to start. - firstPointToRangeStartMsec := rangeStartMsec - points[0].T - if firstPointToRangeStartMsec < 0 { - lastValue = 0.0 // include points[0].V in resultValue - } else { - // If the point before the range is too far from rangeStart, drop it. - if firstPointToRangeStartMsec > scrapeIntervalMsec { //** There's 0 slop here, so we could drop the point even if it was scraped 1 msec early - firstPoint = 1 - } - lastValue = points[firstPoint].V - } - - // Cannot compute rate with 0 or 1 data points. - if len(points)-firstPoint < 2 { - return enh.Out - } - - // Simplest result: the difference beteween first and last values. - resultValue := points[len(points)-1].V - lastValue - - // Debug logging - log.Printf("extendedRate0: isRate: %t, firstPoint: %d, scrapeIntervalMsec: %.1f", isRate, firstPoint, float64(scrapeIntervalMsec)/1000.0) - log.Printf("extendedRate0: enh range: %.3f...%.3f (offset %.3f); starting result: %.1f; lastValue: %.1f", - float64(rangeStartMsec)/1000.0, float64(rangeEndMsec)/1000.0, float64(vs.Offset)/1000.0, resultValue, lastValue) - - { - buffer := new(bytes.Buffer) - for i, point := range points { - if i == elideSamplesAfter && len(points)-1 > elideSamplesAfter { - fmt.Fprintf(buffer, "...") - } else if i > elideSamplesAfter && len(points)-i > elideSamplesAfter { - continue - } else { - if i > 0 { - fmt.Fprintf(buffer, ", ") - } - fmt.Fprintf(buffer, "[%.3f,%.1f]", float64(point.T)/1000.0, point.V) - } - } - log.Println("extendedRate0: samples:", buffer.String()) - } - - // Correct for any case where a process restart caused the increase to turn negative. - for _, point := range points[firstPoint:] { - step := point.V - lastValue - if step < 0 { - resultValue += -step - log.Printf("extendedRate0: correcting for step: %.1f", step) - } - lastValue = point.V - } - - // If the first or last sampled point is outside the range boundaries +/-, assume we are missing a sample at - // that edge and extrapolate from the sampled range up to the requested range. - { - sampledStartMsec := points[firstPoint].T - sampledEndMsec := points[len(points)-1].T - if int64(math.Abs(float64(rangeStartMsec-sampledStartMsec))) < scrapeIntervalMsec || - firstPointToRangeStartMsec < 0 { - sampledStartMsec = rangeStartMsec // Close enough, so snap to start - } - if int64(math.Abs(float64(rangeEndMsec-sampledEndMsec))) < scrapeIntervalMsec { - sampledEndMsec = rangeEndMsec // Close enough, so snap to end - } - if sampledStartMsec != rangeStartMsec || sampledEndMsec != rangeEndMsec { - sampledRangeMsec := sampledEndMsec - sampledStartMsec - requestedRangeMsec := durationMilliseconds(ms.Range) - log.Printf("extendedRate0: sampled{Start,End}tMsec: %d:%d scaling result: %.1f by %d / %d", sampledStartMsec, sampledEndMsec, resultValue, requestedRangeMsec, sampledRangeMsec) - resultValue *= (float64(requestedRangeMsec) / float64(sampledRangeMsec)) - } - } - - if isRate { - resultValue /= ms.Range.Seconds() - } - - log.Printf("extendedRate0: returning result: %.1f", resultValue) - - return append(enh.Out, Sample{ - Point: Point{V: resultValue}, - }) -} - func debugSampleString(points []Point) string { buffer := new(bytes.Buffer) for i, point := range points { @@ -415,21 +306,11 @@ func funcXrate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper return extendedRate(vals, args, enh, true, true) } -// === xrate0(node parser.ValueTypeMatrix) Vector === -func funcXrate0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector { - return extendedRate0(vals, args, enh, true) -} - // === xincrease(node parser.ValueTypeMatrix) Vector === func funcXincrease(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector { return extendedRate(vals, args, enh, true, false) } -// === xincrease0(node parser.ValueTypeMatrix) Vector === -func funcXincrease0(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector { - return extendedRate0(vals, args, enh, false) -} - // Extracts points, rangeStartMsec, rangeEndMsec, rangeSeconds from common params. // Note: the range is [rangeStartMsec, rangeEndMsec). That is, every sample in range has the property: // rangeStartMsec <= sample.T < rangeEndMsec @@ -1454,9 +1335,7 @@ var FunctionCalls = map[string]FunctionCall{ "vector": funcVector, "xdelta": funcXdelta, "xincrease": funcXincrease, - "xincrease0": funcXincrease0, "xrate": funcXrate, - "xrate0": funcXrate0, "year": funcYear, "yincrease": funcYincrease, "yrate": funcYrate, diff --git a/promql/parser/functions.go b/promql/parser/functions.go index feeab564953..ef8dda7d05d 100644 --- a/promql/parser/functions.go +++ b/promql/parser/functions.go @@ -370,24 +370,12 @@ var Functions = map[string]*Function{ ReturnType: ValueTypeVector, ExtRange: true, }, - "xincrease0": { - Name: "xincrease0", - ArgTypes: []ValueType{ValueTypeMatrix}, - ReturnType: ValueTypeVector, - ExtRange: true, - }, "xrate": { Name: "xrate", ArgTypes: []ValueType{ValueTypeMatrix}, ReturnType: ValueTypeVector, ExtRange: true, }, - "xrate0": { - Name: "xrate0", - ArgTypes: []ValueType{ValueTypeMatrix}, - ReturnType: ValueTypeVector, - ExtRange: true, - }, "year": { Name: "year", ArgTypes: []ValueType{ValueTypeVector}, diff --git a/promql/testdata/functions.test b/promql/testdata/functions.test index 895ac12e710..7a662ea69f3 100644 --- a/promql/testdata/functions.test +++ b/promql/testdata/functions.test @@ -17,27 +17,27 @@ eval instant at 50s xincrease(http_requests[40s]) {path="/foo"} 2 {path="/bar"} 8 -eval instant at 50s xincrease0(http_requests[40s]) - {path="/foo"} 2 - {path="/bar"} 8 +# eval instant at 50s xincrease0(http_requests[40s]) +# {path="/foo"} 2 +# {path="/bar"} 8 # Edge case--treat like presample eval instant at 25s xincrease(http_requests[25s]) {path="/foo"} 1 {path="/bar"} 5 -eval instant at 25s xincrease0(http_requests[25s]) - {path="/foo"} 1 - {path="/bar"} 5 +# eval instant at 25s xincrease0(http_requests[25s]) +# {path="/foo"} 1 +# {path="/bar"} 5 # Without presample - 0 Fix for fresh pod start eval instant at 20s xincrease(http_requests[25s]) {path="/foo"} 1 {path="/bar"} 4 -eval instant at 20s xincrease0(http_requests[25s]) - {path="/foo"} 1002 - {path="/bar"} 2005 +# eval instant at 20s xincrease0(http_requests[25s]) +# {path="/foo"} 1002 +# {path="/bar"} 2005 # @@ -53,9 +53,9 @@ eval instant at 25s xrate(http_requests[50s]) {path="/foo"} .02 {path="/bar"} .1 -eval instant at 25s xrate0(http_requests[50s]) - {path="/foo"} 20.04 - {path="/bar"} 40.12 +# eval instant at 25s xrate0(http_requests[50s]) +# {path="/foo"} 20.04 +# {path="/bar"} 40.12 # 2. Eval 1 second earlier compared to (1). # * path="/foo" rate should be same or fractionally higher ("shorter" sample, same actual increase); @@ -69,9 +69,9 @@ eval instant at 24s xrate(http_requests[50s]) {path="/foo"} .02 {path="/bar"} .08 -eval instant at 24s xrate0(http_requests[50s]) - {path="/foo"} 20.04 - {path="/bar"} 40.1 +# eval instant at 24s xrate0(http_requests[50s]) +# {path="/foo"} 20.04 +# {path="/bar"} 40.1 # 3. Eval 1 second later compared to (1). # * path="/foo" rate should be same or fractionally lower ("longer" sample, same actual increase). @@ -85,9 +85,9 @@ eval instant at 26s xrate(http_requests[50s]) {path="/foo"} .02 {path="/bar"} .1 -eval instant at 26s xrate0(http_requests[50s]) - {path="/foo"} 20.04 - {path="/bar"} 40.12 +# eval instant at 26s xrate0(http_requests[50s]) +# {path="/foo"} 20.04 +# {path="/bar"} 40.12 # @@ -103,9 +103,9 @@ eval instant at 75s xrate(http_requests[50s]) {path="/foo"} .02 {path="/bar"} .1 -eval instant at 75s xrate0(http_requests[50s]) - {path="/foo"} .04 - {path="/bar"} .2 +# eval instant at 75s xrate0(http_requests[50s]) +# {path="/foo"} .04 +# {path="/bar"} .2 # 5. Eval 1s earlier compared to (4). # * path="/foo" rate should be same or fractionally lower ("longer" sample, same actual increase). @@ -120,9 +120,9 @@ eval instant at 74s xrate(http_requests[50s]) {path="/foo"} .02 {path="/bar"} .12 -eval instant at 74s xrate0(http_requests[50s]) - {path="/foo"} .038461538461538464 - {path="/bar"} .23076923076923075 +# eval instant at 74s xrate0(http_requests[50s]) +# {path="/foo"} .038461538461538464 +# {path="/bar"} .23076923076923075 # 6. Eval 1s later compared to (4). Rate/increase (should be) fractionally smaller. # * path="/foo" rate should be same or fractionally higher ("shorter" sample, same actual increase); @@ -136,9 +136,9 @@ eval instant at 76s xrate(http_requests[50s]) {path="/foo"} .02 {path="/bar"} .1 -eval instant at 76s xrate0(http_requests[50s]) - {path="/foo"} 0.04166666666666667 - {path="/bar"} 0.20833333333333337 +# eval instant at 76s xrate0(http_requests[50s]) +# {path="/foo"} 0.04166666666666667 +# {path="/bar"} 0.20833333333333337 # # Evaluation of 10 second rate every 10 seconds, not aligned with collection. @@ -169,42 +169,42 @@ eval instant at 9s xrate(http_requests[10s]) {path="/foo"} 0 {path="/bar"} 0.1 -eval instant at 9s xrate0(http_requests[10s]) - {path="/foo"} 100.1 - {path="/bar"} 200.2 +# eval instant at 9s xrate0(http_requests[10s]) +# {path="/foo"} 100.1 +# {path="/bar"} 200.2 eval instant at 19s xrate(http_requests[10s]) {path="/foo"} 0.1 {path="/bar"} 0.2 -eval instant at 19s xrate0(http_requests[10s]) - {path="/foo"} 0.1 - {path="/bar"} 0.2 +# eval instant at 19s xrate0(http_requests[10s]) +# {path="/foo"} 0.1 +# {path="/bar"} 0.2 eval instant at 29s xrate(http_requests[10s]) {path="/foo"} 0 {path="/bar"} 0.2 -eval instant at 29s xrate0(http_requests[10s]) - {path="/foo"} 0 - {path="/bar"} 0.2 +# eval instant at 29s xrate0(http_requests[10s]) +# {path="/foo"} 0 +# {path="/bar"} 0.2 eval instant at 39s xrate(http_requests[10s]) {path="/foo"} 0 {path="/bar"} 0.2 -eval instant at 39s xrate0(http_requests[10s]) - {path="/foo"} 0 - {path="/bar"} 0.2 +# eval instant at 39s xrate0(http_requests[10s]) +# {path="/foo"} 0 +# {path="/bar"} 0.2 # XXX Sees the increase in path="/foo" between timestamps 35 and 40. eval instant at 49s xrate(http_requests[10s]) {path="/foo"} .1 {path="/bar"} 0.2 -eval instant at 49s xrate0(http_requests[10s]) - {path="/foo"} 0.1 - {path="/bar"} 0.2 +# eval instant at 49s xrate0(http_requests[10s]) +# {path="/foo"} 0.1 +# {path="/bar"} 0.2 clear @@ -300,66 +300,66 @@ eval instant at 50s xincrease(http_requests[50s]) {path="/foo"} 100 {path="/bar"} 90 -eval instant at 50s xincrease0(http_requests[50s]) - {path="/foo"} 100 - {path="/bar"} 90 +# eval instant at 50s xincrease0(http_requests[50s]) +# {path="/foo"} 100 +# {path="/bar"} 90 eval instant at 50s xincrease(http_requests[5s]) {path="/foo"} 10 {path="/bar"} 10 -eval instant at 50s xincrease0(http_requests[5s]) - {path="/foo"} 10 - {path="/bar"} 10 +# eval instant at 50s xincrease0(http_requests[5s]) +# {path="/foo"} 10 +# {path="/bar"} 10 eval instant at 50s xincrease(http_requests[3s]) {path="/foo"} 6 {path="/bar"} 6 -eval instant at 50s xincrease0(http_requests[3s]) - {path="/foo"} 10 - {path="/bar"} 10 +# eval instant at 50s xincrease0(http_requests[3s]) +# {path="/foo"} 10 +# {path="/bar"} 10 eval instant at 49s xincrease(http_requests[3s]) -eval instant at 49s xincrease0(http_requests[3s]) +# eval instant at 49s xincrease0(http_requests[3s]) # Tests for xrate()/xrate0(). eval instant at 50s xrate(http_requests[50s]) {path="/foo"} 2 {path="/bar"} 1.8 -eval instant at 50s xrate0(http_requests[50s]) - {path="/foo"} 2 - {path="/bar"} 1.8 +# eval instant at 50s xrate0(http_requests[50s]) +# {path="/foo"} 2 +# {path="/bar"} 1.8 eval instant at 50s xrate(http_requests[100s]) {path="/foo"} 1 {path="/bar"} 0.9 -eval instant at 50s xrate0(http_requests[100s]) - {path="/foo"} 1 - {path="/bar"} 0.9 +# eval instant at 50s xrate0(http_requests[100s]) +# {path="/foo"} 1 +# {path="/bar"} 0.9 eval instant at 50s xrate(http_requests[5s]) {path="/foo"} 2 {path="/bar"} 2 -eval instant at 50s xrate0(http_requests[5s]) - {path="/foo"} 2 - {path="/bar"} 2 +# eval instant at 50s xrate0(http_requests[5s]) +# {path="/foo"} 2 +# {path="/bar"} 2 eval instant at 50s xrate(http_requests[3s]) {path="/foo"} 2 {path="/bar"} 2 -eval instant at 50s xrate0(http_requests[3s]) - {path="/foo"} 3.3333333333333335 - {path="/bar"} 3.3333333333333335 +# eval instant at 50s xrate0(http_requests[3s]) +# {path="/foo"} 3.3333333333333335 +# {path="/bar"} 3.3333333333333335 eval instant at 49s xrate(http_requests[3s]) -eval instant at 49s xrate0(http_requests[3s]) +# eval instant at 49s xrate0(http_requests[3s]) clear @@ -376,8 +376,8 @@ eval instant at 30m increase(http_requests[30m]) eval instant at 30m xincrease(http_requests[30m]) {path="/foo"} 7 -eval instant at 30m xincrease0(http_requests[30m]) - {path="/foo"} 5 +# eval instant at 30m xincrease0(http_requests[30m]) +# {path="/foo"} 5 clear From 94ccfae619fcd66d45167b478b76907cb24dc25b Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Sat, 22 Apr 2023 13:43:07 -0700 Subject: [PATCH 19/29] drop unused inferScrapeInterval --- promql/functions.go | 35 +++++------------------------------ 1 file changed, 5 insertions(+), 30 deletions(-) diff --git a/promql/functions.go b/promql/functions.go index c3b5f84ec9c..d5b13397bab 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -203,29 +203,6 @@ func extendedRate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHel }) } -// Infers the scrape interval in msec by taking the median interval among the first 25. -// By using the median, we avoid the highs (late or missed scrape) and the lows -// (where a late scrape caused the next to be too close). -func inferScrapeInterval(points []Point) int64 { - if len(points) == 0 { - panic(errors.Errorf("no points to work on")) - } - - // Compute up to 25 time intervals and store them in intervalArray. - var intervalArray [25]int64 - var i int - for i = 0; i < (len(points)-1) && i < len(intervalArray); i++ { - intervalArray[i] = points[i+1].T - points[i].T - } - - // Slice and sort the intervals (may be fewer than 25, if points was shorter than 26). - intervals := intervalArray[:i] - sort.Slice(intervals, func(i0, i1 int) bool { return intervals[i0] < intervals[i1] }) - - // Return the median interval. - return intervals[len(intervals)/2] -} - const elideSamplesAfter int = 10 func debugSampleString(points []Point) string { @@ -246,12 +223,11 @@ func debugSampleString(points []Point) string { } // yIncrease is a utility function for yrate/yincrease. -// It calculates the increase of the range (allowing for counter resets and including the startup value from 0.0), +// It calculates the increase of the range (allowing for counter resets and inferring the fresh start value of 0.0), // taking into account the last sample before the range start. func yIncrease(points []Point, rangeStartMsec, rangeEndMsec int64) float64 { - // Debug logging - log.Printf("yincrease: range: %.3f...%.3f", float64(rangeStartMsec)/1000.0, float64(rangeEndMsec)/1000.0) - log.Println("yincrease: samples: ", debugSampleString(points)) + log.Printf("yIncrease: range: %.3f...%.3f\n", float64(rangeStartMsec)/1000.0, float64(rangeEndMsec)/1000.0) + log.Println("yIncrease: samples: ", debugSampleString(points)) lastBeforeRange := float64(0.0) // This provides the 0 fix for a fresh start of a pod. lastInRange := float64(0.0) @@ -267,7 +243,7 @@ func yIncrease(points []Point, rangeStartMsec, rangeEndMsec int64) float64 { } if point.T < rangeEndMsec { lastInRange = point.V - if point.T >= rangeStartMsec && point.V < lastValue { // Counter can only go backwards on a restart. + if point.T >= rangeStartMsec && point.V < lastValue { // If counter went backwards, it must have been a counter reset on process restart inRangeRestartSkew += point.V } } @@ -276,7 +252,7 @@ func yIncrease(points []Point, rangeStartMsec, rangeEndMsec int64) float64 { result := lastInRange - lastBeforeRange + inRangeRestartSkew - log.Printf("yincrease: returning result: %.1f", result) + log.Printf("yIncrease: returning result: %.1f\n", result) return result } @@ -323,7 +299,6 @@ func rangeFromSelectors(vals []parser.Value, args parser.Expressions, enh *EvalN points := vals[0].(Matrix)[0].Points - // TODO: Is rangeSeconds == (rangeEndMsec - rangeStartMsec)/1000.0? If so, let's drop the separate return value. -Colin return points, rangeStartMsec, rangeEndMsec, ms.Range.Seconds() } From ef151f12f01ebd7fa47f110978616612e33969bc Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Sat, 22 Apr 2023 14:37:23 -0700 Subject: [PATCH 20/29] update comments block for yIncrease --- promql/functions.go | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/promql/functions.go b/promql/functions.go index d5b13397bab..401ff26f666 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -222,9 +222,16 @@ func debugSampleString(points []Point) string { return buffer.String() } -// yIncrease is a utility function for yrate/yincrease. -// It calculates the increase of the range (allowing for counter resets and inferring the fresh start value of 0.0), -// taking into account the last sample before the range start. +// yIncrease is a utility function for yincrease/yrate/ydelta. +// It calculates the increase of the range (allowing for counter resets), +// taking into account the last sample before rangeStartMsec. +// It returns the result across the range [rangeStartMsec, rangeEndMsec) +// It always extends the preceding sample's value until the next sample, including the +// unwritten origin sample value at the start of every time series. +// +// It is a linear function, meaning that for adjacent periods p0 and p1 +// ("adjacent" means p0's rangeEndMsec == p1's rangeStartMsec): +// yIncrease(p0) + yIncrease(p1) == yIncrease(p0 + p1) func yIncrease(points []Point, rangeStartMsec, rangeEndMsec int64) float64 { log.Printf("yIncrease: range: %.3f...%.3f\n", float64(rangeStartMsec)/1000.0, float64(rangeEndMsec)/1000.0) log.Println("yIncrease: samples: ", debugSampleString(points)) @@ -243,7 +250,7 @@ func yIncrease(points []Point, rangeStartMsec, rangeEndMsec int64) float64 { } if point.T < rangeEndMsec { lastInRange = point.V - if point.T >= rangeStartMsec && point.V < lastValue { // If counter went backwards, it must have been a counter reset on process restart + if point.T >= rangeStartMsec && point.V < lastValue { // If counter went backwards, it must have been a counter reset on process restart. inRangeRestartSkew += point.V } } From 163708f575595bcfaaab0996568de5f96736a59a Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Sat, 22 Apr 2023 14:38:02 -0700 Subject: [PATCH 21/29] alias ydelta = yincrease for compatibility --- promql/functions.go | 1 + promql/parser/functions.go | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/promql/functions.go b/promql/functions.go index 401ff26f666..629f2ad6f47 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -1319,6 +1319,7 @@ var FunctionCalls = map[string]FunctionCall{ "xincrease": funcXincrease, "xrate": funcXrate, "year": funcYear, + "ydelta": funcYincrease, "yincrease": funcYincrease, "yrate": funcYrate, } diff --git a/promql/parser/functions.go b/promql/parser/functions.go index ef8dda7d05d..e93f7e1b3ac 100644 --- a/promql/parser/functions.go +++ b/promql/parser/functions.go @@ -382,6 +382,12 @@ var Functions = map[string]*Function{ Variadic: 1, ReturnType: ValueTypeVector, }, + "ydelta": { + Name: "ydelta", + ArgTypes: []ValueType{ValueTypeMatrix}, + ReturnType: ValueTypeVector, + ExtRange: true, + }, "yincrease": { Name: "yincrease", ArgTypes: []ValueType{ValueTypeMatrix}, From 900af446fa469fe646e9e13a2b321a190f5cc4bd Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Sat, 22 Apr 2023 14:38:34 -0700 Subject: [PATCH 22/29] extend REPLACE_RATE_FUNCS=2 to replace with yrate/yincrease/ydelta --- promql/functions.go | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/promql/functions.go b/promql/functions.go index 629f2ad6f47..cdbe5563cc2 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -1341,9 +1341,10 @@ var AtModifierUnsafeFunctions = map[string]struct{}{ func init() { // REPLACE_RATE_FUNCS replaces the default rate extrapolation functions - // with xrate functions. This allows for a drop-in replacement and Grafana + // with xrate or yrate functions. This allows for a drop-in replacement and Grafana // auto-completion, Prometheus tooling, Thanos, etc. should still work as expected. - if os.Getenv("REPLACE_RATE_FUNCS") == "1" { + switch os.Getenv("REPLACE_RATE_FUNCS") { + case "1": FunctionCalls["delta"] = FunctionCalls["xdelta"] FunctionCalls["increase"] = FunctionCalls["xincrease"] FunctionCalls["rate"] = FunctionCalls["xrate"] @@ -1361,6 +1362,19 @@ func init() { delete(parser.Functions, "xincrease") delete(parser.Functions, "xrate") fmt.Println("Successfully replaced rate & friends with xrate & friends (and removed xrate & friends function keys).") + case "2": + FunctionCalls["delta"] = FunctionCalls["ydelta"] + parser.Functions["delta"] = parser.Functions["ydelta"] + parser.Functions["delta"].Name = "delta" + + FunctionCalls["increase"] = FunctionCalls["yincrease"] + parser.Functions["increase"] = parser.Functions["yincrease"] + parser.Functions["increase"].Name = "increase" + + FunctionCalls["rate"] = FunctionCalls["yrate"] + parser.Functions["rate"] = parser.Functions["yrate"] + parser.Functions["rate"].Name = "rate" + fmt.Println("Successfully replaced rate/increase/delta with yrate/yincrease/ydelta (and left the latter names available as well).") } } From 3bdabd382b2fd330e6b3de3094b99d4048e08f3c Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Mon, 24 Apr 2023 09:48:44 -0700 Subject: [PATCH 23/29] extend yIncrease with isCounter and implement ydelta using false --- promql/functions.go | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/promql/functions.go b/promql/functions.go index cdbe5563cc2..898350e0f02 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -224,7 +224,7 @@ func debugSampleString(points []Point) string { // yIncrease is a utility function for yincrease/yrate/ydelta. // It calculates the increase of the range (allowing for counter resets), -// taking into account the last sample before rangeStartMsec. +// taking into account the sample at the end of the previous range (just before rangeStartMsec). // It returns the result across the range [rangeStartMsec, rangeEndMsec) // It always extends the preceding sample's value until the next sample, including the // unwritten origin sample value at the start of every time series. @@ -232,11 +232,14 @@ func debugSampleString(points []Point) string { // It is a linear function, meaning that for adjacent periods p0 and p1 // ("adjacent" means p0's rangeEndMsec == p1's rangeStartMsec): // yIncrease(p0) + yIncrease(p1) == yIncrease(p0 + p1) -func yIncrease(points []Point, rangeStartMsec, rangeEndMsec int64) float64 { +func yIncrease(points []Point, rangeStartMsec, rangeEndMsec int64, isCounter bool) float64 { log.Printf("yIncrease: range: %.3f...%.3f\n", float64(rangeStartMsec)/1000.0, float64(rangeEndMsec)/1000.0) log.Println("yIncrease: samples: ", debugSampleString(points)) - lastBeforeRange := float64(0.0) // This provides the 0 fix for a fresh start of a pod. + lastBeforeRange := float64(0.0) // This provides the 0 counter fix for a fresh start of a pod. + if !isCounter && len(points) > 0 { + lastBeforeRange = points[0].V // Gauges don't start at 0. + } lastInRange := float64(0.0) lastValue := float64(0.0) @@ -250,7 +253,9 @@ func yIncrease(points []Point, rangeStartMsec, rangeEndMsec int64) float64 { } if point.T < rangeEndMsec { lastInRange = point.V - if point.T >= rangeStartMsec && point.V < lastValue { // If counter went backwards, it must have been a counter reset on process restart. + if isCounter && + point.T >= rangeStartMsec && + point.V < lastValue { // If counter went backwards, it must have been a counter reset on process restart. inRangeRestartSkew += point.V } } @@ -309,11 +314,20 @@ func rangeFromSelectors(vals []parser.Value, args parser.Expressions, enh *EvalN return points, rangeStartMsec, rangeEndMsec, ms.Range.Seconds() } +// === ydelta(node parser.ValueTypeMatrix) Vector === +func funcYdelta(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector { + points, rangeStartMsec, rangeEndMsec, _ := rangeFromSelectors(vals, args, enh) + + value := yIncrease(points, rangeStartMsec, rangeEndMsec, false) + + return append(enh.Out, Sample{Point: Point{V: value}}) +} + // === yincrease(node parser.ValueTypeMatrix) Vector === func funcYincrease(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector { points, rangeStartMsec, rangeEndMsec, _ := rangeFromSelectors(vals, args, enh) - value := yIncrease(points, rangeStartMsec, rangeEndMsec) + value := yIncrease(points, rangeStartMsec, rangeEndMsec, true) return append(enh.Out, Sample{Point: Point{V: value}}) } @@ -322,7 +336,7 @@ func funcYincrease(vals []parser.Value, args parser.Expressions, enh *EvalNodeHe func funcYrate(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector { points, rangeStartMsec, rangeEndMsec, rangeSeconds := rangeFromSelectors(vals, args, enh) - value := yIncrease(points, rangeStartMsec, rangeEndMsec) / rangeSeconds + value := yIncrease(points, rangeStartMsec, rangeEndMsec, true) / rangeSeconds return append(enh.Out, Sample{Point: Point{V: value}}) } @@ -1319,7 +1333,7 @@ var FunctionCalls = map[string]FunctionCall{ "xincrease": funcXincrease, "xrate": funcXrate, "year": funcYear, - "ydelta": funcYincrease, + "ydelta": funcYdelta, "yincrease": funcYincrease, "yrate": funcYrate, } From 6cbe0717566952210d88de883c9ebc8a436b2d40 Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Tue, 25 Apr 2023 14:52:56 -0700 Subject: [PATCH 24/29] reorder loop in yIncrease for readability --- promql/functions.go | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/promql/functions.go b/promql/functions.go index 898350e0f02..29c2a48b1ac 100644 --- a/promql/functions.go +++ b/promql/functions.go @@ -248,16 +248,17 @@ func yIncrease(points []Point, rangeStartMsec, rangeEndMsec int64, isCounter boo // The points are in time order, so we can just walk the list once and remember the last values // seen "before" and "in" range. If there are no values in range, we use the last value before range. for _, point := range points { - if point.T < rangeStartMsec { - lastBeforeRange = point.V + if point.T >= rangeEndMsec { // Only consider points in [rangeStartMsec, rangeEndMsec). + break } - if point.T < rangeEndMsec { - lastInRange = point.V + lastInRange = point.V + if point.T >= rangeStartMsec { if isCounter && - point.T >= rangeStartMsec && point.V < lastValue { // If counter went backwards, it must have been a counter reset on process restart. inRangeRestartSkew += point.V } + } else { + lastBeforeRange = point.V } lastValue = point.V } From 0cbfd91c9f105e6e1ffb7ac2385a240030c3b0e3 Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Mon, 1 May 2023 20:54:33 -0700 Subject: [PATCH 25/29] new load values at +1000, +2000 --- promql/testdata/functions.test | 1329 +------------------------------- 1 file changed, 31 insertions(+), 1298 deletions(-) diff --git a/promql/testdata/functions.test b/promql/testdata/functions.test index 7a662ea69f3..1e3ab97e214 100644 --- a/promql/testdata/functions.test +++ b/promql/testdata/functions.test @@ -1,4 +1,4 @@ -# Comparison of rate vs xrate vs xrate0. +# Comparison of rate vs xrate vs yrate. load 5s http_requests{path="/foo"} 1001 1001 1001 1002 1002 1002 1002 1002 1003 1003 1003 @@ -6,1318 +6,51 @@ load 5s # With presample eval instant at 50s rate(http_requests[40s]) - {path="/foo"} 0.05 + {path="/foo"} 0.05714285714285714 {path="/bar"} 0.2 eval instant at 50s xrate(http_requests[40s]) {path="/foo"} 0.05 {path="/bar"} 0.2 -eval instant at 50s xincrease(http_requests[40s]) - {path="/foo"} 2 - {path="/bar"} 8 - -# eval instant at 50s xincrease0(http_requests[40s]) -# {path="/foo"} 2 -# {path="/bar"} 8 - -# Edge case--treat like presample -eval instant at 25s xincrease(http_requests[25s]) - {path="/foo"} 1 - {path="/bar"} 5 - -# eval instant at 25s xincrease0(http_requests[25s]) -# {path="/foo"} 1 -# {path="/bar"} 5 - -# Without presample - 0 Fix for fresh pod start -eval instant at 20s xincrease(http_requests[25s]) - {path="/foo"} 1 - {path="/bar"} 4 - -# eval instant at 20s xincrease0(http_requests[25s]) -# {path="/foo"} 1002 -# {path="/bar"} 2005 - - -# -# Timeseries starts inside range, (presumably) goes on after range end. -# - -# 1. Reference eval, aligned with collection. -eval instant at 25s rate(http_requests[50s]) - {path="/foo"} .022 - {path="/bar"} .11 - -eval instant at 25s xrate(http_requests[50s]) - {path="/foo"} .02 - {path="/bar"} .1 - -# eval instant at 25s xrate0(http_requests[50s]) -# {path="/foo"} 20.04 -# {path="/bar"} 40.12 - -# 2. Eval 1 second earlier compared to (1). -# * path="/foo" rate should be same or fractionally higher ("shorter" sample, same actual increase); -# * path="/bar" rate should be same or fractionally lower (80% the increase, 80/96% range covered by sample). -# XXX Seeing ~20% jump for path="/foo" -eval instant at 24s rate(http_requests[50s]) - {path="/foo"} .0265 - {path="/bar"} .106 - -eval instant at 24s xrate(http_requests[50s]) - {path="/foo"} .02 - {path="/bar"} .08 - -# eval instant at 24s xrate0(http_requests[50s]) -# {path="/foo"} 20.04 -# {path="/bar"} 40.1 - -# 3. Eval 1 second later compared to (1). -# * path="/foo" rate should be same or fractionally lower ("longer" sample, same actual increase). -# * path="/bar" rate should be same or fractionally lower ("longer" sample, same actual increase). -# XXX Higher instead of lower for both. -eval instant at 26s rate(http_requests[50s]) - {path="/foo"} .0228 - {path="/bar"} .11399999999999999 - -eval instant at 26s xrate(http_requests[50s]) - {path="/foo"} .02 - {path="/bar"} .1 - -# eval instant at 26s xrate0(http_requests[50s]) -# {path="/foo"} 20.04 -# {path="/bar"} 40.12 - - -# -# Timeseries starts before range, ends within range. -# - -# 4. Reference eval, aligned with collection. -eval instant at 75s rate(http_requests[50s]) - {path="/foo"} .022 - {path="/bar"} .11 - -eval instant at 75s xrate(http_requests[50s]) - {path="/foo"} .02 - {path="/bar"} .1 - -# eval instant at 75s xrate0(http_requests[50s]) -# {path="/foo"} .04 -# {path="/bar"} .2 - -# 5. Eval 1s earlier compared to (4). -# * path="/foo" rate should be same or fractionally lower ("longer" sample, same actual increase). -# * path="/bar" rate should be same or fractionally lower ("longer" sample, same actual increase). -# XXX Higher instead of lower for both. -eval instant at 74s rate(http_requests[50s]) - {path="/foo"} .0228 - {path="/bar"} .114 - -# XXX Higher instead of lower for {path="/bar"}. -eval instant at 74s xrate(http_requests[50s]) - {path="/foo"} .02 - {path="/bar"} .12 - -# eval instant at 74s xrate0(http_requests[50s]) -# {path="/foo"} .038461538461538464 -# {path="/bar"} .23076923076923075 - -# 6. Eval 1s later compared to (4). Rate/increase (should be) fractionally smaller. -# * path="/foo" rate should be same or fractionally higher ("shorter" sample, same actual increase); -# * path="/bar" rate should be same or fractionally lower (80% the increase, 80/96% range covered by sample). -# XXX Seeing ~20% jump for path="/foo", decrease instead of increase for path="/bar". -eval instant at 76s rate(http_requests[50s]) - {path="/foo"} .0265 - {path="/bar"} .106 - -eval instant at 76s xrate(http_requests[50s]) - {path="/foo"} .02 - {path="/bar"} .1 - -# eval instant at 76s xrate0(http_requests[50s]) -# {path="/foo"} 0.04166666666666667 -# {path="/bar"} 0.20833333333333337 - -# -# Evaluation of 10 second rate every 10 seconds, not aligned with collection. -# - -eval instant at 9s rate(http_requests[10s]) - {path="/foo"} 0 - {path="/bar"} 0.2 - -eval instant at 19s rate(http_requests[10s]) - {path="/foo"} 0.2 - {path="/bar"} 0.2 - -eval instant at 29s rate(http_requests[10s]) - {path="/foo"} 0 - {path="/bar"} 0.2 - -eval instant at 39s rate(http_requests[10s]) - {path="/foo"} 0 - {path="/bar"} 0.2 - -# XXX Missed an increase in path="/foo" between timestamps 35 and 40 (both in this eval and the one before). -eval instant at 49s rate(http_requests[10s]) - {path="/foo"} 0 - {path="/bar"} 0.2 - -eval instant at 9s xrate(http_requests[10s]) - {path="/foo"} 0 - {path="/bar"} 0.1 - -# eval instant at 9s xrate0(http_requests[10s]) -# {path="/foo"} 100.1 -# {path="/bar"} 200.2 - -eval instant at 19s xrate(http_requests[10s]) - {path="/foo"} 0.1 +eval instant at 50s yrate(http_requests[40s]) + {path="/foo"} 0.05 {path="/bar"} 0.2 -# eval instant at 19s xrate0(http_requests[10s]) -# {path="/foo"} 0.1 -# {path="/bar"} 0.2 - -eval instant at 29s xrate(http_requests[10s]) - {path="/foo"} 0 - {path="/bar"} 0.2 +eval instant at 50s increase(http_requests[40s]) + {path="/foo"} 2.2857142857142856 + {path="/bar"} 8 -# eval instant at 29s xrate0(http_requests[10s]) -# {path="/foo"} 0 -# {path="/bar"} 0.2 +eval instant at 50s xincrease(http_requests[40s]) + {path="/foo"} 2 + {path="/bar"} 8 -eval instant at 39s xrate(http_requests[10s]) - {path="/foo"} 0 - {path="/bar"} 0.2 +eval instant at 50s yincrease(http_requests[40s]) + {path="/foo"} 2 + {path="/bar"} 8 -# eval instant at 39s xrate0(http_requests[10s]) -# {path="/foo"} 0 -# {path="/bar"} 0.2 -# XXX Sees the increase in path="/foo" between timestamps 35 and 40. -eval instant at 49s xrate(http_requests[10s]) - {path="/foo"} .1 +# Without presample +eval instant at 40s rate(http_requests[40s]) + {path="/foo"} 0.02857142857142857 {path="/bar"} 0.2 -# eval instant at 49s xrate0(http_requests[10s]) -# {path="/foo"} 0.1 -# {path="/bar"} 0.2 - -clear - - - - - -# Testdata for resets() and changes(). -load 5m - http_requests{path="/foo"} 1 2 3 0 1 0 0 1 2 0 - http_requests{path="/bar"} 1 2 3 4 5 1 2 3 4 5 - http_requests{path="/biz"} 0 0 0 0 0 1 1 1 1 1 - -# Tests for resets(). -eval instant at 50m resets(http_requests[5m]) - {path="/foo"} 0 - {path="/bar"} 0 - {path="/biz"} 0 - -eval instant at 50m resets(http_requests[20m]) - {path="/foo"} 1 - {path="/bar"} 0 - {path="/biz"} 0 - -eval instant at 50m resets(http_requests[30m]) - {path="/foo"} 2 - {path="/bar"} 1 - {path="/biz"} 0 - -eval instant at 50m resets(http_requests[50m]) - {path="/foo"} 3 - {path="/bar"} 1 - {path="/biz"} 0 - -eval instant at 50m resets(nonexistent_metric[50m]) - -# Tests for changes(). -eval instant at 50m changes(http_requests[5m]) - {path="/foo"} 0 - {path="/bar"} 0 - {path="/biz"} 0 - -eval instant at 50m changes(http_requests[20m]) - {path="/foo"} 3 - {path="/bar"} 3 - {path="/biz"} 0 - -eval instant at 50m changes(http_requests[30m]) - {path="/foo"} 4 - {path="/bar"} 5 - {path="/biz"} 1 - -eval instant at 50m changes(http_requests[50m]) - {path="/foo"} 8 - {path="/bar"} 9 - {path="/biz"} 1 - -eval instant at 50m changes((http_requests[50m])) - {path="/foo"} 8 - {path="/bar"} 9 - {path="/biz"} 1 - -eval instant at 50m changes(nonexistent_metric[50m]) - -clear - -load 5m - x{a="b"} NaN NaN NaN - x{a="c"} 0 NaN 0 - -eval instant at 15m changes(x[15m]) - {a="b"} 0 - {a="c"} 2 - -clear - -# Tests for increase()/xincrease()/xincrease0()//xrate()/xrate0(). -load 5s - http_requests{path="/foo"} 0+10x10 - http_requests{path="/bar"} 0+10x5 0+10x4 - -# Tests for increase(). -eval instant at 50s increase(http_requests[50s]) - {path="/foo"} 100 - {path="/bar"} 90 - -eval instant at 50s increase(http_requests[100s]) - {path="/foo"} 100 - {path="/bar"} 90 - -# Tests for xincrease()/xincrease0(). -eval instant at 50s xincrease(http_requests[50s]) - {path="/foo"} 100 - {path="/bar"} 90 - -# eval instant at 50s xincrease0(http_requests[50s]) -# {path="/foo"} 100 -# {path="/bar"} 90 - -eval instant at 50s xincrease(http_requests[5s]) - {path="/foo"} 10 - {path="/bar"} 10 - -# eval instant at 50s xincrease0(http_requests[5s]) -# {path="/foo"} 10 -# {path="/bar"} 10 - -eval instant at 50s xincrease(http_requests[3s]) - {path="/foo"} 6 - {path="/bar"} 6 - -# eval instant at 50s xincrease0(http_requests[3s]) -# {path="/foo"} 10 -# {path="/bar"} 10 - -eval instant at 49s xincrease(http_requests[3s]) - -# eval instant at 49s xincrease0(http_requests[3s]) - -# Tests for xrate()/xrate0(). -eval instant at 50s xrate(http_requests[50s]) - {path="/foo"} 2 - {path="/bar"} 1.8 - -# eval instant at 50s xrate0(http_requests[50s]) -# {path="/foo"} 2 -# {path="/bar"} 1.8 - -eval instant at 50s xrate(http_requests[100s]) - {path="/foo"} 1 - {path="/bar"} 0.9 - -# eval instant at 50s xrate0(http_requests[100s]) -# {path="/foo"} 1 -# {path="/bar"} 0.9 - -eval instant at 50s xrate(http_requests[5s]) - {path="/foo"} 2 - {path="/bar"} 2 - -# eval instant at 50s xrate0(http_requests[5s]) -# {path="/foo"} 2 -# {path="/bar"} 2 - -eval instant at 50s xrate(http_requests[3s]) - {path="/foo"} 2 - {path="/bar"} 2 - -# eval instant at 50s xrate0(http_requests[3s]) -# {path="/foo"} 3.3333333333333335 -# {path="/bar"} 3.3333333333333335 - -eval instant at 49s xrate(http_requests[3s]) - -# eval instant at 49s xrate0(http_requests[3s]) - -clear - -# Test for increase()/xincrease/xincrease0 with counter reset. -# When the counter is reset, it always starts at 0. -# So the sequence 3 2 (decreasing counter = reset) is interpreted the same as 3 0 1 2. -# Prometheus assumes it missed the intermediate values 0 and 1. -load 5m - http_requests{path="/foo"} 0 1 2 3 2 3 4 - -eval instant at 30m increase(http_requests[30m]) - {path="/foo"} 7 - -eval instant at 30m xincrease(http_requests[30m]) - {path="/foo"} 7 - -# eval instant at 30m xincrease0(http_requests[30m]) -# {path="/foo"} 5 - -clear - -# Tests for rate(). -load 5m - testcounter_reset_middle 0+10x4 0+10x5 - testcounter_reset_end 0+10x9 0 10 - -# Counter resets at in the middle of range are handled correctly by rate(). -eval instant at 50m rate(testcounter_reset_middle[50m]) - {} 0.03 - -# Counter resets at end of range are ignored by rate(). -eval instant at 50m rate(testcounter_reset_end[5m]) - {} 0 - -clear - -load 5m - calculate_rate_offset{x="a"} 0+10x10 - calculate_rate_offset{x="b"} 0+20x10 - calculate_rate_window 0+80x10 - -# Rates should calculate per-second rates. -eval instant at 50m rate(calculate_rate_window[50m]) - {} 0.26666666666666666 - -eval instant at 50m rate(calculate_rate_offset[10m] offset 5m) - {x="a"} 0.03333333333333333 - {x="b"} 0.06666666666666667 - -clear - -load 4m - testcounter_zero_cutoff{start="0m"} 0+240x10 - testcounter_zero_cutoff{start="1m"} 60+240x10 - testcounter_zero_cutoff{start="2m"} 120+240x10 - testcounter_zero_cutoff{start="3m"} 180+240x10 - testcounter_zero_cutoff{start="4m"} 240+240x10 - testcounter_zero_cutoff{start="5m"} 300+240x10 - -# Zero cutoff for left-side extrapolation. -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="5m"} 0.6 - -# Normal half-interval cutoff for left-side extrapolation. -eval instant at 50m rate(testcounter_zero_cutoff[20m]) - {start="0m"} 0.6 - {start="1m"} 0.6 - {start="2m"} 0.6 - {start="3m"} 0.6 - {start="4m"} 0.6 - {start="5m"} 0.6 - -clear - -# Tests for irate(). -load 5m - http_requests{path="/foo"} 0+10x10 - http_requests{path="/bar"} 0+10x5 0+10x5 - -eval instant at 50m irate(http_requests[50m]) - {path="/foo"} .03333333333333333333 - {path="/bar"} .03333333333333333333 - -# Counter reset. -eval instant at 30m irate(http_requests[50m]) - {path="/foo"} .03333333333333333333 - {path="/bar"} 0 - -clear - -# Tests for delta()/xdelta(). -load 5m - http_requests{path="/foo"} 0 50 300 150 200 - http_requests{path="/bar"} 200 150 300 50 0 - -eval instant at 20m delta(http_requests[20m]) - {path="/foo"} 200 - {path="/bar"} -200 - -eval instant at 20m xdelta(http_requests[20m]) - {path="/foo"} 200 - {path="/bar"} -200 - -eval instant at 20m xdelta(http_requests[19m]) - {path="/foo"} 190 - {path="/bar"} -190 - -eval instant at 20m xdelta(http_requests[1m]) - {path="/foo"} 10 - {path="/bar"} -10 - -clear - -# Tests for idelta(). -load 5m - http_requests{path="/foo"} 0 50 100 150 - http_requests{path="/bar"} 0 50 100 50 - -eval instant at 20m idelta(http_requests[20m]) - {path="/foo"} 50 - {path="/bar"} -50 - -clear - -# Tests for deriv() and predict_linear(). -load 5m - testcounter_reset_middle 0+10x4 0+10x5 - http_requests{job="app-server", instance="1", group="canary"} 0+80x10 - -# deriv should return the same as rate in simple cases. -eval instant at 50m rate(http_requests{group="canary", instance="1", job="app-server"}[50m]) - {group="canary", instance="1", job="app-server"} 0.26666666666666666 - -eval instant at 50m deriv(http_requests{group="canary", instance="1", job="app-server"}[50m]) - {group="canary", instance="1", job="app-server"} 0.26666666666666666 - -# deriv should return correct result. -eval instant at 50m deriv(testcounter_reset_middle[100m]) - {} 0.010606060606060607 - -# predict_linear should return correct result. -# X/s = [ 0, 300, 600, 900,1200,1500,1800,2100,2400,2700,3000] -# Y = [ 0, 10, 20, 30, 40, 0, 10, 20, 30, 40, 50] -# sumX = 16500 -# sumY = 250 -# sumXY = 480000 -# sumX2 = 34650000 -# n = 11 -# covXY = 105000 -# varX = 9900000 -# slope = 0.010606060606060607 -# intercept at t=0: 6.818181818181818 -# intercept at t=3000: 38.63636363636364 -# intercept at t=3000+3600: 76.81818181818181 -eval instant at 50m predict_linear(testcounter_reset_middle[100m], 3600) - {} 76.81818181818181 - -# intercept at t = 3000+3600 = 6600 -eval instant at 50m predict_linear(testcounter_reset_middle[100m] @ 3000, 3600) - {} 76.81818181818181 - -# intercept at t = 600+3600 = 4200 -eval instant at 10m predict_linear(testcounter_reset_middle[100m] @ 3000, 3600) - {} 51.36363636363637 - -# intercept at t = 4200+3600 = 7800 -eval instant at 70m predict_linear(testcounter_reset_middle[100m] @ 3000, 3600) - {} 89.54545454545455 - -# With http_requests, there is a sample value exactly at the end of -# the range, and it has exactly the predicted value, so predict_linear -# can be emulated with deriv. -eval instant at 50m predict_linear(http_requests[50m], 3600) - (http_requests + deriv(http_requests[50m]) * 3600) - {group="canary", instance="1", job="app-server"} 0 - -clear - -# Tests for label_replace. -load 5m - testmetric{src="source-value-10",dst="original-destination-value"} 0 - testmetric{src="source-value-20",dst="original-destination-value"} 1 - -# label_replace does a full-string match and replace. -eval instant at 0m label_replace(testmetric, "dst", "destination-value-$1", "src", "source-value-(.*)") - testmetric{src="source-value-10",dst="destination-value-10"} 0 - testmetric{src="source-value-20",dst="destination-value-20"} 1 - -# label_replace does not do a sub-string match. -eval instant at 0m label_replace(testmetric, "dst", "destination-value-$1", "src", "value-(.*)") - testmetric{src="source-value-10",dst="original-destination-value"} 0 - testmetric{src="source-value-20",dst="original-destination-value"} 1 - -# label_replace works with multiple capture groups. -eval instant at 0m label_replace(testmetric, "dst", "$1-value-$2", "src", "(.*)-value-(.*)") - testmetric{src="source-value-10",dst="source-value-10"} 0 - testmetric{src="source-value-20",dst="source-value-20"} 1 - -# label_replace does not overwrite the destination label if the source label -# does not exist. -eval instant at 0m label_replace(testmetric, "dst", "value-$1", "nonexistent-src", "source-value-(.*)") - testmetric{src="source-value-10",dst="original-destination-value"} 0 - testmetric{src="source-value-20",dst="original-destination-value"} 1 - -# label_replace overwrites the destination label if the source label is empty, -# but matched. -eval instant at 0m label_replace(testmetric, "dst", "value-$1", "nonexistent-src", "(.*)") - testmetric{src="source-value-10",dst="value-"} 0 - testmetric{src="source-value-20",dst="value-"} 1 - -# label_replace does not overwrite the destination label if the source label -# is not matched. -eval instant at 0m label_replace(testmetric, "dst", "value-$1", "src", "non-matching-regex") - testmetric{src="source-value-10",dst="original-destination-value"} 0 - testmetric{src="source-value-20",dst="original-destination-value"} 1 - -eval instant at 0m label_replace((((testmetric))), (("dst")), (("value-$1")), (("src")), (("non-matching-regex"))) - testmetric{src="source-value-10",dst="original-destination-value"} 0 - testmetric{src="source-value-20",dst="original-destination-value"} 1 - -# label_replace drops labels that are set to empty values. -eval instant at 0m label_replace(testmetric, "dst", "", "dst", ".*") - testmetric{src="source-value-10"} 0 - testmetric{src="source-value-20"} 1 - -# label_replace fails when the regex is invalid. -eval_fail instant at 0m label_replace(testmetric, "dst", "value-$1", "src", "(.*") - -# label_replace fails when the destination label name is not a valid Prometheus label name. -eval_fail instant at 0m label_replace(testmetric, "invalid-label-name", "", "src", "(.*)") - -# label_replace fails when there would be duplicated identical output label sets. -eval_fail instant at 0m label_replace(testmetric, "src", "", "", "") - -clear - -# Tests for vector, time and timestamp. -load 10s - metric 1 1 - -eval instant at 0s timestamp(metric) - {} 0 - -eval instant at 5s timestamp(metric) - {} 0 - -eval instant at 5s timestamp(((metric))) - {} 0 - -eval instant at 10s timestamp(metric) - {} 10 - -eval instant at 10s timestamp(((metric))) - {} 10 - -# Tests for label_join. -load 5m - testmetric{src="a",src1="b",src2="c",dst="original-destination-value"} 0 - testmetric{src="d",src1="e",src2="f",dst="original-destination-value"} 1 - -# label_join joins all src values in order. -eval instant at 0m label_join(testmetric, "dst", "-", "src", "src1", "src2") - testmetric{src="a",src1="b",src2="c",dst="a-b-c"} 0 - testmetric{src="d",src1="e",src2="f",dst="d-e-f"} 1 - -# label_join treats non existent src labels as empty strings. -eval instant at 0m label_join(testmetric, "dst", "-", "src", "src3", "src1") - testmetric{src="a",src1="b",src2="c",dst="a--b"} 0 - testmetric{src="d",src1="e",src2="f",dst="d--e"} 1 - -# label_join overwrites the destination label even if the resulting dst label is empty string -eval instant at 0m label_join(testmetric, "dst", "", "emptysrc", "emptysrc1", "emptysrc2") - testmetric{src="a",src1="b",src2="c"} 0 - testmetric{src="d",src1="e",src2="f"} 1 - -# test without src label for label_join -eval instant at 0m label_join(testmetric, "dst", ", ") - testmetric{src="a",src1="b",src2="c"} 0 - testmetric{src="d",src1="e",src2="f"} 1 - -# test without dst label for label_join -load 5m - testmetric1{src="foo",src1="bar",src2="foobar"} 0 - testmetric1{src="fizz",src1="buzz",src2="fizzbuzz"} 1 - -# label_join creates dst label if not present. -eval instant at 0m label_join(testmetric1, "dst", ", ", "src", "src1", "src2") - testmetric1{src="foo",src1="bar",src2="foobar",dst="foo, bar, foobar"} 0 - testmetric1{src="fizz",src1="buzz",src2="fizzbuzz",dst="fizz, buzz, fizzbuzz"} 1 - -clear - -# Tests for vector. -eval instant at 0m vector(1) - {} 1 - -eval instant at 0s vector(time()) - {} 0 - -eval instant at 5s vector(time()) - {} 5 - -eval instant at 60m vector(time()) - {} 3600 - +eval instant at 40s xrate(http_requests[40s]) + {path="/foo"} 0.025 + {path="/bar"} 0.175 -# Tests for clamp_max, clamp_min(), and clamp(). -load 5m - test_clamp{src="clamp-a"} -50 - test_clamp{src="clamp-b"} 0 - test_clamp{src="clamp-c"} 100 +eval instant at 40s yrate(http_requests[40s]) + {path="/foo"} 25.05 + {path="/bar"} 50.2 -eval instant at 0m clamp_max(test_clamp, 75) - {src="clamp-a"} -50 - {src="clamp-b"} 0 - {src="clamp-c"} 75 - -eval instant at 0m clamp_min(test_clamp, -25) - {src="clamp-a"} -25 - {src="clamp-b"} 0 - {src="clamp-c"} 100 - -eval instant at 0m clamp(test_clamp, -25, 75) - {src="clamp-a"} -25 - {src="clamp-b"} 0 - {src="clamp-c"} 75 - -eval instant at 0m clamp_max(clamp_min(test_clamp, -20), 70) - {src="clamp-a"} -20 - {src="clamp-b"} 0 - {src="clamp-c"} 70 - -eval instant at 0m clamp_max((clamp_min(test_clamp, (-20))), (70)) - {src="clamp-a"} -20 - {src="clamp-b"} 0 - {src="clamp-c"} 70 - -eval instant at 0m clamp(test_clamp, 0, NaN) - {src="clamp-a"} NaN - {src="clamp-b"} NaN - {src="clamp-c"} NaN - -eval instant at 0m clamp(test_clamp, NaN, 0) - {src="clamp-a"} NaN - {src="clamp-b"} NaN - {src="clamp-c"} NaN - -eval instant at 0m clamp(test_clamp, 5, -5) - -# Test cases for sgn. -clear -load 5m - test_sgn{src="sgn-a"} -Inf - test_sgn{src="sgn-b"} Inf - test_sgn{src="sgn-c"} NaN - test_sgn{src="sgn-d"} -50 - test_sgn{src="sgn-e"} 0 - test_sgn{src="sgn-f"} 100 - -eval instant at 0m sgn(test_sgn) - {src="sgn-a"} -1 - {src="sgn-b"} 1 - {src="sgn-c"} NaN - {src="sgn-d"} -1 - {src="sgn-e"} 0 - {src="sgn-f"} 1 - - -# Tests for sort/sort_desc. -clear -load 5m - http_requests{job="api-server", instance="0", group="production"} 0+10x10 - http_requests{job="api-server", instance="1", group="production"} 0+20x10 - http_requests{job="api-server", instance="0", group="canary"} 0+30x10 - http_requests{job="api-server", instance="1", group="canary"} 0+40x10 - http_requests{job="api-server", instance="2", group="canary"} NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN - http_requests{job="app-server", instance="0", group="production"} 0+50x10 - http_requests{job="app-server", instance="1", group="production"} 0+60x10 - http_requests{job="app-server", instance="0", group="canary"} 0+70x10 - http_requests{job="app-server", instance="1", group="canary"} 0+80x10 - -eval_ordered instant at 50m sort(http_requests) - http_requests{group="production", instance="0", job="api-server"} 100 - http_requests{group="production", instance="1", job="api-server"} 200 - http_requests{group="canary", instance="0", job="api-server"} 300 - http_requests{group="canary", instance="1", job="api-server"} 400 - http_requests{group="production", instance="0", job="app-server"} 500 - http_requests{group="production", instance="1", job="app-server"} 600 - http_requests{group="canary", instance="0", job="app-server"} 700 - http_requests{group="canary", instance="1", job="app-server"} 800 - http_requests{group="canary", instance="2", job="api-server"} NaN - -eval_ordered instant at 50m sort_desc(http_requests) - http_requests{group="canary", instance="1", job="app-server"} 800 - http_requests{group="canary", instance="0", job="app-server"} 700 - http_requests{group="production", instance="1", job="app-server"} 600 - http_requests{group="production", instance="0", job="app-server"} 500 - http_requests{group="canary", instance="1", job="api-server"} 400 - http_requests{group="canary", instance="0", job="api-server"} 300 - http_requests{group="production", instance="1", job="api-server"} 200 - http_requests{group="production", instance="0", job="api-server"} 100 - http_requests{group="canary", instance="2", job="api-server"} NaN - -# Tests for holt_winters -clear - -# positive trends -load 10s - http_requests{job="api-server", instance="0", group="production"} 0+10x1000 100+30x1000 - http_requests{job="api-server", instance="1", group="production"} 0+20x1000 200+30x1000 - http_requests{job="api-server", instance="0", group="canary"} 0+30x1000 300+80x1000 - http_requests{job="api-server", instance="1", group="canary"} 0+40x2000 - -eval instant at 8000s holt_winters(http_requests[1m], 0.01, 0.1) - {job="api-server", instance="0", group="production"} 8000 - {job="api-server", instance="1", group="production"} 16000 - {job="api-server", instance="0", group="canary"} 24000 - {job="api-server", instance="1", group="canary"} 32000 - -# negative trends -clear -load 10s - http_requests{job="api-server", instance="0", group="production"} 8000-10x1000 - http_requests{job="api-server", instance="1", group="production"} 0-20x1000 - http_requests{job="api-server", instance="0", group="canary"} 0+30x1000 300-80x1000 - http_requests{job="api-server", instance="1", group="canary"} 0-40x1000 0+40x1000 - -eval instant at 8000s holt_winters(http_requests[1m], 0.01, 0.1) - {job="api-server", instance="0", group="production"} 0 - {job="api-server", instance="1", group="production"} -16000 - {job="api-server", instance="0", group="canary"} 24000 - {job="api-server", instance="1", group="canary"} -32000 - -# Tests for avg_over_time -clear -load 10s - metric 1 2 3 4 5 - metric2 1 2 3 4 Inf - metric3 1 2 3 4 -Inf - metric4 1 2 3 Inf -Inf - metric5 Inf 0 Inf - metric5b Inf 0 Inf - metric5c Inf Inf Inf -Inf - metric6 1 2 3 -Inf -Inf - metric6b -Inf 0 -Inf - metric6c -Inf -Inf -Inf Inf - metric7 1 2 -Inf -Inf Inf - metric8 9.988465674311579e+307 9.988465674311579e+307 - metric9 -9.988465674311579e+307 -9.988465674311579e+307 -9.988465674311579e+307 - metric10 -9.988465674311579e+307 9.988465674311579e+307 - -eval instant at 1m avg_over_time(metric[1m]) - {} 3 - -eval instant at 1m sum_over_time(metric[1m])/count_over_time(metric[1m]) - {} 3 - -eval instant at 1m avg_over_time(metric2[1m]) - {} Inf - -eval instant at 1m sum_over_time(metric2[1m])/count_over_time(metric2[1m]) - {} Inf - -eval instant at 1m avg_over_time(metric3[1m]) - {} -Inf - -eval instant at 1m sum_over_time(metric3[1m])/count_over_time(metric3[1m]) - {} -Inf - -eval instant at 1m avg_over_time(metric4[1m]) - {} NaN - -eval instant at 1m sum_over_time(metric4[1m])/count_over_time(metric4[1m]) - {} NaN - -eval instant at 1m avg_over_time(metric5[1m]) - {} Inf - -eval instant at 1m sum_over_time(metric5[1m])/count_over_time(metric5[1m]) - {} Inf - -eval instant at 1m avg_over_time(metric5b[1m]) - {} Inf - -eval instant at 1m sum_over_time(metric5b[1m])/count_over_time(metric5b[1m]) - {} Inf - -eval instant at 1m avg_over_time(metric5c[1m]) - {} NaN - -eval instant at 1m sum_over_time(metric5c[1m])/count_over_time(metric5c[1m]) - {} NaN - -eval instant at 1m avg_over_time(metric6[1m]) - {} -Inf - -eval instant at 1m sum_over_time(metric6[1m])/count_over_time(metric6[1m]) - {} -Inf - -eval instant at 1m avg_over_time(metric6b[1m]) - {} -Inf - -eval instant at 1m sum_over_time(metric6b[1m])/count_over_time(metric6b[1m]) - {} -Inf - -eval instant at 1m avg_over_time(metric6c[1m]) - {} NaN - -eval instant at 1m sum_over_time(metric6c[1m])/count_over_time(metric6c[1m]) - {} NaN - - -eval instant at 1m avg_over_time(metric7[1m]) - {} NaN - -eval instant at 1m sum_over_time(metric7[1m])/count_over_time(metric7[1m]) - {} NaN - -eval instant at 1m avg_over_time(metric8[1m]) - {} 9.988465674311579e+307 - -# This overflows float64. -eval instant at 1m sum_over_time(metric8[1m])/count_over_time(metric8[1m]) - {} Inf - -eval instant at 1m avg_over_time(metric9[1m]) - {} -9.988465674311579e+307 - -# This overflows float64. -eval instant at 1m sum_over_time(metric9[1m])/count_over_time(metric9[1m]) - {} -Inf - -eval instant at 1m avg_over_time(metric10[1m]) - {} 0 - -eval instant at 1m sum_over_time(metric10[1m])/count_over_time(metric10[1m]) - {} 0 - -# Tests for stddev_over_time and stdvar_over_time. -clear -load 10s - metric 0 8 8 2 3 - -eval instant at 1m stdvar_over_time(metric[1m]) - {} 10.56 - -eval instant at 1m stddev_over_time(metric[1m]) - {} 3.249615 - -eval instant at 1m stddev_over_time((metric[1m])) - {} 3.249615 - -# Tests for stddev_over_time and stdvar_over_time #4927. -clear -load 10s - metric 1.5990505637277868 1.5990505637277868 1.5990505637277868 - -eval instant at 1m stdvar_over_time(metric[1m]) - {} 0 - -eval instant at 1m stddev_over_time(metric[1m]) - {} 0 - -# Tests for quantile_over_time -clear - -load 10s - data{test="two samples"} 0 1 - data{test="three samples"} 0 1 2 - data{test="uneven samples"} 0 1 4 - -eval instant at 1m quantile_over_time(0, data[1m]) - {test="two samples"} 0 - {test="three samples"} 0 - {test="uneven samples"} 0 - -eval instant at 1m quantile_over_time(0.5, data[1m]) - {test="two samples"} 0.5 - {test="three samples"} 1 - {test="uneven samples"} 1 - -eval instant at 1m quantile_over_time(0.75, data[1m]) - {test="two samples"} 0.75 - {test="three samples"} 1.5 - {test="uneven samples"} 2.5 - -eval instant at 1m quantile_over_time(0.8, data[1m]) - {test="two samples"} 0.8 - {test="three samples"} 1.6 - {test="uneven samples"} 2.8 - -eval instant at 1m quantile_over_time(1, data[1m]) - {test="two samples"} 1 - {test="three samples"} 2 - {test="uneven samples"} 4 - -eval instant at 1m quantile_over_time(-1, data[1m]) - {test="two samples"} -Inf - {test="three samples"} -Inf - {test="uneven samples"} -Inf - -eval instant at 1m quantile_over_time(2, data[1m]) - {test="two samples"} +Inf - {test="three samples"} +Inf - {test="uneven samples"} +Inf - -eval instant at 1m (quantile_over_time(2, (data[1m]))) - {test="two samples"} +Inf - {test="three samples"} +Inf - {test="uneven samples"} +Inf - -clear - -# Test time-related functions. -eval instant at 0m year() - {} 1970 - -eval instant at 1ms time() - 0.001 - -eval instant at 50m time() - 3000 - -eval instant at 0m year(vector(1136239445)) - {} 2006 - -eval instant at 0m month() - {} 1 - -eval instant at 0m month(vector(1136239445)) - {} 1 - -eval instant at 0m day_of_month() - {} 1 - -eval instant at 0m day_of_month(vector(1136239445)) - {} 2 - -# Thursday. -eval instant at 0m day_of_week() - {} 4 - -eval instant at 0m day_of_week(vector(1136239445)) - {} 1 - -eval instant at 0m hour() - {} 0 - -eval instant at 0m hour(vector(1136239445)) - {} 22 - -eval instant at 0m minute() - {} 0 - -eval instant at 0m minute(vector(1136239445)) - {} 4 - -# 2008-12-31 23:59:59 just before leap second. -eval instant at 0m year(vector(1230767999)) - {} 2008 - -# 2009-01-01 00:00:00 just after leap second. -eval instant at 0m year(vector(1230768000)) - {} 2009 - -# 2016-02-29 23:59:59 February 29th in leap year. -eval instant at 0m month(vector(1456790399)) + day_of_month(vector(1456790399)) / 100 - {} 2.29 - -# 2016-03-01 00:00:00 March 1st in leap year. -eval instant at 0m month(vector(1456790400)) + day_of_month(vector(1456790400)) / 100 - {} 3.01 - -# February 1st 2016 in leap year. -eval instant at 0m days_in_month(vector(1454284800)) - {} 29 - -# February 1st 2017 not in leap year. -eval instant at 0m days_in_month(vector(1485907200)) - {} 28 - -clear - -# Test duplicate labelset in promql output. -load 5m - testmetric1{src="a",dst="b"} 0 - testmetric2{src="a",dst="b"} 1 - -eval_fail instant at 0m changes({__name__=~'testmetric1|testmetric2'}[5m]) - -# Tests for *_over_time -clear - -load 10s - data{type="numbers"} 2 0 3 - data{type="some_nan"} 2 0 NaN - data{type="some_nan2"} 2 NaN 1 - data{type="some_nan3"} NaN 0 1 - data{type="only_nan"} NaN NaN NaN - -eval instant at 1m min_over_time(data[1m]) - {type="numbers"} 0 - {type="some_nan"} 0 - {type="some_nan2"} 1 - {type="some_nan3"} 0 - {type="only_nan"} NaN - -eval instant at 1m max_over_time(data[1m]) - {type="numbers"} 3 - {type="some_nan"} 2 - {type="some_nan2"} 2 - {type="some_nan3"} 1 - {type="only_nan"} NaN - -eval instant at 1m last_over_time(data[1m]) - data{type="numbers"} 3 - data{type="some_nan"} NaN - data{type="some_nan2"} 1 - data{type="some_nan3"} 1 - data{type="only_nan"} NaN - -clear - -# Test for absent() -eval instant at 50m absent(nonexistent) - {} 1 - -eval instant at 50m absent(nonexistent{job="testjob", instance="testinstance", method=~".x"}) - {instance="testinstance", job="testjob"} 1 - -eval instant at 50m absent(nonexistent{job="testjob",job="testjob2",foo="bar"}) - {foo="bar"} 1 - -eval instant at 50m absent(nonexistent{job="testjob",job="testjob2",job="three",foo="bar"}) - {foo="bar"} 1 - -eval instant at 50m absent(nonexistent{job="testjob",job=~"testjob2",foo="bar"}) - {foo="bar"} 1 - -clear - -# Don't return anything when there's something there. -load 5m - http_requests{job="api-server", instance="0", group="production"} 0+10x10 - -eval instant at 50m absent(http_requests) - -eval instant at 50m absent(sum(http_requests)) - -clear - -eval instant at 50m absent(sum(nonexistent{job="testjob", instance="testinstance"})) - {} 1 - -eval instant at 50m absent(max(nonexistant)) - {} 1 - -eval instant at 50m absent(nonexistant > 1) - {} 1 - -eval instant at 50m absent(a + b) - {} 1 - -eval instant at 50m absent(a and b) - {} 1 - -eval instant at 50m absent(rate(nonexistant[5m])) - {} 1 - -clear - -# Testdata for absent_over_time() -eval instant at 1m absent_over_time(http_requests[5m]) - {} 1 - -eval instant at 1m absent_over_time(http_requests{handler="/foo"}[5m]) - {handler="/foo"} 1 - -eval instant at 1m absent_over_time(http_requests{handler!="/foo"}[5m]) - {} 1 - -eval instant at 1m absent_over_time(http_requests{handler="/foo", handler="/bar", handler="/foobar"}[5m]) - {} 1 - -eval instant at 1m absent_over_time(rate(nonexistant[5m])[5m:]) - {} 1 - -eval instant at 1m absent_over_time(http_requests{handler="/foo", handler="/bar", instance="127.0.0.1"}[5m]) - {instance="127.0.0.1"} 1 - -load 1m - http_requests{path="/foo",instance="127.0.0.1",job="httpd"} 1+1x10 - http_requests{path="/bar",instance="127.0.0.1",job="httpd"} 1+1x10 - httpd_handshake_failures_total{instance="127.0.0.1",job="node"} 1+1x15 - httpd_log_lines_total{instance="127.0.0.1",job="node"} 1 - ssl_certificate_expiry_seconds{job="ingress"} NaN NaN NaN NaN NaN - -eval instant at 5m absent_over_time(http_requests[5m]) - -eval instant at 5m absent_over_time(rate(http_requests[5m])[5m:1m]) - -eval instant at 0m absent_over_time(httpd_log_lines_total[30s]) - -eval instant at 1m absent_over_time(httpd_log_lines_total[30s]) - {} 1 - -eval instant at 15m absent_over_time(http_requests[5m]) - -eval instant at 16m absent_over_time(http_requests[5m]) - {} 1 - -eval instant at 16m absent_over_time(http_requests[6m]) - -eval instant at 16m absent_over_time(httpd_handshake_failures_total[1m]) - -eval instant at 16m absent_over_time({instance="127.0.0.1"}[5m]) - -eval instant at 21m absent_over_time({instance="127.0.0.1"}[5m]) - {instance="127.0.0.1"} 1 - -eval instant at 21m absent_over_time({instance="127.0.0.1"}[20m]) - -eval instant at 21m absent_over_time({job="grok"}[20m]) - {job="grok"} 1 - -eval instant at 30m absent_over_time({instance="127.0.0.1"}[5m:5s]) - {} 1 - -eval instant at 5m absent_over_time({job="ingress"}[4m]) - -eval instant at 10m absent_over_time({job="ingress"}[4m]) - {job="ingress"} 1 - -clear - -# Testdata for present_over_time() -eval instant at 1m present_over_time(http_requests[5m]) - -eval instant at 1m present_over_time(http_requests{handler="/foo"}[5m]) - -eval instant at 1m present_over_time(http_requests{handler!="/foo"}[5m]) - -eval instant at 1m present_over_time(http_requests{handler="/foo", handler="/bar", handler="/foobar"}[5m]) - -eval instant at 1m present_over_time(rate(nonexistant[5m])[5m:]) - -eval instant at 1m present_over_time(http_requests{handler="/foo", handler="/bar", instance="127.0.0.1"}[5m]) - -load 1m - http_requests{path="/foo",instance="127.0.0.1",job="httpd"} 1+1x10 - http_requests{path="/bar",instance="127.0.0.1",job="httpd"} 1+1x10 - httpd_handshake_failures_total{instance="127.0.0.1",job="node"} 1+1x15 - httpd_log_lines_total{instance="127.0.0.1",job="node"} 1 - ssl_certificate_expiry_seconds{job="ingress"} NaN NaN NaN NaN NaN - -eval instant at 5m present_over_time(http_requests[5m]) - {instance="127.0.0.1", job="httpd", path="/bar"} 1 - {instance="127.0.0.1", job="httpd", path="/foo"} 1 - -eval instant at 5m present_over_time(rate(http_requests[5m])[5m:1m]) - {instance="127.0.0.1", job="httpd", path="/bar"} 1 - {instance="127.0.0.1", job="httpd", path="/foo"} 1 - -eval instant at 0m present_over_time(httpd_log_lines_total[30s]) - {instance="127.0.0.1",job="node"} 1 - -eval instant at 1m present_over_time(httpd_log_lines_total[30s]) - -eval instant at 15m present_over_time(http_requests[5m]) - {instance="127.0.0.1", job="httpd", path="/bar"} 1 - {instance="127.0.0.1", job="httpd", path="/foo"} 1 - -eval instant at 16m present_over_time(http_requests[5m]) - -eval instant at 16m present_over_time(http_requests[6m]) - {instance="127.0.0.1", job="httpd", path="/bar"} 1 - {instance="127.0.0.1", job="httpd", path="/foo"} 1 - -eval instant at 16m present_over_time(httpd_handshake_failures_total[1m]) - {instance="127.0.0.1", job="node"} 1 - -eval instant at 16m present_over_time({instance="127.0.0.1"}[5m]) - {instance="127.0.0.1",job="node"} 1 - -eval instant at 21m present_over_time({job="grok"}[20m]) - -eval instant at 30m present_over_time({instance="127.0.0.1"}[5m:5s]) - -eval instant at 5m present_over_time({job="ingress"}[4m]) - {job="ingress"} 1 - -eval instant at 10m present_over_time({job="ingress"}[4m]) - -clear - -# Testing exp() sqrt() log2() log10() ln() -load 5m - exp_root_log{l="x"} 10 - exp_root_log{l="y"} 20 - -eval instant at 5m exp(exp_root_log) - {l="x"} 22026.465794806718 - {l="y"} 485165195.4097903 - -eval instant at 5m exp(exp_root_log - 10) - {l="y"} 22026.465794806718 - {l="x"} 1 - -eval instant at 5m exp(exp_root_log - 20) - {l="x"} 4.5399929762484854e-05 - {l="y"} 1 - -eval instant at 5m ln(exp_root_log) - {l="x"} 2.302585092994046 - {l="y"} 2.995732273553991 - -eval instant at 5m ln(exp_root_log - 10) - {l="y"} 2.302585092994046 - {l="x"} -Inf - -eval instant at 5m ln(exp_root_log - 20) - {l="y"} -Inf - {l="x"} NaN - -eval instant at 5m exp(ln(exp_root_log)) - {l="y"} 20 - {l="x"} 10 - -eval instant at 5m sqrt(exp_root_log) - {l="x"} 3.1622776601683795 - {l="y"} 4.47213595499958 - -eval instant at 5m log2(exp_root_log) - {l="x"} 3.3219280948873626 - {l="y"} 4.321928094887363 - -eval instant at 5m log2(exp_root_log - 10) - {l="y"} 3.3219280948873626 - {l="x"} -Inf - -eval instant at 5m log2(exp_root_log - 20) - {l="x"} NaN - {l="y"} -Inf - -eval instant at 5m log10(exp_root_log) - {l="x"} 1 - {l="y"} 1.301029995663981 - -eval instant at 5m log10(exp_root_log - 10) - {l="y"} 1 - {l="x"} -Inf +eval instant at 40s increase(http_requests[40s]) + {path="/foo"} 1.1428571428571428 + {path="/bar"} 8 -eval instant at 5m log10(exp_root_log - 20) - {l="x"} NaN - {l="y"} -Inf +eval instant at 40s xincrease(http_requests[40s]) + {path="/foo"} 1 + {path="/bar"} 7 -clear +eval instant at 40s yincrease(http_requests[40s]) + {path="/foo"} 1002 + {path="/bar"} 2008 From 9f2d038721dd74938b9381eb1e4424baa64da28c Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Mon, 1 May 2023 21:13:37 -0700 Subject: [PATCH 26/29] reset; add load values at +1000, +2000 --- promql/testdata/functions.test | 1223 +++++++++++++++++++++++++++++++- 1 file changed, 1188 insertions(+), 35 deletions(-) diff --git a/promql/testdata/functions.test b/promql/testdata/functions.test index 1e3ab97e214..d02c331b742 100644 --- a/promql/testdata/functions.test +++ b/promql/testdata/functions.test @@ -1,56 +1,1209 @@ -# Comparison of rate vs xrate vs yrate. +# Comparison of rate vs xrate. load 5s http_requests{path="/foo"} 1001 1001 1001 1002 1002 1002 1002 1002 1003 1003 1003 http_requests{path="/bar"} 2001 2002 2003 2004 2005 2006 2007 2008 2009 2010 2011 -# With presample -eval instant at 50s rate(http_requests[40s]) - {path="/foo"} 0.05714285714285714 + +# +# Timeseries starts inside range, (presumably) goes on after range end. +# + +# 1. Reference eval, aligned with collection. +eval instant at 25s rate(http_requests[50s]) + {path="/foo"} 0.027179000000000002 + {path="/bar"} 0.10871600000000001 + +eval instant at 25s xrate(http_requests[50s]) + {path="/foo"} .02 + {path="/bar"} 0.08 + +# 2. Eval 1 second earlier compared to (1). +# * path="/foo" rate should be same or fractionally higher ("shorter" sample, same actual increase); +# * path="/bar" rate should be same or fractionally lower (80% the increase, 80/96% range covered by sample). +# XXX Seeing ~20% jump for path="/foo" +eval instant at 24s rate(http_requests[50s]) + {path="/foo"} 0.026178999999999997 + {path="/bar"} 0.10471599999999999 + +eval instant at 24s xrate(http_requests[50s]) + {path="/foo"} .02 + {path="/bar"} .08 + +# 3. Eval 1 second later compared to (1). +# * path="/foo" rate should be same or fractionally lower ("longer" sample, same actual increase). +# * path="/bar" rate should be same or fractionally lower ("longer" sample, same actual increase). +# XXX Higher instead of lower for both. +eval instant at 26s rate(http_requests[50s]) + {path="/foo"} 0.0225432 + {path="/bar"} 0.112716 + +eval instant at 26s xrate(http_requests[50s]) + {path="/foo"} .02 + {path="/bar"} .1 + + +# +# Timeseries starts before range, ends within range. +# + +# 4. Reference eval, aligned with collection. +eval instant at 75s rate(http_requests[50s]) + {path="/foo"} 0.0222568 + {path="/bar"} 0.11128400000000001 + +eval instant at 75s xrate(http_requests[50s]) + {path="/foo"} .02 + {path="/bar"} 0.12 + +# 5. Eval 1s earlier compared to (4). +# * path="/foo" rate should be same or fractionally lower ("longer" sample, same actual increase). +# * path="/bar" rate should be same or fractionally lower ("longer" sample, same actual increase). +# XXX Higher instead of lower for both. +eval instant at 74s rate(http_requests[50s]) + {path="/foo"} 0.023056800000000002 + {path="/bar"} 0.11528400000000001 + +# XXX Higher instead of lower for {path="/bar"}. +eval instant at 74s xrate(http_requests[50s]) + {path="/foo"} .02 + {path="/bar"} .12 + +# 6. Eval 1s later compared to (4). Rate/increase (should be) fractionally smaller. +# * path="/foo" rate should be same or fractionally higher ("shorter" sample, same actual increase); +# * path="/bar" rate should be same or fractionally lower (80% the increase, 80/96% range covered by sample). +# XXX Seeing ~20% jump for path="/foo", decrease instead of increase for path="/bar". +eval instant at 76s rate(http_requests[50s]) + {path="/foo"} 0.026820999999999998 + {path="/bar"} 0.10728399999999999 + +eval instant at 76s xrate(http_requests[50s]) + {path="/foo"} .02 + {path="/bar"} .1 + +# +# Evaluation of 10 second rate every 10 seconds, not aligned with collection. +# + +eval instant at 9s rate(http_requests[10s]) + {path="/foo"} 0 + {path="/bar"} 0.2 + +eval instant at 19s rate(http_requests[10s]) + {path="/foo"} 0.2 {path="/bar"} 0.2 -eval instant at 50s xrate(http_requests[40s]) - {path="/foo"} 0.05 +eval instant at 29s rate(http_requests[10s]) + {path="/foo"} 0 {path="/bar"} 0.2 -eval instant at 50s yrate(http_requests[40s]) - {path="/foo"} 0.05 +eval instant at 39s rate(http_requests[10s]) + {path="/foo"} 0 {path="/bar"} 0.2 -eval instant at 50s increase(http_requests[40s]) - {path="/foo"} 2.2857142857142856 - {path="/bar"} 8 +# XXX Missed an increase in path="/foo" between timestamps 35 and 40 (both in this eval and the one before). +eval instant at 49s rate(http_requests[10s]) + {path="/foo"} 0 + {path="/bar"} 0.2 -eval instant at 50s xincrease(http_requests[40s]) - {path="/foo"} 2 - {path="/bar"} 8 +eval instant at 9s xrate(http_requests[10s]) + {path="/foo"} 0 + {path="/bar"} 0.1 -eval instant at 50s yincrease(http_requests[40s]) - {path="/foo"} 2 - {path="/bar"} 8 +eval instant at 19s xrate(http_requests[10s]) + {path="/foo"} 0.1 + {path="/bar"} 0.2 +eval instant at 29s xrate(http_requests[10s]) + {path="/foo"} 0 + {path="/bar"} 0.2 + +eval instant at 39s xrate(http_requests[10s]) + {path="/foo"} 0 + {path="/bar"} 0.2 -# Without presample -eval instant at 40s rate(http_requests[40s]) - {path="/foo"} 0.02857142857142857 +# XXX Sees the increase in path="/foo" between timestamps 35 and 40. +eval instant at 49s xrate(http_requests[10s]) + {path="/foo"} .1 {path="/bar"} 0.2 -eval instant at 40s xrate(http_requests[40s]) - {path="/foo"} 0.025 - {path="/bar"} 0.175 +clear + + + + + +# Testdata for resets() and changes(). +load 5m + http_requests{path="/foo"} 1 2 3 0 1 0 0 1 2 0 + http_requests{path="/bar"} 1 2 3 4 5 1 2 3 4 5 + http_requests{path="/biz"} 0 0 0 0 0 1 1 1 1 1 + +# Tests for resets(). +eval instant at 50m resets(http_requests[5m]) + {path="/foo"} 0 + {path="/bar"} 0 + {path="/biz"} 0 + +eval instant at 50m resets(http_requests[20m]) + {path="/foo"} 1 + {path="/bar"} 0 + {path="/biz"} 0 + +eval instant at 50m resets(http_requests[30m]) + {path="/foo"} 2 + {path="/bar"} 1 + {path="/biz"} 0 + +eval instant at 50m resets(http_requests[50m]) + {path="/foo"} 3 + {path="/bar"} 1 + {path="/biz"} 0 + +eval instant at 50m resets(nonexistent_metric[50m]) + +# Tests for changes(). +eval instant at 50m changes(http_requests[5m]) + {path="/foo"} 0 + {path="/bar"} 0 + {path="/biz"} 0 + +eval instant at 50m changes(http_requests[20m]) + {path="/foo"} 3 + {path="/bar"} 3 + {path="/biz"} 0 + +eval instant at 50m changes(http_requests[30m]) + {path="/foo"} 4 + {path="/bar"} 5 + {path="/biz"} 1 + +eval instant at 50m changes(http_requests[50m]) + {path="/foo"} 8 + {path="/bar"} 9 + {path="/biz"} 1 + +eval instant at 50m changes((http_requests[50m])) + {path="/foo"} 8 + {path="/bar"} 9 + {path="/biz"} 1 + +eval instant at 50m changes(nonexistent_metric[50m]) + +clear + +load 5m + x{a="b"} NaN NaN NaN + x{a="c"} 0 NaN 0 + +eval instant at 15m changes(x[15m]) + {a="b"} 0 + {a="c"} 2 + +clear + +# Tests for increase()/xincrease()/xrate(). +load 5s + http_requests{path="/foo"} 0+10x10 + http_requests{path="/bar"} 0+10x5 0+10x4 + +# Tests for increase(). +eval instant at 50s increase(http_requests[50s]) + {path="/foo"} 100 + {path="/bar"} 90 + +eval instant at 50s increase(http_requests[100s]) + {path="/foo"} 100 + {path="/bar"} 90 + +# Tests for xincrease(). +eval instant at 50s xincrease(http_requests[50s]) + {path="/foo"} 100 + {path="/bar"} 90 + +eval instant at 50s xincrease(http_requests[5s]) + {path="/foo"} 10 + {path="/bar"} 10 + +eval instant at 50s xincrease(http_requests[3s]) + {path="/foo"} 6 + {path="/bar"} 6 + +eval instant at 49s xincrease(http_requests[3s]) + +# Tests for xrate(). +eval instant at 50s xrate(http_requests[50s]) + {path="/foo"} 2 + {path="/bar"} 1.8 + +eval instant at 50s xrate(http_requests[100s]) + {path="/foo"} 1 + {path="/bar"} 0.9 + +eval instant at 50s xrate(http_requests[5s]) + {path="/foo"} 2 + {path="/bar"} 2 + +eval instant at 50s xrate(http_requests[3s]) + {path="/foo"} 2 + {path="/bar"} 2 + +eval instant at 49s xrate(http_requests[3s]) + +clear + +# Test for increase()/xincrease with counter reset. +# When the counter is reset, it always starts at 0. +# So the sequence 1003 1002 (decreasing counter = reset) is interpreted the same as 1003 1000 1001 1002. +# Prometheus assumes it missed the intermediate values 1000 and 1001. +load 5m + http_requests{path="/foo"} 1000 1001 1002 1003 1002 1003 1004 + +eval instant at 30m increase(http_requests[30m]) + {path="/foo"} 1007 + +eval instant at 30m xincrease(http_requests[30m]) + {path="/foo"} 1007 + +clear + +# Tests for rate(). +load 5m + testcounter_reset_middle 0+10x4 0+10x5 + testcounter_reset_end 0+10x9 0 10 + +# Counter resets at in the middle of range are handled correctly by rate(). +eval instant at 50m rate(testcounter_reset_middle[50m]) + {} 0.03 + +# Counter resets at end of range are ignored by rate(). +eval instant at 50m rate(testcounter_reset_end[5m]) + {} 0 + +clear + +load 5m + calculate_rate_offset{x="a"} 0+10x10 + calculate_rate_offset{x="b"} 0+20x10 + calculate_rate_window 0+80x10 + +# Rates should calculate per-second rates. +eval instant at 50m rate(calculate_rate_window[50m]) + {} 0.26666666666666666 + +eval instant at 50m rate(calculate_rate_offset[10m] offset 5m) + {x="a"} 0.03333333333333333 + {x="b"} 0.06666666666666667 + +clear + +load 4m + testcounter_zero_cutoff{start="0m"} 0+240x10 + testcounter_zero_cutoff{start="1m"} 60+240x10 + testcounter_zero_cutoff{start="2m"} 120+240x10 + testcounter_zero_cutoff{start="3m"} 180+240x10 + testcounter_zero_cutoff{start="4m"} 240+240x10 + testcounter_zero_cutoff{start="5m"} 300+240x10 + +# Zero cutoff for left-side extrapolation. +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="5m"} 0.6 + +# Normal half-interval cutoff for left-side extrapolation. +eval instant at 50m rate(testcounter_zero_cutoff[20m]) + {start="0m"} 0.6 + {start="1m"} 0.6 + {start="2m"} 0.6 + {start="3m"} 0.6 + {start="4m"} 0.6 + {start="5m"} 0.6 + +clear + +# Tests for irate(). +load 5m + http_requests{path="/foo"} 0+10x10 + http_requests{path="/bar"} 0+10x5 0+10x5 + +eval instant at 50m irate(http_requests[50m]) + {path="/foo"} .03333333333333333333 + {path="/bar"} .03333333333333333333 + +# Counter reset. +eval instant at 30m irate(http_requests[50m]) + {path="/foo"} .03333333333333333333 + {path="/bar"} 0 + +clear + +# Tests for delta()/xdelta(). +load 5m + http_requests{path="/foo"} 0 50 300 150 200 + http_requests{path="/bar"} 200 150 300 50 0 + +eval instant at 20m delta(http_requests[20m]) + {path="/foo"} 200 + {path="/bar"} -200 + +eval instant at 20m xdelta(http_requests[20m]) + {path="/foo"} 200 + {path="/bar"} -200 + +eval instant at 20m xdelta(http_requests[19m]) + {path="/foo"} 190 + {path="/bar"} -190 + +eval instant at 20m xdelta(http_requests[1m]) + {path="/foo"} 10 + {path="/bar"} -10 + +clear + +# Tests for idelta(). +load 5m + http_requests{path="/foo"} 0 50 100 150 + http_requests{path="/bar"} 0 50 100 50 + +eval instant at 20m idelta(http_requests[20m]) + {path="/foo"} 50 + {path="/bar"} -50 + +clear + +# Tests for deriv() and predict_linear(). +load 5m + testcounter_reset_middle 0+10x4 0+10x5 + http_requests{job="app-server", instance="1", group="canary"} 0+80x10 + +# deriv should return the same as rate in simple cases. +eval instant at 50m rate(http_requests{group="canary", instance="1", job="app-server"}[50m]) + {group="canary", instance="1", job="app-server"} 0.26666666666666666 + +eval instant at 50m deriv(http_requests{group="canary", instance="1", job="app-server"}[50m]) + {group="canary", instance="1", job="app-server"} 0.26666666666666666 + +# deriv should return correct result. +eval instant at 50m deriv(testcounter_reset_middle[100m]) + {} 0.010606060606060607 + +# predict_linear should return correct result. +# X/s = [ 0, 300, 600, 900,1200,1500,1800,2100,2400,2700,3000] +# Y = [ 0, 10, 20, 30, 40, 0, 10, 20, 30, 40, 50] +# sumX = 16500 +# sumY = 250 +# sumXY = 480000 +# sumX2 = 34650000 +# n = 11 +# covXY = 105000 +# varX = 9900000 +# slope = 0.010606060606060607 +# intercept at t=0: 6.818181818181818 +# intercept at t=3000: 38.63636363636364 +# intercept at t=3000+3600: 76.81818181818181 +eval instant at 50m predict_linear(testcounter_reset_middle[100m], 3600) + {} 76.81818181818181 + +# intercept at t = 3000+3600 = 6600 +eval instant at 50m predict_linear(testcounter_reset_middle[100m] @ 3000, 3600) + {} 76.81818181818181 + +# intercept at t = 600+3600 = 4200 +eval instant at 10m predict_linear(testcounter_reset_middle[100m] @ 3000, 3600) + {} 51.36363636363637 + +# intercept at t = 4200+3600 = 7800 +eval instant at 70m predict_linear(testcounter_reset_middle[100m] @ 3000, 3600) + {} 89.54545454545455 + +# With http_requests, there is a sample value exactly at the end of +# the range, and it has exactly the predicted value, so predict_linear +# can be emulated with deriv. +eval instant at 50m predict_linear(http_requests[50m], 3600) - (http_requests + deriv(http_requests[50m]) * 3600) + {group="canary", instance="1", job="app-server"} 0 + +clear + +# Tests for label_replace. +load 5m + testmetric{src="source-value-10",dst="original-destination-value"} 0 + testmetric{src="source-value-20",dst="original-destination-value"} 1 + +# label_replace does a full-string match and replace. +eval instant at 0m label_replace(testmetric, "dst", "destination-value-$1", "src", "source-value-(.*)") + testmetric{src="source-value-10",dst="destination-value-10"} 0 + testmetric{src="source-value-20",dst="destination-value-20"} 1 + +# label_replace does not do a sub-string match. +eval instant at 0m label_replace(testmetric, "dst", "destination-value-$1", "src", "value-(.*)") + testmetric{src="source-value-10",dst="original-destination-value"} 0 + testmetric{src="source-value-20",dst="original-destination-value"} 1 + +# label_replace works with multiple capture groups. +eval instant at 0m label_replace(testmetric, "dst", "$1-value-$2", "src", "(.*)-value-(.*)") + testmetric{src="source-value-10",dst="source-value-10"} 0 + testmetric{src="source-value-20",dst="source-value-20"} 1 + +# label_replace does not overwrite the destination label if the source label +# does not exist. +eval instant at 0m label_replace(testmetric, "dst", "value-$1", "nonexistent-src", "source-value-(.*)") + testmetric{src="source-value-10",dst="original-destination-value"} 0 + testmetric{src="source-value-20",dst="original-destination-value"} 1 + +# label_replace overwrites the destination label if the source label is empty, +# but matched. +eval instant at 0m label_replace(testmetric, "dst", "value-$1", "nonexistent-src", "(.*)") + testmetric{src="source-value-10",dst="value-"} 0 + testmetric{src="source-value-20",dst="value-"} 1 + +# label_replace does not overwrite the destination label if the source label +# is not matched. +eval instant at 0m label_replace(testmetric, "dst", "value-$1", "src", "non-matching-regex") + testmetric{src="source-value-10",dst="original-destination-value"} 0 + testmetric{src="source-value-20",dst="original-destination-value"} 1 + +eval instant at 0m label_replace((((testmetric))), (("dst")), (("value-$1")), (("src")), (("non-matching-regex"))) + testmetric{src="source-value-10",dst="original-destination-value"} 0 + testmetric{src="source-value-20",dst="original-destination-value"} 1 + +# label_replace drops labels that are set to empty values. +eval instant at 0m label_replace(testmetric, "dst", "", "dst", ".*") + testmetric{src="source-value-10"} 0 + testmetric{src="source-value-20"} 1 + +# label_replace fails when the regex is invalid. +eval_fail instant at 0m label_replace(testmetric, "dst", "value-$1", "src", "(.*") + +# label_replace fails when the destination label name is not a valid Prometheus label name. +eval_fail instant at 0m label_replace(testmetric, "invalid-label-name", "", "src", "(.*)") + +# label_replace fails when there would be duplicated identical output label sets. +eval_fail instant at 0m label_replace(testmetric, "src", "", "", "") + +clear + +# Tests for vector, time and timestamp. +load 10s + metric 1 1 + +eval instant at 0s timestamp(metric) + {} 0 + +eval instant at 5s timestamp(metric) + {} 0 + +eval instant at 5s timestamp(((metric))) + {} 0 + +eval instant at 10s timestamp(metric) + {} 10 + +eval instant at 10s timestamp(((metric))) + {} 10 + +# Tests for label_join. +load 5m + testmetric{src="a",src1="b",src2="c",dst="original-destination-value"} 0 + testmetric{src="d",src1="e",src2="f",dst="original-destination-value"} 1 + +# label_join joins all src values in order. +eval instant at 0m label_join(testmetric, "dst", "-", "src", "src1", "src2") + testmetric{src="a",src1="b",src2="c",dst="a-b-c"} 0 + testmetric{src="d",src1="e",src2="f",dst="d-e-f"} 1 + +# label_join treats non existent src labels as empty strings. +eval instant at 0m label_join(testmetric, "dst", "-", "src", "src3", "src1") + testmetric{src="a",src1="b",src2="c",dst="a--b"} 0 + testmetric{src="d",src1="e",src2="f",dst="d--e"} 1 + +# label_join overwrites the destination label even if the resulting dst label is empty string +eval instant at 0m label_join(testmetric, "dst", "", "emptysrc", "emptysrc1", "emptysrc2") + testmetric{src="a",src1="b",src2="c"} 0 + testmetric{src="d",src1="e",src2="f"} 1 + +# test without src label for label_join +eval instant at 0m label_join(testmetric, "dst", ", ") + testmetric{src="a",src1="b",src2="c"} 0 + testmetric{src="d",src1="e",src2="f"} 1 + +# test without dst label for label_join +load 5m + testmetric1{src="foo",src1="bar",src2="foobar"} 0 + testmetric1{src="fizz",src1="buzz",src2="fizzbuzz"} 1 + +# label_join creates dst label if not present. +eval instant at 0m label_join(testmetric1, "dst", ", ", "src", "src1", "src2") + testmetric1{src="foo",src1="bar",src2="foobar",dst="foo, bar, foobar"} 0 + testmetric1{src="fizz",src1="buzz",src2="fizzbuzz",dst="fizz, buzz, fizzbuzz"} 1 + +clear + +# Tests for vector. +eval instant at 0m vector(1) + {} 1 + +eval instant at 0s vector(time()) + {} 0 + +eval instant at 5s vector(time()) + {} 5 + +eval instant at 60m vector(time()) + {} 3600 + + +# Tests for clamp_max, clamp_min(), and clamp(). +load 5m + test_clamp{src="clamp-a"} -50 + test_clamp{src="clamp-b"} 0 + test_clamp{src="clamp-c"} 100 + +eval instant at 0m clamp_max(test_clamp, 75) + {src="clamp-a"} -50 + {src="clamp-b"} 0 + {src="clamp-c"} 75 + +eval instant at 0m clamp_min(test_clamp, -25) + {src="clamp-a"} -25 + {src="clamp-b"} 0 + {src="clamp-c"} 100 + +eval instant at 0m clamp(test_clamp, -25, 75) + {src="clamp-a"} -25 + {src="clamp-b"} 0 + {src="clamp-c"} 75 + +eval instant at 0m clamp_max(clamp_min(test_clamp, -20), 70) + {src="clamp-a"} -20 + {src="clamp-b"} 0 + {src="clamp-c"} 70 + +eval instant at 0m clamp_max((clamp_min(test_clamp, (-20))), (70)) + {src="clamp-a"} -20 + {src="clamp-b"} 0 + {src="clamp-c"} 70 + +eval instant at 0m clamp(test_clamp, 0, NaN) + {src="clamp-a"} NaN + {src="clamp-b"} NaN + {src="clamp-c"} NaN + +eval instant at 0m clamp(test_clamp, NaN, 0) + {src="clamp-a"} NaN + {src="clamp-b"} NaN + {src="clamp-c"} NaN + +eval instant at 0m clamp(test_clamp, 5, -5) + +# Test cases for sgn. +clear +load 5m + test_sgn{src="sgn-a"} -Inf + test_sgn{src="sgn-b"} Inf + test_sgn{src="sgn-c"} NaN + test_sgn{src="sgn-d"} -50 + test_sgn{src="sgn-e"} 0 + test_sgn{src="sgn-f"} 100 + +eval instant at 0m sgn(test_sgn) + {src="sgn-a"} -1 + {src="sgn-b"} 1 + {src="sgn-c"} NaN + {src="sgn-d"} -1 + {src="sgn-e"} 0 + {src="sgn-f"} 1 + + +# Tests for sort/sort_desc. +clear +load 5m + http_requests{job="api-server", instance="0", group="production"} 0+10x10 + http_requests{job="api-server", instance="1", group="production"} 0+20x10 + http_requests{job="api-server", instance="0", group="canary"} 0+30x10 + http_requests{job="api-server", instance="1", group="canary"} 0+40x10 + http_requests{job="api-server", instance="2", group="canary"} NaN NaN NaN NaN NaN NaN NaN NaN NaN NaN + http_requests{job="app-server", instance="0", group="production"} 0+50x10 + http_requests{job="app-server", instance="1", group="production"} 0+60x10 + http_requests{job="app-server", instance="0", group="canary"} 0+70x10 + http_requests{job="app-server", instance="1", group="canary"} 0+80x10 + +eval_ordered instant at 50m sort(http_requests) + http_requests{group="production", instance="0", job="api-server"} 100 + http_requests{group="production", instance="1", job="api-server"} 200 + http_requests{group="canary", instance="0", job="api-server"} 300 + http_requests{group="canary", instance="1", job="api-server"} 400 + http_requests{group="production", instance="0", job="app-server"} 500 + http_requests{group="production", instance="1", job="app-server"} 600 + http_requests{group="canary", instance="0", job="app-server"} 700 + http_requests{group="canary", instance="1", job="app-server"} 800 + http_requests{group="canary", instance="2", job="api-server"} NaN + +eval_ordered instant at 50m sort_desc(http_requests) + http_requests{group="canary", instance="1", job="app-server"} 800 + http_requests{group="canary", instance="0", job="app-server"} 700 + http_requests{group="production", instance="1", job="app-server"} 600 + http_requests{group="production", instance="0", job="app-server"} 500 + http_requests{group="canary", instance="1", job="api-server"} 400 + http_requests{group="canary", instance="0", job="api-server"} 300 + http_requests{group="production", instance="1", job="api-server"} 200 + http_requests{group="production", instance="0", job="api-server"} 100 + http_requests{group="canary", instance="2", job="api-server"} NaN + +# Tests for holt_winters +clear + +# positive trends +load 10s + http_requests{job="api-server", instance="0", group="production"} 0+10x1000 100+30x1000 + http_requests{job="api-server", instance="1", group="production"} 0+20x1000 200+30x1000 + http_requests{job="api-server", instance="0", group="canary"} 0+30x1000 300+80x1000 + http_requests{job="api-server", instance="1", group="canary"} 0+40x2000 + +eval instant at 8000s holt_winters(http_requests[1m], 0.01, 0.1) + {job="api-server", instance="0", group="production"} 8000 + {job="api-server", instance="1", group="production"} 16000 + {job="api-server", instance="0", group="canary"} 24000 + {job="api-server", instance="1", group="canary"} 32000 + +# negative trends +clear +load 10s + http_requests{job="api-server", instance="0", group="production"} 8000-10x1000 + http_requests{job="api-server", instance="1", group="production"} 0-20x1000 + http_requests{job="api-server", instance="0", group="canary"} 0+30x1000 300-80x1000 + http_requests{job="api-server", instance="1", group="canary"} 0-40x1000 0+40x1000 + +eval instant at 8000s holt_winters(http_requests[1m], 0.01, 0.1) + {job="api-server", instance="0", group="production"} 0 + {job="api-server", instance="1", group="production"} -16000 + {job="api-server", instance="0", group="canary"} 24000 + {job="api-server", instance="1", group="canary"} -32000 + +# Tests for avg_over_time +clear +load 10s + metric 1 2 3 4 5 + metric2 1 2 3 4 Inf + metric3 1 2 3 4 -Inf + metric4 1 2 3 Inf -Inf + metric5 Inf 0 Inf + metric5b Inf 0 Inf + metric5c Inf Inf Inf -Inf + metric6 1 2 3 -Inf -Inf + metric6b -Inf 0 -Inf + metric6c -Inf -Inf -Inf Inf + metric7 1 2 -Inf -Inf Inf + metric8 9.988465674311579e+307 9.988465674311579e+307 + metric9 -9.988465674311579e+307 -9.988465674311579e+307 -9.988465674311579e+307 + metric10 -9.988465674311579e+307 9.988465674311579e+307 + +eval instant at 1m avg_over_time(metric[1m]) + {} 3 + +eval instant at 1m sum_over_time(metric[1m])/count_over_time(metric[1m]) + {} 3 + +eval instant at 1m avg_over_time(metric2[1m]) + {} Inf + +eval instant at 1m sum_over_time(metric2[1m])/count_over_time(metric2[1m]) + {} Inf + +eval instant at 1m avg_over_time(metric3[1m]) + {} -Inf + +eval instant at 1m sum_over_time(metric3[1m])/count_over_time(metric3[1m]) + {} -Inf + +eval instant at 1m avg_over_time(metric4[1m]) + {} NaN + +eval instant at 1m sum_over_time(metric4[1m])/count_over_time(metric4[1m]) + {} NaN + +eval instant at 1m avg_over_time(metric5[1m]) + {} Inf + +eval instant at 1m sum_over_time(metric5[1m])/count_over_time(metric5[1m]) + {} Inf + +eval instant at 1m avg_over_time(metric5b[1m]) + {} Inf + +eval instant at 1m sum_over_time(metric5b[1m])/count_over_time(metric5b[1m]) + {} Inf + +eval instant at 1m avg_over_time(metric5c[1m]) + {} NaN + +eval instant at 1m sum_over_time(metric5c[1m])/count_over_time(metric5c[1m]) + {} NaN + +eval instant at 1m avg_over_time(metric6[1m]) + {} -Inf + +eval instant at 1m sum_over_time(metric6[1m])/count_over_time(metric6[1m]) + {} -Inf + +eval instant at 1m avg_over_time(metric6b[1m]) + {} -Inf + +eval instant at 1m sum_over_time(metric6b[1m])/count_over_time(metric6b[1m]) + {} -Inf + +eval instant at 1m avg_over_time(metric6c[1m]) + {} NaN + +eval instant at 1m sum_over_time(metric6c[1m])/count_over_time(metric6c[1m]) + {} NaN + + +eval instant at 1m avg_over_time(metric7[1m]) + {} NaN + +eval instant at 1m sum_over_time(metric7[1m])/count_over_time(metric7[1m]) + {} NaN + +eval instant at 1m avg_over_time(metric8[1m]) + {} 9.988465674311579e+307 + +# This overflows float64. +eval instant at 1m sum_over_time(metric8[1m])/count_over_time(metric8[1m]) + {} Inf + +eval instant at 1m avg_over_time(metric9[1m]) + {} -9.988465674311579e+307 + +# This overflows float64. +eval instant at 1m sum_over_time(metric9[1m])/count_over_time(metric9[1m]) + {} -Inf + +eval instant at 1m avg_over_time(metric10[1m]) + {} 0 + +eval instant at 1m sum_over_time(metric10[1m])/count_over_time(metric10[1m]) + {} 0 + +# Tests for stddev_over_time and stdvar_over_time. +clear +load 10s + metric 0 8 8 2 3 + +eval instant at 1m stdvar_over_time(metric[1m]) + {} 10.56 + +eval instant at 1m stddev_over_time(metric[1m]) + {} 3.249615 + +eval instant at 1m stddev_over_time((metric[1m])) + {} 3.249615 + +# Tests for stddev_over_time and stdvar_over_time #4927. +clear +load 10s + metric 1.5990505637277868 1.5990505637277868 1.5990505637277868 + +eval instant at 1m stdvar_over_time(metric[1m]) + {} 0 + +eval instant at 1m stddev_over_time(metric[1m]) + {} 0 + +# Tests for quantile_over_time +clear + +load 10s + data{test="two samples"} 0 1 + data{test="three samples"} 0 1 2 + data{test="uneven samples"} 0 1 4 + +eval instant at 1m quantile_over_time(0, data[1m]) + {test="two samples"} 0 + {test="three samples"} 0 + {test="uneven samples"} 0 + +eval instant at 1m quantile_over_time(0.5, data[1m]) + {test="two samples"} 0.5 + {test="three samples"} 1 + {test="uneven samples"} 1 + +eval instant at 1m quantile_over_time(0.75, data[1m]) + {test="two samples"} 0.75 + {test="three samples"} 1.5 + {test="uneven samples"} 2.5 + +eval instant at 1m quantile_over_time(0.8, data[1m]) + {test="two samples"} 0.8 + {test="three samples"} 1.6 + {test="uneven samples"} 2.8 + +eval instant at 1m quantile_over_time(1, data[1m]) + {test="two samples"} 1 + {test="three samples"} 2 + {test="uneven samples"} 4 + +eval instant at 1m quantile_over_time(-1, data[1m]) + {test="two samples"} -Inf + {test="three samples"} -Inf + {test="uneven samples"} -Inf + +eval instant at 1m quantile_over_time(2, data[1m]) + {test="two samples"} +Inf + {test="three samples"} +Inf + {test="uneven samples"} +Inf + +eval instant at 1m (quantile_over_time(2, (data[1m]))) + {test="two samples"} +Inf + {test="three samples"} +Inf + {test="uneven samples"} +Inf + +clear + +# Test time-related functions. +eval instant at 0m year() + {} 1970 + +eval instant at 1ms time() + 0.001 + +eval instant at 50m time() + 3000 + +eval instant at 0m year(vector(1136239445)) + {} 2006 + +eval instant at 0m month() + {} 1 + +eval instant at 0m month(vector(1136239445)) + {} 1 + +eval instant at 0m day_of_month() + {} 1 + +eval instant at 0m day_of_month(vector(1136239445)) + {} 2 + +# Thursday. +eval instant at 0m day_of_week() + {} 4 + +eval instant at 0m day_of_week(vector(1136239445)) + {} 1 + +eval instant at 0m hour() + {} 0 + +eval instant at 0m hour(vector(1136239445)) + {} 22 + +eval instant at 0m minute() + {} 0 + +eval instant at 0m minute(vector(1136239445)) + {} 4 + +# 2008-12-31 23:59:59 just before leap second. +eval instant at 0m year(vector(1230767999)) + {} 2008 + +# 2009-01-01 00:00:00 just after leap second. +eval instant at 0m year(vector(1230768000)) + {} 2009 + +# 2016-02-29 23:59:59 February 29th in leap year. +eval instant at 0m month(vector(1456790399)) + day_of_month(vector(1456790399)) / 100 + {} 2.29 + +# 2016-03-01 00:00:00 March 1st in leap year. +eval instant at 0m month(vector(1456790400)) + day_of_month(vector(1456790400)) / 100 + {} 3.01 + +# February 1st 2016 in leap year. +eval instant at 0m days_in_month(vector(1454284800)) + {} 29 + +# February 1st 2017 not in leap year. +eval instant at 0m days_in_month(vector(1485907200)) + {} 28 + +clear + +# Test duplicate labelset in promql output. +load 5m + testmetric1{src="a",dst="b"} 0 + testmetric2{src="a",dst="b"} 1 + +eval_fail instant at 0m changes({__name__=~'testmetric1|testmetric2'}[5m]) + +# Tests for *_over_time +clear + +load 10s + data{type="numbers"} 2 0 3 + data{type="some_nan"} 2 0 NaN + data{type="some_nan2"} 2 NaN 1 + data{type="some_nan3"} NaN 0 1 + data{type="only_nan"} NaN NaN NaN + +eval instant at 1m min_over_time(data[1m]) + {type="numbers"} 0 + {type="some_nan"} 0 + {type="some_nan2"} 1 + {type="some_nan3"} 0 + {type="only_nan"} NaN + +eval instant at 1m max_over_time(data[1m]) + {type="numbers"} 3 + {type="some_nan"} 2 + {type="some_nan2"} 2 + {type="some_nan3"} 1 + {type="only_nan"} NaN + +eval instant at 1m last_over_time(data[1m]) + data{type="numbers"} 3 + data{type="some_nan"} NaN + data{type="some_nan2"} 1 + data{type="some_nan3"} 1 + data{type="only_nan"} NaN + +clear + +# Test for absent() +eval instant at 50m absent(nonexistent) + {} 1 + +eval instant at 50m absent(nonexistent{job="testjob", instance="testinstance", method=~".x"}) + {instance="testinstance", job="testjob"} 1 + +eval instant at 50m absent(nonexistent{job="testjob",job="testjob2",foo="bar"}) + {foo="bar"} 1 + +eval instant at 50m absent(nonexistent{job="testjob",job="testjob2",job="three",foo="bar"}) + {foo="bar"} 1 + +eval instant at 50m absent(nonexistent{job="testjob",job=~"testjob2",foo="bar"}) + {foo="bar"} 1 + +clear + +# Don't return anything when there's something there. +load 5m + http_requests{job="api-server", instance="0", group="production"} 0+10x10 + +eval instant at 50m absent(http_requests) + +eval instant at 50m absent(sum(http_requests)) + +clear + +eval instant at 50m absent(sum(nonexistent{job="testjob", instance="testinstance"})) + {} 1 + +eval instant at 50m absent(max(nonexistant)) + {} 1 + +eval instant at 50m absent(nonexistant > 1) + {} 1 + +eval instant at 50m absent(a + b) + {} 1 + +eval instant at 50m absent(a and b) + {} 1 + +eval instant at 50m absent(rate(nonexistant[5m])) + {} 1 + +clear + +# Testdata for absent_over_time() +eval instant at 1m absent_over_time(http_requests[5m]) + {} 1 + +eval instant at 1m absent_over_time(http_requests{handler="/foo"}[5m]) + {handler="/foo"} 1 + +eval instant at 1m absent_over_time(http_requests{handler!="/foo"}[5m]) + {} 1 + +eval instant at 1m absent_over_time(http_requests{handler="/foo", handler="/bar", handler="/foobar"}[5m]) + {} 1 + +eval instant at 1m absent_over_time(rate(nonexistant[5m])[5m:]) + {} 1 + +eval instant at 1m absent_over_time(http_requests{handler="/foo", handler="/bar", instance="127.0.0.1"}[5m]) + {instance="127.0.0.1"} 1 + +load 1m + http_requests{path="/foo",instance="127.0.0.1",job="httpd"} 1+1x10 + http_requests{path="/bar",instance="127.0.0.1",job="httpd"} 1+1x10 + httpd_handshake_failures_total{instance="127.0.0.1",job="node"} 1+1x15 + httpd_log_lines_total{instance="127.0.0.1",job="node"} 1 + ssl_certificate_expiry_seconds{job="ingress"} NaN NaN NaN NaN NaN + +eval instant at 5m absent_over_time(http_requests[5m]) + +eval instant at 5m absent_over_time(rate(http_requests[5m])[5m:1m]) + +eval instant at 0m absent_over_time(httpd_log_lines_total[30s]) + +eval instant at 1m absent_over_time(httpd_log_lines_total[30s]) + {} 1 + +eval instant at 15m absent_over_time(http_requests[5m]) + +eval instant at 16m absent_over_time(http_requests[5m]) + {} 1 + +eval instant at 16m absent_over_time(http_requests[6m]) + +eval instant at 16m absent_over_time(httpd_handshake_failures_total[1m]) + +eval instant at 16m absent_over_time({instance="127.0.0.1"}[5m]) + +eval instant at 21m absent_over_time({instance="127.0.0.1"}[5m]) + {instance="127.0.0.1"} 1 + +eval instant at 21m absent_over_time({instance="127.0.0.1"}[20m]) + +eval instant at 21m absent_over_time({job="grok"}[20m]) + {job="grok"} 1 + +eval instant at 30m absent_over_time({instance="127.0.0.1"}[5m:5s]) + {} 1 + +eval instant at 5m absent_over_time({job="ingress"}[4m]) + +eval instant at 10m absent_over_time({job="ingress"}[4m]) + {job="ingress"} 1 + +clear + +# Testdata for present_over_time() +eval instant at 1m present_over_time(http_requests[5m]) + +eval instant at 1m present_over_time(http_requests{handler="/foo"}[5m]) + +eval instant at 1m present_over_time(http_requests{handler!="/foo"}[5m]) + +eval instant at 1m present_over_time(http_requests{handler="/foo", handler="/bar", handler="/foobar"}[5m]) + +eval instant at 1m present_over_time(rate(nonexistant[5m])[5m:]) + +eval instant at 1m present_over_time(http_requests{handler="/foo", handler="/bar", instance="127.0.0.1"}[5m]) + +load 1m + http_requests{path="/foo",instance="127.0.0.1",job="httpd"} 1+1x10 + http_requests{path="/bar",instance="127.0.0.1",job="httpd"} 1+1x10 + httpd_handshake_failures_total{instance="127.0.0.1",job="node"} 1+1x15 + httpd_log_lines_total{instance="127.0.0.1",job="node"} 1 + ssl_certificate_expiry_seconds{job="ingress"} NaN NaN NaN NaN NaN + +eval instant at 5m present_over_time(http_requests[5m]) + {instance="127.0.0.1", job="httpd", path="/bar"} 1 + {instance="127.0.0.1", job="httpd", path="/foo"} 1 + +eval instant at 5m present_over_time(rate(http_requests[5m])[5m:1m]) + {instance="127.0.0.1", job="httpd", path="/bar"} 1 + {instance="127.0.0.1", job="httpd", path="/foo"} 1 + +eval instant at 0m present_over_time(httpd_log_lines_total[30s]) + {instance="127.0.0.1",job="node"} 1 + +eval instant at 1m present_over_time(httpd_log_lines_total[30s]) + +eval instant at 15m present_over_time(http_requests[5m]) + {instance="127.0.0.1", job="httpd", path="/bar"} 1 + {instance="127.0.0.1", job="httpd", path="/foo"} 1 + +eval instant at 16m present_over_time(http_requests[5m]) + +eval instant at 16m present_over_time(http_requests[6m]) + {instance="127.0.0.1", job="httpd", path="/bar"} 1 + {instance="127.0.0.1", job="httpd", path="/foo"} 1 + +eval instant at 16m present_over_time(httpd_handshake_failures_total[1m]) + {instance="127.0.0.1", job="node"} 1 + +eval instant at 16m present_over_time({instance="127.0.0.1"}[5m]) + {instance="127.0.0.1",job="node"} 1 + +eval instant at 21m present_over_time({job="grok"}[20m]) + +eval instant at 30m present_over_time({instance="127.0.0.1"}[5m:5s]) + +eval instant at 5m present_over_time({job="ingress"}[4m]) + {job="ingress"} 1 + +eval instant at 10m present_over_time({job="ingress"}[4m]) + +clear + +# Testing exp() sqrt() log2() log10() ln() +load 5m + exp_root_log{l="x"} 10 + exp_root_log{l="y"} 20 + +eval instant at 5m exp(exp_root_log) + {l="x"} 22026.465794806718 + {l="y"} 485165195.4097903 + +eval instant at 5m exp(exp_root_log - 10) + {l="y"} 22026.465794806718 + {l="x"} 1 + +eval instant at 5m exp(exp_root_log - 20) + {l="x"} 4.5399929762484854e-05 + {l="y"} 1 + +eval instant at 5m ln(exp_root_log) + {l="x"} 2.302585092994046 + {l="y"} 2.995732273553991 + +eval instant at 5m ln(exp_root_log - 10) + {l="y"} 2.302585092994046 + {l="x"} -Inf + +eval instant at 5m ln(exp_root_log - 20) + {l="y"} -Inf + {l="x"} NaN + +eval instant at 5m exp(ln(exp_root_log)) + {l="y"} 20 + {l="x"} 10 + +eval instant at 5m sqrt(exp_root_log) + {l="x"} 3.1622776601683795 + {l="y"} 4.47213595499958 + +eval instant at 5m log2(exp_root_log) + {l="x"} 3.3219280948873626 + {l="y"} 4.321928094887363 + +eval instant at 5m log2(exp_root_log - 10) + {l="y"} 3.3219280948873626 + {l="x"} -Inf + +eval instant at 5m log2(exp_root_log - 20) + {l="x"} NaN + {l="y"} -Inf -eval instant at 40s yrate(http_requests[40s]) - {path="/foo"} 25.05 - {path="/bar"} 50.2 +eval instant at 5m log10(exp_root_log) + {l="x"} 1 + {l="y"} 1.301029995663981 -eval instant at 40s increase(http_requests[40s]) - {path="/foo"} 1.1428571428571428 - {path="/bar"} 8 +eval instant at 5m log10(exp_root_log - 10) + {l="y"} 1 + {l="x"} -Inf -eval instant at 40s xincrease(http_requests[40s]) - {path="/foo"} 1 - {path="/bar"} 7 +eval instant at 5m log10(exp_root_log - 20) + {l="x"} NaN + {l="y"} -Inf -eval instant at 40s yincrease(http_requests[40s]) - {path="/foo"} 1002 - {path="/bar"} 2008 +clear From 0bdc656d1387a8dd9e7e88cfced6fa0061c88f6d Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Tue, 2 May 2023 08:01:59 -0700 Subject: [PATCH 27/29] add equivalent y* tests for all rate, xrate ones --- promql/testdata/functions.test | 168 +++++++++++++++++++++++++++------ 1 file changed, 139 insertions(+), 29 deletions(-) diff --git a/promql/testdata/functions.test b/promql/testdata/functions.test index d02c331b742..76a34757526 100644 --- a/promql/testdata/functions.test +++ b/promql/testdata/functions.test @@ -18,6 +18,10 @@ eval instant at 25s xrate(http_requests[50s]) {path="/foo"} .02 {path="/bar"} 0.08 +eval instant at 25s yrate(http_requests[50s]) + {path="/foo"} 20.04 + {path="/bar"} 40.1 + # 2. Eval 1 second earlier compared to (1). # * path="/foo" rate should be same or fractionally higher ("shorter" sample, same actual increase); # * path="/bar" rate should be same or fractionally lower (80% the increase, 80/96% range covered by sample). @@ -30,6 +34,10 @@ eval instant at 24s xrate(http_requests[50s]) {path="/foo"} .02 {path="/bar"} .08 +eval instant at 24s yrate(http_requests[50s]) + {path="/foo"} 20.04 + {path="/bar"} 40.1 + # 3. Eval 1 second later compared to (1). # * path="/foo" rate should be same or fractionally lower ("longer" sample, same actual increase). # * path="/bar" rate should be same or fractionally lower ("longer" sample, same actual increase). @@ -42,6 +50,10 @@ eval instant at 26s xrate(http_requests[50s]) {path="/foo"} .02 {path="/bar"} .1 +eval instant at 26s yrate(http_requests[50s]) + {path="/foo"} 20.04 + {path="/bar"} 40.12 + # # Timeseries starts before range, ends within range. @@ -56,6 +68,10 @@ eval instant at 75s xrate(http_requests[50s]) {path="/foo"} .02 {path="/bar"} 0.12 +eval instant at 75s yrate(http_requests[50s]) + {path="/foo"} .02 + {path="/bar"} 0.12 + # 5. Eval 1s earlier compared to (4). # * path="/foo" rate should be same or fractionally lower ("longer" sample, same actual increase). # * path="/bar" rate should be same or fractionally lower ("longer" sample, same actual increase). @@ -69,6 +85,10 @@ eval instant at 74s xrate(http_requests[50s]) {path="/foo"} .02 {path="/bar"} .12 +eval instant at 74s yrate(http_requests[50s]) + {path="/foo"} .02 + {path="/bar"} .12 + # 6. Eval 1s later compared to (4). Rate/increase (should be) fractionally smaller. # * path="/foo" rate should be same or fractionally higher ("shorter" sample, same actual increase); # * path="/bar" rate should be same or fractionally lower (80% the increase, 80/96% range covered by sample). @@ -81,6 +101,10 @@ eval instant at 76s xrate(http_requests[50s]) {path="/foo"} .02 {path="/bar"} .1 +eval instant at 76s yrate(http_requests[50s]) + {path="/foo"} .02 + {path="/bar"} .1 + # # Evaluation of 10 second rate every 10 seconds, not aligned with collection. # @@ -110,23 +134,43 @@ eval instant at 9s xrate(http_requests[10s]) {path="/foo"} 0 {path="/bar"} 0.1 +eval instant at 9s yrate(http_requests[10s]) + {path="/foo"} 100.1 + {path="/bar"} 200.2 + eval instant at 19s xrate(http_requests[10s]) {path="/foo"} 0.1 {path="/bar"} 0.2 +eval instant at 19s yrate(http_requests[10s]) + {path="/foo"} 0.1 + {path="/bar"} 0.2 + eval instant at 29s xrate(http_requests[10s]) {path="/foo"} 0 {path="/bar"} 0.2 +eval instant at 29s yrate(http_requests[10s]) + {path="/foo"} 0 + {path="/bar"} 0.2 + eval instant at 39s xrate(http_requests[10s]) {path="/foo"} 0 {path="/bar"} 0.2 +eval instant at 39s yrate(http_requests[10s]) + {path="/foo"} 0 + {path="/bar"} 0.2 + # XXX Sees the increase in path="/foo" between timestamps 35 and 40. eval instant at 49s xrate(http_requests[10s]) {path="/foo"} .1 {path="/bar"} 0.2 +eval instant at 49s yrate(http_requests[10s]) + {path="/foo"} .1 + {path="/bar"} 0.2 + clear @@ -202,69 +246,115 @@ eval instant at 15m changes(x[15m]) clear -# Tests for increase()/xincrease()/xrate(). +# Tests for increase()/xincrease()/xrate()/yincrease()/yrate(). load 5s - http_requests{path="/foo"} 0+10x10 - http_requests{path="/bar"} 0+10x5 0+10x4 + http_requests{path="/foo"} 1001 1011 1021 1031 1041 1051 1061 1071 1081 1091 1101 + http_requests{path="/bar"} 2001 2011 2021 2031 2041 2051 2001 2011 2021 2031 2041 # Tests for increase(). eval instant at 50s increase(http_requests[50s]) {path="/foo"} 100 - {path="/bar"} 90 + {path="/bar"} 2312.222222222222 + +eval instant at 50s xincrease(http_requests[50s]) + {path="/foo"} 90 + {path="/bar"} 2081 + +eval instant at 50s yincrease(http_requests[50s]) + {path="/foo"} 1091 + {path="/bar"} 4032 eval instant at 50s increase(http_requests[100s]) - {path="/foo"} 100 - {path="/bar"} 90 + {path="/foo"} 104.358 + {path="/bar"} 2412.9888666666666 -# Tests for xincrease(). -eval instant at 50s xincrease(http_requests[50s]) - {path="/foo"} 100 - {path="/bar"} 90 +eval instant at 50s xincrease(http_requests[100s]) + {path="/foo"} 90 + {path="/bar"} 2081 + +eval instant at 50s yincrease(http_requests[100s]) + {path="/foo"} 1091 + {path="/bar"} 4032 + +# eval instant at 50s increase(http_requests[5s]) +# {path="/foo"} 10 +# {path="/bar"} 10 eval instant at 50s xincrease(http_requests[5s]) {path="/foo"} 10 {path="/bar"} 10 -eval instant at 50s xincrease(http_requests[3s]) - {path="/foo"} 6 - {path="/bar"} 6 +eval instant at 50s yincrease(http_requests[5s]) + {path="/foo"} 10 + {path="/bar"} 10 + +# eval instant at 50s xincrease(http_requests[3s]) +# {path="/foo"} 6 +# {path="/bar"} 6 + +eval instant at 50s yincrease(http_requests[3s]) + {path="/foo"} 0 + {path="/bar"} 0 eval instant at 49s xincrease(http_requests[3s]) # Tests for xrate(). eval instant at 50s xrate(http_requests[50s]) - {path="/foo"} 2 - {path="/bar"} 1.8 + {path="/foo"} 1.8 + {path="/bar"} 41.62 + +eval instant at 50s yrate(http_requests[50s]) + {path="/foo"} 21.82 + {path="/bar"} 80.64 eval instant at 50s xrate(http_requests[100s]) - {path="/foo"} 1 - {path="/bar"} 0.9 + {path="/foo"} 0.9 + {path="/bar"} 20.81 + +eval instant at 50s yrate(http_requests[100s]) + {path="/foo"} 10.91 + {path="/bar"} 40.32 eval instant at 50s xrate(http_requests[5s]) {path="/foo"} 2 {path="/bar"} 2 -eval instant at 50s xrate(http_requests[3s]) +eval instant at 50s yincrease(http_requests[5s]) + {path="/foo"} 10 + {path="/bar"} 10 + +eval instant at 50s yrate(http_requests[5s]) {path="/foo"} 2 {path="/bar"} 2 +# eval instant at 50s xrate(http_requests[3s]) +# {path="/foo"} 2 +# {path="/bar"} 2 + +eval instant at 50s yrate(http_requests[3s]) + {path="/foo"} 0 + {path="/bar"} 0 + eval instant at 49s xrate(http_requests[3s]) clear -# Test for increase()/xincrease with counter reset. +# Test for increase()/xincrease()/yincrease() with counter reset. # When the counter is reset, it always starts at 0. -# So the sequence 1003 1002 (decreasing counter = reset) is interpreted the same as 1003 1000 1001 1002. +# So the sequence 1004 1003 (decreasing counter = reset) is interpreted the same as 1004 1001 1002 1003. # Prometheus assumes it missed the intermediate values 1000 and 1001. load 5m - http_requests{path="/foo"} 1000 1001 1002 1003 1002 1003 1004 + http_requests{path="/foo"} 1001 1002 1003 1004 1003 1004 1005 eval instant at 30m increase(http_requests[30m]) - {path="/foo"} 1007 + {path="/foo"} 1208.3999999999999 eval instant at 30m xincrease(http_requests[30m]) {path="/foo"} 1007 +eval instant at 30m yincrease(http_requests[30m]) + {path="/foo"} 2007 + clear # Tests for rate(). @@ -341,26 +431,46 @@ eval instant at 30m irate(http_requests[50m]) clear -# Tests for delta()/xdelta(). +# Tests for delta()/xdelta()/ydelta(). load 5m - http_requests{path="/foo"} 0 50 300 150 200 - http_requests{path="/bar"} 200 150 300 50 0 + http_requests{path="/foo"} 1001 1051 1301 1151 1201 + http_requests{path="/bar"} 2201 2151 2301 2051 2001 eval instant at 20m delta(http_requests[20m]) {path="/foo"} 200 {path="/bar"} -200 eval instant at 20m xdelta(http_requests[20m]) - {path="/foo"} 200 + {path="/foo"} 150 {path="/bar"} -200 +eval instant at 20m ydelta(http_requests[20m]) + {path="/foo"} 150 + {path="/bar"} -150 + +eval instant at 20m delta(http_requests[19m]) + {path="/foo"} 190 + {path="/bar"} -190 + eval instant at 20m xdelta(http_requests[19m]) {path="/foo"} 190 {path="/bar"} -190 -eval instant at 20m xdelta(http_requests[1m]) - {path="/foo"} 10 - {path="/bar"} -10 +eval instant at 20m ydelta(http_requests[19m]) + {path="/foo"} 150 + {path="/bar"} -150 + +# eval instant at 20m delta(http_requests[1m]) +# {path="/foo"} 10 +# {path="/bar"} -10 + +# eval instant at 20m xdelta(http_requests[1m]) +# {path="/foo"} 10 +# {path="/bar"} -10 + +eval instant at 20m ydelta(http_requests[1m]) + {path="/foo"} 0 + {path="/bar"} 0 clear From d933616146404e721c4d35581903cb68b0bd6111 Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Tue, 2 May 2023 14:55:01 -0700 Subject: [PATCH 28/29] update tests for .321 msec offset scrapes when samples start with [12]00[01] --- promql/test.go | 8 +++++- promql/testdata/functions.test | 46 +++++++++++++++++----------------- 2 files changed, 30 insertions(+), 24 deletions(-) diff --git a/promql/test.go b/promql/test.go index a9408bc4c53..ad1706d4858 100644 --- a/promql/test.go +++ b/promql/test.go @@ -317,10 +317,16 @@ func (cmd *loadCmd) set(m labels.Labels, vals ...parser.SequenceValue) { // append the defined time series to the storage. func (cmd *loadCmd) append(a storage.Appender) error { for h, smpls := range cmd.defs { + scrapeOffsetMsec := int64(0) + if len(smpls) > 0 && + (smpls[0].V >= 1000.0 && smpls[0].V <= 1001.0 || smpls[0].V >= 2000.0 && smpls[0].V <= 2001.0) { + scrapeOffsetMsec = 321 + } + m := cmd.metrics[h] for _, s := range smpls { - if _, err := a.Append(0, m, s.T, s.V); err != nil { + if _, err := a.Append(0, m, s.T + scrapeOffsetMsec, s.V); err != nil { return err } } diff --git a/promql/testdata/functions.test b/promql/testdata/functions.test index 76a34757526..851551c6c0c 100644 --- a/promql/testdata/functions.test +++ b/promql/testdata/functions.test @@ -248,33 +248,33 @@ clear # Tests for increase()/xincrease()/xrate()/yincrease()/yrate(). load 5s - http_requests{path="/foo"} 1001 1011 1021 1031 1041 1051 1061 1071 1081 1091 1101 - http_requests{path="/bar"} 2001 2011 2021 2031 2041 2051 2001 2011 2021 2031 2041 + http_requests{path="/foo"} 1000+10x10 + http_requests{path="/bar"} 2000+10x5 2000+10x4 # Tests for increase(). eval instant at 50s increase(http_requests[50s]) {path="/foo"} 100 - {path="/bar"} 2312.222222222222 + {path="/bar"} 2311.1111111111113 eval instant at 50s xincrease(http_requests[50s]) {path="/foo"} 90 - {path="/bar"} 2081 + {path="/bar"} 2080 eval instant at 50s yincrease(http_requests[50s]) - {path="/foo"} 1091 - {path="/bar"} 4032 + {path="/foo"} 1090 + {path="/bar"} 4030 eval instant at 50s increase(http_requests[100s]) {path="/foo"} 104.358 - {path="/bar"} 2412.9888666666666 + {path="/bar"} 2411.829333333333 eval instant at 50s xincrease(http_requests[100s]) {path="/foo"} 90 - {path="/bar"} 2081 + {path="/bar"} 2080 eval instant at 50s yincrease(http_requests[100s]) - {path="/foo"} 1091 - {path="/bar"} 4032 + {path="/foo"} 1090 + {path="/bar"} 4030 # eval instant at 50s increase(http_requests[5s]) # {path="/foo"} 10 @@ -301,19 +301,19 @@ eval instant at 49s xincrease(http_requests[3s]) # Tests for xrate(). eval instant at 50s xrate(http_requests[50s]) {path="/foo"} 1.8 - {path="/bar"} 41.62 + {path="/bar"} 41.6 eval instant at 50s yrate(http_requests[50s]) - {path="/foo"} 21.82 - {path="/bar"} 80.64 + {path="/foo"} 21.8 + {path="/bar"} 80.6 eval instant at 50s xrate(http_requests[100s]) {path="/foo"} 0.9 - {path="/bar"} 20.81 + {path="/bar"} 20.8 eval instant at 50s yrate(http_requests[100s]) - {path="/foo"} 10.91 - {path="/bar"} 40.32 + {path="/foo"} 10.9 + {path="/bar"} 40.3 eval instant at 50s xrate(http_requests[5s]) {path="/foo"} 2 @@ -341,19 +341,19 @@ clear # Test for increase()/xincrease()/yincrease() with counter reset. # When the counter is reset, it always starts at 0. -# So the sequence 1004 1003 (decreasing counter = reset) is interpreted the same as 1004 1001 1002 1003. +# So the sequence 1003 1002 (decreasing counter = reset) is interpreted the same as 1003 1000 1001 1002. # Prometheus assumes it missed the intermediate values 1000 and 1001. load 5m - http_requests{path="/foo"} 1001 1002 1003 1004 1003 1004 1005 + http_requests{path="/foo"} 1000 1001 1002 1003 1002 1003 1004 eval instant at 30m increase(http_requests[30m]) - {path="/foo"} 1208.3999999999999 + {path="/foo"} 1207.2 eval instant at 30m xincrease(http_requests[30m]) - {path="/foo"} 1007 + {path="/foo"} 1006 eval instant at 30m yincrease(http_requests[30m]) - {path="/foo"} 2007 + {path="/foo"} 2005 clear @@ -433,8 +433,8 @@ clear # Tests for delta()/xdelta()/ydelta(). load 5m - http_requests{path="/foo"} 1001 1051 1301 1151 1201 - http_requests{path="/bar"} 2201 2151 2301 2051 2001 + http_requests{path="/foo"} 1000 1050 1300 1150 1200 + http_requests{path="/bar"} 2200 2150 2300 2050 2000 eval instant at 20m delta(http_requests[20m]) {path="/foo"} 200 From af16f0ba18bde30d0f8e2218acc3be493c64e832 Mon Sep 17 00:00:00 2001 From: Colin Kelley Date: Tue, 2 May 2023 15:23:42 -0700 Subject: [PATCH 29/29] update engine_test --- promql/engine_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/promql/engine_test.go b/promql/engine_test.go index 7a5f495548d..d6be67082d9 100644 --- a/promql/engine_test.go +++ b/promql/engine_test.go @@ -1560,7 +1560,7 @@ load 1ms start: 50, end: 80, interval: 10, result: Matrix{ Series{ - Points: []Point{{V: 995, T: 50000}, {V: 994, T: 60000}, {V: 993, T: 70000}, {V: 992, T: 80000}}, + Points: []Point{{V: 996, T: 50000}, {V: 995, T: 60000}, {V: 994, T: 70000}, {V: 993, T: 80000}}, Metric: lblstopk3, }, }, @@ -1578,7 +1578,7 @@ load 1ms start: 70, end: 100, interval: 10, result: Matrix{ Series{ - Points: []Point{{V: 993, T: 70000}, {V: 992, T: 80000}, {V: 991, T: 90000}, {V: 990, T: 100000}}, + Points: []Point{{V: 994, T: 70000}, {V: 993, T: 80000}, {V: 992, T: 90000}, {V: 991, T: 100000}}, Metric: lblstopk3, }, }, @@ -1587,7 +1587,7 @@ load 1ms start: 100, end: 130, interval: 10, result: Matrix{ Series{ - Points: []Point{{V: 990, T: 100000}, {V: 989, T: 110000}, {V: 988, T: 120000}, {V: 987, T: 130000}}, + Points: []Point{{V: 991, T: 100000}, {V: 990, T: 110000}, {V: 989, T: 120000}, {V: 988, T: 130000}}, Metric: lblstopk3, }, },