use std::fs::File;
use std::os::windows::process::ExitStatusExt;
use std::process::ExitStatus;
use fxprof_processed_profile::{Profile, ReferenceTimestamp, SamplingInterval};
use super::etw_gecko;
use super::profile_context::ProfileContext;
use crate::server::{start_server_main, ServerProps};
use crate::shared::ctrl_c::CtrlC;
use crate::shared::included_processes::IncludedProcesses;
use crate::shared::recording_props::{ProfileCreationProps, RecordingMode, RecordingProps};
use crate::shared::save_profile::save_profile_to_file;
use crate::shared::symbol_props::SymbolProps;
use crate::windows::elevated_helper::ElevatedHelperSession;
pub fn start_recording(
recording_mode: RecordingMode,
recording_props: RecordingProps,
profile_creation_props: ProfileCreationProps,
symbol_props: SymbolProps,
server_props: Option<ServerProps>,
) -> Result<ExitStatus, i32> {
let timebase = std::time::SystemTime::now();
let timebase = ReferenceTimestamp::from_system_time(timebase);
let profile = Profile::new(
profile_creation_props.profile_name(),
timebase,
SamplingInterval::from_nanos(1000000), );
let mut elevated_helper = ElevatedHelperSession::new(recording_props.output_file.clone())
.unwrap_or_else(|e| panic!("Couldn't start elevated helper process: {e:?}"));
elevated_helper
.start_xperf(&recording_props, &profile_creation_props, &recording_mode)
.unwrap();
let included_processes = match recording_mode {
RecordingMode::All => {
let ctrl_c_receiver = CtrlC::observe_oneshot();
eprintln!("Profiling all processes...");
eprintln!("Press Ctrl+C to stop.");
let _ = ctrl_c_receiver.blocking_recv();
None
}
RecordingMode::Pid(pid) => {
let ctrl_c_receiver = CtrlC::observe_oneshot();
eprintln!("Profiling process with pid {pid}...");
eprintln!("Press Ctrl+C to stop.");
let _ = ctrl_c_receiver.blocking_recv();
Some(IncludedProcesses {
name_substrings: Vec::new(),
pids: vec![pid],
})
}
RecordingMode::Launch(process_launch_props) => {
let mut ctrl_c_receiver = CtrlC::observe_oneshot();
let mut pids = Vec::new();
for _ in 0..process_launch_props.iteration_count {
let mut child = std::process::Command::new(&process_launch_props.command_name);
child.args(&process_launch_props.args);
child.envs(process_launch_props.env_vars.iter().map(|(k, v)| (k, v)));
let mut child = child.spawn().unwrap();
pids.push(child.id());
let exit_status = child.wait().unwrap();
if !exit_status.success() {
eprintln!(
"Skipping remaining iterations due to non-success exit status: \"{}\"",
exit_status
);
break;
}
}
ctrl_c_receiver.close();
Some(IncludedProcesses {
name_substrings: Vec::new(),
pids,
})
}
};
eprintln!("Stopping xperf...");
let (kernel_output_file, user_output_file) = elevated_helper
.stop_xperf()
.expect("Should have produced a merged ETL file");
elevated_helper.shutdown();
eprintln!("Processing ETL trace...");
let output_file = recording_props.output_file.clone();
let arch = profile_creation_props
.override_arch
.clone()
.unwrap_or(get_native_arch().to_string());
let unstable_presymbolicate = profile_creation_props.unstable_presymbolicate;
let mut context =
ProfileContext::new(profile, &arch, included_processes, profile_creation_props);
let extra_etls = match &user_output_file {
Some(user_etl) => vec![user_etl.clone()],
None => Vec::new(),
};
etw_gecko::process_etl_files(&mut context, &kernel_output_file, &extra_etls);
if let Some(win_version) = winver::WindowsVersion::detect() {
context.set_os_name(&format!("Windows {win_version}"))
}
let profile = context.finish();
if !recording_props.keep_etl {
std::fs::remove_file(&kernel_output_file).unwrap_or_else(|_| {
panic!(
"Failed to delete ETL file {:?}",
kernel_output_file.to_str().unwrap()
)
});
if let Some(user_output_file) = &user_output_file {
std::fs::remove_file(user_output_file).unwrap_or_else(|_| {
panic!(
"Failed to delete ETL file {:?}",
user_output_file.to_str().unwrap()
)
});
}
} else {
eprintln!("ETL path: {}", kernel_output_file.to_str().unwrap());
if let Some(user_output_file) = &user_output_file {
eprintln!("User ETL path: {}", user_output_file.to_str().unwrap());
}
}
save_profile_to_file(&profile, &output_file).expect("Couldn't write JSON");
if unstable_presymbolicate {
crate::shared::symbol_precog::presymbolicate(
&profile,
&output_file.with_extension("syms.json"),
);
}
if let Some(server_props) = server_props {
let libinfo_map = crate::profile_json_preparse::parse_libinfo_map_from_profile_file(
File::open(&output_file).expect("Couldn't open file we just wrote"),
&output_file,
)
.expect("Couldn't parse libinfo map from profile file");
start_server_main(&output_file, server_props, symbol_props, libinfo_map);
}
Ok(ExitStatus::from_raw(0))
}
#[cfg(target_arch = "x86")]
fn get_native_arch() -> &'static str {
"x86"
}
#[cfg(target_arch = "x86_64")]
fn get_native_arch() -> &'static str {
"x86_64"
}
#[cfg(target_arch = "aarch64")]
fn get_native_arch() -> &'static str {
"arm64"
}