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;
12 use std::marker::PhantomData;
13 use std::sync::{Arc, Mutex};
14 use std::time::Instant;
15 use std::io::prelude::*;
18 const OUTPUT_WIDTH_IN_PX: u64 = 1000;
19 const TIME_LINE_HEIGHT_IN_PX: u64 = 7;
20 const TIME_LINE_HEIGHT_STRIDE_IN_PX: usize = 10;
26 work_package_kind: WorkPackageKind,
29 #[derive(Clone, Copy, Hash, Eq, PartialEq, Debug)]
30 pub struct TimelineId(pub usize);
35 open_work_package: Option<(Instant, WorkPackageKind)>,
39 pub struct TimeGraph {
40 data: Arc<Mutex<HashMap<TimelineId, PerThread>>>,
43 #[derive(Clone, Copy)]
44 pub struct WorkPackageKind(pub &'static [&'static str]);
46 pub struct RaiiToken {
49 // The token must not be Send:
50 _marker: PhantomData<*const ()>
54 impl Drop for RaiiToken {
56 self.graph.end(self.timeline);
61 pub fn new() -> TimeGraph {
63 data: Arc::new(Mutex::new(HashMap::new()))
69 work_package_kind: WorkPackageKind) -> RaiiToken {
71 let mut table = self.data.lock().unwrap();
73 let data = table.entry(timeline).or_insert(PerThread {
75 open_work_package: None,
78 assert!(data.open_work_package.is_none());
79 data.open_work_package = Some((Instant::now(), work_package_kind));
89 fn end(&self, timeline: TimelineId) {
90 let end = Instant::now();
92 let mut table = self.data.lock().unwrap();
93 let data = table.get_mut(&timeline).unwrap();
95 if let Some((start, work_package_kind)) = data.open_work_package {
96 data.timings.push(Timing {
102 bug!("end timing without start?")
105 data.open_work_package = None;
108 pub fn dump(&self, output_filename: &str) {
109 let table = self.data.lock().unwrap();
111 for data in table.values() {
112 assert!(data.open_work_package.is_none());
115 let mut timelines: Vec<PerThread> =
116 table.values().map(|data| data.clone()).collect();
118 timelines.sort_by_key(|timeline| timeline.timings[0].start);
120 let earliest_instant = timelines[0].timings[0].start;
121 let latest_instant = timelines.iter()
122 .map(|timeline| timeline.timings
128 let max_distance = distance(earliest_instant, latest_instant);
130 let mut file = File::create(format!("{}.html", output_filename)).unwrap();
132 writeln!(file, "<html>").unwrap();
133 writeln!(file, "<head></head>").unwrap();
134 writeln!(file, "<body>").unwrap();
138 for (line_index, timeline) in timelines.iter().enumerate() {
139 let line_top = line_index * TIME_LINE_HEIGHT_STRIDE_IN_PX;
141 for span in &timeline.timings {
142 let start = distance(earliest_instant, span.start);
143 let end = distance(earliest_instant, span.end);
145 let start = normalize(start, max_distance, OUTPUT_WIDTH_IN_PX);
146 let end = normalize(end, max_distance, OUTPUT_WIDTH_IN_PX);
148 let colors = span.work_package_kind.0;
150 writeln!(file, "<div style='position:absolute; \
155 background:{};'></div>",
159 TIME_LINE_HEIGHT_IN_PX,
160 colors[color % colors.len()]
167 writeln!(file, "</body>").unwrap();
168 writeln!(file, "</html>").unwrap();
172 fn distance(zero: Instant, x: Instant) -> u64 {
174 let duration = x.duration_since(zero);
175 (duration.as_secs() * 1_000_000_000 + duration.subsec_nanos() as u64) // / div
178 fn normalize(distance: u64, max: u64, max_pixels: u64) -> u64 {
179 (max_pixels * distance) / max