f66036bd61
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
96 lines
4.1 KiB
Rust
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());
|
|
}
|