diff --git a/src/ci.rs b/src/ci.rs index c60becd..833c1b2 100644 --- a/src/ci.rs +++ b/src/ci.rs @@ -23,6 +23,7 @@ struct CiArgs { duration: Option, format: Option, output: Option, + diff_only: bool, /// Bytes per second. Fail if the rolling-window growth rate exceeds this. growth_rate: Option, sample_interval: Option, @@ -54,6 +55,11 @@ pub fn ci_main(args: &[String]) -> i32 { None }; + if parsed.diff_only && !parsed.leak_check { + eprintln!("error: --diff-only requires --leak-check to be active"); + return 1; + } + let start = Instant::now(); let poll_interval = Duration::from_millis(parsed.sample_interval.unwrap_or(1000)); let mut sys = System::new_all(); @@ -163,7 +169,13 @@ pub fn ci_main(args: &[String]) -> i32 { // Export report if requested. if let Some(ref format_type) = parsed.format { if let Some(current_heap) = last_captured_heap { - let blocks = current_heap; + let mut blocks = current_heap; + + if parsed.diff_only { + if let Some(ref prev) = baseline { + blocks = blocks.into_iter().filter(|b| !prev.contains(b)).collect(); + } + } let target_path = parsed.output.clone().unwrap_or_else(|| match format_type { FormatType::Json => "heap_dump.json".to_string(), @@ -227,6 +239,7 @@ fn resolve_target(target: &CiTarget) -> Result<(u32, Option), AppError> { fn parse_ci_args(args: &[String]) -> Result { let mut max_memory = None; let mut leak_check = false; + let mut diff_only = false; let mut duration = None; let mut target = None; let mut format = None; @@ -252,6 +265,10 @@ fn parse_ci_args(args: &[String]) -> Result { leak_check = true; i += 1; } + "--diff-only" => { + diff_only = true; + i += 1; + } "--duration" => { if i + 1 < args.len() { let val = args[i + 1] @@ -357,6 +374,7 @@ fn parse_ci_args(args: &[String]) -> Result { target, max_memory, leak_check, + diff_only, duration, format, output, diff --git a/src/os/windows.rs b/src/os/windows.rs index c5d0d85..cfc4543 100644 --- a/src/os/windows.rs +++ b/src/os/windows.rs @@ -139,6 +139,7 @@ pub fn walk_regions(pid: u32) -> Vec { /// A `Vec` with address, size, free status, and page protection /// for each parsed heap block. pub fn walk_heap(pid: u32) -> Vec { + use std::time::{Duration, Instant}; use windows::Win32::Foundation::CloseHandle; use windows::Win32::System::Diagnostics::Debug::ReadProcessMemory; use windows::Win32::System::Diagnostics::ToolHelp::{ @@ -150,7 +151,6 @@ pub fn walk_heap(pid: u32) -> Vec { use windows::Win32::System::Threading::{ OpenProcess, PROCESS_QUERY_INFORMATION, PROCESS_VM_READ, }; - use std::time::{Duration, Instant}; // --- tunable safety limits --- const MAX_BLOCKS_PER_HEAP: usize = 100_000; @@ -218,7 +218,9 @@ pub fn walk_heap(pid: u32) -> Vec { } if blocks_this_heap >= MAX_BLOCKS_PER_HEAP { // likely corrupted/hostile heap metadata — stop this heap, move to next - eprintln!("walk_heap: heap at {heap_base:#x} exceeded block cap, skipping rest"); + eprintln!( + "walk_heap: heap at {heap_base:#x} exceeded block cap, skipping rest" + ); break; } diff --git a/src/types.rs b/src/types.rs index 621c6fd..bf3fc44 100644 --- a/src/types.rs +++ b/src/types.rs @@ -50,7 +50,7 @@ pub enum RegionProtect { Other, } -#[derive(Clone, Debug, Serialize)] +#[derive(Clone, Debug, PartialEq, Serialize)] pub struct HeapBlock { pub address: usize, pub size: usize,