2019-02-05 11:20:45 -06:00
|
|
|
use crate::session::config::Options;
|
2018-05-19 12:50:58 -05:00
|
|
|
|
2018-06-05 22:05:30 -05:00
|
|
|
use std::fs;
|
2018-12-05 22:48:25 -06:00
|
|
|
use std::io::{self, StderrLock, Write};
|
2018-12-19 12:29:23 -06:00
|
|
|
use std::time::Instant;
|
2018-05-19 12:50:58 -05:00
|
|
|
|
2018-07-23 20:52:39 -05:00
|
|
|
macro_rules! define_categories {
|
|
|
|
($($name:ident,)*) => {
|
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
|
|
pub enum ProfileCategory {
|
|
|
|
$($name),*
|
2018-05-19 12:50:58 -05:00
|
|
|
}
|
|
|
|
|
2018-08-29 08:21:01 -05:00
|
|
|
#[allow(nonstandard_style)]
|
2018-07-23 20:52:39 -05:00
|
|
|
struct Categories<T> {
|
|
|
|
$($name: T),*
|
2018-05-19 12:50:58 -05:00
|
|
|
}
|
|
|
|
|
2018-07-23 20:52:39 -05:00
|
|
|
impl<T: Default> Categories<T> {
|
|
|
|
fn new() -> Categories<T> {
|
|
|
|
Categories {
|
|
|
|
$($name: T::default()),*
|
|
|
|
}
|
|
|
|
}
|
2018-05-19 12:50:58 -05:00
|
|
|
}
|
|
|
|
|
2018-07-23 20:52:39 -05:00
|
|
|
impl<T> Categories<T> {
|
|
|
|
fn get(&self, category: ProfileCategory) -> &T {
|
|
|
|
match category {
|
|
|
|
$(ProfileCategory::$name => &self.$name),*
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn set(&mut self, category: ProfileCategory, value: T) {
|
|
|
|
match category {
|
|
|
|
$(ProfileCategory::$name => self.$name = value),*
|
|
|
|
}
|
|
|
|
}
|
2018-05-19 12:50:58 -05:00
|
|
|
}
|
|
|
|
|
2018-07-23 20:52:39 -05:00
|
|
|
struct CategoryData {
|
|
|
|
times: Categories<u64>,
|
|
|
|
query_counts: Categories<(u64, u64)>,
|
2018-05-29 22:12:49 -05:00
|
|
|
}
|
|
|
|
|
2018-07-23 20:52:39 -05:00
|
|
|
impl CategoryData {
|
|
|
|
fn new() -> CategoryData {
|
|
|
|
CategoryData {
|
|
|
|
times: Categories::new(),
|
|
|
|
query_counts: Categories::new(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-05 22:48:25 -06:00
|
|
|
fn print(&self, lock: &mut StderrLock<'_>) {
|
2018-12-10 20:16:19 -06:00
|
|
|
writeln!(lock, "| Phase | Time (ms) \
|
|
|
|
| Time (%) | Queries | Hits (%)")
|
2018-07-23 20:52:39 -05:00
|
|
|
.unwrap();
|
2018-12-10 20:16:19 -06:00
|
|
|
writeln!(lock, "| ---------------- | -------------- \
|
|
|
|
| -------- | -------------- | --------")
|
2018-07-23 20:52:39 -05:00
|
|
|
.unwrap();
|
|
|
|
|
2018-12-10 20:16:19 -06:00
|
|
|
let total_time = ($(self.times.$name + )* 0) as f32;
|
|
|
|
|
2018-07-23 20:52:39 -05:00
|
|
|
$(
|
2018-12-24 06:35:37 -06:00
|
|
|
let (hits, computed) = self.query_counts.$name;
|
|
|
|
let total = hits + computed;
|
2018-07-23 20:52:39 -05:00
|
|
|
let (hits, total) = if total > 0 {
|
|
|
|
(format!("{:.2}",
|
|
|
|
(((hits as f32) / (total as f32)) * 100.0)), total.to_string())
|
|
|
|
} else {
|
2018-08-23 03:14:52 -05:00
|
|
|
(String::new(), String::new())
|
2018-07-23 20:52:39 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
writeln!(
|
|
|
|
lock,
|
2018-12-10 20:16:19 -06:00
|
|
|
"| {0: <16} | {1: <14} | {2: <8.2} | {3: <14} | {4: <8}",
|
2018-07-23 20:52:39 -05:00
|
|
|
stringify!($name),
|
|
|
|
self.times.$name / 1_000_000,
|
2018-12-10 20:16:19 -06:00
|
|
|
((self.times.$name as f32) / total_time) * 100.0,
|
2018-07-23 20:52:39 -05:00
|
|
|
total,
|
2018-12-10 20:16:19 -06:00
|
|
|
hits,
|
2018-07-23 20:52:39 -05:00
|
|
|
).unwrap();
|
|
|
|
)*
|
|
|
|
}
|
|
|
|
|
|
|
|
fn json(&self) -> String {
|
|
|
|
let mut json = String::from("[");
|
|
|
|
|
|
|
|
$(
|
2018-12-24 06:35:37 -06:00
|
|
|
let (hits, computed) = self.query_counts.$name;
|
|
|
|
let total = hits + computed;
|
2018-07-23 20:52:39 -05:00
|
|
|
|
2018-11-19 22:26:00 -06:00
|
|
|
//normalize hits to 0%
|
|
|
|
let hit_percent =
|
|
|
|
if total > 0 {
|
|
|
|
((hits as f32) / (total as f32)) * 100.0
|
|
|
|
} else {
|
|
|
|
0.0
|
|
|
|
};
|
|
|
|
|
2018-07-23 20:52:39 -05:00
|
|
|
json.push_str(&format!(
|
2018-11-25 16:43:00 -06:00
|
|
|
"{{ \"category\": \"{}\", \"time_ms\": {},\
|
2018-11-19 22:26:00 -06:00
|
|
|
\"query_count\": {}, \"query_hits\": {} }},",
|
2018-07-23 20:52:39 -05:00
|
|
|
stringify!($name),
|
|
|
|
self.times.$name / 1_000_000,
|
|
|
|
total,
|
2018-11-19 22:26:00 -06:00
|
|
|
format!("{:.2}", hit_percent)
|
2018-07-23 20:52:39 -05:00
|
|
|
));
|
|
|
|
)*
|
|
|
|
|
2018-11-19 22:26:00 -06:00
|
|
|
//remove the trailing ',' character
|
|
|
|
json.pop();
|
|
|
|
|
2018-07-23 20:52:39 -05:00
|
|
|
json.push(']');
|
|
|
|
|
|
|
|
json
|
|
|
|
}
|
2018-06-11 22:41:11 -05:00
|
|
|
}
|
2018-06-05 22:05:30 -05:00
|
|
|
}
|
2018-05-19 12:50:58 -05:00
|
|
|
}
|
|
|
|
|
2018-07-23 20:52:39 -05:00
|
|
|
define_categories! {
|
|
|
|
Parsing,
|
|
|
|
Expansion,
|
|
|
|
TypeChecking,
|
|
|
|
BorrowChecking,
|
|
|
|
Codegen,
|
|
|
|
Linking,
|
|
|
|
Other,
|
|
|
|
}
|
|
|
|
|
2018-05-19 12:50:58 -05:00
|
|
|
pub struct SelfProfiler {
|
|
|
|
timer_stack: Vec<ProfileCategory>,
|
|
|
|
data: CategoryData,
|
|
|
|
current_timer: Instant,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SelfProfiler {
|
|
|
|
pub fn new() -> SelfProfiler {
|
|
|
|
let mut profiler = SelfProfiler {
|
|
|
|
timer_stack: Vec::new(),
|
|
|
|
data: CategoryData::new(),
|
|
|
|
current_timer: Instant::now(),
|
|
|
|
};
|
|
|
|
|
|
|
|
profiler.start_activity(ProfileCategory::Other);
|
|
|
|
|
|
|
|
profiler
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn start_activity(&mut self, category: ProfileCategory) {
|
|
|
|
match self.timer_stack.last().cloned() {
|
|
|
|
None => {
|
|
|
|
self.current_timer = Instant::now();
|
|
|
|
},
|
|
|
|
Some(current_category) if current_category == category => {
|
|
|
|
//since the current category is the same as the new activity's category,
|
|
|
|
//we don't need to do anything with the timer, we just need to push it on the stack
|
|
|
|
}
|
|
|
|
Some(current_category) => {
|
|
|
|
let elapsed = self.stop_timer();
|
|
|
|
|
|
|
|
//record the current category's time
|
|
|
|
let new_time = self.data.times.get(current_category) + elapsed;
|
|
|
|
self.data.times.set(current_category, new_time);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//push the new category
|
|
|
|
self.timer_stack.push(category);
|
|
|
|
}
|
|
|
|
|
2019-01-07 11:50:51 -06:00
|
|
|
pub fn record_computed_queries(&mut self, category: ProfileCategory, count: usize) {
|
2018-12-24 06:35:37 -06:00
|
|
|
let (hits, computed) = *self.data.query_counts.get(category);
|
|
|
|
self.data.query_counts.set(category, (hits, computed + count as u64));
|
2018-05-29 22:12:49 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn record_query_hit(&mut self, category: ProfileCategory) {
|
2018-12-24 06:35:37 -06:00
|
|
|
let (hits, computed) = *self.data.query_counts.get(category);
|
|
|
|
self.data.query_counts.set(category, (hits + 1, computed));
|
2018-05-29 22:12:49 -05:00
|
|
|
}
|
|
|
|
|
2018-05-19 12:50:58 -05:00
|
|
|
pub fn end_activity(&mut self, category: ProfileCategory) {
|
|
|
|
match self.timer_stack.pop() {
|
|
|
|
None => bug!("end_activity() was called but there was no running activity"),
|
2018-07-20 20:28:37 -05:00
|
|
|
Some(c) =>
|
2018-05-19 12:50:58 -05:00
|
|
|
assert!(
|
2018-07-20 20:28:37 -05:00
|
|
|
c == category,
|
2018-05-19 12:50:58 -05:00
|
|
|
"end_activity() was called but a different activity was running"),
|
|
|
|
}
|
|
|
|
|
|
|
|
//check if the new running timer is in the same category as this one
|
|
|
|
//if it is, we don't need to do anything
|
|
|
|
if let Some(c) = self.timer_stack.last() {
|
|
|
|
if *c == category {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-07-20 20:28:37 -05:00
|
|
|
//the new timer is different than the previous,
|
|
|
|
//so record the elapsed time and start a new timer
|
2018-05-19 12:50:58 -05:00
|
|
|
let elapsed = self.stop_timer();
|
|
|
|
let new_time = self.data.times.get(category) + elapsed;
|
|
|
|
self.data.times.set(category, new_time);
|
|
|
|
}
|
|
|
|
|
|
|
|
fn stop_timer(&mut self) -> u64 {
|
2018-12-19 12:29:23 -06:00
|
|
|
let elapsed = self.current_timer.elapsed();
|
2018-05-19 12:50:58 -05:00
|
|
|
|
|
|
|
self.current_timer = Instant::now();
|
|
|
|
|
|
|
|
(elapsed.as_secs() * 1_000_000_000) + (elapsed.subsec_nanos() as u64)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn print_results(&mut self, opts: &Options) {
|
|
|
|
self.end_activity(ProfileCategory::Other);
|
|
|
|
|
2018-07-20 20:28:37 -05:00
|
|
|
assert!(
|
|
|
|
self.timer_stack.is_empty(),
|
|
|
|
"there were timers running when print_results() was called");
|
2018-05-19 12:50:58 -05:00
|
|
|
|
2018-12-05 22:48:25 -06:00
|
|
|
let out = io::stderr();
|
2018-05-19 12:50:58 -05:00
|
|
|
let mut lock = out.lock();
|
|
|
|
|
2018-07-20 20:28:37 -05:00
|
|
|
let crate_name =
|
|
|
|
opts.crate_name
|
|
|
|
.as_ref()
|
|
|
|
.map(|n| format!(" for {}", n))
|
|
|
|
.unwrap_or_default();
|
2018-05-19 12:50:58 -05:00
|
|
|
|
|
|
|
writeln!(lock, "Self profiling results{}:", crate_name).unwrap();
|
2018-05-30 22:41:20 -05:00
|
|
|
writeln!(lock).unwrap();
|
2018-05-19 12:50:58 -05:00
|
|
|
|
|
|
|
self.data.print(&mut lock);
|
|
|
|
|
|
|
|
writeln!(lock).unwrap();
|
|
|
|
writeln!(lock, "Optimization level: {:?}", opts.optimize).unwrap();
|
|
|
|
|
|
|
|
let incremental = if opts.incremental.is_some() { "on" } else { "off" };
|
|
|
|
writeln!(lock, "Incremental: {}", incremental).unwrap();
|
|
|
|
}
|
|
|
|
|
2018-06-13 21:08:41 -05:00
|
|
|
pub fn save_results(&self, opts: &Options) {
|
|
|
|
let category_data = self.data.json();
|
2018-07-20 20:28:37 -05:00
|
|
|
let compilation_options =
|
|
|
|
format!("{{ \"optimization_level\": \"{:?}\", \"incremental\": {} }}",
|
|
|
|
opts.optimize,
|
|
|
|
if opts.incremental.is_some() { "true" } else { "false" });
|
2018-06-13 21:08:41 -05:00
|
|
|
|
|
|
|
let json = format!("{{ \"category_data\": {}, \"compilation_options\": {} }}",
|
|
|
|
category_data,
|
|
|
|
compilation_options);
|
|
|
|
|
|
|
|
fs::write("self_profiler_results.json", json).unwrap();
|
2018-06-05 22:05:30 -05:00
|
|
|
}
|
2018-06-20 06:57:22 -05:00
|
|
|
}
|