1use std::borrow::ToOwned;
8use std::collections::{BTreeMap, HashMap};
9use std::fs::File;
10use std::io::{self, Write};
11use std::path::Path;
12use std::thread;
13
14use ipc_channel::ipc::{self, IpcReceiver};
15use profile_traits::time::{
16 ProfilerCategory, ProfilerChan, ProfilerData, ProfilerMsg, TimerMetadata,
17 TimerMetadataFrameType, TimerMetadataReflowType,
18};
19use servo_config::opts::OutputOptions;
20use time::Duration;
21
22use crate::trace_dump::TraceDump;
23
24pub trait Formattable {
25 fn format(&self, output: &Option<OutputOptions>) -> String;
26}
27
28impl Formattable for Option<TimerMetadata> {
29 fn format(&self, output: &Option<OutputOptions>) -> String {
30 match *self {
31 Some(ref meta) => {
33 let url = &*meta.url;
34 match *output {
35 Some(OutputOptions::FileName(_)) => {
36 let incremental = match meta.incremental {
38 TimerMetadataReflowType::Incremental => "yes",
39 TimerMetadataReflowType::FirstReflow => "no",
40 };
41 let iframe = match meta.iframe {
42 TimerMetadataFrameType::RootWindow => "yes",
43 TimerMetadataFrameType::IFrame => "no",
44 };
45 format!(" {}\t{}\t{}", incremental, iframe, url)
46 },
47 _ => {
48 let url = if url.len() > 30 { &url[..30] } else { url };
50 let incremental = match meta.incremental {
51 TimerMetadataReflowType::Incremental => " yes",
52 TimerMetadataReflowType::FirstReflow => " no ",
53 };
54 let iframe = match meta.iframe {
55 TimerMetadataFrameType::RootWindow => " yes",
56 TimerMetadataFrameType::IFrame => " no ",
57 };
58 format!(" {:14} {:9} {:30}", incremental, iframe, url)
59 },
60 }
61 },
62 None => match *output {
63 Some(OutputOptions::FileName(_)) => {
64 format!(" {}\t{}\t{}", " N/A", " N/A", " N/A")
65 },
66 _ => format!(" {:14} {:9} {:30}", " N/A", " N/A", " N/A"),
67 },
68 }
69 }
70}
71
72type ProfilerBuckets = BTreeMap<(ProfilerCategory, Option<TimerMetadata>), Vec<Duration>>;
73
74pub struct Profiler {
76 pub port: IpcReceiver<ProfilerMsg>,
77 buckets: ProfilerBuckets,
78 output: Option<OutputOptions>,
79 pub last_msg: Option<ProfilerMsg>,
80 trace: Option<TraceDump>,
81 blocked_layout_queries: HashMap<String, u32>,
82}
83
84impl Profiler {
85 pub fn create(output: &Option<OutputOptions>, file_path: Option<String>) -> ProfilerChan {
86 let (chan, port) = ipc::channel().unwrap();
87 match *output {
88 Some(ref option) => {
89 let outputoption = option.clone();
91 thread::Builder::new()
92 .name("TimeProfiler".to_owned())
93 .spawn(move || {
94 let trace = file_path.as_ref().and_then(|p| TraceDump::new(p).ok());
95 let mut profiler = Profiler::new(port, trace, Some(outputoption));
96 profiler.start();
97 })
98 .expect("Thread spawning failed");
99 match *option {
101 OutputOptions::FileName(_) => { },
102 OutputOptions::Stdout(period) => {
103 let chan = chan.clone();
105 thread::Builder::new()
106 .name("TimeProfTimer".to_owned())
107 .spawn(move || {
108 loop {
109 thread::sleep(std::time::Duration::from_secs_f64(period));
110 if chan.send(ProfilerMsg::Print).is_err() {
111 break;
112 }
113 }
114 })
115 .expect("Thread spawning failed");
116 },
117 }
118 },
119 None => {
120 if file_path.is_some() {
122 thread::Builder::new()
124 .name("TimeProfiler".to_owned())
125 .spawn(move || {
126 let trace = file_path.as_ref().and_then(|p| TraceDump::new(p).ok());
127 let mut profiler = Profiler::new(port, trace, None);
128 profiler.start();
129 })
130 .expect("Thread spawning failed");
131 } else {
132 thread::Builder::new()
134 .name("TimeProfiler".to_owned())
135 .spawn(move || {
136 loop {
137 match port.recv() {
138 Err(_) => break,
139 Ok(ProfilerMsg::Exit(chan)) => {
140 let _ = chan.send(());
141 break;
142 },
143 _ => {},
144 }
145 }
146 })
147 .expect("Thread spawning failed");
148 }
149 },
150 }
151
152 ProfilerChan(chan)
153 }
154
155 pub fn new(
156 port: IpcReceiver<ProfilerMsg>,
157 trace: Option<TraceDump>,
158 output: Option<OutputOptions>,
159 ) -> Profiler {
160 Profiler {
161 port,
162 buckets: BTreeMap::new(),
163 output,
164 last_msg: None,
165 trace,
166 blocked_layout_queries: HashMap::new(),
167 }
168 }
169
170 pub fn start(&mut self) {
171 while let Ok(msg) = self.port.recv() {
172 if !self.handle_msg(msg) {
173 break;
174 }
175 }
176 }
177
178 fn find_or_insert(&mut self, k: (ProfilerCategory, Option<TimerMetadata>), duration: Duration) {
179 self.buckets.entry(k).or_default().push(duration);
180 }
181
182 fn handle_msg(&mut self, msg: ProfilerMsg) -> bool {
183 match msg.clone() {
184 ProfilerMsg::Time(category_and_metadata, (start_time, end_time)) => {
185 if let Some(ref mut trace) = self.trace {
186 trace.write_one(&category_and_metadata, start_time, end_time);
187 }
188 self.find_or_insert(category_and_metadata, end_time - start_time);
189 },
190 ProfilerMsg::Print => {
191 if let Some(ProfilerMsg::Time(..)) = self.last_msg {
192 self.print_buckets();
194 }
195 },
196 ProfilerMsg::Get(k, sender) => {
197 let vec_option = self.buckets.get(&k);
198 match vec_option {
199 Some(vec_entry) => sender
200 .send(ProfilerData::Record(vec_entry.to_vec()))
201 .unwrap(),
202 None => sender.send(ProfilerData::NoRecords).unwrap(),
203 };
204 },
205 ProfilerMsg::BlockedLayoutQuery(url) => {
206 *self.blocked_layout_queries.entry(url).or_insert(0) += 1;
207 },
208 ProfilerMsg::Exit(chan) => {
209 self.print_buckets();
210 let _ = chan.send(());
211 return false;
212 },
213 };
214 self.last_msg = Some(msg);
215 true
216 }
217
218 pub fn get_statistics(data: &[Duration]) -> (Duration, Duration, Duration, Duration) {
220 debug_assert!(
221 data.windows(2).all(|window| window[0] <= window[1]),
222 "Data must be sorted"
223 );
224
225 let data_len = data.len();
226 debug_assert!(data_len > 0);
227 let (mean, median, min, max) = (
228 data.iter().sum::<Duration>() / data_len as u32,
229 data[data_len / 2],
230 data[0],
231 data[data_len - 1],
232 );
233 (mean, median, min, max)
234 }
235
236 fn print_buckets(&mut self) {
237 match self.output {
238 Some(OutputOptions::FileName(ref filename)) => {
239 let path = Path::new(&filename);
240 let mut file = match File::create(path) {
241 Err(e) => panic!("Couldn't create {}: {}", path.display(), e),
242 Ok(file) => file,
243 };
244 writeln!(
245 file,
246 "_category_\t_incremental?_\t_iframe?_\t_url_\t_mean (ms)_\t\
247 _median (ms)_\t_min (ms)_\t_max (ms)_\t_events_"
248 )
249 .unwrap();
250 for ((category, meta), ref mut data) in &mut self.buckets {
251 data.sort_by(|a, b| a.partial_cmp(b).expect("No NaN values in profiles"));
252 let data_len = data.len();
253 if data_len > 0 {
254 let (mean, median, min, max) = Self::get_statistics(data);
255 writeln!(
256 file,
257 "{}\t{}\t{:15.4}\t{:15.4}\t{:15.4}\t{:15.4}\t{:15}",
258 category.variant_name(),
259 meta.format(&self.output),
260 mean.as_seconds_f64() * 1000.,
261 median.as_seconds_f64() * 1000.,
262 min.as_seconds_f64() * 1000.,
263 max.as_seconds_f64() * 1000.,
264 data_len
265 )
266 .unwrap();
267 }
268 }
269
270 writeln!(file, "_url\t_blocked layout queries_").unwrap();
271 for (url, count) in &self.blocked_layout_queries {
272 writeln!(file, "{}\t{}", url, count).unwrap();
273 }
274 },
275 Some(OutputOptions::Stdout(_)) => {
276 let stdout = io::stdout();
277 let mut lock = stdout.lock();
278
279 writeln!(
280 &mut lock,
281 "{:35} {:14} {:9} {:30} {:15} {:15} {:-15} {:-15} {:-15}",
282 "_category_",
283 "_incremental?_",
284 "_iframe?_",
285 " _url_",
286 " _mean (ms)_",
287 " _median (ms)_",
288 " _min (ms)_",
289 " _max (ms)_",
290 " _events_"
291 )
292 .unwrap();
293 for ((category, meta), ref mut data) in &mut self.buckets {
294 data.sort_by(|a, b| a.partial_cmp(b).expect("No NaN values in profiles"));
295 let data_len = data.len();
296 if data_len > 0 {
297 let (mean, median, min, max) = Self::get_statistics(data);
298 writeln!(
299 &mut lock,
300 "{:-35}{} {:15.4} {:15.4} {:15.4} {:15.4} {:15}",
301 category.variant_name(),
302 meta.format(&self.output),
303 mean.as_seconds_f64() * 1000.,
304 median.as_seconds_f64() * 1000.,
305 min.as_seconds_f64() * 1000.,
306 max.as_seconds_f64() * 1000.,
307 data_len
308 )
309 .unwrap();
310 }
311 }
312 writeln!(&mut lock).unwrap();
313
314 writeln!(&mut lock, "_url_\t_blocked layout queries_").unwrap();
315 for (url, count) in &self.blocked_layout_queries {
316 writeln!(&mut lock, "{}\t{}", url, count).unwrap();
317 }
318 writeln!(&mut lock).unwrap();
319 },
320 None => { },
321 };
322 }
323}