1 // Copyright 2017 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
11 use std::collections::HashMap;
13 use std::io::prelude::*;
14 use std::marker::PhantomData;
16 use std::sync::{Arc, Mutex};
17 use std::time::Instant;
19 const OUTPUT_WIDTH_IN_PX: u64 = 1000;
20 const TIME_LINE_HEIGHT_IN_PX: u64 = 20;
21 const TIME_LINE_HEIGHT_STRIDE_IN_PX: usize = 30;
27 work_package_kind: WorkPackageKind,
29 events: Vec<(String, Instant)>,
32 #[derive(Clone, Copy, Hash, Eq, PartialEq, Debug)]
33 pub struct TimelineId(pub usize);
38 open_work_package: Option<(Instant, WorkPackageKind, String)>,
42 pub struct TimeGraph {
43 data: Arc<Mutex<HashMap<TimelineId, PerThread>>>,
46 #[derive(Clone, Copy)]
47 pub struct WorkPackageKind(pub &'static [&'static str]);
50 token: Option<RaiiToken>,
56 events: Vec<(String, Instant)>,
57 // The token must not be Send:
58 _marker: PhantomData<*const ()>
62 impl Drop for RaiiToken {
64 self.graph.end(self.timeline, mem::replace(&mut self.events, Vec::new()));
69 pub fn new() -> TimeGraph {
71 data: Arc::new(Mutex::new(HashMap::new()))
77 work_package_kind: WorkPackageKind,
78 name: &str) -> Timeline {
80 let mut table = self.data.lock().unwrap();
82 let data = table.entry(timeline).or_insert(PerThread {
84 open_work_package: None,
87 assert!(data.open_work_package.is_none());
88 data.open_work_package = Some((Instant::now(), work_package_kind, name.to_string()));
92 token: Some(RaiiToken {
101 fn end(&self, timeline: TimelineId, events: Vec<(String, Instant)>) {
102 let end = Instant::now();
104 let mut table = self.data.lock().unwrap();
105 let data = table.get_mut(&timeline).unwrap();
107 if let Some((start, work_package_kind, name)) = data.open_work_package.take() {
108 data.timings.push(Timing {
116 bug!("end timing without start?")
120 pub fn dump(&self, output_filename: &str) {
121 let table = self.data.lock().unwrap();
123 for data in table.values() {
124 assert!(data.open_work_package.is_none());
127 let mut threads: Vec<PerThread> =
128 table.values().map(|data| data.clone()).collect();
130 threads.sort_by_key(|timeline| timeline.timings[0].start);
132 let earliest_instant = threads[0].timings[0].start;
133 let latest_instant = threads.iter()
134 .map(|timeline| timeline.timings
140 let max_distance = distance(earliest_instant, latest_instant);
142 let mut file = File::create(format!("{}.html", output_filename)).unwrap();
153 height: {total_height}px;
175 total_height = threads.len() * TIME_LINE_HEIGHT_STRIDE_IN_PX,
176 width = OUTPUT_WIDTH_IN_PX,
180 for (line_index, thread) in threads.iter().enumerate() {
181 let line_top = line_index * TIME_LINE_HEIGHT_STRIDE_IN_PX;
183 for span in &thread.timings {
184 let start = distance(earliest_instant, span.start);
185 let end = distance(earliest_instant, span.end);
187 let start = normalize(start, max_distance, OUTPUT_WIDTH_IN_PX);
188 let end = normalize(end, max_distance, OUTPUT_WIDTH_IN_PX);
190 let colors = span.work_package_kind.0;
192 writeln!(file, "<a href='#timing{}'
197 background:{};'>{}</a>",
202 TIME_LINE_HEIGHT_IN_PX,
203 colors[color % colors.len()],
216 for thread in threads.iter() {
217 for timing in &thread.timings {
218 let colors = timing.work_package_kind.0;
219 let height = TIME_LINE_HEIGHT_STRIDE_IN_PX * timing.events.len();
220 writeln!(file, "<div class='timeline'
222 style='background:{};height:{}px;'>",
224 colors[idx % colors.len()],
227 let max = distance(timing.start, timing.end);
228 for (i, &(ref event, time)) in timing.events.iter().enumerate() {
230 let time = distance(timing.start, time);
231 let at = normalize(time, max, OUTPUT_WIDTH_IN_PX);
232 writeln!(file, "<span class='event'
234 top:{}px;'>{}</span>",
236 TIME_LINE_HEIGHT_IN_PX * i,
239 writeln!(file, "</div>").unwrap();
251 pub fn noop() -> Timeline {
252 Timeline { token: None }
255 /// Record an event which happened at this moment on this timeline.
257 /// Events are displayed in the eventual HTML output where you can click on
258 /// a particular timeline and it'll expand to all of the events that
259 /// happened on that timeline. This can then be used to drill into a
260 /// particular timeline and see what events are happening and taking the
262 pub fn record(&mut self, name: &str) {
263 if let Some(ref mut token) = self.token {
264 token.events.push((name.to_string(), Instant::now()));
269 fn distance(zero: Instant, x: Instant) -> u64 {
271 let duration = x.duration_since(zero);
272 (duration.as_secs() * 1_000_000_000 + duration.subsec_nanos() as u64) // / div
275 fn normalize(distance: u64, max: u64, max_pixels: u64) -> u64 {
276 (max_pixels * distance) / max