|
1 |
| -use crate::session::config::Options; |
2 |
| - |
| 1 | +use std::collections::{BTreeMap, HashMap}; |
3 | 2 | use std::fs;
|
4 |
| -use std::io::{self, StderrLock, Write}; |
| 3 | +use std::io::{self, Write}; |
| 4 | +use std::thread::ThreadId; |
5 | 5 | use std::time::Instant;
|
6 | 6 |
|
7 |
| -macro_rules! define_categories { |
8 |
| - ($($name:ident,)*) => { |
9 |
| - #[derive(Clone, Copy, Debug, PartialEq, Eq)] |
10 |
| - pub enum ProfileCategory { |
11 |
| - $($name),* |
| 7 | +use crate::session::config::{Options, OptLevel}; |
| 8 | + |
| 9 | +#[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)] |
| 10 | +pub enum ProfileCategory { |
| 11 | + Parsing, |
| 12 | + Expansion, |
| 13 | + TypeChecking, |
| 14 | + BorrowChecking, |
| 15 | + Codegen, |
| 16 | + Linking, |
| 17 | + Other, |
| 18 | +} |
| 19 | + |
| 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 }, |
| 28 | +} |
| 29 | + |
| 30 | +impl ProfilerEvent { |
| 31 | + fn is_start_event(&self) -> bool { |
| 32 | + use self::ProfilerEvent::*; |
| 33 | + |
| 34 | + match self { |
| 35 | + QueryStart { .. } | GenericActivityStart { .. } => true, |
| 36 | + QueryEnd { .. } | GenericActivityEnd { .. } | |
| 37 | + QueryCacheHit { .. } | QueryCount { .. } => false, |
12 | 38 | }
|
| 39 | + } |
| 40 | +} |
13 | 41 |
|
14 |
| - #[allow(nonstandard_style)] |
15 |
| - struct Categories<T> { |
16 |
| - $($name: T),* |
| 42 | +pub struct SelfProfiler { |
| 43 | + events: HashMap<ThreadId, Vec<ProfilerEvent>>, |
| 44 | +} |
| 45 | + |
| 46 | +struct CategoryResultData { |
| 47 | + query_times: BTreeMap<&'static str, u64>, |
| 48 | + query_cache_stats: BTreeMap<&'static str, (u64, u64)>, //(hits, total) |
| 49 | +} |
| 50 | + |
| 51 | +impl CategoryResultData { |
| 52 | + fn new() -> CategoryResultData { |
| 53 | + CategoryResultData { |
| 54 | + query_times: BTreeMap::new(), |
| 55 | + query_cache_stats: BTreeMap::new(), |
17 | 56 | }
|
| 57 | + } |
18 | 58 |
|
19 |
| - impl<T: Default> Categories<T> { |
20 |
| - fn new() -> Categories<T> { |
21 |
| - Categories { |
22 |
| - $($name: T::default()),* |
23 |
| - } |
24 |
| - } |
| 59 | + fn total_time(&self) -> u64 { |
| 60 | + let mut total = 0; |
| 61 | + for (_, time) in &self.query_times { |
| 62 | + total += time; |
25 | 63 | }
|
26 | 64 |
|
27 |
| - impl<T> Categories<T> { |
28 |
| - fn get(&self, category: ProfileCategory) -> &T { |
29 |
| - match category { |
30 |
| - $(ProfileCategory::$name => &self.$name),* |
31 |
| - } |
32 |
| - } |
| 65 | + total |
| 66 | + } |
33 | 67 |
|
34 |
| - fn set(&mut self, category: ProfileCategory, value: T) { |
35 |
| - match category { |
36 |
| - $(ProfileCategory::$name => self.$name = value),* |
37 |
| - } |
38 |
| - } |
39 |
| - } |
| 68 | + fn total_cache_data(&self) -> (u64, u64) { |
| 69 | + let (mut hits, mut total) = (0, 0); |
40 | 70 |
|
41 |
| - struct CategoryData { |
42 |
| - times: Categories<u64>, |
43 |
| - query_counts: Categories<(u64, u64)>, |
| 71 | + for (_, (h, t)) in &self.query_cache_stats { |
| 72 | + hits += h; |
| 73 | + total += t; |
44 | 74 | }
|
45 | 75 |
|
46 |
| - impl CategoryData { |
47 |
| - fn new() -> CategoryData { |
48 |
| - CategoryData { |
49 |
| - times: Categories::new(), |
50 |
| - query_counts: Categories::new(), |
51 |
| - } |
52 |
| - } |
| 76 | + (hits, total) |
| 77 | + } |
| 78 | +} |
53 | 79 |
|
54 |
| - fn print(&self, lock: &mut StderrLock<'_>) { |
55 |
| - writeln!(lock, "| Phase | Time (ms) \ |
56 |
| - | Time (%) | Queries | Hits (%)") |
57 |
| - .unwrap(); |
58 |
| - writeln!(lock, "| ---------------- | -------------- \ |
59 |
| - | -------- | -------------- | --------") |
60 |
| - .unwrap(); |
61 |
| - |
62 |
| - let total_time = ($(self.times.$name + )* 0) as f32; |
63 |
| - |
64 |
| - $( |
65 |
| - let (hits, computed) = self.query_counts.$name; |
66 |
| - let total = hits + computed; |
67 |
| - let (hits, total) = if total > 0 { |
68 |
| - (format!("{:.2}", |
69 |
| - (((hits as f32) / (total as f32)) * 100.0)), total.to_string()) |
70 |
| - } else { |
71 |
| - (String::new(), String::new()) |
72 |
| - }; |
| 80 | +impl Default for CategoryResultData { |
| 81 | + fn default() -> CategoryResultData { |
| 82 | + CategoryResultData::new() |
| 83 | + } |
| 84 | +} |
73 | 85 |
|
74 |
| - writeln!( |
75 |
| - lock, |
76 |
| - "| {0: <16} | {1: <14} | {2: <8.2} | {3: <14} | {4: <8}", |
77 |
| - stringify!($name), |
78 |
| - self.times.$name / 1_000_000, |
79 |
| - ((self.times.$name as f32) / total_time) * 100.0, |
80 |
| - total, |
81 |
| - hits, |
82 |
| - ).unwrap(); |
83 |
| - )* |
84 |
| - } |
| 86 | +struct CalculatedResults { |
| 87 | + categories: BTreeMap<ProfileCategory, CategoryResultData>, |
| 88 | + crate_name: Option<String>, |
| 89 | + optimization_level: OptLevel, |
| 90 | + incremental: bool, |
| 91 | + verbose: bool, |
| 92 | +} |
85 | 93 |
|
86 |
| - fn json(&self) -> String { |
87 |
| - let mut json = String::from("["); |
88 |
| - |
89 |
| - $( |
90 |
| - let (hits, computed) = self.query_counts.$name; |
91 |
| - let total = hits + computed; |
92 |
| - |
93 |
| - //normalize hits to 0% |
94 |
| - let hit_percent = |
95 |
| - if total > 0 { |
96 |
| - ((hits as f32) / (total as f32)) * 100.0 |
97 |
| - } else { |
98 |
| - 0.0 |
99 |
| - }; |
100 |
| - |
101 |
| - json.push_str(&format!( |
102 |
| - "{{ \"category\": \"{}\", \"time_ms\": {},\ |
103 |
| - \"query_count\": {}, \"query_hits\": {} }},", |
104 |
| - stringify!($name), |
105 |
| - self.times.$name / 1_000_000, |
106 |
| - total, |
107 |
| - format!("{:.2}", hit_percent) |
108 |
| - )); |
109 |
| - )* |
| 94 | +impl CalculatedResults { |
| 95 | + fn new() -> CalculatedResults { |
| 96 | + CalculatedResults { |
| 97 | + categories: BTreeMap::new(), |
| 98 | + crate_name: None, |
| 99 | + optimization_level: OptLevel::No, |
| 100 | + incremental: false, |
| 101 | + verbose: false, |
| 102 | + } |
| 103 | + } |
110 | 104 |
|
111 |
| - //remove the trailing ',' character |
112 |
| - json.pop(); |
| 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(); |
113 | 108 |
|
114 |
| - json.push(']'); |
| 109 | + for (query, time) in data.query_times { |
| 110 | + *cr1_data.query_times.entry(query).or_default() += time; |
| 111 | + } |
115 | 112 |
|
116 |
| - json |
| 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)); |
| 115 | + *h += hits; |
| 116 | + *t += total; |
117 | 117 | }
|
118 | 118 | }
|
| 119 | + |
| 120 | + cr1 |
| 121 | + } |
| 122 | + |
| 123 | + fn total_time(&self) -> u64 { |
| 124 | + let mut total = 0; |
| 125 | + |
| 126 | + for (_, data) in &self.categories { |
| 127 | + total += data.total_time(); |
| 128 | + } |
| 129 | + |
| 130 | + total |
| 131 | + } |
| 132 | + |
| 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; |
| 138 | + |
| 139 | + self |
119 | 140 | }
|
120 | 141 | }
|
121 | 142 |
|
122 |
| -define_categories! { |
123 |
| - Parsing, |
124 |
| - Expansion, |
125 |
| - TypeChecking, |
126 |
| - BorrowChecking, |
127 |
| - Codegen, |
128 |
| - Linking, |
129 |
| - Other, |
| 143 | +fn time_between_ns(start: Instant, end: Instant) -> u64 { |
| 144 | + if start < end { |
| 145 | + let time = end - start; |
| 146 | + (time.as_secs() * 1_000_000_000) + (time.subsec_nanos() as u64) |
| 147 | + } else { |
| 148 | + debug!("time_between_ns: ignorning instance of end < start"); |
| 149 | + 0 |
| 150 | + } |
130 | 151 | }
|
131 | 152 |
|
132 |
| -pub struct SelfProfiler { |
133 |
| - timer_stack: Vec<ProfileCategory>, |
134 |
| - data: CategoryData, |
135 |
| - current_timer: Instant, |
| 153 | +fn calculate_percent(numerator: u64, denominator: u64) -> f32 { |
| 154 | + if denominator > 0 { |
| 155 | + ((numerator as f32) / (denominator as f32)) * 100.0 |
| 156 | + } else { |
| 157 | + 0.0 |
| 158 | + } |
136 | 159 | }
|
137 | 160 |
|
138 | 161 | impl SelfProfiler {
|
139 | 162 | pub fn new() -> SelfProfiler {
|
140 | 163 | let mut profiler = SelfProfiler {
|
141 |
| - timer_stack: Vec::new(), |
142 |
| - data: CategoryData::new(), |
143 |
| - current_timer: Instant::now(), |
| 164 | + events: HashMap::new(), |
144 | 165 | };
|
145 | 166 |
|
146 | 167 | profiler.start_activity(ProfileCategory::Other);
|
147 | 168 |
|
148 | 169 | profiler
|
149 | 170 | }
|
150 | 171 |
|
| 172 | + #[inline] |
151 | 173 | pub fn start_activity(&mut self, category: ProfileCategory) {
|
152 |
| - match self.timer_stack.last().cloned() { |
153 |
| - None => { |
154 |
| - self.current_timer = Instant::now(); |
155 |
| - }, |
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 |
159 |
| - } |
160 |
| - Some(current_category) => { |
161 |
| - let elapsed = self.stop_timer(); |
| 174 | + self.record(ProfilerEvent::GenericActivityStart { |
| 175 | + category, |
| 176 | + time: Instant::now(), |
| 177 | + }) |
| 178 | + } |
162 | 179 |
|
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); |
166 |
| - } |
167 |
| - } |
| 180 | + #[inline] |
| 181 | + pub fn end_activity(&mut self, category: ProfileCategory) { |
| 182 | + self.record(ProfilerEvent::GenericActivityEnd { |
| 183 | + category, |
| 184 | + time: Instant::now(), |
| 185 | + }) |
| 186 | + } |
168 | 187 |
|
169 |
| - //push the new category |
170 |
| - self.timer_stack.push(category); |
| 188 | + #[inline] |
| 189 | + pub fn record_computed_queries( |
| 190 | + &mut self, |
| 191 | + query_name: &'static str, |
| 192 | + category: ProfileCategory, |
| 193 | + count: usize) |
| 194 | + { |
| 195 | + self.record(ProfilerEvent::QueryCount { |
| 196 | + query_name, |
| 197 | + category, |
| 198 | + count, |
| 199 | + }) |
171 | 200 | }
|
172 | 201 |
|
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)); |
| 202 | + #[inline] |
| 203 | + pub fn record_query_hit(&mut self, query_name: &'static str, category: ProfileCategory) { |
| 204 | + self.record(ProfilerEvent::QueryCacheHit { |
| 205 | + query_name, |
| 206 | + category, |
| 207 | + }) |
176 | 208 | }
|
177 | 209 |
|
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)); |
| 210 | + #[inline] |
| 211 | + pub fn start_query(&mut self, query_name: &'static str, category: ProfileCategory) { |
| 212 | + self.record(ProfilerEvent::QueryStart { |
| 213 | + query_name, |
| 214 | + category, |
| 215 | + time: Instant::now(), |
| 216 | + }); |
181 | 217 | }
|
182 | 218 |
|
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"), |
186 |
| - Some(c) => |
187 |
| - assert!( |
188 |
| - c == category, |
189 |
| - "end_activity() was called but a different activity was running"), |
| 219 | + #[inline] |
| 220 | + pub fn end_query(&mut self, query_name: &'static str, category: ProfileCategory) { |
| 221 | + self.record(ProfilerEvent::QueryEnd { |
| 222 | + query_name, |
| 223 | + category, |
| 224 | + time: Instant::now(), |
| 225 | + }) |
| 226 | + } |
| 227 | + |
| 228 | + #[inline] |
| 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(); |
| 232 | + |
| 233 | + events.push(event); |
| 234 | + } |
| 235 | + |
| 236 | + fn calculate_thread_results(events: &Vec<ProfilerEvent>) -> CalculatedResults { |
| 237 | + use self::ProfilerEvent::*; |
| 238 | + |
| 239 | + assert!( |
| 240 | + events.last().map(|e| !e.is_start_event()).unwrap_or(true), |
| 241 | + "there was an event running when calculate_reslts() was called" |
| 242 | + ); |
| 243 | + |
| 244 | + let mut results = CalculatedResults::new(); |
| 245 | + |
| 246 | + //(event, child time to subtract) |
| 247 | + let mut query_stack = Vec::new(); |
| 248 | + |
| 249 | + for event in events { |
| 250 | + match event { |
| 251 | + QueryStart { .. } | GenericActivityStart { .. } => { |
| 252 | + query_stack.push((event, 0)); |
| 253 | + }, |
| 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, |
| 258 | + time: start_time, |
| 259 | + category: _ }, child_time_to_subtract)) = previous_query { |
| 260 | + assert_eq!( |
| 261 | + p_query_name, |
| 262 | + query_name, |
| 263 | + "Saw a query end but the previous query wasn't the corresponding start" |
| 264 | + ); |
| 265 | + |
| 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(); |
| 269 | + |
| 270 | + *result_data.query_times.entry(query_name).or_default() += self_time_ns; |
| 271 | + |
| 272 | + if let Some((_, child_time_to_subtract)) = query_stack.last_mut() { |
| 273 | + *child_time_to_subtract += time_ns; |
| 274 | + } |
| 275 | + } else { |
| 276 | + bug!("Saw a query end but the previous event wasn't a query start"); |
| 277 | + } |
| 278 | + } |
| 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 { |
| 284 | + assert_eq!( |
| 285 | + previous_category, |
| 286 | + category, |
| 287 | + "Saw an end but the previous event wasn't the corresponding start" |
| 288 | + ); |
| 289 | + |
| 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(); |
| 293 | + |
| 294 | + *result_data.query_times |
| 295 | + .entry("{time spent not running queries}") |
| 296 | + .or_default() += self_time_ns; |
| 297 | + |
| 298 | + if let Some((_, child_time_to_subtract)) = query_stack.last_mut() { |
| 299 | + *child_time_to_subtract += time_ns; |
| 300 | + } |
| 301 | + } else { |
| 302 | + bug!("Saw an activity end but the previous event wasn't an activity start"); |
| 303 | + } |
| 304 | + }, |
| 305 | + QueryCacheHit { category, query_name } => { |
| 306 | + let result_data = results.categories.entry(*category).or_default(); |
| 307 | + |
| 308 | + let (hits, total) = |
| 309 | + result_data.query_cache_stats.entry(query_name).or_insert((0, 0)); |
| 310 | + *hits += 1; |
| 311 | + *total += 1; |
| 312 | + }, |
| 313 | + QueryCount { category, query_name, count } => { |
| 314 | + let result_data = results.categories.entry(*category).or_default(); |
| 315 | + |
| 316 | + let (_, totals) = |
| 317 | + result_data.query_cache_stats.entry(query_name).or_insert((0, 0)); |
| 318 | + *totals += *count as u64; |
| 319 | + }, |
| 320 | + } |
190 | 321 | }
|
191 | 322 |
|
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() { |
195 |
| - if *c == category { |
196 |
| - return; |
| 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; |
197 | 327 | }
|
198 | 328 | }
|
199 | 329 |
|
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); |
| 330 | + results |
205 | 331 | }
|
206 | 332 |
|
207 |
| - fn stop_timer(&mut self) -> u64 { |
208 |
| - let elapsed = self.current_timer.elapsed(); |
209 |
| - |
210 |
| - self.current_timer = Instant::now(); |
211 |
| - |
212 |
| - (elapsed.as_secs() * 1_000_000_000) + (elapsed.subsec_nanos() as u64) |
| 333 | + fn get_results(&self, opts: &Options) -> CalculatedResults { |
| 334 | + self.events |
| 335 | + .iter() |
| 336 | + .map(|(_, r)| SelfProfiler::calculate_thread_results(r)) |
| 337 | + .fold(CalculatedResults::new(), CalculatedResults::consolidate) |
| 338 | + .with_options(opts) |
213 | 339 | }
|
214 | 340 |
|
215 | 341 | pub fn print_results(&mut self, opts: &Options) {
|
216 | 342 | self.end_activity(ProfileCategory::Other);
|
217 | 343 |
|
218 |
| - assert!( |
219 |
| - self.timer_stack.is_empty(), |
220 |
| - "there were timers running when print_results() was called"); |
| 344 | + let results = self.get_results(opts); |
| 345 | + |
| 346 | + let total_time = results.total_time() as f32; |
221 | 347 |
|
222 | 348 | let out = io::stderr();
|
223 | 349 | let mut lock = out.lock();
|
224 | 350 |
|
225 |
| - let crate_name = |
226 |
| - opts.crate_name |
227 |
| - .as_ref() |
228 |
| - .map(|n| format!(" for {}", n)) |
229 |
| - .unwrap_or_default(); |
| 351 | + let crate_name = results.crate_name.map(|n| format!(" for {}", n)).unwrap_or_default(); |
230 | 352 |
|
231 | 353 | writeln!(lock, "Self profiling results{}:", crate_name).unwrap();
|
232 | 354 | writeln!(lock).unwrap();
|
233 | 355 |
|
234 |
| - self.data.print(&mut lock); |
| 356 | + writeln!(lock, "| Phase | Time (ms) \ |
| 357 | + | Time (%) | Queries | Hits (%)") |
| 358 | + .unwrap(); |
| 359 | + writeln!(lock, "| ----------------------------------------- | -------------- \ |
| 360 | + | -------- | -------------- | --------") |
| 361 | + .unwrap(); |
| 362 | + |
| 363 | + let mut categories: Vec<_> = results.categories.iter().collect(); |
| 364 | + categories.sort_by(|(_, data1), (_, data2)| data2.total_time().cmp(&data1.total_time())); |
| 365 | + |
| 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); |
| 369 | + |
| 370 | + writeln!( |
| 371 | + lock, |
| 372 | + "| {0: <41} | {1: >14} | {2: >8.2} | {3: >14} | {4: >8}", |
| 373 | + format!("{:?}", category), |
| 374 | + data.total_time(), |
| 375 | + ((data.total_time() as f32) / total_time) * 100.0, |
| 376 | + category_total, |
| 377 | + format!("{:.2}", category_hit_percent), |
| 378 | + ).unwrap(); |
| 379 | + |
| 380 | + //in verbose mode, show individual query data |
| 381 | + if results.verbose { |
| 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)); |
| 385 | + |
| 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); |
| 389 | + |
| 390 | + writeln!( |
| 391 | + lock, |
| 392 | + "| - {0: <39} | {1: >14} | {2: >8.2} | {3: >14} | {4: >8}", |
| 393 | + query, |
| 394 | + time, |
| 395 | + ((*time as f32) / total_time) * 100.0, |
| 396 | + total, |
| 397 | + format!("{:.2}", hit_percent), |
| 398 | + ).unwrap(); |
| 399 | + } |
| 400 | + } |
| 401 | + } |
235 | 402 |
|
236 | 403 | writeln!(lock).unwrap();
|
237 | 404 | writeln!(lock, "Optimization level: {:?}", opts.optimize).unwrap();
|
238 |
| - |
239 |
| - let incremental = if opts.incremental.is_some() { "on" } else { "off" }; |
240 |
| - writeln!(lock, "Incremental: {}", incremental).unwrap(); |
| 405 | + writeln!(lock, "Incremental: {}", if results.incremental { "on" } else { "off" }).unwrap(); |
241 | 406 | }
|
242 | 407 |
|
243 | 408 | pub fn save_results(&self, opts: &Options) {
|
244 |
| - let category_data = self.data.json(); |
| 409 | + let results = self.get_results(opts); |
| 410 | + |
245 | 411 | let compilation_options =
|
246 | 412 | format!("{{ \"optimization_level\": \"{:?}\", \"incremental\": {} }}",
|
247 |
| - opts.optimize, |
248 |
| - if opts.incremental.is_some() { "true" } else { "false" }); |
| 413 | + results.optimization_level, |
| 414 | + if results.incremental { "true" } else { "false" }); |
| 415 | + |
| 416 | + let mut category_data = String::new(); |
| 417 | + |
| 418 | + for (category, data) in &results.categories { |
| 419 | + let (hits, total) = data.total_cache_data(); |
| 420 | + let hit_percent = calculate_percent(hits, total); |
| 421 | + |
| 422 | + category_data.push_str(&format!("{{ \"category\": \"{:?}\", \"time_ms\": {}, \ |
| 423 | + \"query_count\": {}, \"query_hits\": {} }}", |
| 424 | + category, |
| 425 | + data.total_time(), |
| 426 | + total, |
| 427 | + format!("{:.2}", hit_percent))); |
| 428 | + } |
249 | 429 |
|
250 | 430 | let json = format!("{{ \"category_data\": {}, \"compilation_options\": {} }}",
|
251 | 431 | category_data,
|
|
0 commit comments