// Copyright 2017 The Rust Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution and at // http://rust-lang.org/COPYRIGHT. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. use std::collections::HashMap; use std::fs::File; use std::io::prelude::*; use std::marker::PhantomData; use std::mem; use std::sync::{Arc, Mutex}; use std::time::Instant; const OUTPUT_WIDTH_IN_PX: u64 = 1000; const TIME_LINE_HEIGHT_IN_PX: u64 = 20; const TIME_LINE_HEIGHT_STRIDE_IN_PX: usize = 30; #[derive(Clone)] struct Timing { start: Instant, end: Instant, work_package_kind: WorkPackageKind, name: String, events: Vec<(String, Instant)>, } #[derive(Clone, Copy, Hash, Eq, PartialEq, Debug)] pub struct TimelineId(pub usize); #[derive(Clone)] struct PerThread { timings: Vec, open_work_package: Option<(Instant, WorkPackageKind, String)>, } #[derive(Clone)] pub struct TimeGraph { data: Arc>>, } #[derive(Clone, Copy)] pub struct WorkPackageKind(pub &'static [&'static str]); pub struct Timeline { token: Option, } struct RaiiToken { graph: TimeGraph, timeline: TimelineId, events: Vec<(String, Instant)>, // The token must not be Send: _marker: PhantomData<*const ()> } impl Drop for RaiiToken { fn drop(&mut self) { self.graph.end(self.timeline, mem::replace(&mut self.events, Vec::new())); } } impl TimeGraph { pub fn new() -> TimeGraph { TimeGraph { data: Arc::new(Mutex::new(HashMap::new())) } } pub fn start(&self, timeline: TimelineId, work_package_kind: WorkPackageKind, name: &str) -> Timeline { { let mut table = self.data.lock().unwrap(); let data = table.entry(timeline).or_insert(PerThread { timings: Vec::new(), open_work_package: None, }); assert!(data.open_work_package.is_none()); data.open_work_package = Some((Instant::now(), work_package_kind, name.to_string())); } Timeline { token: Some(RaiiToken { graph: self.clone(), timeline, events: Vec::new(), _marker: PhantomData, }), } } fn end(&self, timeline: TimelineId, events: Vec<(String, Instant)>) { let end = Instant::now(); let mut table = self.data.lock().unwrap(); let data = table.get_mut(&timeline).unwrap(); if let Some((start, work_package_kind, name)) = data.open_work_package.take() { data.timings.push(Timing { start, end, work_package_kind, name, events, }); } else { bug!("end timing without start?") } } pub fn dump(&self, output_filename: &str) { let table = self.data.lock().unwrap(); for data in table.values() { assert!(data.open_work_package.is_none()); } let mut threads: Vec = table.values().map(|data| data.clone()).collect(); threads.sort_by_key(|timeline| timeline.timings[0].start); let earliest_instant = threads[0].timings[0].start; let latest_instant = threads.iter() .map(|timeline| timeline.timings .last() .unwrap() .end) .max() .unwrap(); let max_distance = distance(earliest_instant, latest_instant); let mut file = File::create(format!("{}.html", output_filename)).unwrap(); writeln!(file, "
", total_height = threads.len() * TIME_LINE_HEIGHT_STRIDE_IN_PX, width = OUTPUT_WIDTH_IN_PX, ).unwrap(); let mut color = 0; for (line_index, thread) in threads.iter().enumerate() { let line_top = line_index * TIME_LINE_HEIGHT_STRIDE_IN_PX; for span in &thread.timings { let start = distance(earliest_instant, span.start); let end = distance(earliest_instant, span.end); let start = normalize(start, max_distance, OUTPUT_WIDTH_IN_PX); let end = normalize(end, max_distance, OUTPUT_WIDTH_IN_PX); let colors = span.work_package_kind.0; writeln!(file, "{}", color, line_top, start, end - start, TIME_LINE_HEIGHT_IN_PX, colors[color % colors.len()], span.name, ).unwrap(); color += 1; } } writeln!(file, "
").unwrap(); let mut idx = 0; for thread in threads.iter() { for timing in &thread.timings { let colors = timing.work_package_kind.0; let height = TIME_LINE_HEIGHT_STRIDE_IN_PX * timing.events.len(); writeln!(file, "
", idx, colors[idx % colors.len()], height).unwrap(); idx += 1; let max = distance(timing.start, timing.end); for (i, &(ref event, time)) in timing.events.iter().enumerate() { let i = i as u64; let time = distance(timing.start, time); let at = normalize(time, max, OUTPUT_WIDTH_IN_PX); writeln!(file, "{}", at, TIME_LINE_HEIGHT_IN_PX * i, event).unwrap(); } writeln!(file, "
").unwrap(); } } writeln!(file, " ").unwrap(); } } impl Timeline { pub fn noop() -> Timeline { Timeline { token: None } } /// Record an event which happened at this moment on this timeline. /// /// Events are displayed in the eventual HTML output where you can click on /// a particular timeline and it'll expand to all of the events that /// happened on that timeline. This can then be used to drill into a /// particular timeline and see what events are happening and taking the /// most time. pub fn record(&mut self, name: &str) { if let Some(ref mut token) = self.token { token.events.push((name.to_string(), Instant::now())); } } } fn distance(zero: Instant, x: Instant) -> u64 { let duration = x.duration_since(zero); (duration.as_secs() * 1_000_000_000 + duration.subsec_nanos() as u64) // / div } fn normalize(distance: u64, max: u64, max_pixels: u64) -> u64 { (max_pixels * distance) / max }