1 use crate::session::config::Options;
4 use std::io::{self, StderrLock, Write};
5 use std::time::Instant;
7 macro_rules! define_categories {
8 ($($name:ident,)*) => {
9 #[derive(Clone, Copy, Debug, PartialEq, Eq)]
10 pub enum ProfileCategory {
14 #[allow(nonstandard_style)]
15 struct Categories<T> {
19 impl<T: Default> Categories<T> {
20 fn new() -> Categories<T> {
22 $($name: T::default()),*
27 impl<T> Categories<T> {
28 fn get(&self, category: ProfileCategory) -> &T {
30 $(ProfileCategory::$name => &self.$name),*
34 fn set(&mut self, category: ProfileCategory, value: T) {
36 $(ProfileCategory::$name => self.$name = value),*
42 times: Categories<u64>,
43 query_counts: Categories<(u64, u64)>,
47 fn new() -> CategoryData {
49 times: Categories::new(),
50 query_counts: Categories::new(),
54 fn print(&self, lock: &mut StderrLock<'_>) {
55 writeln!(lock, "| Phase | Time (ms) \
56 | Time (%) | Queries | Hits (%)")
58 writeln!(lock, "| ---------------- | -------------- \
59 | -------- | -------------- | --------")
62 let total_time = ($(self.times.$name + )* 0) as f32;
65 let (hits, computed) = self.query_counts.$name;
66 let total = hits + computed;
67 let (hits, total) = if total > 0 {
69 (((hits as f32) / (total as f32)) * 100.0)), total.to_string())
71 (String::new(), String::new())
76 "| {0: <16} | {1: <14} | {2: <8.2} | {3: <14} | {4: <8}",
78 self.times.$name / 1_000_000,
79 ((self.times.$name as f32) / total_time) * 100.0,
86 fn json(&self) -> String {
87 let mut json = String::from("[");
90 let (hits, computed) = self.query_counts.$name;
91 let total = hits + computed;
93 //normalize hits to 0%
96 ((hits as f32) / (total as f32)) * 100.0
101 json.push_str(&format!(
102 "{{ \"category\": \"{}\", \"time_ms\": {},\
103 \"query_count\": {}, \"query_hits\": {} }},",
105 self.times.$name / 1_000_000,
107 format!("{:.2}", hit_percent)
111 //remove the trailing ',' character
132 pub struct SelfProfiler {
133 timer_stack: Vec<ProfileCategory>,
135 current_timer: Instant,
139 pub fn new() -> SelfProfiler {
140 let mut profiler = SelfProfiler {
141 timer_stack: Vec::new(),
142 data: CategoryData::new(),
143 current_timer: Instant::now(),
146 profiler.start_activity(ProfileCategory::Other);
151 pub fn start_activity(&mut self, category: ProfileCategory) {
152 match self.timer_stack.last().cloned() {
154 self.current_timer = Instant::now();
156 Some(current_category) if current_category == category => {
157 //since the current category is the same as the new activity's category,
158 //we don't need to do anything with the timer, we just need to push it on the stack
160 Some(current_category) => {
161 let elapsed = self.stop_timer();
163 //record the current category's time
164 let new_time = self.data.times.get(current_category) + elapsed;
165 self.data.times.set(current_category, new_time);
169 //push the new category
170 self.timer_stack.push(category);
173 pub fn record_computed_queries(&mut self, category: ProfileCategory, count: usize) {
174 let (hits, computed) = *self.data.query_counts.get(category);
175 self.data.query_counts.set(category, (hits, computed + count as u64));
178 pub fn record_query_hit(&mut self, category: ProfileCategory) {
179 let (hits, computed) = *self.data.query_counts.get(category);
180 self.data.query_counts.set(category, (hits + 1, computed));
183 pub fn end_activity(&mut self, category: ProfileCategory) {
184 match self.timer_stack.pop() {
185 None => bug!("end_activity() was called but there was no running activity"),
189 "end_activity() was called but a different activity was running"),
192 //check if the new running timer is in the same category as this one
193 //if it is, we don't need to do anything
194 if let Some(c) = self.timer_stack.last() {
200 //the new timer is different than the previous,
201 //so record the elapsed time and start a new timer
202 let elapsed = self.stop_timer();
203 let new_time = self.data.times.get(category) + elapsed;
204 self.data.times.set(category, new_time);
207 fn stop_timer(&mut self) -> u64 {
208 let elapsed = self.current_timer.elapsed();
210 self.current_timer = Instant::now();
212 (elapsed.as_secs() * 1_000_000_000) + (elapsed.subsec_nanos() as u64)
215 pub fn print_results(&mut self, opts: &Options) {
216 self.end_activity(ProfileCategory::Other);
219 self.timer_stack.is_empty(),
220 "there were timers running when print_results() was called");
222 let out = io::stderr();
223 let mut lock = out.lock();
228 .map(|n| format!(" for {}", n))
229 .unwrap_or_default();
231 writeln!(lock, "Self profiling results{}:", crate_name).unwrap();
232 writeln!(lock).unwrap();
234 self.data.print(&mut lock);
236 writeln!(lock).unwrap();
237 writeln!(lock, "Optimization level: {:?}", opts.optimize).unwrap();
239 let incremental = if opts.incremental.is_some() { "on" } else { "off" };
240 writeln!(lock, "Incremental: {}", incremental).unwrap();
243 pub fn save_results(&self, opts: &Options) {
244 let category_data = self.data.json();
245 let compilation_options =
246 format!("{{ \"optimization_level\": \"{:?}\", \"incremental\": {} }}",
248 if opts.incremental.is_some() { "true" } else { "false" });
250 let json = format!("{{ \"category_data\": {}, \"compilation_options\": {} }}",
252 compilation_options);
254 fs::write("self_profiler_results.json", json).unwrap();