rust/src/librustc_codegen_llvm/time_graph.rs

279 lines
8.6 KiB
Rust

// 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 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, 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<Timing>,
open_work_package: Option<(Instant, WorkPackageKind, String)>,
}
#[derive(Clone)]
pub struct TimeGraph {
data: Arc<Mutex<HashMap<TimelineId, PerThread>>>,
}
#[derive(Clone, Copy)]
pub struct WorkPackageKind(pub &'static [&'static str]);
pub struct Timeline {
token: Option<RaiiToken>,
}
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<PerThread> =
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, "
<html>
<head>
<style>
#threads a {{
position: absolute;
overflow: hidden;
}}
#threads {{
height: {total_height}px;
width: {width}px;
}}
.timeline {{
display: none;
width: {width}px;
position: relative;
}}
.timeline:target {{
display: block;
}}
.event {{
position: absolute;
}}
</style>
</head>
<body>
<div id='threads'>
",
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, "<a href='#timing{}'
style='top:{}px; \
left:{}px; \
width:{}px; \
height:{}px; \
background:{};'>{}</a>",
color,
line_top,
start,
end - start,
TIME_LINE_HEIGHT_IN_PX,
colors[color % colors.len()],
span.name,
).unwrap();
color += 1;
}
}
writeln!(file, "
</div>
").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, "<div class='timeline'
id='timing{}'
style='background:{};height:{}px;'>",
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, "<span class='event'
style='left:{}px;\
top:{}px;'>{}</span>",
at,
TIME_LINE_HEIGHT_IN_PX * i,
event).unwrap();
}
writeln!(file, "</div>").unwrap();
}
}
writeln!(file, "
</body>
</html>
").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
}