Files
Brummel f66036bd61 feat(aura-engine): Source::resident_records — probe ring residency via the trait
The residency probe was an inherent method on the concrete M1FieldSource, so a Box<dyn Source> consumer could not read it without downcasting (the adjacent friction noted in #95). Hoist it onto the Source trait as resident_records(&self) -> Option<usize> with a default of None, mirroring bounds()'s Option-for-unknown idiom: None = "does not report" (the eager VecSource, which has no streaming ring), distinct from Some(0) = "reports, holds none" (e.g. exhausted).

M1FieldSource overrides it to return Some(chunk len). Callers updated (streaming_seam, the two deep-dive fieldtest bins). Tests: a hermetic VecSource default-is-None check, and a &dyn Source probe in the gated residency test proving M1FieldSource residency is readable without a downcast.

refs #95
2026-06-17 23:32:50 +02:00

96 lines
4.1 KiB
Rust

// Task 1 (deep dive) — WHERE does the memory go? The docs (C12 realization
// cycle 0041; the streaming_seam.rs residency test) claim a M1FieldSource keeps
// O(one chunk) resident, "independent of window length". Yet the 7-year breakout
// run grew RSS to ~127 MiB. Is that the SOURCES (which the docs promise is
// bounded), or the recorder SINKS accumulating every bar into unbounded mpsc
// channels + the drain_trace Vec?
//
// This probe isolates the two by driving the SAME sources two ways over the SAME
// 7-year window:
// (A) full breakout harness with all 4 recorder taps + drain_trace (what the
// shipped example does), then
// (B) the four M1FieldSource streams driven to exhaustion with NO harness, NO
// sinks — peek/next only, the exact loop streaming_seam.rs calls bounded.
// VmHWM (peak RSS, /proc/self/status) is read after each. PUBLIC SURFACE ONLY:
// Source::{peek,next,resident_records} are the public trait methods the docs use.
use std::sync::Arc;
use chrono::TimeZone;
use data_server::{DataServer, DEFAULT_DATA_PATH};
use aura_engine::Source;
use aura_ingest::{M1Field, M1FieldSource};
#[path = "../../crates/aura-ingest/examples/shared/breakout_real.rs"]
mod breakout_real;
use breakout_real::*;
fn vmhwm_mib() -> u64 {
let status = std::fs::read_to_string("/proc/self/status").unwrap_or_default();
for line in status.lines() {
if let Some(rest) = line.strip_prefix("VmHWM:") {
let kib: u64 = rest.split_whitespace().next().and_then(|s| s.parse().ok()).unwrap_or(0);
return kib / 1024;
}
}
0
}
fn utc_span_ms(y0: i32, m0: u32, y1: i32, m1: u32) -> (i64, i64) {
let from = chrono::Utc.with_ymd_and_hms(y0, m0, 1, 0, 0, 0).unwrap().timestamp_millis();
let (ny, nm) = if m1 == 12 { (y1 + 1, 1) } else { (y1, m1 + 1) };
let next = chrono::Utc.with_ymd_and_hms(ny, nm, 1, 0, 0, 0).unwrap().timestamp_millis();
(from, next - 1)
}
fn main() {
let server = Arc::new(DataServer::new(DEFAULT_DATA_PATH));
if !server.has_symbol(SYMBOL) {
println!("skip: no local {SYMBOL} data");
return;
}
let (from_ms, to_ms) = utc_span_ms(2018, 1, 2024, 12);
println!("=== Task 1 deep dive: residency attribution (7-year GER40 window) ===");
println!("VmHWM at start = {} MiB\n", vmhwm_mib());
// (B) FIRST (so its peak is measured before the harness allocates): drive the
// four field sources to exhaustion with no harness and no sinks. Per the docs
// each keeps O(one chunk) resident — peak resident_records should stay tiny.
{
// resident_records() is now on the public Source trait (#95 follow-up): a
// consumer driving boxed `dyn Source`s can read residency without a
// downcast. We use the concrete type here, per field, exactly as the docs'
// residency test does; the value is Option (None = a source that does not
// report, e.g. the eager VecSource), unwrapped to 0 for the max.
let fields = [M1Field::Open, M1Field::High, M1Field::Low, M1Field::Close];
let mut peak_resident = 0usize;
let mut total = 0u64;
for f in fields {
let mut s = M1FieldSource::open(&server, SYMBOL, Some(from_ms), Some(to_ms), f).unwrap();
while s.peek().is_some() {
peak_resident = peak_resident.max(s.resident_records().unwrap_or(0));
Source::next(&mut s);
total += 1;
}
}
println!("(B) 4 raw sources, NO harness/sinks:");
println!(" streamed {total} records, peak resident_records/source = {peak_resident}");
println!(" VmHWM after = {} MiB", vmhwm_mib());
}
// (A) the full shipped breakout harness with 4 recorder taps + drain_trace.
{
let sources = open_ohlc_sources(&server, from_ms, to_ms).unwrap();
let (mut h, taps) = build_harness();
h.run(sources);
let trace = drain_trace(&taps);
println!("\n(A) full breakout harness + 4 recorder taps + drain_trace:");
println!(" trace rows = {}", trace.len());
println!(" VmHWM after = {} MiB", vmhwm_mib());
}
println!("\nVmHWM final = {} MiB", vmhwm_mib());
}