1#[macro_use]
26extern crate lazy_static;
27
28use std::sync::Mutex;
29use std::collections::HashMap;
30use std::collections::hash_map::{Entry};
31
32struct Profile {
33 times: HashMap<Vec<&'static str>, std::time::Duration>,
34 counts: HashMap<Vec<&'static str>, usize>,
35 stack: Vec<&'static str>,
36 started: std::time::Instant,
37}
38
39fn add_to_map<K: std::hash::Hash + std::cmp::Eq>(m: &mut HashMap<K, std::time::Duration>,
40 k: K, d: std::time::Duration) {
41 match m.entry(k) {
42 Entry::Occupied(mut o) => {
43 *o.get_mut() += d;
44 },
45 Entry::Vacant(v) => {
46 v.insert(d);
47 },
48 }
49}
50fn increment_map<K: std::hash::Hash + std::cmp::Eq>(m: &mut HashMap<K, usize>,
51 k: K, n: usize) {
52 match m.entry(k) {
53 Entry::Occupied(mut o) => {
54 *o.get_mut() += n;
55 },
56 Entry::Vacant(v) => {
57 v.insert(n);
58 },
59 }
60}
61
62impl Profile {
63 fn new() -> Profile {
64 Profile {
65 times: HashMap::new(),
66 counts: HashMap::new(),
67 started: std::time::Instant::now(),
68 stack: Vec::new(),
69 }
70 }
71 fn add_time(&mut self, now: std::time::Instant) {
72 if now > self.started {
73 let d = now.duration_since(self.started);
74 add_to_map(&mut self.times, self.stack.clone(), d);
75 }
76 }
77}
78
79lazy_static! {
80 static ref PROFILE: Mutex<Profile> = Mutex::new(Profile::new());
81}
82
83pub struct Guard {
85}
86
87impl Drop for Guard {
88 fn drop(&mut self) {
89 let now = std::time::Instant::now();
90 let mut m = PROFILE.lock().unwrap();
91 m.add_time(now);
92 m.stack.pop();
93 m.started = std::time::Instant::now();
94 }
95}
96
97impl Guard {
98 pub fn replace(&self, task: &'static str) {
110 let now = std::time::Instant::now();
111 let mut m = PROFILE.lock().unwrap();
112 m.add_time(now);
113 m.stack.pop();
114 m.stack.push(task);
115 let st = m.stack.clone();
116 increment_map(&mut m.counts, st, 1);
117 m.started = std::time::Instant::now();
118 }
119}
120
121pub fn push(task: &'static str) -> Guard {
131 let now = std::time::Instant::now();
132 let mut m = PROFILE.lock().unwrap();
133 m.add_time(now);
134 m.stack.push(task);
135 let st = m.stack.clone();
136 increment_map(&mut m.counts, st, 1);
137 m.started = std::time::Instant::now();
138 Guard {}
139}
140
141pub fn clear() {
143 let mut m = PROFILE.lock().unwrap();
144 m.times = HashMap::new();
145 m.counts = HashMap::new();
146 m.stack = Vec::new();
147 m.started = std::time::Instant::now();
148}
149
150fn pretty_stack(v: &Vec<&'static str>) -> String {
151 let mut out = String::new();
152 for s in v {
153 out.push_str(s);
154 out.push_str(":");
155 }
156 out
157}
158
159fn duration_to_f64(t: std::time::Duration) -> f64 {
160 t.as_secs() as f64 + (t.subsec_nanos() as f64)*1e-9
161}
162
163fn pretty_time(t: f64) -> String {
164 if t < 1e-7 {
165 format!("{:.2} ns", t*1e9)
166 } else if t < 1e-4 {
167 format!("{:.2} us", t*1e6)
168 } else if t < 1e-2 {
169 format!("{:.2} ms", t*1e3)
170 } else if t >= 1e2 {
171 format!("{:.2e} s", t)
172 } else {
173 format!("{:.2} s", t)
174 }
175}
176
177pub fn report() -> String {
181 let now = std::time::Instant::now();
182 let mut m = PROFILE.lock().unwrap();
183 m.add_time(now);
184 let mut out = String::new();
185 let mut total_time = std::time::Duration::from_secs(0);
186 for &v in m.times.values() {
187 total_time += v;
188 }
189 let mut keys: Vec<_> = m.times.keys().collect();
190 keys.sort();
191 let mut cum: HashMap<&'static str, std::time::Duration> = HashMap::new();
192 let mut cumcount: HashMap<&'static str, usize> = HashMap::new();
193 for &k in keys.iter() {
194 for &s in k.iter() {
195 add_to_map(&mut cum, s, m.times[k]);
196 increment_map(&mut cumcount, s, m.counts[k]);
197 }
198 }
199 let mut shortkeys: Vec<_> = cum.keys().collect();
200 shortkeys.sort_by_key(|&s| cum[s]);
201 shortkeys.reverse();
202 let total_f64 = duration_to_f64(total_time);
203 for s in shortkeys {
204 let mut ways: HashMap<Vec<&'static str>, std::time::Duration> = HashMap::new();
205 let mut wayscount: HashMap<Vec<&'static str>, usize> = HashMap::new();
206 for &k in keys.iter().filter(|&k| k.contains(s)) {
207 let mut vv = Vec::from(k.split(|&ss| ss == *s).next().unwrap());
208 vv.push(s);
209 add_to_map(&mut ways, vv.clone(), m.times[k]);
210 increment_map(&mut wayscount, vv, m.counts[k]);
211 }
212 let mut waykeys: Vec<_> = ways.keys().collect();
213 waykeys.sort_by_key(|&k| ways[k]);
214 waykeys.reverse();
215 let percent = 100.0*duration_to_f64(cum[s])/total_f64;
216 if waykeys.len() > 1 {
217 out.push_str(&format!("{:4.1}% {} {} ({}, {})\n",
218 percent, &s,
219 pretty_time(duration_to_f64(cum[s])), cumcount[s],
220 pretty_time(duration_to_f64(cum[s])/cumcount[s] as f64)));
221 for &k in waykeys.iter().filter(|&k| k.contains(s)) {
222 let percent = 100.0*duration_to_f64(ways[k])/total_f64;
223 out.push_str(&format!(" {:4.1}% {} {} ({}, {})\n",
224 percent, &pretty_stack(k),
225 pretty_time(duration_to_f64(ways[k])),
226 wayscount[k],
227 pretty_time(duration_to_f64(ways[k])/wayscount[k] as f64)));
228 }
229 } else {
230 out.push_str(&format!("{:4.1}% {} {} ({}, {})\n", percent,
231 &pretty_stack(waykeys[0]),
232 pretty_time(duration_to_f64(cum[s])),
233 cumcount[s],
234 pretty_time(duration_to_f64(cum[s])/cumcount[s] as f64)));
235 }
236 }
237 out
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244
245 lazy_static! {
246 static ref TEST_LOCK: Mutex<()> = Mutex::new(());
247 }
248
249 #[test]
250 fn it_works() {
251 let mut _m = TEST_LOCK.lock().unwrap();
252 clear();
253 push("hello world");
254 let rep = report();
255 println!("\n{}", rep);
256 assert!(rep.contains("hello world"));
257 }
258 #[test]
259 fn nesting() {
260 let mut _m = TEST_LOCK.lock().unwrap();
261 clear();
262 {
263 let _a = push("hello");
264 let _b = push("world");
265 }
266 let rep = report();
267 println!("\n{}", rep);
268 assert!(rep.contains("hello:world"));
269 }
270 #[test]
271 fn replace_works() {
272 let mut _m = TEST_LOCK.lock().unwrap();
273 clear();
274 {
275 let _a = push("first");
276 let _b = push("greet");
277 _b.replace("world");
278 }
279 {
280 let _a = push("second");
281 let _b = push("greet");
282 _b.replace("world");
283 }
284 let rep = report();
285 println!("\n{}", rep);
286 assert!(!rep.contains("hello:world"));
287 assert!(rep.contains("first:world"));
288 assert!(rep.contains("first:greet"));
289 assert!(rep.contains("second:world"));
290 assert!(rep.contains("second:greet"));
291 }
292 #[test]
293 fn replace_timings() {
294 let mut _m = TEST_LOCK.lock().unwrap();
295 clear();
296 {
297 let _a = push("first");
298 std::thread::sleep(std::time::Duration::from_secs(2));
299 let _b = push("hello");
300 std::thread::sleep(std::time::Duration::from_secs(3));
301 _b.replace("world");
302 std::thread::sleep(std::time::Duration::from_secs(1));
303 }
304 {
305 let _a = push("second");
306 std::thread::sleep(std::time::Duration::from_secs(1));
307 let _b = push("greet");
308 std::thread::sleep(std::time::Duration::from_secs(4));
309 _b.replace("world");
310 std::thread::sleep(std::time::Duration::from_secs(3));
311 }
312 let rep = report();
313 println!("\n{}", rep);
314 assert!(!rep.contains("hello:world"));
315 assert!(rep.contains("first:world"));
316 assert!(rep.contains("first:hello"));
317 assert!(rep.contains("second:world"));
318 assert!(rep.contains("second:greet"));
319 assert!(rep.contains("first: 6"));
320 assert!(rep.contains("first:hello: 3"));
321 assert!(rep.contains("first:world: 1"));
322 assert!(rep.contains("second: 8"));
323 assert!(rep.contains("second:greet: 4"));
324 assert!(rep.contains("second:world: 3"));
325 assert!(rep.contains("world 4"));
326 }
327}