1 use std::collections::{BTreeMap, HashMap};
3 use std::io::{self, Write};
4 use std::thread::ThreadId;
5 use std::time::Instant;
7 use crate::session::config::{Options, OptLevel};
9 #[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)]
10 pub enum ProfileCategory {
20 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
21 pub enum ProfilerEvent {
22 QueryStart { query_name: &'static str, category: ProfileCategory, time: Instant },
23 QueryEnd { query_name: &'static str, category: ProfileCategory, time: Instant },
24 GenericActivityStart { category: ProfileCategory, time: Instant },
25 GenericActivityEnd { category: ProfileCategory, time: Instant },
26 QueryCacheHit { query_name: &'static str, category: ProfileCategory },
27 QueryCount { query_name: &'static str, category: ProfileCategory, count: usize },
31 fn is_start_event(&self) -> bool {
32 use self::ProfilerEvent::*;
35 QueryStart { .. } | GenericActivityStart { .. } => true,
36 QueryEnd { .. } | GenericActivityEnd { .. } |
37 QueryCacheHit { .. } | QueryCount { .. } => false,
42 pub struct SelfProfiler {
43 events: HashMap<ThreadId, Vec<ProfilerEvent>>,
46 struct CategoryResultData {
47 query_times: BTreeMap<&'static str, u64>,
48 query_cache_stats: BTreeMap<&'static str, (u64, u64)>, //(hits, total)
51 impl CategoryResultData {
52 fn new() -> CategoryResultData {
54 query_times: BTreeMap::new(),
55 query_cache_stats: BTreeMap::new(),
59 fn total_time(&self) -> u64 {
61 for (_, time) in &self.query_times {
68 fn total_cache_data(&self) -> (u64, u64) {
69 let (mut hits, mut total) = (0, 0);
71 for (_, (h, t)) in &self.query_cache_stats {
80 impl Default for CategoryResultData {
81 fn default() -> CategoryResultData {
82 CategoryResultData::new()
86 struct CalculatedResults {
87 categories: BTreeMap<ProfileCategory, CategoryResultData>,
88 crate_name: Option<String>,
89 optimization_level: OptLevel,
94 impl CalculatedResults {
95 fn new() -> CalculatedResults {
97 categories: BTreeMap::new(),
99 optimization_level: OptLevel::No,
105 fn consolidate(mut cr1: CalculatedResults, cr2: CalculatedResults) -> CalculatedResults {
106 for (category, data) in cr2.categories {
107 let cr1_data = cr1.categories.entry(category).or_default();
109 for (query, time) in data.query_times {
110 *cr1_data.query_times.entry(query).or_default() += time;
113 for (query, (hits, total)) in data.query_cache_stats {
114 let (h, t) = cr1_data.query_cache_stats.entry(query).or_insert((0, 0));
123 fn total_time(&self) -> u64 {
126 for (_, data) in &self.categories {
127 total += data.total_time();
133 fn with_options(mut self, opts: &Options) -> CalculatedResults {
134 self.crate_name = opts.crate_name.clone();
135 self.optimization_level = opts.optimize;
136 self.incremental = opts.incremental.is_some();
137 self.verbose = opts.debugging_opts.verbose;
143 fn time_between_ns(start: Instant, end: Instant) -> u64 {
145 let time = end - start;
146 (time.as_secs() * 1_000_000_000) + (time.subsec_nanos() as u64)
148 debug!("time_between_ns: ignorning instance of end < start");
153 fn calculate_percent(numerator: u64, denominator: u64) -> f32 {
155 ((numerator as f32) / (denominator as f32)) * 100.0
162 pub fn new() -> SelfProfiler {
163 let mut profiler = SelfProfiler {
164 events: HashMap::new(),
167 profiler.start_activity(ProfileCategory::Other);
173 pub fn start_activity(&mut self, category: ProfileCategory) {
174 self.record(ProfilerEvent::GenericActivityStart {
176 time: Instant::now(),
181 pub fn end_activity(&mut self, category: ProfileCategory) {
182 self.record(ProfilerEvent::GenericActivityEnd {
184 time: Instant::now(),
189 pub fn record_computed_queries(
191 query_name: &'static str,
192 category: ProfileCategory,
195 self.record(ProfilerEvent::QueryCount {
203 pub fn record_query_hit(&mut self, query_name: &'static str, category: ProfileCategory) {
204 self.record(ProfilerEvent::QueryCacheHit {
211 pub fn start_query(&mut self, query_name: &'static str, category: ProfileCategory) {
212 self.record(ProfilerEvent::QueryStart {
215 time: Instant::now(),
220 pub fn end_query(&mut self, query_name: &'static str, category: ProfileCategory) {
221 self.record(ProfilerEvent::QueryEnd {
224 time: Instant::now(),
229 fn record(&mut self, event: ProfilerEvent) {
230 let thread_id = std::thread::current().id();
231 let events = self.events.entry(thread_id).or_default();
236 fn calculate_thread_results(events: &Vec<ProfilerEvent>) -> CalculatedResults {
237 use self::ProfilerEvent::*;
240 events.last().map(|e| !e.is_start_event()).unwrap_or(true),
241 "there was an event running when calculate_reslts() was called"
244 let mut results = CalculatedResults::new();
246 //(event, child time to subtract)
247 let mut query_stack = Vec::new();
249 for event in events {
251 QueryStart { .. } | GenericActivityStart { .. } => {
252 query_stack.push((event, 0));
254 QueryEnd { query_name, category, time: end_time } => {
255 let previous_query = query_stack.pop();
256 if let Some((QueryStart {
257 query_name: p_query_name,
259 category: _ }, child_time_to_subtract)) = previous_query {
263 "Saw a query end but the previous query wasn't the corresponding start"
266 let time_ns = time_between_ns(*start_time, *end_time);
267 let self_time_ns = time_ns - child_time_to_subtract;
268 let result_data = results.categories.entry(*category).or_default();
270 *result_data.query_times.entry(query_name).or_default() += self_time_ns;
272 if let Some((_, child_time_to_subtract)) = query_stack.last_mut() {
273 *child_time_to_subtract += time_ns;
276 bug!("Saw a query end but the previous event wasn't a query start");
279 GenericActivityEnd { category, time: end_time } => {
280 let previous_event = query_stack.pop();
281 if let Some((GenericActivityStart {
282 category: previous_category,
283 time: start_time }, child_time_to_subtract)) = previous_event {
287 "Saw an end but the previous event wasn't the corresponding start"
290 let time_ns = time_between_ns(*start_time, *end_time);
291 let self_time_ns = time_ns - child_time_to_subtract;
292 let result_data = results.categories.entry(*category).or_default();
294 *result_data.query_times
295 .entry("{time spent not running queries}")
296 .or_default() += self_time_ns;
298 if let Some((_, child_time_to_subtract)) = query_stack.last_mut() {
299 *child_time_to_subtract += time_ns;
302 bug!("Saw an activity end but the previous event wasn't an activity start");
305 QueryCacheHit { category, query_name } => {
306 let result_data = results.categories.entry(*category).or_default();
309 result_data.query_cache_stats.entry(query_name).or_insert((0, 0));
313 QueryCount { category, query_name, count } => {
314 let result_data = results.categories.entry(*category).or_default();
317 result_data.query_cache_stats.entry(query_name).or_insert((0, 0));
318 *totals += *count as u64;
323 //normalize the times to ms
324 for (_, data) in &mut results.categories {
325 for (_, time) in &mut data.query_times {
326 *time = *time / 1_000_000;
333 fn get_results(&self, opts: &Options) -> CalculatedResults {
336 .map(|(_, r)| SelfProfiler::calculate_thread_results(r))
337 .fold(CalculatedResults::new(), CalculatedResults::consolidate)
341 pub fn print_results(&mut self, opts: &Options) {
342 self.end_activity(ProfileCategory::Other);
344 let results = self.get_results(opts);
346 let total_time = results.total_time() as f32;
348 let out = io::stderr();
349 let mut lock = out.lock();
351 let crate_name = results.crate_name.map(|n| format!(" for {}", n)).unwrap_or_default();
353 writeln!(lock, "Self profiling results{}:", crate_name).unwrap();
354 writeln!(lock).unwrap();
356 writeln!(lock, "| Phase | Time (ms) \
357 | Time (%) | Queries | Hits (%)")
359 writeln!(lock, "| ----------------------------------------- | -------------- \
360 | -------- | -------------- | --------")
363 let mut categories: Vec<_> = results.categories.iter().collect();
364 categories.sort_by(|(_, data1), (_, data2)| data2.total_time().cmp(&data1.total_time()));
366 for (category, data) in categories {
367 let (category_hits, category_total) = data.total_cache_data();
368 let category_hit_percent = calculate_percent(category_hits, category_total);
372 "| {0: <41} | {1: >14} | {2: >8.2} | {3: >14} | {4: >8}",
373 format!("{:?}", category),
375 ((data.total_time() as f32) / total_time) * 100.0,
377 format!("{:.2}", category_hit_percent),
380 //in verbose mode, show individual query data
382 //don't show queries that took less than 1ms
383 let mut times: Vec<_> = data.query_times.iter().filter(|(_, t)| **t > 0).collect();
384 times.sort_by(|(_, time1), (_, time2)| time2.cmp(time1));
386 for (query, time) in times {
387 let (hits, total) = data.query_cache_stats.get(query).unwrap_or(&(0, 0));
388 let hit_percent = calculate_percent(*hits, *total);
392 "| - {0: <39} | {1: >14} | {2: >8.2} | {3: >14} | {4: >8}",
395 ((*time as f32) / total_time) * 100.0,
397 format!("{:.2}", hit_percent),
403 writeln!(lock).unwrap();
404 writeln!(lock, "Optimization level: {:?}", opts.optimize).unwrap();
405 writeln!(lock, "Incremental: {}", if results.incremental { "on" } else { "off" }).unwrap();
408 pub fn save_results(&self, opts: &Options) {
409 let results = self.get_results(opts);
411 let compilation_options =
412 format!("{{ \"optimization_level\": \"{:?}\", \"incremental\": {} }}",
413 results.optimization_level,
414 if results.incremental { "true" } else { "false" });
416 let mut category_data = String::new();
418 for (category, data) in &results.categories {
419 let (hits, total) = data.total_cache_data();
420 let hit_percent = calculate_percent(hits, total);
422 category_data.push_str(&format!("{{ \"category\": \"{:?}\", \"time_ms\": {}, \
423 \"query_count\": {}, \"query_hits\": {} }}",
427 format!("{:.2}", hit_percent)));
430 let json = format!("{{ \"category_data\": {}, \"compilation_options\": {} }}",
432 compilation_options);
434 fs::write("self_profiler_results.json", json).unwrap();