@@ -3,10 +3,11 @@ use clap::Parser;
33use ipc_channel:: ipc:: { self } ;
44use log:: { debug, info} ;
55use memtrack:: { MemtrackIpcMessage , Tracker , handle_ipc_message} ;
6- use runner_shared:: artifacts:: { ArtifactExt , MemtrackArtifact , MemtrackEvent } ;
7- use std:: path:: PathBuf ;
6+ use runner_shared:: artifacts:: { ArtifactExt , MemtrackArtifact , MemtrackEvent , MemtrackWriter } ;
7+ use std:: path:: { Path , PathBuf } ;
88use std:: process:: Command ;
99use std:: sync:: atomic:: { AtomicBool , Ordering } ;
10+ use std:: sync:: mpsc:: channel;
1011use std:: sync:: { Arc , Mutex } ;
1112use std:: thread;
1213
@@ -51,10 +52,8 @@ fn main() -> Result<()> {
5152 } => {
5253 debug ! ( "Starting memtrack for command: {command}" ) ;
5354
54- let ( root_pid, events, status) =
55- track_command ( & command, ipc_server) . context ( "Failed to track command" ) ?;
56- let result = MemtrackArtifact { events } ;
57- result. save_with_pid_to ( & out_dir, root_pid as libc:: pid_t ) ?;
55+ let status =
56+ track_command ( & command, ipc_server, & out_dir) . context ( "Failed to track command" ) ?;
5857
5958 std:: process:: exit ( status. code ( ) . unwrap_or ( 1 ) ) ;
6059 }
@@ -64,7 +63,8 @@ fn main() -> Result<()> {
6463fn track_command (
6564 cmd_string : & str ,
6665 ipc_server_name : Option < String > ,
67- ) -> anyhow:: Result < ( u32 , Vec < MemtrackEvent > , std:: process:: ExitStatus ) > {
66+ out_dir : & Path ,
67+ ) -> anyhow:: Result < std:: process:: ExitStatus > {
6868 let tracker = Tracker :: new ( ) ?;
6969
7070 let tracker_arc = Arc :: new ( Mutex :: new ( tracker) ) ;
@@ -95,37 +95,67 @@ fn track_command(
9595 let event_rx = { tracker_arc. lock ( ) . unwrap ( ) . track ( root_pid) ? } ;
9696 info ! ( "Spawned child with pid {root_pid}" ) ;
9797
98- // Spawn event processing thread
99- let process_events = Arc :: new ( AtomicBool :: new ( true ) ) ;
100- let process_events_clone = process_events. clone ( ) ;
101- let processing_thread = thread:: spawn ( move || {
102- let mut events = Vec :: new ( ) ;
98+ // Create the artifact, then drain and write the events to disk
99+ let file_name = MemtrackArtifact :: file_name ( Some ( root_pid) ) ;
100+ let out_file = std:: fs:: File :: create ( out_dir. join ( file_name) ) ?;
101+
102+ let ( write_tx, write_rx) = channel :: < MemtrackEvent > ( ) ;
103+
104+ // Stage A: Fast drain thread - This is required so that we immediately clear the ring buffer
105+ // because it only has a limited size.
106+ static DRAIN_EVENTS : AtomicBool = AtomicBool :: new ( true ) ;
107+ let write_tx_clone = write_tx. clone ( ) ;
108+ let drain_thread = thread:: spawn ( move || {
103109 loop {
104- if !process_events_clone . load ( Ordering :: Relaxed ) {
110+ if !DRAIN_EVENTS . load ( Ordering :: Relaxed ) {
105111 break ;
106112 }
107-
108113 let Ok ( event) = event_rx. try_recv ( ) else {
109114 continue ;
110115 } ;
116+ let _ = write_tx_clone. send ( event. into ( ) ) ;
117+ }
118+ } ) ;
119+
120+ // Stage B: Writer thread - Immediately writes the events to disk
121+ let writer_thread = thread:: spawn ( move || -> anyhow:: Result < ( ) > {
122+ let mut writer = MemtrackWriter :: new ( out_file) ?;
111123
112- events. push ( event. into ( ) ) ;
124+ while let Ok ( first) = write_rx. recv ( ) {
125+ writer. write_event ( & first) ?;
126+
127+ // Drain any backlog in a tight loop (batching)
128+ while let Ok ( ev) = write_rx. try_recv ( ) {
129+ writer. write_event ( & ev) ?;
130+ }
113131 }
114- events
132+ writer. finish ( ) ?;
133+
134+ Ok ( ( ) )
115135 } ) ;
116136
117137 // Wait for the command to complete
118138 let status = child. wait ( ) . context ( "Failed to wait for command" ) ?;
119139 info ! ( "Command exited with status: {status}" ) ;
120140
121- info ! ( "Waiting for the event processing thread to finish" ) ;
122- process_events. store ( false , Ordering :: Relaxed ) ;
123- let events = processing_thread
141+ // Close the write channel to signal threads to finish
142+ DRAIN_EVENTS . store ( false , Ordering :: Relaxed ) ;
143+ drop ( write_tx) ;
144+
145+ // Wait for drain thread to finish
146+ info ! ( "Waiting for the drain thread to finish" ) ;
147+ drain_thread
148+ . join ( )
149+ . map_err ( |_| anyhow:: anyhow!( "Failed to join drain thread" ) ) ?;
150+
151+ // Wait for writer thread to finish and propagate errors
152+ info ! ( "Waiting for the writer thread to finish" ) ;
153+ writer_thread
124154 . join ( )
125- . map_err ( |_| anyhow:: anyhow!( "Failed to join event thread" ) ) ?;
155+ . map_err ( |_| anyhow:: anyhow!( "Failed to join writer thread" ) ) ? ?;
126156
127157 // IPC thread will exit when channel closes
128158 drop ( ipc_handle) ;
129159
130- Ok ( ( root_pid as u32 , events , status) )
160+ Ok ( status)
131161}
0 commit comments