global: snapshot

This commit is contained in:
nym21
2026-03-15 11:25:21 +01:00
parent 9e36a4188a
commit 9626c7de32
54 changed files with 884 additions and 750 deletions
@@ -89,6 +89,17 @@ where
});
let start = index.to_usize();
// Truncate all vecs to start once, so the loop only pushes
macro_rules! truncate_vec {
($($vec:ident),*) => {
$(if let Some(ref mut v) = $vec {
v.truncate_if_needed_at(start)?;
})*
};
}
truncate_vec!(first, last, min, max, average, sum, cumulative, median, pct10, pct25, pct75, pct90);
let fi_len = first_indexes.len();
let first_indexes_batch: Vec<A> = first_indexes.collect_range_at(start, fi_len);
let count_indexes_batch: Vec<StoredU64> = count_indexes.collect_range_at(start, fi_len);
@@ -97,8 +108,7 @@ where
.into_iter()
.zip(count_indexes_batch)
.enumerate()
.try_for_each(|(j, (first_index, count_index))| -> Result<()> {
let idx = start + j;
.try_for_each(|(_, (first_index, count_index))| -> Result<()> {
let count = u64::from(count_index) as usize;
// Effective count after skipping (e.g., skip coinbase for fee calculations)
@@ -113,17 +123,15 @@ where
} else {
T::from(0_usize)
};
first_vec.truncate_push_at(idx, f)?;
first_vec.push(f);
}
if let Some(ref mut last_vec) = last {
if effective_count == 0 {
// If all items skipped, use zero
last_vec.truncate_push_at(idx, T::from(0_usize))?;
last_vec.push(T::from(0_usize));
} else {
let last_index = first_index + (count - 1);
let v = source.collect_one_at(last_index.to_usize()).unwrap();
last_vec.truncate_push_at(idx, v)?;
last_vec.push(source.collect_one_at(last_index.to_usize()).unwrap());
}
}
@@ -143,12 +151,10 @@ where
});
if let Some(ref mut min_vec) = min {
let v = min_val.or(max_val).unwrap_or_else(|| T::from(0_usize));
min_vec.truncate_push_at(idx, v)?;
min_vec.push(min_val.or(max_val).unwrap_or_else(|| T::from(0_usize)));
}
if let Some(ref mut max_vec) = max {
let v = max_val.or(min_val).unwrap_or_else(|| T::from(0_usize));
max_vec.truncate_push_at(idx, v)?;
max_vec.push(max_val.or(min_val).unwrap_or_else(|| T::from(0_usize)));
}
} else if needs_percentiles || needs_minmax {
let mut values: Vec<T> = source.collect_range_at(
@@ -157,21 +163,18 @@ where
);
if values.is_empty() {
// Handle edge case where all items were skipped
macro_rules! push_zero {
($($vec:ident),*) => {
$(if let Some(ref mut v) = $vec {
v.truncate_push_at(idx, T::from(0_usize))?;
v.push(T::from(0_usize));
})*
};
}
push_zero!(max, pct90, pct75, median, pct25, pct10, min, average, sum);
if let Some(ref mut cumulative_vec) = cumulative {
let t = cumulative_val.unwrap();
cumulative_vec.truncate_push_at(idx, t)?;
cumulative_vec.push(cumulative_val.unwrap());
}
} else if needs_percentiles {
// Compute aggregates from unsorted values first to avoid clone
let aggregate_result = if needs_aggregates {
let len = values.len();
let sum_val = values.iter().copied().fold(T::from(0), |a, b| a + b);
@@ -180,53 +183,52 @@ where
None
};
// Sort in-place — no clone needed
values.sort_unstable();
if let Some(ref mut max_vec) = max {
max_vec.truncate_push_at(idx, *values.last().unwrap())?;
max_vec.push(*values.last().unwrap());
}
if let Some(ref mut pct90_vec) = pct90 {
pct90_vec.truncate_push_at(idx, get_percentile(&values, 0.90))?;
pct90_vec.push(get_percentile(&values, 0.90));
}
if let Some(ref mut pct75_vec) = pct75 {
pct75_vec.truncate_push_at(idx, get_percentile(&values, 0.75))?;
pct75_vec.push(get_percentile(&values, 0.75));
}
if let Some(ref mut median_vec) = median {
median_vec.truncate_push_at(idx, get_percentile(&values, 0.50))?;
median_vec.push(get_percentile(&values, 0.50));
}
if let Some(ref mut pct25_vec) = pct25 {
pct25_vec.truncate_push_at(idx, get_percentile(&values, 0.25))?;
pct25_vec.push(get_percentile(&values, 0.25));
}
if let Some(ref mut pct10_vec) = pct10 {
pct10_vec.truncate_push_at(idx, get_percentile(&values, 0.10))?;
pct10_vec.push(get_percentile(&values, 0.10));
}
if let Some(ref mut min_vec) = min {
min_vec.truncate_push_at(idx, *values.first().unwrap())?;
min_vec.push(*values.first().unwrap());
}
if let Some((len, sum_val)) = aggregate_result {
if let Some(ref mut average_vec) = average {
average_vec.truncate_push_at(idx, sum_val / len)?;
average_vec.push(sum_val / len);
}
if needs_sum_or_cumulative {
if let Some(ref mut sum_vec) = sum {
sum_vec.truncate_push_at(idx, sum_val)?;
sum_vec.push(sum_val);
}
if let Some(ref mut cumulative_vec) = cumulative {
let t = cumulative_val.unwrap() + sum_val;
cumulative_val.replace(t);
cumulative_vec.truncate_push_at(idx, t)?;
cumulative_vec.push(t);
}
}
}
} else if needs_minmax {
if let Some(ref mut min_vec) = min {
min_vec.truncate_push_at(idx, *values.iter().min().unwrap())?;
min_vec.push(*values.iter().min().unwrap());
}
if let Some(ref mut max_vec) = max {
max_vec.truncate_push_at(idx, *values.iter().max().unwrap())?;
max_vec.push(*values.iter().max().unwrap());
}
if needs_aggregates {
@@ -234,23 +236,22 @@ where
let sum_val = values.into_iter().fold(T::from(0), |a, b| a + b);
if let Some(ref mut average_vec) = average {
average_vec.truncate_push_at(idx, sum_val / len)?;
average_vec.push(sum_val / len);
}
if needs_sum_or_cumulative {
if let Some(ref mut sum_vec) = sum {
sum_vec.truncate_push_at(idx, sum_val)?;
sum_vec.push(sum_val);
}
if let Some(ref mut cumulative_vec) = cumulative {
let t = cumulative_val.unwrap() + sum_val;
cumulative_val.replace(t);
cumulative_vec.truncate_push_at(idx, t)?;
cumulative_vec.push(t);
}
}
}
}
} else if needs_aggregates {
// Aggregates only (sum/average/cumulative) — no Vec allocation needed
let efi = effective_first_index.to_usize();
let (sum_val, len) = source.fold_range_at(
efi,
@@ -265,17 +266,17 @@ where
} else {
T::from(0_usize)
};
average_vec.truncate_push_at(idx, avg)?;
average_vec.push(avg);
}
if needs_sum_or_cumulative {
if let Some(ref mut sum_vec) = sum {
sum_vec.truncate_push_at(idx, sum_val)?;
sum_vec.push(sum_val);
}
if let Some(ref mut cumulative_vec) = cumulative {
let t = cumulative_val.unwrap() + sum_val;
cumulative_val.replace(t);
cumulative_vec.truncate_push_at(idx, t)?;
cumulative_vec.push(t);
}
}
}
@@ -348,6 +349,19 @@ where
let zero = T::from(0_usize);
let mut values: Vec<T> = Vec::new();
for vec in [
&mut *min,
&mut *max,
&mut *average,
&mut *median,
&mut *pct10,
&mut *pct25,
&mut *pct75,
&mut *pct90,
] {
vec.truncate_if_needed_at(start)?;
}
count_indexes_batch
.iter()
.enumerate()
@@ -378,7 +392,7 @@ where
&mut *pct75,
&mut *pct90,
] {
vec.truncate_push_at(idx, zero)?;
vec.push(zero);
}
} else {
source.collect_range_into_at(range_start_usize, range_end_usize, &mut values);
@@ -390,14 +404,14 @@ where
values.sort_unstable();
max.truncate_push_at(idx, *values.last().unwrap())?;
pct90.truncate_push_at(idx, get_percentile(&values, 0.90))?;
pct75.truncate_push_at(idx, get_percentile(&values, 0.75))?;
median.truncate_push_at(idx, get_percentile(&values, 0.50))?;
pct25.truncate_push_at(idx, get_percentile(&values, 0.25))?;
pct10.truncate_push_at(idx, get_percentile(&values, 0.10))?;
min.truncate_push_at(idx, *values.first().unwrap())?;
average.truncate_push_at(idx, avg)?;
max.push(*values.last().unwrap());
pct90.push(get_percentile(&values, 0.90));
pct75.push(get_percentile(&values, 0.75));
median.push(get_percentile(&values, 0.50));
pct25.push(get_percentile(&values, 0.25));
pct10.push(get_percentile(&values, 0.10));
min.push(*values.first().unwrap());
average.push(avg);
}
Ok(())
@@ -107,9 +107,21 @@ where
let starts_batch = window_starts.collect_range_at(skip, end);
for v in [
&mut *average_out,
&mut *min_out,
&mut *max_out,
&mut *p10_out,
&mut *p25_out,
&mut *median_out,
&mut *p75_out,
&mut *p90_out,
] {
v.truncate_if_needed_at(skip)?;
}
for (j, start) in starts_batch.into_iter().enumerate() {
let i = skip + j;
let v = partial_values[i - range_start];
let v = partial_values[skip + j - range_start];
let start_usize = start.to_usize();
window.advance(v, start_usize, partial_values, range_start);
@@ -125,19 +137,19 @@ where
&mut *p75_out,
&mut *p90_out,
] {
v.truncate_push_at(i, zero)?;
v.push(zero);
}
} else {
average_out.truncate_push_at(i, T::from(window.average()))?;
min_out.truncate_push_at(i, T::from(window.min()))?;
max_out.truncate_push_at(i, T::from(window.max()))?;
average_out.push(T::from(window.average()));
min_out.push(T::from(window.min()));
max_out.push(T::from(window.max()));
let [p10, p25, p50, p75, p90] =
window.percentiles(&[0.10, 0.25, 0.50, 0.75, 0.90]);
p10_out.truncate_push_at(i, T::from(p10))?;
p25_out.truncate_push_at(i, T::from(p25))?;
median_out.truncate_push_at(i, T::from(p50))?;
p75_out.truncate_push_at(i, T::from(p75))?;
p90_out.truncate_push_at(i, T::from(p90))?;
p10_out.push(T::from(p10));
p25_out.push(T::from(p25));
median_out.push(T::from(p50));
p75_out.push(T::from(p75));
p90_out.push(T::from(p90));
}
if average_out.batch_limit_reached() {
@@ -35,6 +35,7 @@ impl RollingDistributionSlot {
})
}
#[allow(clippy::too_many_arguments)]
pub(crate) fn compute(
&mut self,
max_from: Height,
@@ -1,6 +1,6 @@
use brk_error::Result;
use brk_traversable::{Traversable, TreeNode};
use brk_types::{Cents, Height, Version};
use brk_types::{Cents, Version};
use vecdb::{AnyExportableVec, Database, ReadOnlyClone, Ro, Rw, StorageMode, WritableVec};
use crate::indexes;
@@ -38,16 +38,12 @@ impl PercentilesVecs {
Ok(Self { vecs })
}
/// Push percentile prices at this height (in cents).
pub(crate) fn truncate_push(
&mut self,
height: Height,
percentile_prices: &[Cents; PERCENTILES_LEN],
) -> Result<()> {
/// Push percentile prices (in cents).
#[inline(always)]
pub(crate) fn push(&mut self, percentile_prices: &[Cents; PERCENTILES_LEN]) {
for (i, v) in self.vecs.iter_mut().enumerate() {
v.cents.height.truncate_push(height, percentile_prices[i])?;
v.cents.height.push(percentile_prices[i]);
}
Ok(())
}
/// Validate computed versions or reset if mismatched.
@@ -46,12 +46,7 @@ impl RatioPerBlockPercentiles {
macro_rules! import_ratio {
($suffix:expr) => {
RatioPerBlock::forced_import_raw(
db,
&format!("{name}_{}", $suffix),
v,
indexes,
)?
RatioPerBlock::forced_import_raw(db, &format!("{name}_{}", $suffix), v, indexes)?
};
}
@@ -126,12 +121,15 @@ impl RatioPerBlockPercentiles {
const PCTS: [f64; 6] = [0.01, 0.02, 0.05, 0.95, 0.98, 0.99];
let mut out = [0u32; 6];
for (offset, &ratio) in new_ratios.iter().enumerate() {
for vec in pct_vecs.iter_mut() {
vec.truncate_if_needed_at(start)?;
}
for &ratio in new_ratios.iter() {
self.expanding_pct.add(*ratio);
self.expanding_pct.quantiles(&PCTS, &mut out);
let idx = start + offset;
for (vec, &val) in pct_vecs.iter_mut().zip(out.iter()) {
vec.truncate_push_at(idx, BasisPoints32::from(val))?;
vec.push(BasisPoints32::from(val));
}
}
}
@@ -79,9 +79,9 @@ where
let cached = cached_start.clone();
let starts_version = cached.version();
// Change: source[h] - source[ago] as C (via f64)
// Absolute change: source[h] - source[ago] as C (via f64)
let change_vec = LazyDeltaVec::<Height, S, C, DeltaChange>::new(
&format!("{full_name}_change"),
&full_name,
version,
src.clone(),
starts_version,
@@ -91,7 +91,7 @@ where
},
);
let change_resolutions = Resolutions::forced_import(
&format!("{full_name}_change"),
&full_name,
change_vec.read_only_boxed_clone(),
version,
indexes,
@@ -102,15 +102,16 @@ where
};
// Rate BPS: (source[h] - source[ago]) / source[ago] as B (via f64)
let rate_bps_name = format!("{full_name}_rate_bps");
let rate_vec = LazyDeltaVec::<Height, S, B, DeltaRate>::new(
&format!("{full_name}_rate_bps"),
&rate_bps_name,
version,
src.clone(),
starts_version,
move || cached.get(),
);
let rate_resolutions = Resolutions::forced_import(
&format!("{full_name}_rate_bps"),
&rate_bps_name,
rate_vec.read_only_boxed_clone(),
version,
indexes,
@@ -121,28 +122,30 @@ where
};
// Ratio: bps / 10000
let rate_ratio_name = format!("{full_name}_rate_ratio");
let ratio = LazyPerBlock {
height: LazyVecFrom1::transformed::<B::ToRatio>(
&format!("{full_name}_rate_ratio"),
&rate_ratio_name,
version,
bps.height.read_only_boxed_clone(),
),
resolutions: Box::new(DerivedResolutions::from_derived_computed::<B::ToRatio>(
&format!("{full_name}_rate_ratio"),
&rate_ratio_name,
version,
&bps.resolutions,
)),
};
// Percent: bps / 100
let rate_name = format!("{full_name}_rate");
let percent = LazyPerBlock {
height: LazyVecFrom1::transformed::<B::ToPercent>(
&format!("{full_name}_rate"),
&rate_name,
version,
bps.height.read_only_boxed_clone(),
),
resolutions: Box::new(DerivedResolutions::from_derived_computed::<B::ToPercent>(
&format!("{full_name}_rate"),
&rate_name,
version,
&bps.resolutions,
)),
@@ -214,9 +217,10 @@ where
let cached = cached_start.clone();
let starts_version = cached.version();
// Change cents: source[h] - source[ago] as C (via f64)
// Absolute change (cents): source[h] - source[ago] as C (via f64)
let cents_name = format!("{full_name}_cents");
let change_vec = LazyDeltaVec::<Height, S, C, DeltaChange>::new(
&format!("{full_name}_change"),
&cents_name,
version,
src.clone(),
starts_version,
@@ -226,7 +230,7 @@ where
},
);
let change_resolutions = Resolutions::forced_import(
&format!("{full_name}_change"),
&cents_name,
change_vec.read_only_boxed_clone(),
version,
indexes,
@@ -236,15 +240,15 @@ where
resolutions: Box::new(change_resolutions),
};
// Change USD: lazy from cents delta
// Absolute change (usd): lazy from cents delta
let usd = LazyPerBlock {
height: LazyVecFrom1::transformed::<C::ToDollars>(
&format!("{full_name}_change_usd"),
&full_name,
version,
cents.height.read_only_boxed_clone(),
),
resolutions: Box::new(DerivedResolutions::from_derived_computed::<C::ToDollars>(
&format!("{full_name}_change_usd"),
&full_name,
version,
&cents.resolutions,
)),
@@ -253,15 +257,16 @@ where
let absolute = LazyDeltaFiatFromHeight { usd, cents };
// Rate BPS: (source[h] - source[ago]) / source[ago] as B (via f64)
let rate_bps_name = format!("{full_name}_rate_bps");
let rate_vec = LazyDeltaVec::<Height, S, B, DeltaRate>::new(
&format!("{full_name}_rate_bps"),
&rate_bps_name,
version,
src.clone(),
starts_version,
move || cached.get(),
);
let rate_resolutions = Resolutions::forced_import(
&format!("{full_name}_rate_bps"),
&rate_bps_name,
rate_vec.read_only_boxed_clone(),
version,
indexes,
@@ -271,27 +276,29 @@ where
resolutions: Box::new(rate_resolutions),
};
let rate_ratio_name = format!("{full_name}_rate_ratio");
let ratio = LazyPerBlock {
height: LazyVecFrom1::transformed::<B::ToRatio>(
&format!("{full_name}_rate_ratio"),
&rate_ratio_name,
version,
bps.height.read_only_boxed_clone(),
),
resolutions: Box::new(DerivedResolutions::from_derived_computed::<B::ToRatio>(
&format!("{full_name}_rate_ratio"),
&rate_ratio_name,
version,
&bps.resolutions,
)),
};
let rate_name = format!("{full_name}_rate");
let percent = LazyPerBlock {
height: LazyVecFrom1::transformed::<B::ToPercent>(
&format!("{full_name}_rate"),
&rate_name,
version,
bps.height.read_only_boxed_clone(),
),
resolutions: Box::new(DerivedResolutions::from_derived_computed::<B::ToPercent>(
&format!("{full_name}_rate"),
&rate_name,
version,
&bps.resolutions,
)),
@@ -163,11 +163,11 @@ impl StdDevPerBlockExtended {
0.5, 1.0, 1.5, 2.0, 2.5, 3.0, -0.5, -1.0, -1.5, -2.0, -2.5, -3.0,
];
for (vec, mult) in self.mut_band_height_vecs().zip(MULTIPLIERS) {
vec.truncate_if_needed_at(start)?;
for (offset, _) in source_data.iter().enumerate() {
let index = start + offset;
let average = sma_data[offset];
let sd = sd_data[offset];
vec.truncate_push_at(index, average + StoredF32::from(mult * *sd))?;
vec.push(average + StoredF32::from(mult * *sd));
}
}