This repository has been archived on 2026-09-15. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
OxideJS/crates/oxide_cli/src/bench/leak_detect.rs
T
2026-06-24 10:46:59 +08:00

142 lines
5.1 KiB
Rust

use std::collections::VecDeque;
use std::process::ExitCode;
use std::sync::Arc;
use std::time::Instant;
use oxide_compiler::compiler::{compiled_module_hash, Compiler};
use oxide_kernel::kernel::KernelCore;
use oxide_parser::Allocator;
use oxide_vm::vm_pool::VmPool;
use crate::bench::metrics::MetricCollection;
use crate::bench::output::format_json;
use crate::bench::BenchConfig;
pub struct LeakSampler {
window: VecDeque<(usize, f64)>,
window_size: usize,
}
impl LeakSampler {
pub fn new(window_size: usize) -> Self {
Self {
window: VecDeque::new(),
window_size,
}
}
pub fn add_sample(&mut self, iteration: usize, value: f64) -> Option<LeakVerdict> {
self.window.push_back((iteration, value));
if self.window.len() >= self.window_size {
self.window.pop_front();
}
if self.window.len() < 10 {
return None;
}
let n = self.window.len() as f64;
let sum_x: f64 = self.window.iter().map(|(i, _)| *i as f64).sum();
let sum_y: f64 = self.window.iter().map(|(_, v)| v).sum();
let sum_xy: f64 = self.window.iter().map(|(i, v)| *i as f64 * v).sum();
let sum_x2: f64 = self.window.iter().map(|(i, _)| (*i as f64).powi(2)).sum();
let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_x2 - sum_x * sum_x);
let intercept = (sum_y - slope * sum_x) / n;
let y_mean = sum_y / n;
let ss_res: f64 = self
.window
.iter()
.map(|(i, v)| (v - (slope * *i as f64 + intercept)).powi(2))
.sum();
let ss_tot: f64 = self.window.iter().map(|(_, v)| (v - y_mean).powi(2)).sum();
let r2 = if ss_tot == 0.0 { 1.0 } else { 1.0 - ss_res / ss_tot };
if r2 > 0.9 && slope > 0.0 {
Some(LeakVerdict { slope, r2 })
} else {
None
}
}
}
pub struct LeakVerdict {
pub slope: f64,
pub r2: f64,
}
pub fn run_leak_detect(config: &BenchConfig, kernel: &Arc<KernelCore>, pool: &Arc<VmPool>) -> ExitCode {
let js =
"var ITERATIONS = 1000; var obj = {}; for (var i = 0; i < ITERATIONS; i++) { obj['key' + i] = i; } obj['key0']";
let allocator = Allocator::default();
let program = match oxide_parser::parse(&allocator, js) {
Ok(p) => p,
Err(_) => return ExitCode::FAILURE,
};
let compiler = Compiler::new();
let hash = compiled_module_hash(&program);
let module = match kernel.code_forge().get_or_insert_with(hash, || compiler.compile(&program)) {
Ok(m) => m,
Err(_) => return ExitCode::FAILURE,
};
let mut samplers: Vec<(&str, LeakSampler)> = vec![
("session_objects", LeakSampler::new(20)),
("session_bytes", LeakSampler::new(20)),
("code_forge_entries", LeakSampler::new(20)),
("symbol_registry", LeakSampler::new(20)),
];
let mut results: Vec<MetricCollection> = Vec::new();
for i in 0..config.iterations as usize {
let start = Instant::now();
let mut guard = pool.spawn();
let vm = guard.vm_mut();
let _ = vm.run(&module);
let elapsed = start.elapsed();
for (name, sampler) in &mut samplers {
let value: f64 = match *name {
"session_objects" => vm.session_object_count() as f64,
"session_bytes" => vm.session_bytes_allocated() as f64,
"code_forge_entries" => kernel.code_forge().len() as f64,
"symbol_registry" => vm.symbol_registry_len() as f64,
_ => continue,
};
if let Some(verdict) = sampler.add_sample(i, value) {
eprintln!(
"[LEAK] {}: slope={:.6} R²={:.4} over {} samples",
name,
verdict.slope,
verdict.r2,
sampler.window.len(),
);
}
}
if i == config.iterations as usize - 1 {
let guard = pool.spawn();
let vm = guard.vm();
results.push(MetricCollection {
test_name: "leak_detect".to_string(),
wall_time_us: elapsed.as_micros() as u64,
session_objects: vm.session_object_count(),
session_bytes: vm.session_bytes_allocated(),
epoch_objects: vm.epoch_object_count(),
epoch_bytes: 0,
gc_trigger_count: vm.session_gc_stats().total_collections,
gc_bytes_freed: vm.session_gc_stats().last_collection_bytes_freed,
gc_objects_scanned: vm.session_gc_stats().last_collection_objects_scanned,
gc_collection_us: vm.session_gc_stats().last_collection_duration_us,
instruction_count: vm.instruction_count(),
compile_time_us: 0,
exec_time_us: 0,
ic_hit_rate: vm.ic_hit_rate(),
ic_hits: vm.ic_hit_count(),
ic_misses: vm.ic_miss_count(),
});
}
}
let json = format_json(&results);
println!("{}", json);
ExitCode::SUCCESS
}