diff --git a/src/compiler.rs b/src/compiler.rs index 77b6fca..445c4a6 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -77,7 +77,19 @@ impl Capture { self.origin = origin; } + /// Variables that turn on the wrappers, for a build started with `Command`. + #[cfg(target_os = "macos")] + pub fn child_environment(&self) -> impl Iterator { + self.child_environment.iter().map(|(name, value)| { + ( + OsStr::from_bytes(name.as_bytes()), + OsStr::from_bytes(value.as_bytes()), + ) + }) + } + /// Apply already-prepared variables in the forked child without allocating. + #[cfg(target_os = "linux")] pub unsafe fn configure_child(&self) { for (name, value) in &self.child_environment { // SAFETY: both strings are retained by `self`, NUL terminated, and diff --git a/src/macos.rs b/src/macos.rs index f169b1b..725d4e5 100644 --- a/src/macos.rs +++ b/src/macos.rs @@ -17,12 +17,13 @@ use self::event::Message; use self::tracker::{Clock, TraceSink, Tracker}; use crate::blind_spots::BlindSpots; +use crate::compiler::Capture; use crate::perfetto::Writer; use std::ffi::{CString, OsString}; use std::io; use std::path::Path; use std::process::Command; -use std::time::{Duration, Instant}; +use std::time::{Duration, Instant, SystemTime}; mod event; mod kdebug; @@ -71,6 +72,7 @@ pub fn record( prepared: Prepared, command: &[OsString], writer: &mut Writer, + compilers: &mut Capture, blind_spots: &mut BlindSpots, file_events: bool, ) -> io::Result { @@ -81,8 +83,11 @@ pub fn record( } = prepared; let program = command.first().expect("validated command"); + // Compiler profiles carry wall-clock times; both origins are the same instant. + compilers.set_origin(SystemTime::now()); let mut build = Command::new(program) .args(&command[1..]) + .envs(compilers.child_environment()) .spawn() .map_err(|error| { io::Error::new( @@ -99,7 +104,7 @@ pub fn record( ); collector.seed_threads(threads); - let mut sink = TraceSink { writer }; + let mut sink = TraceSink { writer, compilers }; let mut tracker = Tracker::new( &mut sink, blind_spots, diff --git a/src/macos/tracker.rs b/src/macos/tracker.rs index 76f0bb3..17dad01 100644 --- a/src/macos/tracker.rs +++ b/src/macos/tracker.rs @@ -6,6 +6,7 @@ use super::event::{Event, Message}; use crate::blind_spots::BlindSpots; +use crate::compiler::Capture; use crate::model::{FileOpen, Process, Rename, Segment}; use crate::perfetto::Writer; use std::collections::{HashMap, HashSet}; @@ -26,11 +27,14 @@ pub trait Sink { fn segment(&mut self, process: Process, segment: &Segment) -> io::Result<()>; fn file_open(&mut self, pid: i32, open: &FileOpen) -> io::Result<()>; fn rename(&mut self, pid: i32, rename: &Rename) -> io::Result<()>; + /// A process ended; import any compiler profile it left behind. + fn process_exited(&mut self, pid: i32, segment_start_ns: u64); } -/// The trace being recorded. +/// The trace being recorded, with the compiler profiles that feed into it. pub struct TraceSink<'a> { pub writer: &'a mut Writer, + pub compilers: &'a mut Capture, } impl Sink for TraceSink<'_> { @@ -46,6 +50,10 @@ impl Sink for TraceSink<'_> { fn rename(&mut self, pid: i32, rename: &Rename) -> io::Result<()> { self.writer.rename(pid, rename) } + fn process_exited(&mut self, pid: i32, segment_start_ns: u64) { + self.compilers + .process_exited(pid, segment_start_ns, self.writer); + } } /// Converts `mach_absolute_time` ticks to nanoseconds since recording began. @@ -338,7 +346,9 @@ impl<'a, S: Sink> Tracker<'a, S> { ); state.segment.exit_code = Some(exit_code); announce(self.sink, &mut self.announced, pid)?; - self.sink.segment(state.process, &state.segment) + self.sink.segment(state.process, &state.segment)?; + self.sink.process_exited(pid, state.segment.start_ns); + Ok(()) } } @@ -388,6 +398,9 @@ mod tests { .push(format!("rename {pid} {} -> {}", rename.from, rename.to)); Ok(()) } + fn process_exited(&mut self, pid: i32, segment_start_ns: u64) { + self.0.push(format!("exited {pid} {segment_start_ns}")); + } } fn clock() -> Clock { @@ -469,10 +482,13 @@ mod tests { "start 12", "open 12 30 /src/a.o flags=1101", r#"segment 12 parent=10 build_parent=10 execed=true 15..40 "cc -c a.c" cwd=/src exit=Some(0)"#, + "exited 12 15", "start 11", r#"segment 11 parent=10 build_parent=10 execed=true 10..50 "sh -c true" cwd=/src exit=Some(2)"#, + "exited 11 10", "start 10", r#"segment 10 parent=0 build_parent=0 execed=true 0..60 "make -j2" cwd=/src exit=Some(0)"#, + "exited 10 0", ] ); } @@ -495,6 +511,8 @@ mod tests { [ r#"segment 10 parent=0 build_parent=0 execed=true 0..5 "sh -c exec cc" cwd=/ exit=None"#, r#"segment 10 parent=0 build_parent=0 execed=true 5..9 "cc" cwd=/ exit=Some(137)"#, + // Profiles are matched to the program that last ran in the pid. + "exited 10 5", ] ); } diff --git a/src/main.rs b/src/main.rs index 6a67da4..922b51e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -7,7 +7,7 @@ mod report; mod args; #[cfg_attr(not(any(target_os = "linux", target_os = "macos")), allow(dead_code))] mod blind_spots; -#[cfg(target_os = "linux")] +#[cfg(any(target_os = "linux", target_os = "macos"))] mod compiler; mod handoff; #[cfg(target_os = "linux")] @@ -20,8 +20,7 @@ mod util; // platform the viewer ships on. #[cfg_attr(not(any(target_os = "linux", target_os = "macos")), allow(dead_code))] mod model; -// Compiler traces are Linux-only for now. -#[cfg_attr(not(target_os = "linux"), allow(dead_code))] +#[cfg_attr(not(any(target_os = "linux", target_os = "macos")), allow(dead_code))] mod perfetto; use args::{Handoff, Wait}; @@ -31,7 +30,7 @@ use std::process::ExitCode; fn main() -> ExitCode { report::init(); - #[cfg(target_os = "linux")] + #[cfg(any(target_os = "linux", target_os = "macos"))] if let Some(code) = compiler::run_wrapper() { return code; } @@ -114,7 +113,6 @@ fn record( error!("could not write recording options: {error}"); return ExitCode::FAILURE; } - #[cfg(target_os = "linux")] let mut compilers = compiler::Capture::new(compiler_traces); let mut blind_spots = blind_spots::BlindSpots::default(); #[cfg(target_os = "linux")] @@ -130,6 +128,7 @@ fn record( prepared, &command, &mut writer, + &mut compilers, &mut blind_spots, file_events, );