1 // Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 use session::config::Options;
14 use std::io::{self, StdoutLock, Write};
15 use std::time::{Duration, Instant};
17 macro_rules! define_categories {
18 ($($name:ident,)*) => {
19 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
20 pub enum ProfileCategory {
24 #[allow(nonstandard_style)]
25 struct Categories<T> {
29 impl<T: Default> Categories<T> {
30 fn new() -> Categories<T> {
32 $($name: T::default()),*
37 impl<T> Categories<T> {
38 fn get(&self, category: ProfileCategory) -> &T {
40 $(ProfileCategory::$name => &self.$name),*
44 fn set(&mut self, category: ProfileCategory, value: T) {
46 $(ProfileCategory::$name => self.$name = value),*
52 times: Categories<u64>,
53 query_counts: Categories<(u64, u64)>,
57 fn new() -> CategoryData {
59 times: Categories::new(),
60 query_counts: Categories::new(),
64 fn print(&self, lock: &mut StdoutLock<'_>) {
65 writeln!(lock, "| Phase | Time (ms) | Queries | Hits (%) |")
67 writeln!(lock, "| ---------------- | -------------- | -------------- | -------- |")
71 let (hits, total) = self.query_counts.$name;
72 let (hits, total) = if total > 0 {
74 (((hits as f32) / (total as f32)) * 100.0)), total.to_string())
76 (String::new(), String::new())
81 "| {0: <16} | {1: <14} | {2: <14} | {3: <8} |",
83 self.times.$name / 1_000_000,
90 fn json(&self) -> String {
91 let mut json = String::from("[");
94 let (hits, total) = self.query_counts.$name;
96 //normalize hits to 0%
99 ((hits as f32) / (total as f32)) * 100.0
104 json.push_str(&format!(
105 "{{ \"category\": {}, \"time_ms\": {},
106 \"query_count\": {}, \"query_hits\": {} }},",
108 self.times.$name / 1_000_000,
110 format!("{:.2}", hit_percent)
114 //remove the trailing ',' character
135 pub struct SelfProfiler {
136 timer_stack: Vec<ProfileCategory>,
138 current_timer: Instant,
142 pub fn new() -> SelfProfiler {
143 let mut profiler = SelfProfiler {
144 timer_stack: Vec::new(),
145 data: CategoryData::new(),
146 current_timer: Instant::now(),
149 profiler.start_activity(ProfileCategory::Other);
154 pub fn start_activity(&mut self, category: ProfileCategory) {
155 match self.timer_stack.last().cloned() {
157 self.current_timer = Instant::now();
159 Some(current_category) if current_category == category => {
160 //since the current category is the same as the new activity's category,
161 //we don't need to do anything with the timer, we just need to push it on the stack
163 Some(current_category) => {
164 let elapsed = self.stop_timer();
166 //record the current category's time
167 let new_time = self.data.times.get(current_category) + elapsed;
168 self.data.times.set(current_category, new_time);
172 //push the new category
173 self.timer_stack.push(category);
176 pub fn record_query(&mut self, category: ProfileCategory) {
177 let (hits, total) = *self.data.query_counts.get(category);
178 self.data.query_counts.set(category, (hits, total + 1));
181 pub fn record_query_hit(&mut self, category: ProfileCategory) {
182 let (hits, total) = *self.data.query_counts.get(category);
183 self.data.query_counts.set(category, (hits + 1, total));
186 pub fn end_activity(&mut self, category: ProfileCategory) {
187 match self.timer_stack.pop() {
188 None => bug!("end_activity() was called but there was no running activity"),
192 "end_activity() was called but a different activity was running"),
195 //check if the new running timer is in the same category as this one
196 //if it is, we don't need to do anything
197 if let Some(c) = self.timer_stack.last() {
203 //the new timer is different than the previous,
204 //so record the elapsed time and start a new timer
205 let elapsed = self.stop_timer();
206 let new_time = self.data.times.get(category) + elapsed;
207 self.data.times.set(category, new_time);
210 fn stop_timer(&mut self) -> u64 {
211 let elapsed = if cfg!(windows) {
212 // On Windows, timers don't always appear to be monotonic (see #51648)
213 // which can lead to panics when calculating elapsed time.
214 // Work around this by testing to see if the current time is less than
215 // our recorded time, and if it is, just returning 0.
216 let now = Instant::now();
217 if self.current_timer >= now {
220 self.current_timer.elapsed()
223 self.current_timer.elapsed()
226 self.current_timer = Instant::now();
228 (elapsed.as_secs() * 1_000_000_000) + (elapsed.subsec_nanos() as u64)
231 pub fn print_results(&mut self, opts: &Options) {
232 self.end_activity(ProfileCategory::Other);
235 self.timer_stack.is_empty(),
236 "there were timers running when print_results() was called");
238 let out = io::stdout();
239 let mut lock = out.lock();
244 .map(|n| format!(" for {}", n))
245 .unwrap_or_default();
247 writeln!(lock, "Self profiling results{}:", crate_name).unwrap();
248 writeln!(lock).unwrap();
250 self.data.print(&mut lock);
252 writeln!(lock).unwrap();
253 writeln!(lock, "Optimization level: {:?}", opts.optimize).unwrap();
255 let incremental = if opts.incremental.is_some() { "on" } else { "off" };
256 writeln!(lock, "Incremental: {}", incremental).unwrap();
259 pub fn save_results(&self, opts: &Options) {
260 let category_data = self.data.json();
261 let compilation_options =
262 format!("{{ \"optimization_level\": \"{:?}\", \"incremental\": {} }}",
264 if opts.incremental.is_some() { "true" } else { "false" });
266 let json = format!("{{ \"category_data\": {}, \"compilation_options\": {} }}",
268 compilation_options);
270 fs::write("self_profiler_results.json", json).unwrap();