use std::fs;
use std::path::PathBuf;
use std::process::{Command, Output};
use anyhow::{anyhow, Result};
use crate::opt::Opts;
pub(crate) fn check_existence() -> Result<()> {
let path = ["/", "usr", "bin", "instruments"].iter().collect::<PathBuf>();
if path.exists() {
Ok(())
} else {
Err(anyhow!(
"/usr/bin/instruments does not exist. \
Please install the Xcode Command Line Tools."
))
}
}
pub(crate) fn list() -> Result<String> {
let Output { status, stdout, .. } =
Command::new("instruments").args(&["-s", "templates"]).output()?;
if !status.success() {
return Err(anyhow!("'instruments -s templates' command errored"));
}
let templates = String::from_utf8(stdout)?;
let mut output: String = "Instruments provides the following built-in templates.\n\
Aliases are indicated in parentheses.\n"
.into();
let mut templates = templates
.lines()
.skip(1)
.map(|line| (line, abbrev_name(line.trim().trim_matches('"'))))
.collect::<Vec<_>>();
if templates.is_empty() {
return Err(anyhow!("no templates returned from 'instruments -s templates'"));
}
let max_width = templates.iter().map(|(l, _)| l.len()).max().unwrap();
templates.sort_by_key(|&(_, abbrv)| abbrv.is_none());
for (name, abbrv) in templates {
output.push('\n');
output.push_str(name);
if let Some(abbrv) = abbrv {
let some_spaces = " ";
let lpad = (max_width - name.len()).min(some_spaces.len());
output.push_str(&some_spaces[..lpad]);
output.push_str(&format!("({})", abbrv));
}
}
Ok(output)
}
pub(crate) fn run(args: &Opts, exec_path: PathBuf, workspace_root: &PathBuf) -> Result<PathBuf> {
let outfile = get_out_file(args, &exec_path, &workspace_root)?;
let template = resolve_template(&args);
let mut command = Command::new("instruments");
command.args(&["-t", &template]).arg("-D").arg(&outfile);
if let Some(limit) = args.limit {
command.args(&["-l", &limit.to_string()]);
}
command.arg(&exec_path);
if !args.target_args.is_empty() {
command.args(args.target_args.as_slice());
}
let output = command.output()?;
if !output.status.success() {
let stderr =
String::from_utf8(output.stderr).unwrap_or_else(|_| "failed to capture stderr".into());
Err(anyhow!("instruments errored: {}", stderr))
} else {
Ok(outfile)
}
}
fn get_out_file(args: &Opts, exec_path: &PathBuf, workspace_root: &PathBuf) -> Result<PathBuf> {
if let Some(path) = args.output.clone() {
return Ok(path);
}
let exec_name = exec_path
.file_stem()
.and_then(|s| s.to_str())
.ok_or_else(|| anyhow!("invalid exec path {:?}", exec_path))?;
let filename = format!("{}_{}", exec_name, now_timestamp());
let mut path = get_target_dir(workspace_root)?;
path.push(filename);
path.set_extension("trace");
Ok(path)
}
fn get_target_dir(workspace_root: &PathBuf) -> Result<PathBuf> {
let mut target_dir = workspace_root.clone();
target_dir.push("target");
target_dir.push("instruments");
if !target_dir.exists() {
fs::create_dir_all(&target_dir)
.map_err(|e| anyhow!("failed to create {:?}: {}", &target_dir, e))?;
}
Ok(target_dir)
}
fn now_timestamp() -> impl std::fmt::Display {
use chrono::prelude::*;
let now = Local::now();
let fmt = "%FT%T";
now.format(&fmt)
}
fn resolve_template(args: &Opts) -> &str {
match args.template.as_str() {
"time" => "Time Profiler",
"alloc" => "Allocations",
"io" => "File Activity",
"sys" => "System Trace",
other => other,
}
}
fn abbrev_name(template: &str) -> Option<&'static str> {
match template {
"Time Profiler" => Some("time"),
"Allocations" => Some("alloc"),
"File Activity" => Some("io"),
"System Trace" => Some("sys"),
_ => None,
}
}