forked from cmyr/cargo-instruments
-
Notifications
You must be signed in to change notification settings - Fork 0
/
instruments.rs
155 lines (128 loc) · 4.32 KB
/
instruments.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
//! interfacing with the `instruments` command line tool
use std::fs;
use std::path::PathBuf;
use std::process::{Command, Output};
use failure::{format_err, Error};
use crate::opt::Opts;
/// Check that `instruments` is in $PATH.
pub(crate) fn check_existence() -> Result<(), Error> {
let path = ["/", "usr", "bin", "instruments"].iter().collect::<PathBuf>();
if path.exists() {
Ok(())
} else {
Err(format_err!(
"/usr/bin/instruments does not exist. \
Please install the Xcode Command Line Tools."
))
}
}
/// Return a string listing available templates.
pub(crate) fn list() -> Result<String, Error> {
let Output { status, stdout, .. } =
Command::new("instruments").args(&["-s", "templates"]).output()?;
if !status.success() {
return Err(format_err!("'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(format_err!("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, Error> {
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(format_err!("instruments errored: {}", stderr))
} else {
Ok(outfile)
}
}
fn get_out_file(
args: &Opts,
exec_path: &PathBuf,
workspace_root: &PathBuf,
) -> Result<PathBuf, Error> {
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(|| format_err!("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, Error> {
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| format_err!("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,
}
}