diff --git a/src/promql/src/extension_plan/fused_series/tests.rs b/src/promql/src/extension_plan/fused_series/tests.rs index 017f4ef6628..c3837187474 100644 --- a/src/promql/src/extension_plan/fused_series/tests.rs +++ b/src/promql/src/extension_plan/fused_series/tests.rs @@ -165,6 +165,7 @@ fn rate_tree_of(schema: Arc) -> NativeTree { range_ms: RANGE_MS, offset_ms: 0, anchor_ms: None, + modifier: None, }, // The input is raw samples, so it has no grid of its own. input_grid: None, @@ -201,6 +202,7 @@ fn instant_window_tree() -> NativeTree { range_ms: RANGE_MS, offset_ms: 0, anchor_ms: None, + modifier: None, }, input_grid: Some(Arc::clone(&grid)), output_grid: grid, @@ -248,6 +250,7 @@ fn nested_window_tree() -> NativeTree { range_ms: 1_000, offset_ms: 0, anchor_ms: None, + modifier: None, }, // The inner window's input is raw samples, so it has no grid of its own. input_grid: None, @@ -262,6 +265,7 @@ fn nested_window_tree() -> NativeTree { range_ms: 2_000, offset_ms: 0, anchor_ms: None, + modifier: None, }, // The outer window reads the inner one's evaluated output, on that output's grid, // and reports one value per field there like the inner one does. diff --git a/src/promql/src/native/display.rs b/src/promql/src/native/display.rs index 3bced152e37..7d578f884ea 100644 --- a/src/promql/src/native/display.rs +++ b/src/promql/src/native/display.rs @@ -196,9 +196,13 @@ fn aggregate_kind(kind: AggregateKind) -> String { /// A window: what it runs, over which range, and where it lands. fn window(op: &BoundWindow) -> String { format!( - "{} [{}ms] offset {} anchor {} on grid {}", + "{} [{}ms]{} offset {} anchor {} on grid {}", function(op.function), op.window.range_ms, + match op.window.modifier { + Some(modifier) => format!(" {} lookback {}ms", modifier.name(), modifier.before_ms()), + None => String::new(), + }, op.window.offset_ms, match op.window.anchor_ms { Some(anchor_ms) => anchor_ms.to_string(), diff --git a/src/promql/src/native/display/tests.rs b/src/promql/src/native/display/tests.rs index 84210f4ee0c..caad9390be3 100644 --- a/src/promql/src/native/display/tests.rs +++ b/src/promql/src/native/display/tests.rs @@ -24,7 +24,7 @@ use super::super::plan::{ }; use crate::map::MathOp; use crate::native::plan::MapOp; -use crate::range_eval::{RangeFunction, RangeWindow}; +use crate::range_eval::{RangeFunction, RangeModifier, RangeWindow}; use crate::select::SortOrder; use crate::vector_match::{BinaryMatching, BinaryOp, Cardinality, FillValues, MatchingLabels}; @@ -81,6 +81,7 @@ fn window_over( range_ms, offset_ms, anchor_ms, + modifier: None, }, input_grid: None, output_grid: Arc::clone(&out), @@ -466,3 +467,30 @@ fn a_vector_operation_names_its_binding() { assert_eq!(tree.to_string().lines().nth(1), Some(line)); } } + +/// A window read with a range modifier names it and the lookback it reads around the window. +#[test] +fn a_modified_window_names_its_modifier() { + let out = grid(0, 60_000, 4); + let mut node = window_over( + raw_input(), + RangeFunction::Rate, + 300_000, + 0, + None, + Arc::clone(&out), + ); + if let NativeExpr::Window { op, .. } = &mut node.expr { + op.window.modifier = Some(RangeModifier::Smoothed { + lookback_ms: 300_000, + }); + } + let tree = NativeTree::try_new(node).unwrap(); + assert_eq!( + tree.to_string().lines().nth(1), + Some( + " root Window rate [300000ms] smoothed lookback 300000ms offset 0 anchor - on grid \ + 0/60000x4" + ) + ); +} diff --git a/src/promql/src/native/execute/tests.rs b/src/promql/src/native/execute/tests.rs index 2c03d2feb60..69ebef36959 100644 --- a/src/promql/src/native/execute/tests.rs +++ b/src/promql/src/native/execute/tests.rs @@ -172,6 +172,7 @@ fn window_with( range_ms, offset_ms: 0, anchor_ms: None, + modifier: None, }, input_grid, output_grid: Arc::clone(&output_grid), @@ -195,6 +196,7 @@ fn window_over_fields(count: usize, output_grid: Arc) -> NativeNode { range_ms: 1, offset_ms: 0, anchor_ms: None, + modifier: None, }, input_grid: None, output_grid: Arc::clone(&output_grid), @@ -833,6 +835,7 @@ fn a_window_over_raw_input_evaluates_onto_its_own_grid() { range_ms: 300_000, offset_ms: 0, anchor_ms: None, + modifier: None, }, RangeFunction::LastOverTime, ) @@ -947,6 +950,7 @@ fn a_window_map_window_chain_keeps_each_layer_on_its_own_grid() { range_ms: 120_000, offset_ms: 0, anchor_ms: None, + modifier: None, }, RangeFunction::LastOverTime, ) @@ -976,6 +980,7 @@ fn a_window_map_window_chain_keeps_each_layer_on_its_own_grid() { range_ms: 240_000, offset_ms: 0, anchor_ms: None, + modifier: None, }, RangeFunction::LastOverTime, ) diff --git a/src/promql/src/native/metrics.rs b/src/promql/src/native/metrics.rs index 68eae22f536..fb0e769d221 100644 --- a/src/promql/src/native/metrics.rs +++ b/src/promql/src/native/metrics.rs @@ -404,6 +404,7 @@ mod tests { range_ms: 300_000, offset_ms: 0, anchor_ms: None, + modifier: None, }, input_grid: None, output_grid: Arc::clone(&grid), diff --git a/src/promql/src/native/plan/tests.rs b/src/promql/src/native/plan/tests.rs index f19d4f3aa12..e6d8ef49d2b 100644 --- a/src/promql/src/native/plan/tests.rs +++ b/src/promql/src/native/plan/tests.rs @@ -178,6 +178,7 @@ fn window_fields(inner: NativeNode, out: Arc, count: usize) -> NativeN range_ms: 300_000, offset_ms: 0, anchor_ms: None, + modifier: None, }, input_grid: None, output_grid: Arc::clone(&out), @@ -202,6 +203,7 @@ fn window_on( range_ms: 300_000, offset_ms: 0, anchor_ms: None, + modifier: None, }, input_grid, output_grid: Arc::clone(&out), @@ -358,6 +360,7 @@ fn a_window_cannot_change_the_field_count() { range_ms: 30_000, offset_ms: 0, anchor_ms: None, + modifier: None, }, input_grid: Some(Arc::clone(&out)), output_grid: Arc::clone(&out), diff --git a/src/promql/src/range_eval.rs b/src/promql/src/range_eval.rs index 02af5288cdc..11aa896690a 100644 --- a/src/promql/src/range_eval.rs +++ b/src/promql/src/range_eval.rs @@ -42,7 +42,9 @@ pub use function::RangeFunction; pub(crate) use function::quantile; /// The samples a range selector reads at evaluation time `T`: source timestamps in -/// `(E - range_ms, E]`, where `E` is `T - offset_ms`, or `anchor_ms - offset_ms` with `@`. +/// `(E - range_ms, E]`, where `E` is `T - offset_ms`, or `anchor_ms - offset_ms` with `@`. A +/// modifier widens that: `anchored` also reads the lookback before `E - range_ms`, `smoothed` the +/// lookback on both sides, and a scan feeding the window has to read that much more. #[derive(Clone, Copy, Debug)] pub struct RangeWindow { /// Positive window length in milliseconds. @@ -52,6 +54,46 @@ pub struct RangeWindow { /// The resolved `@` timestamp in milliseconds, with `start()` and `end()` already replaced /// by the statement's start and end. pub anchor_ms: Option, + /// The experimental `anchored` or `smoothed` selector modifier, which also reads samples + /// around the window. + pub modifier: Option, +} + +/// The experimental range selector modifiers of Prometheus, which read samples beyond the +/// window to pin its boundary values. Not to be confused with an `@` anchor, which fixes the +/// window's end. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum RangeModifier { + /// `anchored`: the window also reads the last sample at or before its start, up to the + /// statement's lookback before it, and takes it as the value at the start. + Anchored { lookback_ms: i64 }, + /// `smoothed`: the window also reads samples up to the lookback before its start and after its + /// end, and interpolates the values at both boundaries. + Smoothed { lookback_ms: i64 }, +} + +impl RangeModifier { + /// How far before the window start the window also reads. + pub(crate) fn before_ms(self) -> i64 { + match self { + Self::Anchored { lookback_ms } | Self::Smoothed { lookback_ms } => lookback_ms, + } + } + + /// How far after the window end the window also reads. + pub(crate) fn after_ms(self) -> i64 { + match self { + Self::Anchored { .. } => 0, + Self::Smoothed { lookback_ms } => lookback_ms, + } + } + + pub(crate) fn name(self) -> &'static str { + match self { + Self::Anchored { .. } => "anchored", + Self::Smoothed { .. } => "smoothed", + } + } } /// The valid samples of one field inside one window, in source order. @@ -63,7 +105,8 @@ pub(crate) struct FloatWindow<'a> { /// that does not correct counters and for every window of a series that never reset. pub counter_resets: &'a [CounterReset], /// Evaluation timestamp `T`. `timestamps` lie in `(T - range_ms, T]`, except for an - /// `@`-anchored `predict_linear`, which evaluates the first point's window at every `T`. + /// `@`-anchored `predict_linear`, which evaluates the first point's window at every `T`, and + /// for a range modifier, which widens the window by the lookback as [`RangeWindow`] says. pub eval_ts: i64, pub range_ms: i64, } @@ -522,6 +565,9 @@ impl RangeEvaluator { format!("invalid {name} factor. Expected: 0 < factor < 1, got: {factor}") }); } + if let Some(modifier) = window.modifier { + check_modifier(input, modifier, function)?; + } let fold_offset = fold_offset(grid.spec(), window)?; let step_invariant = !matches!(function, RangeFunction::PredictLinear(_)); if !step_invariant { @@ -536,20 +582,23 @@ impl RangeEvaluator { } } } - let mut evaluator = Self::with_kernel( - input, - grid, - window, - function.kernel(fold_offset), - step_invariant, - function.indexes_counter_resets(), - )?; + // A modified window corrects counters over the samples it holds rather than through the + // reset index, and has no batch form. + let (kernel, index_resets) = match window.modifier { + Some(modifier) => (function.modified_kernel(modifier), false), + None => ( + function.kernel(fold_offset), + function.indexes_counter_resets(), + ), + }; + let mut evaluator = + Self::with_kernel(input, grid, window, kernel, step_invariant, index_resets)?; evaluator.drops_metric_name = function.drops_metric_name(); evaluator.window_error = window_error; evaluator.function = Some(function); // An anchored selector replays one window at every point, so its windows do not advance // with the grid and a batch would only repeat the same call. - if matches!(evaluator.replay, Replay::PerPoint) { + if matches!(evaluator.replay, Replay::PerPoint) && window.modifier.is_none() { evaluator.batch = function.batch_kernel(); } Ok(evaluator) @@ -1016,6 +1065,20 @@ impl RangeEvaluator { self.window.offset_ms )) })?; + // Refused here rather than when a point is evaluated: a smoothed point can be decided + // before the rest of its window arrives, so whether a later histogram is seen would + // depend on how the input is chunked. + if let Some(modifier) = self.window.modifier + && row + .iter() + .any(|sample| matches!(sample, SampleRef::Histogram(_))) + && self.decided_window_reads(shifted) + { + return Err(DataFusionError::NotImplemented(format!( + "the {} range modifier over native histogram samples", + modifier.name() + ))); + } for (field, sample) in self.fields.iter_mut().zip(&row) { match sample { SampleRef::Missing => {} @@ -1039,6 +1102,34 @@ impl RangeEvaluator { Ok(()) } + /// Whether a sample at `timestamp` on the evaluation timeline lies in the window of a point + /// this evaluator decides: a window ending at `E` reads `(E - range - before, E + after]`, and + /// only the first point's window is read when the result is replayed. + fn decided_window_reads(&self, timestamp: i64) -> bool { + let (before, after) = self.window.modifier.map_or((0, 0), |modifier| { + ( + i128::from(modifier.before_ms()), + i128::from(modifier.after_ms()), + ) + }); + let spec = self.grid.spec(); + let ends = match self.replay { + Replay::PerPoint => i128::from(spec.len), + Replay::Once | Replay::EveryPoint => 1, + }; + let (start, step) = (i128::from(spec.start_ms), i128::from(spec.step_ms)); + // The window ends that read the sample are those in `[timestamp - after, timestamp + + // range + before)`; the first grid point at or after the lower bound decides. + let lowest = i128::from(timestamp) - after; + let highest = i128::from(timestamp) + i128::from(self.window.range_ms) + before; + let first = if lowest <= start { + 0 + } else { + (lowest - start + step - 1) / step + }; + first < ends && start + first * step < highest + } + /// Evaluates grid points up to `ready` on the evaluation timeline, or all remaining points /// when `ready` is `None`. fn evaluate(&mut self, ready: Option) -> Result<()> { @@ -1046,7 +1137,34 @@ impl RangeEvaluator { let state = self.series.as_mut().expect("checked by check_chunk"); let spec = self.grid.spec(); let grid = self.grid.timestamps().map_err(external)?; - let range = i128::from(self.window.range_ms); + // A modifier also reads samples up to the lookback ahead of the window start, so they are + // kept that much longer. + let before = self + .window + .modifier + .map_or(0, |modifier| i128::from(modifier.before_ms())); + // `smoothed` interpolates the window end with the first sample at or after it, which can + // arrive up to the lookback later. A point is decided once every field holds a sample at + // or after it, or once the lookback past it has arrived. The input's last row does not say + // that alone: a stale row is dropped, and a field can hold no value in it. + let ready = match (ready, self.window.modifier) { + (Some(ready), Some(RangeModifier::Smoothed { lookback_ms })) => { + let held = self + .fields + .iter() + .map(|field| { + field + .timestamps + .last() + .map_or(i128::MIN, |&t| i128::from(t)) + }) + .min() + .unwrap_or(i128::MIN); + Some(held.max(ready - i128::from(lookback_ms))) + } + (ready, _) => ready, + }; + let range = i128::from(self.window.range_ms) + before; let mut empty_tail = false; while state.cursor < points_read { let eval_ts = grid[state.cursor]; @@ -1079,7 +1197,9 @@ impl RangeEvaluator { break; }; if earliest > eval_ts { - // Skip the points whose windows end before the earliest sample. + // Skip the points whose windows end before the earliest sample. A smoothed window + // reads past its end, but without a sample at or before its end it has no result, + // as Prometheus returns nothing when the first sample is after the end. let step = i128::from(spec.step_ms); let steps = (i128::from(earliest) - i128::from(spec.start_ms) + step - 1) / step; state.cursor = usize::try_from(steps).map_or(grid.len(), |i| i.min(grid.len())); @@ -1095,6 +1215,7 @@ impl RangeEvaluator { function: self.function, range_ms: self.window.range_ms, offset_ms: self.window.offset_ms, + modifier: self.window.modifier, }; match self.replay { Replay::PerPoint => { @@ -1225,6 +1346,7 @@ struct RowEvaluator<'a> { function: Option, range_ms: i64, offset_ms: i64, + modifier: Option, } impl RowEvaluator<'_> { @@ -1237,6 +1359,8 @@ impl RowEvaluator<'_> { row: &mut Vec>, ) -> Result<()> { row.clear(); + // A smoothed window also reads the samples after its end. + let window_end = window_end + self.modifier.map_or(0, RangeModifier::after_ms); for field in self.fields { let timestamps = &field.timestamps[field.head..]; let len = timestamps.partition_point(|×tamp| timestamp <= window_end); @@ -1267,6 +1391,53 @@ impl RowEvaluator<'_> { } } +/// Rejects a modifier on a function Prometheus does not allow it with, as Prometheus rejects the +/// query (`AnchoredSafeFunctions`, `SmoothedSafeFunctions`), and on evaluated input, which reads +/// no raw samples around its windows. +fn check_modifier( + input: &SeriesSchema, + modifier: RangeModifier, + function: RangeFunction, +) -> Result<()> { + if input.eval_grid.is_some() { + return Err(DataFusionError::NotImplemented(format!( + "the {} range modifier over evaluated series", + modifier.name() + ))); + } + let allowed = match modifier { + RangeModifier::Anchored { .. } => matches!( + function, + RangeFunction::Rate + | RangeFunction::Increase + | RangeFunction::Delta + | RangeFunction::Resets + | RangeFunction::Changes + ), + RangeModifier::Smoothed { .. } => matches!( + function, + RangeFunction::Rate | RangeFunction::Increase | RangeFunction::Delta + ), + }; + if !allowed { + let list = match modifier { + RangeModifier::Anchored { .. } => "changes, delta, increase, rate, resets", + RangeModifier::Smoothed { .. } => "delta, increase, rate", + }; + return Err(DataFusionError::Plan(format!( + "{} modifier can only be used with: {list} - not with {function:?}", + modifier.name() + ))); + } + if modifier.before_ms() < 0 { + return Err(DataFusionError::Plan(format!( + "lookback must not be negative, got {} ms", + modifier.before_ms() + ))); + } + Ok(()) +} + /// The offset samples are shifted by: the selector offset, or for `@` the offset that places /// the anchored window at the first grid point. fn fold_offset(grid: GridSpec, window: RangeWindow) -> Result { diff --git a/src/promql/src/range_eval/function.rs b/src/promql/src/range_eval/function.rs index 55f14264aa7..8415539d631 100644 --- a/src/promql/src/range_eval/function.rs +++ b/src/promql/src/range_eval/function.rs @@ -20,7 +20,7 @@ use crate::functions::{EdgeKind, count_edges, extremum, instant_delta, kahan_inc}; use crate::go_sort; -use crate::range_eval::{BatchKernel, FloatWindow, FloatWindows, WindowKernel}; +use crate::range_eval::{BatchKernel, FloatWindow, FloatWindows, RangeModifier, WindowKernel}; /// A PromQL range-vector function over float and native histogram samples. #[derive(Clone, Copy, Debug, PartialEq)] @@ -170,6 +170,114 @@ impl RangeFunction { } } +impl RangeFunction { + /// Returns the kernel computing this function over windows read with `modifier`, for the + /// functions the modifier is allowed with (`RangeEvaluator::try_new` checks that). + /// + /// The window holds the samples the modifier reads around it as well, on the evaluation + /// timeline, so the window itself is `(eval_ts - range_ms, eval_ts]`. + pub(crate) fn modified_kernel(self, modifier: RangeModifier) -> WindowKernel { + let smoothed = matches!(modifier, RangeModifier::Smoothed { .. }); + match self { + Self::Rate => Box::new(move |window| extended_rate(&window, smoothed, true, true)), + Self::Increase => Box::new(move |window| extended_rate(&window, smoothed, true, false)), + Self::Delta => Box::new(move |window| extended_rate(&window, smoothed, false, false)), + Self::Resets => Box::new(|window| { + anchored_values(&window).map(|values| count_edges(values, EdgeKind::Resets) as f64) + }), + Self::Changes => Box::new(|window| { + anchored_values(&window).map(|values| count_edges(values, EdgeKind::Changes) as f64) + }), + _ => unreachable!("RangeEvaluator::try_new rejects {self:?} with a range modifier"), + } + } +} + +/// Go `sort.Search(n, predicate)`: the first index in `[0, n)` the predicate holds at, or `n`. +fn search(n: usize, predicate: impl Fn(usize) -> bool) -> usize { + (0..n).find(|&i| predicate(i)).unwrap_or(n) +} + +/// Prometheus `extendedRate`: `rate`, `increase` or `delta` over an `anchored` or `smoothed` +/// window. The value at the window start is the last sample at or before it, or for `smoothed` +/// the interpolation between it and the next one; the end is picked or interpolated the same way. +/// Nothing is extrapolated. +fn extended_rate( + window: &FloatWindow<'_>, + smoothed: bool, + is_counter: bool, + is_rate: bool, +) -> Option { + let (timestamps, values) = (window.timestamps, window.values); + let range_start = window.eval_ts - window.range_ms; + let range_end = window.eval_ts; + let mut last = timestamps.len() - 1; + let first = search(last, |i| timestamps[i] > range_start).saturating_sub(1); + if smoothed { + last = search(last, |i| timestamps[i] >= range_end); + } + if timestamps[last] <= range_start || (smoothed && timestamps[first] > range_end) { + return None; + } + let left = if smoothed && timestamps[first] < range_start { + interpolate(window, first, range_start, is_counter) + } else { + values[first] + }; + let right = if smoothed && last > 0 && timestamps[last] > range_end { + interpolate(window, last - 1, range_end, is_counter) + } else { + values[last] + }; + let mut result = right - left; + if is_counter { + // Prometheus `correctForCounterResets` over the samples strictly inside the window; the + // boundary values already account for the resets at the edges. + let inner_start = first + usize::from(timestamps[first] <= range_start); + let inner_end = last + 1 - usize::from(timestamps[last] >= range_end); + let mut correction = 0.0; + let mut previous = left; + for &value in &values[inner_start..inner_end] { + if value < previous { + correction += previous; + } + previous = value; + } + if right < previous { + correction += previous; + } + result += correction; + } + if is_rate { + result /= window.range_ms as f64 / 1000.0; + } + Some(result) +} + +/// Prometheus `interpolate` between the samples at `index` and `index + 1`, at `t`: a counter +/// that reset between them restarts from zero. +fn interpolate(window: &FloatWindow<'_>, index: usize, t: i64, is_counter: bool) -> f64 { + let (t1, t2) = (window.timestamps[index], window.timestamps[index + 1]); + let (mut y1, y2) = (window.values[index], window.values[index + 1]); + if is_counter && y2 < y1 { + y1 = 0.0; + } + y1 + (y2 - y1) * (t - t1) as f64 / (t2 - t1) as f64 +} + +/// Prometheus `pickFirstSampleIndices` for an `anchored` window of floats: the values from the +/// last sample at or before the window start on, or `None` when no sample lies after the start. +fn anchored_values<'a>(window: &FloatWindow<'a>) -> Option<&'a [f64]> { + let range_start = window.eval_ts - window.range_ms; + let after = window + .timestamps + .partition_point(|×tamp| timestamp <= range_start); + if after >= window.timestamps.len() { + return None; + } + Some(&window.values[after.saturating_sub(1)..]) +} + /// Prometheus `funcSumOverTime`. fn sum(values: &[f64]) -> f64 { let (sum, c) = values diff --git a/src/promql/src/range_eval/modifier_cases.txt b/src/promql/src/range_eval/modifier_cases.txt new file mode 100644 index 00000000000..fd8f69cd64a --- /dev/null +++ b/src/promql/src/range_eval/modifier_cases.txt @@ -0,0 +1,105 @@ +# Generated by promql-compat cmd/narthex-golden-rangemod built for amd64 (Prometheus v3.15.0 engine +# with EnableExtendedRangeSelectors): seed 1, then `-aligned -seed 2`, whose whole-second times put +# samples on the window and lookback bounds. Each line: function modifier range_ms offset_ms +# start_ms step_ms points lookback_ms anchor_ms ; samples ; result, where anchor_ms is `-` without +# `@`, every point is `timestamp:hex value bits`, and `error` means the engine rejected the query. +rate anchored 3203 4376 10729 3203 8 10000 - ; 12166:4058b98f850e2f55 13861:405d7ba9e3a40d84 15136:4062fc5e07225a19 17632:4068605a85491805 20580:406dc8e3bdf1e0b9 21122:7ff0000000000002 23954:4017cad70ce12290 40439:40408064e23c76a0 42003:4046d70da3c88e06 43163:404e44925e1bc410 45363:4001c87cbd1748e6 46067:4043a85d21d8de1b 46224:40550a8e60b3b97e ; 17135:0 20338:40308aff454a8f11 23541:402aed7808077661 26744:402b0432383430e9 29947:3ffdb66b45b6c48a +rate anchored 2872 -9435 50589 2871 1 10000 31476 ; 9950:4088c577ca0544e9 11984:7e37e43c8800759c 13321:c0838c0ff7fa2fa4 13874:c0666b5684a00464 15226:407ca5c8836eaa59 17637:c0110e1a977efd76 18191:7ff0000000000002 28575:405db23bc56aacea 29470:c08d9bc1f16f2de9 30537:c0846581a8f6ae40 31519:fe37e43c8800759c 31907:c085d09345924b70 32811:c009b0f1252e9e5c 41427:408ad9c94240aa8c 42504:c030c9035fcdf245 42810:c06aafefa0ee2712 45069:c08919dc00149b6b 45892:8000000000000000 48861:4061b6810ffb521f 50766:407348a4f0f2a765 52806:7ff0000000000002 77506:c0730a2f7176b9cf ; +rate anchored 16686 1719 33222 805 3 10000 51827 ; 17293:405c1d0f5d641851 19693:4022e765ad246949 41304:4040e82e34369c40 48323:405453b76ca69c8f 48505:4019cd32af581925 50976:404659b3cb82c1b5 56236:4048295f2ec78f13 57657:40567bb8246dafd6 59209:405e5994aa021930 61249:40175a77bee6ba18 ; 33222:4009dd0699f90728 34027:4009dd0699f90728 34832:4009dd0699f90728 +rate anchored 18683 129 16259 3266 16 300000 - ; 18018:7ff8000000000001 19355:7ff8000000000001 19900:7ff8000000000001 20138:7ff8000000000001 20745:7ff8000000000001 22166:7ff8000000000001 24284:7ff8000000000001 ; 19525:7ff8000000000001 22791:7ff8000000000001 26057:7ff8000000000001 29323:7ff8000000000001 32589:7ff8000000000001 35855:7ff8000000000001 39121:7ff8000000000001 42387:7ff8000000000001 +rate anchored 984 7477 28191 984 12 1000 - ; 17575:c08be36e438e16c6 19860:c0788654f3c58b54 21147:c078b98624c6904c 21694:408c5423db7f2f64 24069:c025473bd7a419aa ; 28191:0 29175:408cca0fa0a07902 32127:0 +rate anchored 3049 0 24692 3049 12 10000 12965 ; 15108:c0548eca36e7fce0 16527:4033bb18646943a3 16648:408a977cb2edf5db 19115:fff0000000000000 32592:c07e5e3a84b6b89d 32973:4050b9c91b3eec4d 35858:c07f3431cdb5821b 38831:7ff0000000000002 39359:7ff0000000000002 ; +increase anchored 3652 0 56275 3651 19 300000 - ; 14671:408007b799a7068a 16070:c07135648a137a30 17565:4073af026bd58d22 19610:c085e508445a12a7 21464:4078f940a79eb460 42278:c0890ee08fb7b676 43330:c07fc6a84f0b701f 45423:408517746ccd1de8 47397:408c50b6e5ccea61 48603:c063130f2a05b0b9 49498:c07f6d596949100c 51529:4036ada8c2e53540 ; +increase anchored 1000 864 35990 702 20 1000 - ; 8509:7ff0000000000002 9359:7ff0000000000000 10734:7ff0000000000000 13708:7ff0000000000000 13784:7ff0000000000000 14698:7ff0000000000000 20023:7ff0000000000000 20268:7ff0000000000000 31755:7ff0000000000000 32681:7ff0000000000000 33882:7ff0000000000000 36059:7ff0000000000002 36494:7ff0000000000000 38408:7ff0000000000000 41051:7ff0000000000000 41273:7ff0000000000000 ; 37394:fff8000000000000 38096:fff8000000000000 39500:fff8000000000000 40202:fff8000000000000 42308:fff8000000000000 43010:fff8000000000000 +increase anchored 3010 3589 5854 3010 5 300000 - ; 4072:404c5786206217b8 5305:404fec6e9f26cf56 5933:3feed8311674bd24 8581:4047597793a96b6d ; 8864:0 11874:402041250c7a2a4c 14884:4046de16cf4f9878 +increase anchored 3095 -159 28739 3094 6 10000 - ; 12939:405106063c601e24 13697:4053640429d37487 30047:7ff0000000000002 31540:405c87fa6db241f8 33248:3feee0aecb6cd578 33532:40475e9a0dd074e3 33631:40499bc5882ea420 34002:404b30619b7735d0 34199:40501f9731ee8e02 36451:405232e1d399ed19 ; 31833:0 34927:40501f9731ee8e02 38021:40209a550d5af8b8 +increase anchored 16153 3237 17656 3097 19 1000 - ; 8074:4056c32d197ee8c2 9174:c054eef94ff0a9c8 9267:406b015f694bfd8e 10403:c074f9e9d073eb4b 10794:408600422bdad53f 12305:7ff8000000000001 15028:c06182bd90109d71 15927:c07a7d97bc49ad13 18146:c074b2b2cf7968e2 36723:4073251adbf2bb0a 37874:c0656981fa308e2e 39123:7ff0000000000002 39686:7ff8000000000001 39894:c089fd410d9d58f4 42090:7ff8000000000001 44390:4084037caf5129e6 45334:406cdf06eec2b178 46886:c05137cbb9b8ed13 49066:406388f088c867c4 49381:c062f22ccf8c35ec 52218:4073c66f856aec38 53831:c088ed39e788a744 56364:7ff8000000000001 62554:c03a5c83c0a9e86d 63363:c075da093f572de8 ; 17656:7ff8000000000001 20753:c075be46cfabfd04 23850:c06fe6c3c5b771a5 26947:c06fe6c3c5b771a5 30044:c060f44f8e1b989f 33141:c074b2b2cf7968e2 36238:40572b93b34110c4 42432:c0656981fa308e2e 45529:7ff8000000000001 48626:408b3b3e6b01d644 51723:40891444f3cab8a0 54820:408b60ef5950e2b8 57917:40866dd8e6ece298 61014:7ff8000000000001 64111:7ff8000000000001 67208:4064b21279b71de0 70305:c0777fd17b61cc6f 73402:407a5aa253af8219 +increase anchored 4779 -3547 11378 2386 13 300000 - ; 13142:407388c20b1f60e3 15581:c08450c9e87aab65 18253:7ff0000000000002 18781:c0890d4247d29b41 37018:c07b344d71457301 48380:c085ef9cbbec8ca7 69462:4050039c58050c54 69535:c071c1c0650bbecf 70456:c0873aa0ff053489 70517:c08ac1945f452863 ; 11378:0 13764:c08450c9e87aab64 16150:c096af061826a353 18536:c0890d4247d29b41 35238:4076e6371e5fc381 37624:4076e6371e5fc381 +delta anchored 13557 9317 17957 2297 4 10000 - ; 19253:c087688906d0f106 28225:407f4c6570265486 28469:c07cede62fadf795 30270:c08aab4bc806bf9d 31462:8000000000000000 33907:c0848b2e8e8c5b1f 35643:c08e40aeaca18a72 36445:8000000000000000 ; +delta anchored 17673 0 11429 1908 18 300000 25517 ; 16107:405100e57b0127cc 17355:4053ad9b60e18d20 18813:4057433bbb3c92af 31277:405f01809168d81e 31863:40652ba3b1c0b567 34068:4069996733cd3d94 35279:406c37f5a80bf33d ; 11429:4039095900edab8c 13337:4039095900edab8c 15245:4039095900edab8c 17153:4039095900edab8c 19061:4039095900edab8c 20969:4039095900edab8c 22877:4039095900edab8c 24785:4039095900edab8c 26693:4039095900edab8c 28601:4039095900edab8c 30509:4039095900edab8c 32417:4039095900edab8c 34325:4039095900edab8c 36233:4039095900edab8c 38141:4039095900edab8c 40049:4039095900edab8c 41957:4039095900edab8c 43865:4039095900edab8c +delta anchored 294 -8152 22932 294 15 10000 - ; 548:fe37e43c8800759c 3527:7ff0000000000002 5741:fe37e43c8800759c 8652:fe37e43c8800759c 23924:fe37e43c8800759c 24912:fe37e43c8800759c 26031:fe37e43c8800759c 27015:fe37e43c8800759c ; +delta anchored 766 -1763 5382 765 12 3000 - ; 4859:404ace691f296edb 6438:405529673ffab928 7361:4057e30929de1df7 7967:405a25020d7f621e 9193:4061991006732df2 10675:4065a7c551401118 ; 5382:403f08cac19806ea 6147:4025cd0f4f1b2678 6912:40220fc71d0a2138 7677:40421a3bfecdf38c 9207:40403ad52b338c98 +delta anchored 1008 6825 50791 1007 17 10000 - ; 8269:fe37e43c8800759c 8332:fe37e43c8800759c 11296:fe37e43c8800759c 13754:fe37e43c8800759c 16207:fe37e43c8800759c 17655:fe37e43c8800759c 18640:fe37e43c8800759c 20958:fe37e43c8800759c 21931:fe37e43c8800759c 22630:fe37e43c8800759c 23776:fe37e43c8800759c 24582:fe37e43c8800759c 24874:fe37e43c8800759c 26270:fe37e43c8800759c 26413:fe37e43c8800759c 26957:7ff0000000000002 28162:fe37e43c8800759c 36702:fe37e43c8800759c 38056:fe37e43c8800759c 40102:fe37e43c8800759c 56894:fe37e43c8800759c 57297:fe37e43c8800759c 69885:fe37e43c8800759c 70124:fe37e43c8800759c ; 63882:0 64889:0 +delta anchored 1108 8341 16331 1108 5 3000 - ; 8913:40812367fb9f30ce 11574:c082258b8b2a353e 11993:408d362594c1118b 14077:40544e25caf4418d 14449:407e1d08eb0fd16d 14941:c0656182b8771c97 16651:c0698c1d179f2be3 17433:c08df8da7ed48b84 ; 17439:0 20763:4078257b3243c17a +resets anchored 18490 7481 42633 3404 17 10000 35488 ; 20392:40533bbbf3113a16 22755:3ffe206649edeacf 22890:403d528aea4598a2 25669:404f085ef12b2fb7 28490:40599f1c08bee535 29193:405befe8d24365f9 31110:405ca131c8b453c6 34075:40620ee83cb6a14b 35031:4067f66c71468df4 35862:40699ffed6969d50 36384:406b1760abf58a51 38670:407074cc20831a18 40983:4070c79b6dd2aa5b 42525:4071bd7eefabe2d2 45463:40725109e136071f 46050:4074734077b2b99f 47347:40765041f78561f6 47462:4079550eee2d8f70 ; 42633:3ff0000000000000 46037:3ff0000000000000 49441:3ff0000000000000 52845:3ff0000000000000 56249:3ff0000000000000 59653:3ff0000000000000 63057:3ff0000000000000 66461:3ff0000000000000 69865:3ff0000000000000 73269:3ff0000000000000 76673:3ff0000000000000 80077:3ff0000000000000 83481:3ff0000000000000 86885:3ff0000000000000 90289:3ff0000000000000 93693:3ff0000000000000 97097:3ff0000000000000 +resets anchored 3755 0 69281 3755 13 300000 41459 ; 24837:408d16addd5395e6 26179:c0795033bdcbcbbb 28214:c07392e499eebf17 30075:c08a1e619ce1fce0 31147:c062c58546d1bbce 33465:c0715a73ed3e0b73 35042:c065fa91a4078529 36045:40764c56cc3b83b2 38078:408a05db3ca1c196 38557:7ff0000000000002 41118:4087ab0118b51e55 41788:c06efeabf2cccfb4 44714:c043e2b46e484032 44984:408667aaf035b2ae 46406:4067725048e78556 63654:c053eb297910e474 65672:405a55d5ee29e787 67823:c08d96a76b2e04e0 69071:c08dedffc6fc34b0 70934:c07c9f2d394c2225 72606:c053e7ad7c91fa1d 72901:c08d7906a0728643 ; 69281:3ff0000000000000 73036:3ff0000000000000 76791:3ff0000000000000 80546:3ff0000000000000 84301:3ff0000000000000 88056:3ff0000000000000 91811:3ff0000000000000 95566:3ff0000000000000 99321:3ff0000000000000 103076:3ff0000000000000 106831:3ff0000000000000 110586:3ff0000000000000 114341:3ff0000000000000 +resets anchored 1000 0 11810 1502 8 300000 41867 ; 10086:4086ded57a657b31 12944:fff0000000000000 15051:408bb11ba7be6c59 16762:408ace6a598f2384 18778:c073ee200ca5c302 21567:c086ae5de878332f 22557:407e29bd181a51e0 25112:c04c894a1a7b72db 27244:c07ac9e04deefda7 27585:fff0000000000000 45755:c0769eea3ee7e3b7 46054:8000000000000000 ; +resets anchored 7935 4627 15168 3964 10 10000 19782 ; 13096:fe37e43c8800759c 15971:fe37e43c8800759c 17283:fe37e43c8800759c ; 15168:0 19132:0 23096:0 27060:0 31024:0 34988:0 38952:0 42916:0 46880:0 50844:0 +resets anchored 8007 0 37919 413 12 10000 - ; 16783:fe37e43c8800759c 19749:fe37e43c8800759c 20608:fe37e43c8800759c 23184:fe37e43c8800759c 24874:fe37e43c8800759c 26528:fe37e43c8800759c 26984:fe37e43c8800759c 39907:fe37e43c8800759c 42633:fe37e43c8800759c 44748:fe37e43c8800759c 45663:fe37e43c8800759c 45876:fe37e43c8800759c 47512:fe37e43c8800759c 47609:fe37e43c8800759c 48864:fe37e43c8800759c 49544:fe37e43c8800759c 50383:fe37e43c8800759c 69492:fe37e43c8800759c 72290:fe37e43c8800759c ; 39984:0 40397:0 40810:0 41223:0 41636:0 42049:0 42462:0 +resets anchored 1000 0 98349 2519 3 3000 - ; 7924:404aee9e667122d2 8906:40591c6267cf0406 10201:405d086041847788 12064:4061f7db64e71c70 31693:40637fc998483aa1 33083:4067f5f25b70a1f8 46772:3fb7629b3ff3a32a 66479:401bbb516ea3256d 68056:40302e19c80338dd 81776:403b4e73fc1a5868 81931:4051839be68d2596 104863:4052f2c42a4e364a 106828:4057778fc242ff28 107146:405ed3221f8e55aa 107972:4062216e7d7eabe7 108538:4063ce864309025d 108670:3ff813f0af5d8448 110613:4033aa6188e6125e 113345:404281024fe016cf ; +changes anchored 2464 0 28925 2463 6 10000 - ; 21308:fff0000000000000 21941:fff0000000000000 22599:fff0000000000000 24601:fff0000000000000 37984:fff0000000000000 39854:fff0000000000000 40151:fff0000000000000 ; 38777:0 41240:0 +changes anchored 18173 5164 35889 3595 8 3000 76564 ; 18680:7ff8000000000001 20322:406da29152dc62a3 20464:7ff8000000000001 22421:4069de8e632c7f90 23624:c0836950478a9396 23858:40825d98791ee38e 26037:c07036842a8ccaf7 28943:c08962c97cff1e88 52768:7ff8000000000001 54002:407703398404780c 54931:4084c178eecf0c04 55924:c08df8d0c188bc43 66406:7ff8000000000001 67727:40551c0bddf3f9b8 68321:7ff8000000000001 90437:c06b45efc89b8802 92226:4089d248760eb798 93446:c076632114fd86ea 94260:c079ef5d398c3011 94842:4040206f11798515 95097:c058e38b009c9c60 95245:408020c5d310834c 97066:40732c5f2ff10249 99763:c072f8b4ca71312a 101491:408f2413a059dcfa ; 35889:4018000000000000 39484:4018000000000000 43079:4018000000000000 46674:4018000000000000 50269:4018000000000000 53864:4018000000000000 57459:4018000000000000 61054:4018000000000000 +changes anchored 1000 3855 18659 2347 10 1000 18341 ; 4869:405b0c3643d3053e 7189:405e4041de12bbec 9806:406281d022cce15a 11123:40637a7fcd8e1614 14094:4067573e8dc4e0f0 16443:4067dc8c10a62f50 18665:406ddf9f65a1556e 20015:7ff0000000000002 20748:4071ea2d8cec025e 20887:4073631161d60148 21215:40758f6413fbf5ce 22762:4075d30ba19304bf 23231:40789216c23a9381 25552:4003af66cc7d531d 26761:7ff0000000000002 ; 18659:0 21006:0 23353:0 25700:0 28047:0 30394:0 32741:0 35088:0 37435:0 39782:0 +changes anchored 3230 0 40590 3229 20 300000 - ; 14699:4060808ccab4cda7 17264:4062c15c45798849 17713:40184db6bc862150 20013:401c3449ae531ff7 20938:4038be27f2a16169 21766:404490fd36e9f800 22816:402223eec1b9bd0b 24937:4006cfc7b9bd2876 25575:40385966e5055933 26734:405034885f9034a1 29012:4057512cc6218d00 29559:405a7be33c57f3c4 46964:4060e28cb6631471 47406:4063892653050253 49874:4069b5dcdcc757ae 57072:406d438e2ff5ce78 59452:406e1fd025a1faca 61053:4070ec3f26f26bf2 79549:4072914517b270c9 ; 47048:3ff0000000000000 50277:4000000000000000 59964:4000000000000000 63193:3ff0000000000000 82567:3ff0000000000000 +changes anchored 1000 2288 41045 3224 7 1000 - ; 15114:c08ba8f4511a0d5f 21538:408aa3e1c2b205b8 22859:7ff0000000000002 22936:c06e19fc15a274fc 25769:c058cf99a6ec031c 26500:40822d86898f226d 27941:c051c067aa73296a 30166:c061e3344fbae689 30385:c08da191f218c254 30561:7ff0000000000002 51874:c084dd878ba59d87 53505:c0651d6087baae4c 54685:408111156824479c 56075:c00034e8110a1930 58004:403e2fb89d32de98 61000:4085cc6c8ab6dce7 62986:c02673e34579d94a 64308:7ff8000000000001 65667:4056fca64bd22248 66705:c048262f83ddb522 69229:407ae51eca5c5d35 70162:7ff8000000000001 70964:406a2f5166055d5d ; 57165:3ff0000000000000 60389:0 +changes anchored 1000 0 23116 3340 12 300000 - ; 4317:4037ac331b362eb7 5033:40405ea2a0a14f65 7082:404c6a9981eac166 8005:4054d2a4c8de62bb 9268:405eff0b1b6c2ede 9599:406592e71d0e401f 11704:4069068e3b4c957a 14161:4069be10f6348e92 16422:406a17913396134e 18303:406aa38b743b7422 18818:406f354823c8718b ; +rate smoothed 2641 -3060 62992 1317 17 1000 - ; 13843:4048aa20aac4e5b2 14902:40536a9fe02a2427 17617:4058d41ee7f207c9 17665:7ff0000000000002 19644:40624827ececeef3 22260:4065c89e1fa6fec8 25189:40677b30be5a7698 27338:402394305daef0e1 28510:4047dc7b2178c214 31085:40515eb71857be94 33364:405c1d08ff69a980 34983:4062fa9c58ee13e8 35803:406923fb21f25cdd 56682:406ef830f695e1f9 59660:407155bda622511a ; +rate smoothed 1000 0 58189 2919 19 10000 34157 ; 3056:4021c659bead8de8 4214:4018ed3e75f57383 4692:4033056429f94c46 7624:40405a1df1de0c37 9411:4045d39d38104d08 9704:404b0df115706e80 10386:4056240827f243b6 12933:406040607174720e 15543:4060ac407612be9f 17242:406551b49e4098b5 19917:4066cd9c323bdd42 20348:406c055cae1333ed 22625:406d4f239d72c949 24662:4070ca7d85441104 26340:4071fdc90de172b5 28650:4073546dcf20cd78 28793:40760f0fe4627e83 47525:407895e4d4c29fb2 60894:407a78d2e342759d ; +rate smoothed 2073 0 65868 2073 14 300000 - ; 16224:7e37e43c8800759c 17907:7e37e43c8800759c 18889:7e37e43c8800759c 34939:7e37e43c8800759c 49338:7e37e43c8800759c 51345:7e37e43c8800759c 53804:7e37e43c8800759c 54159:7e37e43c8800759c 55577:7e37e43c8800759c 57288:7e37e43c8800759c 59493:7e37e43c8800759c 61347:7e37e43c8800759c 61349:7e37e43c8800759c 63967:7e37e43c8800759c ; 65868:0 +rate smoothed 1333 0 3332 1333 9 300000 - ; 1591:405815b60ec6eeba 3238:405e2eeb1282b1ec ; 3332:402b8885a1581390 +rate smoothed 1210 4490 56269 1209 12 3000 - ; 13780:403ae3c88d046e8c 14708:404c6d1f75cdf3de 17683:3ffc5297bf295353 19068:4041cac3ed43be56 20903:4051a4252fb0de3f 22866:4054104988fca2f8 45595:405d75ec23e6fb93 48130:405ec5907c114ab1 50736:40145025e5185532 52876:40466e35b6ecb041 55604:40510db5dd722d76 56522:405b244ebf017bb0 58619:400b2cdfb1e49b50 60679:40306b1d168b4ae1 ; 56269:40304b1480014bac 57478:4031a96642554fc2 58687:40211f01e047dcfa 59896:40211f01e047dcf7 61105:404170e7128c50d0 62314:3ff9eb12a19b67d1 63523:4009d3c86bb1c34d 64732:401948d2f6a7c2b2 65941:40024e1c53c96d38 +rate smoothed 1000 -9390 17461 445 3 300000 - ; 19157:fe37e43c8800759c 20516:fe37e43c8800759c 20620:fe37e43c8800759c 21069:fe37e43c8800759c 22233:fe37e43c8800759c 22497:fe37e43c8800759c 23383:fe37e43c8800759c 25129:fe37e43c8800759c 25480:fe37e43c8800759c 27336:fe37e43c8800759c 45541:fe37e43c8800759c 48082:fe37e43c8800759c 48339:fe37e43c8800759c 49706:fe37e43c8800759c 50910:fe37e43c8800759c 52542:fe37e43c8800759c ; 17461:0 17906:0 18351:0 +increase smoothed 15878 -3594 18719 3372 14 300000 - ; 20984:4023a5694d957eda 22032:404bd3b84cc970c2 22167:40528322b7f12189 22843:4054d8f46288acba 24798:401e9547684ab04c 26636:4044aaa19f063f10 29423:40562f712874c381 ; 18719:40508f92eeec0cb6 22091:40585e10972af8e8 25463:406382a2205593fa 28835:406449dc30a56030 32207:406449dc30a56030 35579:4055be4ed54575dc 38951:404770c14ea279aa +increase smoothed 1000 3793 37613 2756 6 300000 - ; 10703:7ff8000000000001 11754:7ff8000000000001 14566:7ff8000000000001 14767:c071cdfeed175e81 15725:c076f0318692b593 17090:c06325e66ce9af38 17633:408823acda58cc1e 18368:c0707e5a211769cd 19659:40802c50505b36f9 40799:40868a558e77f4ff 42791:7ff8000000000001 44380:c0756e879019dedc 45049:7ff8000000000001 46201:408a2bec75684b04 46469:7ff8000000000001 47853:407128fb7e3b1044 48757:c03a82e6920159be 51124:c06fb775c8193bda 53844:402ddea22d03978f 54193:7ff8000000000001 56671:4089db33bfca4e00 59145:408a4cbf1d882edb 61792:407d3da46554f3cc ; 37613:402346c86c99dc80 40369:402346c86c99dc40 43125:402346c86c99dc80 45881:7ff8000000000001 48637:7ff8000000000001 51393:7ff8000000000001 +increase smoothed 2294 0 7513 2294 16 300000 13069 ; 11100:c062d70b85a57a47 11529:7ff8000000000001 12596:c0805d2b4a7c9ba2 13506:4087d296e5e48d5d 14357:c05ceebaa128b0ca ; 7513:407277ee5c34663a 9807:407277ee5c34663a 12101:407277ee5c34663a 14395:407277ee5c34663a 16689:407277ee5c34663a 18983:407277ee5c34663a 21277:407277ee5c34663a 23571:407277ee5c34663a 25865:407277ee5c34663a 28159:407277ee5c34663a 30453:407277ee5c34663a 32747:407277ee5c34663a 35041:407277ee5c34663a 37335:407277ee5c34663a 39629:407277ee5c34663a 41923:407277ee5c34663a +increase smoothed 1926 -8951 30308 1926 3 3000 40176 ; 20628:4052cd31e2e51649 27763:4055b55102ac613d 30175:4059e01b9aedd676 30477:405f87948ce974e4 32468:40650eeb5cebbf5a 33870:40673725d635fe46 36656:4067a130a67a75b7 37982:406be2f2eb28c3aa 39221:406faaf465135686 41583:40708adb66cf7104 56266:40737bd079e7b4c2 73079:40747ac2bb0b3e35 74323:4076422cc2b2168a 74909:4077278a87b66f48 75539:40785334b3e53932 78525:4079d76564ad9b9a 99936:3fc7c6178f064348 101044:403a9bd0f177f659 ; +increase smoothed 1000 3775 26754 3808 2 3000 15710 ; 16547:7ff0000000000000 18932:7ff0000000000000 19186:7ff0000000000000 20523:7ff0000000000000 20667:7ff0000000000000 22174:7ff0000000000000 23797:7ff0000000000000 24223:7ff0000000000000 26841:7ff0000000000000 28847:7ff0000000000000 ; +increase smoothed 5365 9707 88208 2679 1 3000 - ; 35143:404dee7707474228 49752:4056cefb44d25b26 50963:4061428d133c6898 52373:40655f5b33150d32 54971:406561ff8ed502d5 75697:4069359bea315f63 78559:406a1ffaa22f2cce 89287:4070053285279c32 92242:4071033d81775f7e 92498:40739df826397e40 94850:4074167ed5da1ce0 116615:4076a09d16ec9b78 117448:4076f4ba8754a5a0 ; 88208:401cb3d9fa019660 +delta smoothed 1000 -3564 2504 373 12 1000 - ; 6638:40525ba066c333dc 9202:4054976005516cdf 11513:405870dbacb8576a 12417:4061ccb65e13e4d5 ; 3250:0 3623:0 3996:0 4742:400bdfb4f58f8f20 5861:0 6234:0 6607:0 +delta smoothed 3041 8573 21775 3120 14 1000 - ; 22028:404f7e185631c28f 23492:405ad425c6615c80 25667:405d46d925f98268 25809:401e86447412dd42 25830:4041c860c6a40584 26550:40516dc3b4d2da7a 28690:4055cb0c2f79dd36 28971:405672266cc7417b 30278:405dc9a78874e772 32609:40637f6cf3ec904d 34777:4065e1a9ea38f36a 35655:4068a42fbe0af0b4 38500:406d7643be5eef44 38647:40712348086cac88 ; 31135:40302b601d509236 34255:4037fef927d32a18 37375:4045c9c239ea4ea0 40495:404c1700580f64c6 43615:4039bbdc1bf9d520 46735:4047b4af259d9c50 49855:4043413149e9a730 +delta smoothed 1000 -1686 50687 747 5 10000 32894 ; 21209:40126d6052a7b04c 21248:4022247f5eaf1daa 39868:404189b414758ff7 42214:404b884c6c6563fb 44571:404ed649e70c7496 46670:404fc3dc2dd45330 47888:4055ff815ec65ce9 49022:405f15c38717b9bb 49878:405ff77d121f8f13 52406:40617568fd3746ee 53653:40651bc64c9675dc 54893:4067abbdf2bb4b74 57744:406a277420c62369 71738:406f6d17ec66a5ca 73847:407178dfbdde6136 73860:4071b396866af87a ; +delta smoothed 1000 -3942 28077 1354 6 10000 - ; 7240:4051613c02526715 9604:40533f6801f44f52 11984:40534738b1cbd487 12676:4055366fd535200a 14568:7ff0000000000002 14844:405dc52d791ba11a 23835:40643dbc0985e0f3 25559:40680258afb118c6 31895:406b60e9861d1f32 32981:406cf0cf96118efe 34354:40714bb6454d7469 36908:40730291a994c471 37106:3ff3fad34d4a6c4a 37572:4047ba4175e7d8c5 ; 28077:40149d570b1bdf00 29431:4033e735f2c927b0 30785:4038a66baff3e400 32139:40257a998e6a7520 33493:c070935bfe17acfe +delta smoothed 7463 -6766 31881 3728 8 1000 6607 ; 6043:c070594b557dbfa4 6383:0 8587:407077a400322464 11249:408ec38f0085b73f 13413:c08d111e13518ae1 13996:405b49ab555a807d 14894:c07edb0289931910 15014:406ade5b9e90d9c0 15217:0 16823:406b829b257684cf 18067:408d84381395cca1 20015:7ff0000000000002 20581:8000000000000000 23250:7ff8000000000001 25177:40791bc3217324e1 26090:c088678cebb69297 26092:c080bc423d2195ac 27719:4080384cd9e633b6 29522:7e37e43c8800759c 31165:406b1f19a0ed697e 33453:7ff0000000000000 36451:408b6a11ae86772a ; 31881:c083c95a3cac070f 35609:c083c95a3cac070f 39337:c083c95a3cac070f 43065:c083c95a3cac070f 46793:c083c95a3cac070f 50521:c083c95a3cac070f 54249:c083c95a3cac070f 57977:c083c95a3cac070f +delta smoothed 5667 0 42617 2830 4 300000 - ; 19722:8000000000000000 21896:8000000000000000 23766:8000000000000000 26118:8000000000000000 29093:8000000000000000 31289:8000000000000000 33210:8000000000000000 35697:8000000000000000 37027:8000000000000000 54472:8000000000000000 56997:8000000000000000 58301:8000000000000000 60676:8000000000000000 67813:8000000000000000 69664:7ff0000000000002 93023:8000000000000000 93897:8000000000000000 ; 42617:0 45447:0 48277:0 51107:0 +sum_over_time anchored 3779 -574 28241 3779 14 1000 - ; 15394:c061b01094917d95 16648:c0861f6a686b8c3b 18394:c0872a3cd06c992f 42598:405f66bcd94c5961 43882:c076ab32a25abba3 44341:c07ee08826dd081c 58799:c043c325a091b9ba 61393:c0840cd2371ce78d 63675:c075943b78713618 66360:4088c87cecc89510 ; error +resets smoothed 833 0 8763 3010 13 300000 - ; 9735:fff0000000000000 11303:fff0000000000000 13598:fff0000000000000 15646:7ff0000000000002 16023:fff0000000000000 17267:fff0000000000000 18848:fff0000000000000 19760:fff0000000000000 21507:fff0000000000000 23287:fff0000000000000 25726:fff0000000000000 28537:fff0000000000000 29804:fff0000000000000 31635:fff0000000000000 32316:fff0000000000000 34045:fff0000000000000 35812:fff0000000000000 37193:fff0000000000000 38650:fff0000000000000 39727:fff0000000000000 42296:fff0000000000000 42964:fff0000000000000 ; error +rate anchored 4000 1000 27000 4000 18 1000 24000 ; 2000:40406cd5f32ded5e 3000:404ff7def921b249 5000:7ff0000000000002 7000:4056e88d49582654 8000:405b44524b2b568e 10000:405f4a68564ad85e 26000:40624f0602309700 28000:4063677dc66fe737 31000:406615fa9678287e 34000:400019a3d6ac026b ; +rate anchored 4000 -5000 37000 4000 6 3000 - ; 16000:40424ab6bf684d3b 18000:4072a855eff91b89 21000:c07da9e8e547b025 22000:7ff0000000000000 24000:406fa25d56ba1b44 25000:c07c0ec31f14b6a7 27000:c08b3d1b1e820b52 29000:4082383033147c57 30000:fe37e43c8800759c 31000:c07a9890b63f2240 34000:40877176baa7d8e8 54000:408bb890f19ce56f 69000:c08815021b69709e 71000:406c33b30353621c 74000:c07da6a4342a812b 98000:c062ea3c7ae50cfa 101000:8000000000000000 104000:408b0497c0da6af7 107000:fff0000000000000 115000:408c24066ee148c0 124000:c061bd640cd60b26 125000:c074c60742a53a57 139000:c081b4700d58291e 141000:fe37e43c8800759c ; 49000:0 +rate anchored 1000 0 24000 3000 9 3000 51000 ; 10000:7e37e43c8800759c 11000:7e37e43c8800759c 12000:7e37e43c8800759c 15000:7e37e43c8800759c 17000:7e37e43c8800759c 20000:7e37e43c8800759c 23000:7e37e43c8800759c 24000:7e37e43c8800759c 25000:7e37e43c8800759c 26000:7e37e43c8800759c 29000:7e37e43c8800759c 32000:7e37e43c8800759c 34000:7e37e43c8800759c 37000:7e37e43c8800759c 38000:7e37e43c8800759c 39000:7e37e43c8800759c 57000:7e37e43c8800759c 60000:7e37e43c8800759c 63000:7e37e43c8800759c 65000:7e37e43c8800759c 66000:7e37e43c8800759c ; +rate anchored 7000 0 23000 1000 19 1000 - ; 16000:40582eab53f62334 18000:405d2431217b68f4 19000:4061f108c67d5c3b 40000:4063b88309703cab ; 23000:401abea082539e4e 24000:400ed1b8c6d846e5 25000:400ed1b8c6d846e5 40000:0 41000:0 +rate anchored 17000 7000 23000 1000 5 2000 25000 ; 20000:c0884ff71177f5cb 33000:c08ce6b9c872146c 34000:c0797fe42d6805ff 35000:c079ddba0d25f16c 36000:7ff0000000000002 39000:40826813b78c30df 42000:c08df243db27df23 43000:c079c5ba6281fd77 44000:4086f4e20c54d3d6 ; +rate anchored 8000 0 47000 4000 11 1000 - ; 19000:3ff4b5790ccefc38 20000:3ffc05c7d9a2693b 21000:4041a92dcbb6fc5f 30000:404a090359d11ddd 31000:40557796d236e3e2 32000:405730b9013735f8 35000:405ef8c95be83fda 38000:40606a8082ca838a 40000:4060b8c6b41ef8f1 43000:4066c86d4e84c269 45000:406971076143b482 ; 47000:402170815a497722 51000:400544d095f790c8 +increase anchored 4000 6000 13000 2000 17 1000 10000 ; 7000:40654bfe87cf9193 8000:7ff8000000000001 16000:7ff8000000000001 18000:407e2d1fbb482afa 19000:c0832ab2681800be 22000:7ff8000000000001 23000:4076ac93651c05b7 24000:c085c98d9b5b47e7 ; +increase anchored 1000 -6000 22000 3000 3 2000 - ; 9000:4044c4def4849d8f 11000:404e5602cb75c1b4 13000:405b4173bf45321a 16000:405f68ab6c2ac263 19000:4060fd33880a67aa ; +increase anchored 4000 0 44000 4000 19 3000 - ; 13000:fff0000000000000 14000:fff0000000000000 16000:fff0000000000000 18000:fff0000000000000 20000:fff0000000000000 21000:fff0000000000000 24000:fff0000000000000 25000:fff0000000000000 45000:fff0000000000000 46000:fff0000000000000 49000:fff0000000000000 51000:fff0000000000000 54000:fff0000000000000 66000:fff0000000000000 68000:fff0000000000000 69000:fff0000000000000 71000:fff0000000000000 72000:fff0000000000000 ; 48000:fff8000000000000 52000:fff8000000000000 56000:fff8000000000000 68000:fff8000000000000 72000:fff8000000000000 +increase anchored 3000 4000 53000 3000 14 10000 - ; 9000:40524b843508cc7a 10000:405c60954a2f3288 13000:405e33ea0c7ec982 16000:4061196e039c836c 36000:40654cb6a199a0dc 37000:406a7f8831660c58 39000:406b47bb9eb0a97f 42000:406f55daf68bcc1a 57000:4070a273de027eac 60000:40734d10bb92164d 66000:4074513e914be5d4 67000:407591ae175095f2 70000:4075d20dcade9e0a 71000:40783e56799289ca 72000:407941e7e4a90cb9 ; 62000:0 65000:404554e6ec7cbd08 71000:404224eaddf3fd28 74000:401017ece3820600 77000:404b7ed0ce537578 +increase anchored 3000 0 36000 3000 18 1000 - ; 5000:7e37e43c8800759c 7000:7e37e43c8800759c 10000:7e37e43c8800759c 31000:7ff0000000000002 34000:7e37e43c8800759c 35000:7e37e43c8800759c 37000:7e37e43c8800759c 39000:7e37e43c8800759c ; 36000:0 39000:0 +increase anchored 2000 4000 101000 1000 3 3000 - ; 25000:405cb7f7f72612fe 28000:40032d1edc1fd09c 30000:4048eb956dcc0390 33000:404a51867aadf906 36000:4051f2788eca2aac 37000:40597d07b9bab6b9 40000:40603965673aa390 41000:406407a9a6e65aed 62000:40645d4399cec612 72000:4066208f16ed4502 74000:4067c056decc1a50 92000:3fd836e7310b4fed 93000:40468f65df6d1ea4 116000:4053f99d47b7e510 122000:4056d31d4b2d6dbd 124000:40585b4e983d709d 125000:405950649489a8b6 128000:405bed7b1c8d3f25 131000:405cef64312dcbc4 133000:405dbe9fa2d1d7a2 134000:4063b5f29e81eb2c ; +delta anchored 1000 2000 11000 2000 20 10000 - ; 9000:c05d8503ad939600 15000:407982de81a545a9 18000:c08def07c5545504 ; 11000:0 17000:4080720fb6851594 +delta anchored 2000 -8000 28000 2000 10 1000 - ; 3000:c089eeaace69a57c 6000:c07da43eb8895e22 8000:40766a96af09d1f5 10000:408111209e82984d 13000:40850590b03dd3e2 15000:407578c8fc7cfe32 18000:40421fb9552e1e95 21000:c08024df4b348da2 24000:408b2eef969f4056 27000:4075809fd821aba9 ; +delta anchored 3000 0 25000 3000 3 1000 - ; 16000:7ff0000000000000 18000:7ff0000000000000 21000:7ff0000000000000 24000:7ff0000000000000 26000:7ff0000000000000 ; 25000:fff8000000000000 28000:fff8000000000000 +delta anchored 3000 8000 56000 1000 9 10000 - ; 19000:c08d9588899e8748 42000:c0839ab0f6a62637 45000:c05f5e9ff1aa5947 58000:0 60000:c0608df231cdf5f1 63000:4071e6651c74986a 64000:4052828fb70b4f37 65000:c07816ff79a33b06 68000:c0760b1b9802c818 69000:408d4e17e78c7bce 70000:4056a1dc22356f7d ; +delta anchored 8000 0 21000 1000 18 10000 - ; 5000:408c1abd21df83bd 6000:c032af48e414790b 8000:c08cee812f848e40 10000:c080868c7bb85f76 13000:408c901d7fb6f120 15000:7ff0000000000002 16000:40832331bb09ecc9 18000:406dd26de9e48460 21000:c0570975ca7f8fb3 23000:c04960809e523cc6 30000:408ec6fcf7e562f8 33000:c086d1e30e1a3b90 34000:c06db6e8dff41c1e 35000:c08bd16b42a8df59 37000:c071c65401c4e989 39000:c04bea00d85563ee 40000:7ff0000000000000 63000:c0897a6c439e8efb 64000:c088fd8f95488c09 66000:c07453eb235ab6f5 ; 21000:c08f714c3906e316 22000:c08f714c3906e316 23000:c08e2625899c14ec 24000:c084b939c4ef1095 25000:c084b939c4ef1095 26000:c072154708bc89c9 27000:c072154708bc89c9 28000:c072154708bc89c9 29000:4044b26af6ace2a0 30000:4090d415d89aaa77 31000:40902e8280e54362 32000:40902e8280e54362 33000:c0853bdb043517c4 34000:c0675ec8b85f8cec 35000:c08a3b6338c3bb8d 36000:c08a3b6338c3bb8d 37000:c06d3487dbf543e0 38000:c093d5137c63ebde +delta anchored 12000 4000 18000 1000 4 10000 14000 ; 6000:405440298f9f0500 7000:7ff0000000000002 10000:4055cf70785c680e 13000:405fbcd785f68717 14000:4065d91d9008b7c4 17000:40686d4d9bdb96e5 18000:4069bb70f4491255 21000:406fbea42fe50259 24000:40726894cce41315 27000:407472b106df46ac 30000:407732fc75e31b44 32000:40787161935e143d 34000:407a7f0f7fb1ff2b 35000:407d94f536474ed4 38000:407ead2328c72c6d ; 18000:4018f46e8bd630e0 19000:4018f46e8bd630e0 20000:4018f46e8bd630e0 21000:4018f46e8bd630e0 +resets anchored 1000 -3000 26000 2000 8 10000 28000 ; 17000:fe37e43c8800759c 19000:fe37e43c8800759c 20000:fe37e43c8800759c 21000:fe37e43c8800759c 24000:fe37e43c8800759c 26000:fe37e43c8800759c 28000:fe37e43c8800759c ; +resets anchored 9000 9000 -1000 1000 7 1000 - ; 3000:4046c704279c72b3 4000:404ca616b22de091 5000:405764e1c936cfaa 8000:4061ee995735a2b6 9000:4067d1216a6a8a7a ; +resets anchored 1000 0 10000 3000 5 2000 - ; 13000:fe37e43c8800759c 24000:fe37e43c8800759c 25000:fe37e43c8800759c 26000:fe37e43c8800759c 27000:fe37e43c8800759c ; 13000:0 +resets anchored 2000 0 60000 2000 16 10000 15000 ; 13000:c0795554fd9f4646 16000:fe37e43c8800759c 17000:c05718a4f8bf229e 19000:40458870bf33cb46 20000:c072b8103aab708a 23000:c07e5150f7cf8a77 25000:c081add687fd50dd 26000:407f4f40e4b9aa71 29000:408423b4e4ade483 30000:c0602d5d80909295 32000:407946d1f5d9202f 34000:c08d67714b579a36 37000:407e93189bc737f4 40000:fe37e43c8800759c 43000:c07e8de139ee23af 46000:40771368aeccaa7f 48000:4062200bd9fdf36f 50000:405be563a33c3f41 52000:c08901bd7511618f 53000:c076e8bcda3d4912 54000:c081a85c69cfe861 56000:c08bb3889b39687e 59000:c07f10ece6b63caf 61000:c073b1971a969c20 62000:408ac5ff2505c160 ; +resets anchored 1000 1000 28000 3000 17 1000 - ; 3000:4089d2d854555af1 4000:c0450daefc6bbb55 6000:40509bb96d768405 7000:7ff8000000000001 10000:c06a400af929bdc4 11000:c07819a9a50bdd13 13000:402f0e8bc5f0f400 16000:4087a8044c02c270 19000:7e37e43c8800759c 20000:c08899a3128e6be9 22000:0 32000:7ff0000000000002 35000:408b88de6756aa14 37000:4085e2c2bee19698 40000:408e1d69dce2bdef ; +resets anchored 3000 0 15000 3000 2 2000 - ; 17000:40519c286a70b98d 19000:4058d71c5dee08c9 21000:405ee5549f5e4ddd 22000:406391b5c447bc47 ; 18000:0 +changes anchored 20000 -7000 47000 1000 11 3000 - ; 13000:c076e396aa61d8e3 16000:c084724f80ced482 18000:408b15141a243b0b 21000:7ff0000000000002 22000:c08dceafcf03c5eb 25000:c0888d6477098fd2 28000:40875392cb95208c 29000:4072d331defb5985 32000:c0712edfca2cba63 34000:40890d69c65cad25 37000:c0745f9a85cca161 38000:7ff8000000000001 40000:408c586a51004a72 43000:c06d10f6cd1254a0 44000:40740c0538dc7cb7 45000:7ff0000000000002 47000:7ff8000000000001 48000:40865a2f0ac18233 50000:c07c5967907c4db9 51000:c06e88c312990fb7 52000:c08c9827646edbec 54000:7ff8000000000001 55000:7ff8000000000001 56000:408365020bb6631c ; 47000:4026000000000000 48000:4026000000000000 49000:4028000000000000 50000:4026000000000000 51000:4024000000000000 52000:4024000000000000 53000:4022000000000000 54000:4022000000000000 55000:4022000000000000 56000:4020000000000000 57000:401c000000000000 +changes anchored 3000 0 20000 3000 15 3000 - ; 16000:4056f369bfd3ccc4 19000:405e58ccc4809bfa 21000:40642bc752cde74a ; 20000:3ff0000000000000 23000:3ff0000000000000 +changes anchored 8000 0 47000 4000 18 1000 - ; 17000:404310404aeb78ca 19000:404d2affef50ae06 21000:404e5ce902ea5bde 23000:405a8675a589af18 25000:405f545627530bd4 28000:4063f7e073e77ef1 29000:40646e8be58bd5d0 30000:4067dc5f2a8927bd 31000:4068330c85346bdf 34000:406c27d1c7246a2c 35000:406fb6847dc261e2 38000:40716801201b72c0 39000:4071b9e64af05e03 42000:407289b022a1ab84 64000:4072be4bae621a28 65000:4075cb4848adbf18 67000:4009a31efa509e3a 68000:402e2d2f55df24a4 69000:401ca2b9a59851a1 71000:40490465d1f8f601 74000:40538d2b46afbe6e 77000:405be54400b8c909 80000:4062a9740b28284e ; 47000:3ff0000000000000 67000:4000000000000000 71000:4014000000000000 75000:4010000000000000 79000:4000000000000000 83000:3ff0000000000000 87000:0 +changes anchored 6000 -9000 21000 1000 2 3000 - ; 7000:7ff8000000000001 8000:7ff8000000000001 9000:7ff8000000000001 32000:7ff8000000000001 33000:7ff8000000000001 36000:7ff8000000000001 ; +changes anchored 3000 -3000 27000 3000 8 10000 - ; 21000:4040228a01e3a7a3 22000:408592d318571d07 23000:c08e32da768b60e0 25000:408a56b6806baef1 26000:407c7d0d3bed82f4 29000:c08d3c72eceb6f1e 32000:40747d9475c27a02 ; 27000:3ff0000000000000 30000:3ff0000000000000 +changes anchored 1000 5000 39000 1000 13 1000 - ; 33000:40611a8ee39c52bb 35000:406226e01cc066b5 36000:406361639494d980 39000:40685d54dc02607d 40000:406b3bfc13a8e7e4 42000:406cb7f25a9ba427 44000:406dfed18569e20d 46000:406ff8715eb720b7 47000:4071619057f74530 49000:407169a6476013a2 50000:4006f8cbb368eff2 69000:40395afab2e074be 75000:405049b6677b9d98 77000:4053450cd70bdc5f 78000:405995ff9bd69ac9 79000:405eb34c15b858fb 80000:40638a9bf7ae0f92 ; 40000:0 41000:3ff0000000000000 44000:0 45000:3ff0000000000000 47000:0 49000:0 51000:0 +rate smoothed 8000 7000 30000 1000 8 3000 - ; 12000:40605eac2e9fe8e4 14000:406285fb9cbc7609 28000:401a301ce64fe526 30000:403ac192995a47f2 ; 35000:0 36000:3ff4358b5fc64ea9 37000:4004358b5fc64ea8 +rate smoothed 8000 -8000 13000 4000 6 3000 - ; 11000:fe37e43c8800759c 14000:fe37e43c8800759c ; 13000:0 +rate smoothed 8000 4000 134000 3000 17 2000 - ; 39000:40548d58eedd4a5a 54000:4060653ef5b63223 56000:4061a51c550966cd 59000:4063257ee903cda7 62000:4019ed5c5f259db0 63000:402e68b7146de8f7 64000:4038d7a2fe627d10 66000:40476964896e2f25 69000:40500d846da5b557 71000:405963ee9d80ccb8 73000:405bb88f9b0f9dbe 75000:405c208fda69d776 78000:405e87a02bc1fa33 80000:4062d93401c94466 82000:40669e3b3e92b14a 103000:406923050c3ab449 106000:406c4f6a24e40b87 109000:4020487ee57323ad 126000:4040ad0fccdf3384 128000:404f6e1dea9e45b6 130000:4051929895f0bf5c ; 134000:401278215f024b34 137000:401278215f024b34 140000:3fedb89a0a19c810 +rate smoothed 2000 9000 27000 4000 11 2000 - ; 21000:c0820020e89e5a1e 22000:408dd8fe18bf82cb 23000:c08ae14221abb881 26000:c06c90e7c7ee8d95 28000:c05f7d8e6563eef6 29000:406129448bbe05ed 32000:4089d62eacd9220a ; 31000:4087ec8f80aeee74 35000:406a51603f95717a 39000:406ed1443b29146a +rate smoothed 4000 -3000 15000 2000 6 10000 9000 ; 10000:7ff8000000000001 11000:408232d425ec7df8 13000:40578fc51240e9f5 ; 15000:7ff8000000000001 17000:7ff8000000000001 19000:7ff8000000000001 21000:7ff8000000000001 23000:7ff8000000000001 25000:7ff8000000000001 +rate smoothed 3000 0 37000 3000 13 2000 - ; 7000:c0807e0133901042 12000:407d6c6d2d9997e7 15000:7ff0000000000002 18000:7ff8000000000001 19000:c067818922a6174f 22000:40850d143623c6d0 24000:7ff0000000000002 25000:7ff8000000000001 26000:7ff8000000000001 29000:40872365ca9a9d19 30000:407a46b8a9cb5b8a 31000:4082b3e2d5801ee1 32000:407815b2dfc0dc11 35000:c068926d4bb2c8f0 37000:c070b0fdfddb77b7 38000:4080ae0997bde787 41000:c082fe4eead5adb4 44000:408bfe1db58a5549 47000:c08ecba650721ed2 49000:c08e2a3aa30729ee ; 37000:c0564152a7cf4a49 40000:40607bfe310a7855 43000:4074e1f763b8e4e2 46000:c06b5fb04781e281 49000:401ae74791d37b55 +increase smoothed 7000 6000 31000 2000 14 1000 - ; 15000:8000000000000000 16000:8000000000000000 17000:8000000000000000 20000:8000000000000000 23000:8000000000000000 25000:8000000000000000 28000:8000000000000000 31000:8000000000000000 33000:8000000000000000 34000:8000000000000000 49000:8000000000000000 50000:8000000000000000 52000:8000000000000000 55000:8000000000000000 57000:8000000000000000 59000:8000000000000000 60000:8000000000000000 63000:8000000000000000 65000:8000000000000000 66000:8000000000000000 ; 31000:0 33000:0 35000:0 37000:0 39000:0 41000:0 43000:0 45000:0 55000:0 57000:0 +increase smoothed 1000 0 42000 3000 12 10000 30000 ; 11000:fff0000000000000 13000:fff0000000000000 15000:fff0000000000000 17000:fff0000000000000 18000:fff0000000000000 21000:fff0000000000000 23000:fff0000000000000 24000:fff0000000000000 27000:fff0000000000000 28000:fff0000000000000 30000:fff0000000000000 32000:fff0000000000000 34000:7ff0000000000002 35000:fff0000000000000 36000:fff0000000000000 39000:fff0000000000000 40000:fff0000000000000 41000:fff0000000000000 42000:fff0000000000000 45000:fff0000000000000 ; 42000:fff8000000000000 45000:fff8000000000000 48000:fff8000000000000 51000:fff8000000000000 54000:fff8000000000000 57000:fff8000000000000 60000:fff8000000000000 63000:fff8000000000000 66000:fff8000000000000 69000:fff8000000000000 72000:fff8000000000000 75000:fff8000000000000 +increase smoothed 16000 0 32000 1000 2 10000 32000 ; 17000:c0634d306d68d18b 18000:7ff8000000000001 21000:c08f2025be7eada1 24000:40711a573a7a0f2b 27000:407f9a897e07173d 29000:c04ce1b55220086a 32000:c08546e99950ad49 35000:c08723f2fb082970 36000:408a5d4c587db59b 45000:c08b0b2666e607fd 47000:406237910cc595c2 50000:40754fa3bfc2805e 52000:c06a1aaa84e836e1 54000:c0878649c253c6db 56000:c07362e0a345b506 59000:7e37e43c8800759c ; 32000:c053a3a0a0a76e70 33000:c053a3a0a0a76e70 +increase smoothed 1000 -6000 61000 1000 20 10000 - ; 7000:c07b8af008fec86d 10000:4082d94be5a11bba 12000:c08489df6e2fe3a8 14000:408a3161368b06c9 16000:c0603d960152e964 17000:c082b4eccc283557 39000:c059daefc7040b48 41000:40647b53ac693309 44000:c0875e542f136dd4 46000:c0753d3a149894eb 49000:c06fb0612689e8e7 51000:c077399afc9850c2 54000:c07340dc4cd751d6 55000:4081a073ccc98e08 58000:c08c7be2bf18126c ; +increase smoothed 1000 -3000 23000 1000 1 10000 91000 ; 6000:4054396a10776bf8 8000:40557a68d920e118 9000:4013b71979b64cba 30000:4040696d2ef66bcd 32000:4054443988337fc9 35000:405881fa3122f065 36000:405bef2e57844214 38000:4060c2f2da829e38 56000:40635cc9cc5b79c0 59000:406861e34591932e 72000:406d66b77c5e41be 75000:40206e9cc3b90538 77000:4026b140627e8f22 80000:404e9173bcca4604 81000:405504df5b379130 84000:4056905dc1715bb6 86000:4023d98e79c6b072 92000:404d0bbda11100f0 94000:4051474014d812a9 97000:405dc45e5c2922fc 98000:405fa716ef3c6a68 101000:3fee178968962127 104000:4042af5bf26b2f1b 128000:4050e68d016658f5 ; 23000:40160b0a227c9188 +increase smoothed 2000 0 45000 1000 10 10000 - ; 16000:40562af6aac4536a 19000:405aa97c40f78c50 22000:3ff22f538269c50f 23000:4039fff3686ffa77 24000:4047f68e589e3db4 25000:4050053ed416a43d 26000:40557497be604856 28000:405bf2f071c1eba9 31000:40610694894ca539 34000:406332fb54fd59a5 35000:40635caa0fc1fdc4 48000:4066054ee9c09cad 49000:406bbc11ef2b93f4 52000:407069f902b2876a 57000:4072a0a3d9b70dd2 58000:4014726d436eb91e 61000:402de5bb2a3f5f4d ; 45000:400a2db99d7c4400 46000:400a2db99d7c4400 48000:0 49000:4046db0c15abdd1c 50000:404da58c334dd648 51000:403b2a007687e4a8 52000:403b2a007687e4a8 53000:4034aa561e85a078 54000:402c55578d06b860 +delta smoothed 2000 -9000 32000 1000 18 2000 - ; 17000:408c65bd3c2245c2 37000:4066c3081c643352 39000:7ff0000000000002 41000:c0759d738f23c30d 42000:7ff0000000000000 44000:c063e5fa7d05ab4f 46000:408bdab60d097eca 49000:c0834df035d3ba87 ; 32000:0 33000:7ff0000000000000 34000:fff8000000000000 35000:fff0000000000000 36000:fff8000000000000 37000:40906a1a562574cf 38000:40363c5d50cc3690 39000:c08f706ed73e2635 40000:c08f706ed73e2636 41000:0 +delta smoothed 1000 -1000 6000 2000 8 1000 14000 ; 5000:7e37e43c8800759c 26000:7e37e43c8800759c 28000:7e37e43c8800759c 30000:7e37e43c8800759c 33000:7e37e43c8800759c 35000:7e37e43c8800759c 36000:7e37e43c8800759c 44000:7e37e43c8800759c 46000:7e37e43c8800759c 49000:7e37e43c8800759c 51000:7e37e43c8800759c ; +delta smoothed 1000 0 27000 1000 13 2000 19000 ; 22000:404b915586fba7b9 23000:4051584b0fccc123 ; +delta smoothed 1000 1000 38000 1000 9 1000 68000 ; 11000:4084438a06ca1209 12000:408e42caba9fbbad 13000:c081dfec40e0c343 16000:c041b75ead0e6248 18000:4078e1537b3df263 21000:c08e122e7713b614 24000:40606d898919f696 26000:c041dbf6d0acd9d5 29000:c086b141fc51005f 30000:406c1ed3bb7a7784 32000:7ff8000000000001 34000:7ff8000000000001 37000:7ff8000000000001 39000:408af6130ff1df64 42000:408617ebd0998a1d 45000:4083c4262d0621a2 46000:4087ca9394810f6e 49000:407795e71f384399 50000:404a6b82cc45a31f 62000:4087696823946f5c 63000:c07dd21e9f099c93 64000:405d7cd019ad3a82 66000:c08ea6eab1fbdaa9 ; +delta smoothed 5000 0 44000 1000 7 2000 - ; 14000:7ff8000000000001 15000:7ff8000000000001 16000:7ff8000000000001 32000:7ff8000000000001 35000:7ff8000000000001 37000:7ff8000000000001 54000:7ff8000000000001 55000:7ff8000000000001 58000:7ff8000000000001 59000:7ff8000000000001 62000:7ff8000000000001 63000:7ff8000000000001 65000:7ff8000000000001 67000:7ff8000000000001 91000:7ff8000000000001 93000:7ff8000000000001 95000:7ff8000000000001 ; +delta smoothed 2000 8000 1000 1000 14 3000 - ; 6000:c07fbab882d565c9 7000:c05f00806903debb 8000:8000000000000000 11000:fff0000000000000 14000:c076f997069dfd25 16000:c076f769d5d1c420 18000:4084f08d7118eda5 38000:7ff0000000000000 ; 14000:0 +sum_over_time anchored 2000 3000 36000 2000 18 3000 - ; 10000:4053192f704357ca 11000:405dc834c382e588 14000:40646db7892deb54 15000:40668f319496236a 16000:4017e34efe7f6577 19000:403465a6ba815223 20000:7ff0000000000002 22000:404ccc8d885c356a 24000:40536325643a3575 25000:400d633dafcc61cd 32000:400fb879f879edb8 42000:402380a54d79d914 44000:403c0791714ba724 45000:40447ec39b2a6eed 48000:400516cff8130018 51000:40365b5c06a01bb1 52000:404c42c122b09838 55000:7ff0000000000002 57000:40596af11fe20f24 58000:405ef9ff7ec0035d 61000:4060b3c01a3ba108 ; error +resets smoothed 6000 4000 8000 3000 16 1000 - ; 5000:40611da7aa37842d 8000:c054425e84827c15 9000:7ff0000000000002 ; error diff --git a/src/promql/src/range_eval/tests.rs b/src/promql/src/range_eval/tests.rs index 0c9aba0e139..2d2e8b7c3ad 100644 --- a/src/promql/src/range_eval/tests.rs +++ b/src/promql/src/range_eval/tests.rs @@ -39,8 +39,8 @@ use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; use super::{ - CounterReset, FloatWindow, RangeEvaluator, RangeFunction, RangeWindow, WindowKernel, - subquery_grid, + CounterReset, FloatWindow, RangeEvaluator, RangeFunction, RangeModifier, RangeWindow, + WindowKernel, subquery_grid, }; use crate::evaluated::EvaluatedChunk; use crate::functions::{ @@ -653,6 +653,7 @@ fn random_window(rng: &mut StdRng) -> (Arc, RangeWindow) { range_ms, offset_ms, anchor_ms, + modifier: None, }, ) } @@ -917,6 +918,7 @@ fn an_ordinary_float_chunk_is_appended_in_bulk() { range_ms: 20_000, offset_ms: 0, anchor_ms: None, + modifier: None, }, ); assert!(evaluator.push(&chunk(&rows, 2, 0, true)).unwrap().is_some()); @@ -959,6 +961,7 @@ fn a_chunk_with_histograms_is_not_appended_in_bulk() { range_ms: 20_000, offset_ms: 0, anchor_ms: None, + modifier: None, }, RangeFunction::PresentOverTime, ) @@ -1099,6 +1102,7 @@ fn a_selected_histogram_keeps_its_start_timestamp() { range_ms: 20_000, offset_ms: 0, anchor_ms: None, + modifier: None, }, function, ) @@ -1157,6 +1161,7 @@ fn a_computed_histogram_drops_its_start_timestamp() { range_ms: 20_000, offset_ms: 0, anchor_ms: None, + modifier: None, }, RangeFunction::SumOverTime, ) @@ -1208,6 +1213,7 @@ fn a_retained_histogram_is_charged_for_its_buckets() { range_ms: 600_000, offset_ms: 0, anchor_ms: None, + modifier: None, }, // A function that counts the histogram, so what the row holds is a float: the two // figures then differ in the payload of the retained sample alone, not in what the row @@ -1259,6 +1265,7 @@ fn an_evicted_histogram_releases_its_payload() { range_ms: 20_000, offset_ms: 0, anchor_ms: None, + modifier: None, }, RangeFunction::CountOverTime, ) @@ -1313,6 +1320,7 @@ fn a_fixed_window_over_histograms_keeps_the_estimate_bounded() { range_ms: 1, offset_ms: 0, anchor_ms: None, + modifier: None, }, RangeFunction::CountOverTime, ) @@ -1348,6 +1356,7 @@ fn a_batch_spanning_fields_and_the_flush_threshold_matches_the_per_window_path() range_ms: 30_000, offset_ms: 0, anchor_ms: None, + modifier: None, }; // Counter-like values, each field resetting at a different point, growing at a different // rate, and missing at a different interval. The gaps are what make the fields' windows @@ -1455,6 +1464,7 @@ fn a_histogram_window_keeps_rate_on_the_per_window_path() { range_ms: 30_000, offset_ms: 0, anchor_ms: None, + modifier: None, }, RangeFunction::Rate, ) @@ -1480,6 +1490,7 @@ fn a_function_without_a_batch_form_stays_on_the_per_window_path() { range_ms: 30_000, offset_ms: 0, anchor_ms: None, + modifier: None, }, RangeFunction::SumOverTime, ) @@ -1494,6 +1505,7 @@ fn empty_input_and_series_without_results() { range_ms: 10, offset_ms: 0, anchor_ms: None, + modifier: None, }; probe_evaluator(1, grid(0, 10, 4), window).finish().unwrap(); @@ -1541,6 +1553,7 @@ fn a_push_that_completes_nothing_hands_out_no_chunk() { range_ms: 10, offset_ms: 0, anchor_ms: None, + modifier: None, }; let mut evaluator = probe_evaluator(1, grid(10, 10, 3), window); // The only sample sits inside the first window, so the first point cannot be settled yet: no @@ -1587,6 +1600,7 @@ fn a_series_that_ends_early_still_closes() { range_ms: 10, offset_ms: 0, anchor_ms: None, + modifier: None, }; let mut evaluator = probe_evaluator(1, grid(0, 10, 5), window); let first = chunk(&[(0, vec![Some(1.0)]), (10, vec![Some(2.0)])], 1, 0, false); @@ -1626,6 +1640,7 @@ fn decided_missing_positions_cost_no_rows_and_do_not_delay_the_close() { range_ms: 1_000, offset_ms: 0, anchor_ms: None, + modifier: None, }; let mut evaluator = RangeEvaluator::try_new( &raw_schema(1), @@ -1681,6 +1696,7 @@ fn the_closing_chunk_is_labelled_like_the_chunks_before_it() { range_ms: 20, offset_ms: 0, anchor_ms: None, + modifier: None, }; let mut evaluator = RangeEvaluator::try_new(&raw_schema(1), grid(0, 10, 6), window, RangeFunction::Rate) @@ -1762,6 +1778,7 @@ fn one_ms() -> RangeWindow { range_ms: 1, offset_ms: 0, anchor_ms: None, + modifier: None, } } @@ -1921,6 +1938,7 @@ fn a_sparse_series_folds_like_the_same_data_with_explicit_nulls() { range_ms: 3_000, offset_ms: 0, anchor_ms: None, + modifier: None, }; let rows = [ (0, vec![Some(1.0)]), @@ -1967,6 +1985,7 @@ fn a_window_reads_across_the_gap_between_runs_of_one_series() { range_ms: 11, offset_ms: 0, anchor_ms: None, + modifier: None, }; let mut evaluator = RangeEvaluator::try_new(&schema, grid(10, 1, 1), window, RangeFunction::SumOverTime) @@ -2008,6 +2027,7 @@ fn raw_chunk_boundaries_do_not_change_the_rows() { range_ms: 2_000, offset_ms: 0, anchor_ms: None, + modifier: None, }; let rows = [ (0, vec![Some(1.0)]), @@ -2136,6 +2156,7 @@ fn an_anchored_replay_stays_one_contiguous_piece() { range_ms: 10, offset_ms: 0, anchor_ms: Some(5), + modifier: None, }; for step_invariant in [true, false] { let mut evaluator = if step_invariant { @@ -2202,6 +2223,7 @@ fn rejects_invalid_parameters() { range_ms: 0, offset_ms: 0, anchor_ms: None, + modifier: None, }; assert!(matches!( RangeEvaluator::try_new( @@ -2217,6 +2239,7 @@ fn rejects_invalid_parameters() { range_ms: 10, offset_ms: i64::MAX - 20, anchor_ms: None, + modifier: None, }; assert!(matches!( RangeEvaluator::try_new( @@ -2232,6 +2255,7 @@ fn rejects_invalid_parameters() { range_ms: 10, offset_ms: 1, anchor_ms: Some(i64::MIN), + modifier: None, }; assert!(matches!( RangeEvaluator::try_new( @@ -2257,6 +2281,7 @@ fn rejects_invalid_chunk_sequences() { range_ms: 10, offset_ms: 0, anchor_ms: None, + modifier: None, }; let new = || probe_evaluator(1, grid(0, 10, 4), window); let row = |timestamp| (timestamp, vec![Some(1.0)]); @@ -2295,6 +2320,7 @@ fn offset_overflow_fails_unless_the_sample_is_stale() { range_ms: 10, offset_ms: 10, anchor_ms: None, + modifier: None, }; let late = i64::MAX - 5; let mut evaluator = probe_evaluator(1, grid(0, 10, 4), window); @@ -2343,6 +2369,7 @@ fn histogram_samples_need_a_function_and_drop_stale_markers() { range_ms: 10, offset_ms: 0, anchor_ms: None, + modifier: None, }; assert!(matches!( probe_evaluator(1, grid(0, 10, 4), window).push(&piece), @@ -2369,6 +2396,7 @@ fn retains_only_samples_later_windows_need() { range_ms: 50, offset_ms: 0, anchor_ms: None, + modifier: None, }; let mut evaluator = probe_evaluator(1, grid(0, 10, 1_000), window); let rows: Vec = (0..10_000).map(|i| (i, vec![Some(1.0)])).collect(); @@ -2389,6 +2417,7 @@ fn reset_index_is_released_with_the_samples_it_reaches_back_to() { range_ms: 50, offset_ms: 0, anchor_ms: None, + modifier: None, }; let mut evaluator = probe_evaluator(1, grid(0, 10, 1_000), window); // A sawtooth that resets every 10 samples. @@ -2435,6 +2464,7 @@ fn counter_resets_follow_the_window_and_survive_chunking() { range_ms: 20_000, offset_ms: 0, anchor_ms: None, + modifier: None, }; // Every chunk starts on a sample an earlier window corrected, so the index has to reach // across the chunk boundary. @@ -2558,6 +2588,7 @@ fn nulls_change_the_reset_positions_of_each_field_independently() { range_ms: 100_000, offset_ms: 0, anchor_ms: None, + modifier: None, }; // One entry per kernel call, in field order within each grid point. @@ -2780,6 +2811,7 @@ fn rejects_input_of_the_other_kind() { range_ms: 10, offset_ms: 0, anchor_ms: None, + modifier: None, }; let evaluated = SeriesSchema { fields: raw_schema(1).fields, @@ -2878,6 +2910,7 @@ fn range_functions_match_prometheus() { range_ms, offset_ms, anchor_ms, + modifier: None, }; let mut expected = vec![vec![None]; points as usize]; for (timestamp, value) in &expected_points { @@ -2917,6 +2950,7 @@ fn rejects_double_exponential_smoothing_factors_outside_the_unit_interval() { range_ms: 10, offset_ms: 0, anchor_ms: None, + modifier: None, }; for (smoothing, trend) in [(0.0, 0.5), (1.0, 0.5), (0.5, 0.0), (0.5, 1.5)] { let evaluator = || { @@ -2967,6 +3001,7 @@ fn range_functions_drop_metadata_labels() { range_ms: 10_000, offset_ms: 0, anchor_ms: None, + modifier: None, }; let rows: Vec = vec![(1_000, vec![Some(1.0)]), (2_000, vec![Some(2.0)])]; let nameless = MetricName::try_new(None, labels(&["host"])).unwrap(); @@ -2998,6 +3033,7 @@ fn ts_of_extremum_moves_past_a_leading_nan() { range_ms: 10_000, offset_ms: 0, anchor_ms: None, + modifier: None, }; let rows: Vec = [(1_000, f64::NAN), (2_000, 5.0), (3_000, 7.0)] .map(|(ts, value)| (ts, vec![Some(value)])) @@ -3030,6 +3066,7 @@ fn waiting_for_a_point_drops_samples_behind_its_window() { range_ms: 10, offset_ms: 0, anchor_ms: None, + modifier: None, }; let mut evaluator = probe_evaluator(1, grid(0, 100_000, 2), window); for chunk_index in 0..900 { @@ -3058,6 +3095,7 @@ fn ts_of_last_over_time_reports_zero_for_negative_timestamps() { range_ms: 2_000, offset_ms: 0, anchor_ms: None, + modifier: None, }; let rows: Vec = vec![(-1_000, vec![Some(7.0)])]; for (function, expected) in [ @@ -3087,6 +3125,7 @@ fn quantile_picks_the_signed_zero_go_picks() { range_ms: 20_000, offset_ms: 0, anchor_ms: None, + modifier: None, }; let (z, n) = (0.0, -0.0); let values = [ @@ -3146,6 +3185,7 @@ fn samples_after_the_last_window_are_validated_but_not_retained() { range_ms: 10, offset_ms: 0, anchor_ms: None, + modifier: None, }; let mut evaluator = probe_evaluator(1, grid(0, 10, 2), window); for chunk_index in 0..900 { @@ -3160,6 +3200,7 @@ fn samples_after_the_last_window_are_validated_but_not_retained() { range_ms: 10, offset_ms: 10, anchor_ms: None, + modifier: None, }; let late = i64::MAX - 5; let mut evaluator = probe_evaluator(1, grid(0, 10, 4), window); @@ -3190,6 +3231,7 @@ fn anchored_predict_linear_regresses_around_each_point() { range_ms: 30, offset_ms, anchor_ms: Some(50), + modifier: None, }; let mut evaluator = RangeEvaluator::try_new( &raw_schema(1), @@ -3225,6 +3267,7 @@ fn rejects_a_subquery_grid_longer_than_u64() { range_ms: 7, offset_ms: -6, anchor_ms: None, + modifier: None, }; assert!(matches!( subquery_grid(outer, window, 1), @@ -3240,6 +3283,7 @@ fn anchored_window_before_the_series_retains_nothing() { range_ms: 10, offset_ms: 0, anchor_ms: Some(5), + modifier: None, }; let mut evaluator = probe_evaluator(1, grid(100, 10, 3), window); for chunk_index in 0..100 { @@ -3293,6 +3337,7 @@ fn absent_over_time_is_absent_over_present_over_time() { range_ms, offset_ms: 0, anchor_ms: None, + modifier: None, }; let mut present = Vec::new(); let mut expected = None; @@ -3448,6 +3493,7 @@ fn histogram_range_functions_match_prometheus() { range_ms: case.spec[1].parse().unwrap(), offset_ms: case.spec[2].parse().unwrap(), anchor_ms: None, + modifier: None, }; // One chunk per sample also checks that histograms survive across chunks. for split in [false, true] { @@ -3535,6 +3581,7 @@ fn evaluated_histograms_drop_counter_reset_hints() { range_ms: 30, offset_ms: 0, anchor_ms: None, + modifier: None, }; let mut evaluator = RangeEvaluator::try_new(&schema, grid(10, 10, 1), window, RangeFunction::Resets).unwrap(); @@ -3617,6 +3664,7 @@ fn a_valued_prefix_composes_with_an_empty_closing_chunk() { range_ms: 2_000, offset_ms: 0, anchor_ms: None, + modifier: None, }; let points = Points::from([ (0, f64::to_bits(1.0)), @@ -3707,6 +3755,7 @@ fn one_step_window(step_ms: i64) -> RangeWindow { range_ms: step_ms, offset_ms: 0, anchor_ms: None, + modifier: None, } } @@ -4183,6 +4232,7 @@ fn a_window_at_a_later_run_reads_the_earlier_run() { range_ms: 12_000, offset_ms: 0, anchor_ms: None, + modifier: None, }; let outer = grid(11_000, 1_000, 1); let mut evaluator = @@ -4383,11 +4433,13 @@ fn two_composed_evaluators_agree_with_the_dense_reference() { range_ms: 3_000, offset_ms: 0, anchor_ms: None, + modifier: None, }; let outer_window = RangeWindow { range_ms: 2_000, offset_ms: 0, anchor_ms: None, + modifier: None, }; let rows = vec![ (0, vec![Some(1.0)]), @@ -4490,3 +4542,201 @@ fn two_composed_evaluators_agree_with_the_dense_reference() { "the composition lost or invented a value" ); } + +/// The `anchored` and `smoothed` range modifiers evaluated by the Prometheus v3.15.0 engine; see +/// the header of `modifier_cases.txt`. Each case is pushed as one chunk and as one chunk per +/// sample, so a smoothed point is only decided once the samples after its window have arrived. +#[test] +fn range_modifiers_match_prometheus() { + let parse_points = |points: &str| -> Vec<(i64, f64)> { + points + .split_whitespace() + .map(|point| { + let (timestamp, bits) = point.split_once(':').unwrap(); + ( + timestamp.parse().unwrap(), + f64::from_bits(u64::from_str_radix(bits, 16).unwrap()), + ) + }) + .collect() + }; + let (mut checked, mut rejected) = (0, 0); + for line in include_str!("modifier_cases.txt").lines() { + if line.starts_with('#') { + continue; + } + let mut parts = line.split(';'); + let header: Vec<_> = parts.next().unwrap().split_whitespace().collect(); + let samples = parse_points(parts.next().unwrap()); + let result = parts.next().unwrap().trim(); + let function = range_function(header[0], &[]); + let numbers: Vec = header[2..8].iter().map(|n| n.parse().unwrap()).collect(); + let [range_ms, offset_ms, start_ms, step_ms, points, lookback_ms] = numbers[..] else { + panic!("bad header {line}"); + }; + let modifier = match header[1] { + "anchored" => RangeModifier::Anchored { lookback_ms }, + "smoothed" => RangeModifier::Smoothed { lookback_ms }, + other => panic!("unknown modifier {other}"), + }; + let window = RangeWindow { + range_ms, + offset_ms, + anchor_ms: (header[8] != "-").then(|| header[8].parse().unwrap()), + modifier: Some(modifier), + }; + let grid = grid(start_ms, step_ms, points as u64); + if result == "error" { + assert!( + RangeEvaluator::try_new(&raw_schema(1), grid, window, function).is_err(), + "{line}" + ); + rejected += 1; + continue; + } + let mut expected = vec![vec![None]; points as usize]; + for (timestamp, value) in parse_points(result) { + expected[((timestamp - start_ms) / step_ms) as usize][0] = Some(value); + } + let rows: Vec = samples + .iter() + .map(|(timestamp, value)| (*timestamp, vec![Some(*value)])) + .collect(); + let one_chunk = vec![chunk(&rows, 1, 0, true)]; + let per_sample: Vec<_> = rows + .iter() + .enumerate() + .map(|(index, row)| chunk(std::slice::from_ref(row), 1, 0, index + 1 == rows.len())) + .collect(); + for pieces in [one_chunk, per_sample] { + let mut evaluator = + RangeEvaluator::try_new(&raw_schema(1), Arc::clone(&grid), window, function) + .unwrap(); + let chunks = collect_chunks(&mut evaluator, &pieces); + let output = fold_chunks(&chunks, evaluator.output_schema()); + assert_series_eq_any_nan( + output.as_ref(), + &expected, + &output_metric(function, 0), + line, + ); + } + checked += 1; + } + assert!(checked >= 80 && rejected == 4, "{checked} {rejected}"); +} + +/// A smoothed window interpolates its end with the first sample after it, which can arrive in a +/// later chunk even when the chunk before ends at the point: here that chunk's last row is stale +/// and dropped, so `delta(m[2s] smoothed)` at 61s must wait for the sample at 63s and interpolate +/// 3 at the end, as Prometheus does, rather than take the 2 at 59s. +#[test] +fn smoothed_waits_for_the_sample_after_a_point_its_chunk_ended_on() { + let window = RangeWindow { + range_ms: 2_000, + offset_ms: 0, + anchor_ms: None, + modifier: Some(RangeModifier::Smoothed { lookback_ms: 3_000 }), + }; + let mut evaluator = RangeEvaluator::try_new( + &raw_schema(1), + grid(61_000, 1_000, 1), + window, + RangeFunction::Delta, + ) + .unwrap(); + let first: Vec = vec![ + (57_000, vec![Some(1.0)]), + (59_000, vec![Some(2.0)]), + (61_000, vec![Some(stale())]), + ]; + let pieces = [ + chunk(&first, 1, 0, false), + chunk(&[(63_000, vec![Some(4.0)])], 1, 0, true), + ]; + let chunks = collect_chunks(&mut evaluator, &pieces); + let output = fold_chunks(&chunks, evaluator.output_schema()); + assert_series_eq_any_nan( + output.as_ref(), + &vec![vec![Some(1.0)]], + &output_metric(RangeFunction::Delta, 0), + "smoothed delta at 61s", + ); +} + +/// `delta(m[2s] smoothed)` with a 3s lookback at 61s: floats at 59s and 61s, then `histogram` at +/// `histogram_ts`, either as one chunk or with the histogram in a closing chunk of its own. +fn smoothed_delta_with_histogram( + histogram_ts: i64, + one_chunk: bool, +) -> datafusion::error::Result> { + let window = RangeWindow { + range_ms: 2_000, + offset_ms: 0, + anchor_ms: None, + modifier: Some(RangeModifier::Smoothed { lookback_ms: 3_000 }), + }; + let schema = raw_schema(1); + let mut evaluator = RangeEvaluator::try_new( + &schema, + grid(61_000, 1_000, 1), + window, + RangeFunction::Delta, + ) + .unwrap(); + let floats: Vec = vec![(59_000, vec![Some(2.0)]), (61_000, vec![Some(4.0)])]; + let histogram = histogram_chunk(&schema, 1, histogram_ts); + let pieces = if one_chunk { + let float_chunk = chunk(&floats, 1, 0, false); + let field = Samples::try_from_chunks(vec![ + float_chunk.fields.as_slice()[0].clone(), + histogram.fields.as_slice()[0].clone(), + ]) + .unwrap(); + let time = + RawTimestamps::try_new(ScalarBuffer::from(vec![59_000, 61_000, histogram_ts])).unwrap(); + vec![ + RawSeriesChunk::try_new( + &schema, + float_chunk.primary_key.clone(), + float_chunk.metric.clone(), + time, + SeriesFields::try_from_vec(vec![field]).unwrap(), + true, + ) + .unwrap(), + ] + } else { + let mut closing = histogram; + closing.is_last = true; + vec![chunk(&floats, 1, 0, false), closing] + }; + let mut chunks = Vec::new(); + for piece in &pieces { + chunks.extend(evaluator.push(piece)?); + } + Ok(chunks) +} + +/// The smoothed point at 61s is decided by the float at 61s, before a histogram at 62s inside its +/// extended window arrives. The histogram is still refused, however the input is chunked, rather +/// than refused in one chunk and swallowed after the point was handed over in another. +#[test] +fn a_smoothed_window_refuses_a_histogram_whatever_the_chunking() { + for one_chunk in [true, false] { + let error = smoothed_delta_with_histogram(62_000, one_chunk).unwrap_err(); + assert!( + matches!(error, DataFusionError::NotImplemented(_)), + "one chunk: {one_chunk}: {error}" + ); + } +} + +/// A histogram past the extended window of every point is not read by any of them, so it is +/// accepted: the 61s window with a 3s lookback reads up to 64s. +#[test] +fn a_histogram_outside_every_smoothed_window_is_accepted() { + for one_chunk in [true, false] { + smoothed_delta_with_histogram(65_000, one_chunk).unwrap(); + } +} diff --git a/src/query/src/promql/native_compile.rs b/src/query/src/promql/native_compile.rs index 64ccdef5239..373f3ac2294 100644 --- a/src/query/src/promql/native_compile.rs +++ b/src/query/src/promql/native_compile.rs @@ -946,6 +946,7 @@ fn window_selection( range_ms, offset_ms, anchor_ms, + modifier: None, }, // The input is raw samples, so it has no grid of its own. input_grid: None, @@ -1024,6 +1025,7 @@ fn subquery_window( range_ms, offset_ms, anchor_ms, + modifier: None, }; let child_spec = subquery_grid(context.grid.spec(), window, step_ms)?.ok_or_else(|| { not_implemented(&format!(