use std::{
io::{self, IsTerminal},
os::unix::process::{CommandExt, ExitStatusExt},
path::Path,
process::{Command, ExitStatus, Stdio},
time::Duration,
};
use crate::{
format::{self, OutputFormat},
record::BenchmarkRecord,
sampler::{SamplerHandle, SamplerOptions},
signals::SignalForwarder,
};
#[derive(Debug)]
pub struct RunOutcome {
pub record: BenchmarkRecord,
pub status: ExitStatus,
}
impl RunOutcome {
#[must_use]
pub fn exit_code(&self) -> i32 {
exit_code_for(self.status)
}
}
#[derive(Debug, Clone)]
pub struct RunOptions {
pub interval: Duration,
pub output_path: Box<Path>,
pub format: OutputFormat,
pub force_summary: bool,
pub trace_path: Option<Box<Path>>,
}
pub fn run_command(command: &str, args: &[String], options: &RunOptions) -> io::Result<RunOutcome> {
let mut cmd = Command::new(command);
cmd.args(args);
cmd.stdin(Stdio::inherit());
cmd.stdout(Stdio::inherit());
cmd.stderr(Stdio::inherit());
cmd.process_group(0);
let mut child = cmd.spawn()?;
#[allow(clippy::cast_possible_wrap)]
let child_pid = child.id() as i32;
let signals = SignalForwarder::install(child_pid)?;
let sampler = SamplerHandle::spawn(
child_pid,
SamplerOptions { interval: options.interval, trace_path: options.trace_path.clone() },
);
let status = child.wait()?;
let record = sampler.stop();
drop(signals);
format::write_to_path(&record, &options.output_path, options.format)?;
if options.force_summary || io::stderr().is_terminal() {
eprintln!("tricorder: {}", record.summary_line());
}
Ok(RunOutcome { record, status })
}
fn exit_code_for(status: ExitStatus) -> i32 {
if let Some(code) = status.code() {
code
} else if let Some(sig) = status.signal() {
128 + sig
} else {
1
}
}
#[cfg(test)]
mod tests {
use std::os::unix::process::ExitStatusExt;
use super::*;
#[test]
fn exit_code_normal_exit_passes_through() {
let status = ExitStatus::from_raw(7 << 8); assert_eq!(exit_code_for(status), 7);
}
#[test]
fn exit_code_signal_uses_128_plus_signum() {
let status = ExitStatus::from_raw(15); assert_eq!(exit_code_for(status), 128 + 15);
}
}