7bbcd005b3
Don't format!() string literals Prefer `to_string()` to `format!()` take 2, this time targetting string literals. In some cases (`&format!("...")` -> `"..."`) also removes allocations. Occurences of `format!("")` are changed to `String::new()`.
315 lines
9.2 KiB
Rust
315 lines
9.2 KiB
Rust
// Copyright 2012-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 super::*;
|
|
use syntax_pos::SpanData;
|
|
use rustc::util::common::QueryMsg;
|
|
use std::fs::File;
|
|
use std::time::{Duration, Instant};
|
|
use std::collections::hash_map::HashMap;
|
|
use rustc::dep_graph::{DepNode};
|
|
|
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
|
pub struct Query {
|
|
pub span: SpanData,
|
|
pub msg: QueryMsg,
|
|
}
|
|
pub enum Effect {
|
|
QueryBegin(Query, CacheCase),
|
|
TimeBegin(String),
|
|
TaskBegin(DepNode),
|
|
}
|
|
pub enum CacheCase {
|
|
Hit, Miss
|
|
}
|
|
/// Recursive trace structure
|
|
pub struct Rec {
|
|
pub effect: Effect,
|
|
pub start: Instant,
|
|
pub dur_self: Duration,
|
|
pub dur_total: Duration,
|
|
pub extent: Box<Vec<Rec>>,
|
|
}
|
|
pub struct QueryMetric {
|
|
pub count: usize,
|
|
pub dur_self: Duration,
|
|
pub dur_total: Duration,
|
|
}
|
|
|
|
pub fn cons_of_query_msg(q: &trace::Query) -> String {
|
|
let s = format!("{:?}", q.msg);
|
|
let cons: Vec<&str> = s.split(|d| d == '(' || d == '{').collect();
|
|
assert!(cons.len() > 0 && cons[0] != "");
|
|
cons[0].to_string()
|
|
}
|
|
|
|
pub fn cons_of_key(k: &DepNode) -> String {
|
|
let s = format!("{:?}", k);
|
|
let cons: Vec<&str> = s.split(|d| d == '(' || d == '{').collect();
|
|
assert!(cons.len() > 0 && cons[0] != "");
|
|
cons[0].to_string()
|
|
}
|
|
|
|
// First return value is text; second return value is a CSS class
|
|
pub fn html_of_effect(eff: &Effect) -> (String, String) {
|
|
match *eff {
|
|
Effect::TimeBegin(ref msg) => {
|
|
(msg.clone(),
|
|
"time-begin".to_string())
|
|
},
|
|
Effect::TaskBegin(ref key) => {
|
|
let cons = cons_of_key(key);
|
|
(cons.clone(), format!("{} task-begin", cons))
|
|
},
|
|
Effect::QueryBegin(ref qmsg, ref cc) => {
|
|
let cons = cons_of_query_msg(qmsg);
|
|
(cons.clone(),
|
|
format!("{} {}",
|
|
cons,
|
|
match *cc {
|
|
CacheCase::Hit => "hit",
|
|
CacheCase::Miss => "miss",
|
|
}))
|
|
}
|
|
}
|
|
}
|
|
|
|
// First return value is text; second return value is a CSS class
|
|
fn html_of_duration(_start: &Instant, dur: &Duration) -> (String, String) {
|
|
use rustc::util::common::duration_to_secs_str;
|
|
(duration_to_secs_str(dur.clone()),
|
|
"".to_string()
|
|
)
|
|
}
|
|
|
|
fn html_of_fraction(frac: f64) -> (String, String) {
|
|
let css = {
|
|
if frac > 0.50 { "frac-50".to_string() }
|
|
else if frac > 0.40 { "frac-40".to_string() }
|
|
else if frac > 0.30 { "frac-30".to_string() }
|
|
else if frac > 0.20 { "frac-20".to_string() }
|
|
else if frac > 0.10 { "frac-10".to_string() }
|
|
else if frac > 0.05 { "frac-05".to_string() }
|
|
else if frac > 0.02 { "frac-02".to_string() }
|
|
else if frac > 0.01 { "frac-01".to_string() }
|
|
else if frac > 0.001 { "frac-001".to_string() }
|
|
else { "frac-0".to_string() }
|
|
};
|
|
let percent = frac * 100.0;
|
|
if percent > 0.1 { (format!("{:.1}%", percent), css) }
|
|
else { ("< 0.1%".to_string(), css) }
|
|
}
|
|
|
|
fn total_duration(traces: &[Rec]) -> Duration {
|
|
let mut sum : Duration = Duration::new(0,0);
|
|
for t in traces.iter() {
|
|
sum += t.dur_total;
|
|
}
|
|
return sum
|
|
}
|
|
|
|
fn duration_div(nom: Duration, den: Duration) -> f64 {
|
|
fn to_nanos(d: Duration) -> u64 {
|
|
d.as_secs() * 1_000_000_000 + d.subsec_nanos() as u64
|
|
}
|
|
|
|
to_nanos(nom) as f64 / to_nanos(den) as f64
|
|
}
|
|
|
|
fn write_traces_rec(file: &mut File, traces: &[Rec], total: Duration, depth: usize) {
|
|
for t in traces {
|
|
let (eff_text, eff_css_classes) = html_of_effect(&t.effect);
|
|
let (dur_text, dur_css_classes) = html_of_duration(&t.start, &t.dur_total);
|
|
let fraction = duration_div(t.dur_total, total);
|
|
let percent = fraction * 100.0;
|
|
let (frc_text, frc_css_classes) = html_of_fraction(fraction);
|
|
write!(file, "<div class=\"trace depth-{} extent-{}{} {} {} {}\">\n",
|
|
depth,
|
|
t.extent.len(),
|
|
/* Heuristic for 'important' CSS class: */
|
|
if t.extent.len() > 5 || percent >= 1.0 {
|
|
" important" }
|
|
else { "" },
|
|
eff_css_classes,
|
|
dur_css_classes,
|
|
frc_css_classes,
|
|
).unwrap();
|
|
write!(file, "<div class=\"eff\">{}</div>\n", eff_text).unwrap();
|
|
write!(file, "<div class=\"dur\">{}</div>\n", dur_text).unwrap();
|
|
write!(file, "<div class=\"frc\">{}</div>\n", frc_text).unwrap();
|
|
write_traces_rec(file, &t.extent, total, depth + 1);
|
|
write!(file, "</div>\n").unwrap();
|
|
}
|
|
}
|
|
|
|
fn compute_counts_rec(counts: &mut HashMap<String,QueryMetric>, traces: &[Rec]) {
|
|
for t in traces.iter() {
|
|
match t.effect {
|
|
Effect::TimeBegin(ref msg) => {
|
|
let qm = match counts.get(msg) {
|
|
Some(_qm) => { panic!("TimeBegin with non-unique, repeat message") }
|
|
None => QueryMetric{
|
|
count: 1,
|
|
dur_self: t.dur_self,
|
|
dur_total: t.dur_total,
|
|
}};
|
|
counts.insert(msg.clone(), qm);
|
|
},
|
|
Effect::TaskBegin(ref key) => {
|
|
let cons = cons_of_key(key);
|
|
let qm = match counts.get(&cons) {
|
|
Some(qm) =>
|
|
QueryMetric{
|
|
count: qm.count + 1,
|
|
dur_self: qm.dur_self + t.dur_self,
|
|
dur_total: qm.dur_total + t.dur_total,
|
|
},
|
|
None => QueryMetric{
|
|
count: 1,
|
|
dur_self: t.dur_self,
|
|
dur_total: t.dur_total,
|
|
}};
|
|
counts.insert(cons, qm);
|
|
},
|
|
Effect::QueryBegin(ref qmsg, ref _cc) => {
|
|
let qcons = cons_of_query_msg(qmsg);
|
|
let qm = match counts.get(&qcons) {
|
|
Some(qm) =>
|
|
QueryMetric{
|
|
count: qm.count + 1,
|
|
dur_total: qm.dur_total + t.dur_total,
|
|
dur_self: qm.dur_self + t.dur_self
|
|
},
|
|
None => QueryMetric{
|
|
count: 1,
|
|
dur_total: t.dur_total,
|
|
dur_self: t.dur_self,
|
|
}
|
|
};
|
|
counts.insert(qcons, qm);
|
|
}
|
|
}
|
|
compute_counts_rec(counts, &t.extent)
|
|
}
|
|
}
|
|
|
|
pub fn write_counts(count_file: &mut File, counts: &mut HashMap<String,QueryMetric>) {
|
|
use rustc::util::common::duration_to_secs_str;
|
|
use std::cmp::Reverse;
|
|
|
|
let mut data = counts.iter().map(|(ref cons, ref qm)|
|
|
(cons.clone(), qm.count.clone(), qm.dur_total.clone(), qm.dur_self.clone())
|
|
).collect::<Vec<_>>();
|
|
data.sort_by_key(|k| Reverse(k.3));
|
|
for (cons, count, dur_total, dur_self) in data {
|
|
write!(count_file, "{}, {}, {}, {}\n",
|
|
cons, count,
|
|
duration_to_secs_str(dur_total),
|
|
duration_to_secs_str(dur_self)
|
|
).unwrap();
|
|
}
|
|
}
|
|
|
|
pub fn write_traces(html_file: &mut File, counts_file: &mut File, traces: &[Rec]) {
|
|
let capacity = traces.iter().fold(0, |acc, t| acc + 1 + t.extent.len());
|
|
let mut counts : HashMap<String, QueryMetric> = HashMap::with_capacity(capacity);
|
|
compute_counts_rec(&mut counts, traces);
|
|
write_counts(counts_file, &mut counts);
|
|
|
|
let total : Duration = total_duration(traces);
|
|
write_traces_rec(html_file, traces, total, 0)
|
|
}
|
|
|
|
pub fn write_style(html_file: &mut File) {
|
|
write!(html_file,"{}", "
|
|
body {
|
|
font-family: sans-serif;
|
|
background: black;
|
|
}
|
|
.trace {
|
|
color: black;
|
|
display: inline-block;
|
|
border-style: solid;
|
|
border-color: red;
|
|
border-width: 1px;
|
|
border-radius: 5px;
|
|
padding: 0px;
|
|
margin: 1px;
|
|
font-size: 0px;
|
|
}
|
|
.task-begin {
|
|
border-width: 1px;
|
|
color: white;
|
|
border-color: #ff8;
|
|
font-size: 0px;
|
|
}
|
|
.miss {
|
|
border-color: red;
|
|
border-width: 1px;
|
|
}
|
|
.extent-0 {
|
|
padding: 2px;
|
|
}
|
|
.time-begin {
|
|
border-width: 4px;
|
|
font-size: 12px;
|
|
color: white;
|
|
border-color: #afa;
|
|
}
|
|
.important {
|
|
border-width: 3px;
|
|
font-size: 12px;
|
|
color: white;
|
|
border-color: #f77;
|
|
}
|
|
.hit {
|
|
padding: 0px;
|
|
border-color: blue;
|
|
border-width: 3px;
|
|
}
|
|
.eff {
|
|
color: #fff;
|
|
display: inline-block;
|
|
}
|
|
.frc {
|
|
color: #7f7;
|
|
display: inline-block;
|
|
}
|
|
.dur {
|
|
display: none
|
|
}
|
|
.frac-50 {
|
|
padding: 10px;
|
|
border-width: 10px;
|
|
font-size: 32px;
|
|
}
|
|
.frac-40 {
|
|
padding: 8px;
|
|
border-width: 8px;
|
|
font-size: 24px;
|
|
}
|
|
.frac-30 {
|
|
padding: 6px;
|
|
border-width: 6px;
|
|
font-size: 18px;
|
|
}
|
|
.frac-20 {
|
|
padding: 4px;
|
|
border-width: 6px;
|
|
font-size: 16px;
|
|
}
|
|
.frac-10 {
|
|
padding: 2px;
|
|
border-width: 6px;
|
|
font-size: 14px;
|
|
}
|
|
").unwrap();
|
|
}
|