317 lines
8.6 KiB
Rust
317 lines
8.6 KiB
Rust
#![allow(non_camel_case_types)]
|
|
|
|
use rustc_data_structures::{fx::FxHashMap, sync::Lock};
|
|
|
|
use std::cell::{RefCell, Cell};
|
|
use std::fmt::Debug;
|
|
use std::hash::Hash;
|
|
use std::time::{Duration, Instant};
|
|
|
|
use std::sync::mpsc::{Sender};
|
|
use syntax_pos::{SpanData};
|
|
use syntax::symbol::{Symbol, sym};
|
|
use rustc_macros::HashStable;
|
|
use crate::dep_graph::{DepNode};
|
|
use crate::session::Session;
|
|
|
|
#[cfg(test)]
|
|
mod tests;
|
|
|
|
// The name of the associated type for `Fn` return types.
|
|
pub const FN_OUTPUT_NAME: Symbol = sym::Output;
|
|
|
|
// Useful type to use with `Result<>` indicate that an error has already
|
|
// been reported to the user, so no need to continue checking.
|
|
#[derive(Clone, Copy, Debug, RustcEncodable, RustcDecodable, HashStable)]
|
|
pub struct ErrorReported;
|
|
|
|
thread_local!(static TIME_DEPTH: Cell<usize> = Cell::new(0));
|
|
|
|
/// Parameters to the `Dump` variant of type `ProfileQueriesMsg`.
|
|
#[derive(Clone,Debug)]
|
|
pub struct ProfQDumpParams {
|
|
/// A base path for the files we will dump.
|
|
pub path:String,
|
|
/// To ensure that the compiler waits for us to finish our dumps.
|
|
pub ack:Sender<()>,
|
|
/// Toggle dumping a log file with every `ProfileQueriesMsg`.
|
|
pub dump_profq_msg_log:bool,
|
|
}
|
|
|
|
#[allow(nonstandard_style)]
|
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
pub struct QueryMsg {
|
|
pub query: &'static str,
|
|
pub msg: Option<String>,
|
|
}
|
|
|
|
/// A sequence of these messages induce a trace of query-based incremental compilation.
|
|
// FIXME(matthewhammer): Determine whether we should include cycle detection here or not.
|
|
#[derive(Clone,Debug)]
|
|
pub enum ProfileQueriesMsg {
|
|
/// Begin a timed pass.
|
|
TimeBegin(String),
|
|
/// End a timed pass.
|
|
TimeEnd,
|
|
/// Begin a task (see `dep_graph::graph::with_task`).
|
|
TaskBegin(DepNode),
|
|
/// End a task.
|
|
TaskEnd,
|
|
/// Begin a new query.
|
|
/// Cannot use `Span` because queries are sent to other thread.
|
|
QueryBegin(SpanData, QueryMsg),
|
|
/// Query is satisfied by using an already-known value for the given key.
|
|
CacheHit,
|
|
/// Query requires running a provider; providers may nest, permitting queries to nest.
|
|
ProviderBegin,
|
|
/// Query is satisfied by a provider terminating with a value.
|
|
ProviderEnd,
|
|
/// Dump a record of the queries to the given path.
|
|
Dump(ProfQDumpParams),
|
|
/// Halt the profiling/monitoring background thread.
|
|
Halt
|
|
}
|
|
|
|
/// If enabled, send a message to the profile-queries thread.
|
|
pub fn profq_msg(sess: &Session, msg: ProfileQueriesMsg) {
|
|
if let Some(s) = sess.profile_channel.borrow().as_ref() {
|
|
s.send(msg).unwrap()
|
|
} else {
|
|
// Do nothing.
|
|
}
|
|
}
|
|
|
|
/// Set channel for profile queries channel.
|
|
pub fn profq_set_chan(sess: &Session, s: Sender<ProfileQueriesMsg>) -> bool {
|
|
let mut channel = sess.profile_channel.borrow_mut();
|
|
if channel.is_none() {
|
|
*channel = Some(s);
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
/// Read the current depth of `time()` calls. This is used to
|
|
/// encourage indentation across threads.
|
|
pub fn time_depth() -> usize {
|
|
TIME_DEPTH.with(|slot| slot.get())
|
|
}
|
|
|
|
/// Sets the current depth of `time()` calls. The idea is to call
|
|
/// `set_time_depth()` with the result from `time_depth()` in the
|
|
/// parent thread.
|
|
pub fn set_time_depth(depth: usize) {
|
|
TIME_DEPTH.with(|slot| slot.set(depth));
|
|
}
|
|
|
|
pub fn time<T, F>(sess: &Session, what: &str, f: F) -> T where
|
|
F: FnOnce() -> T,
|
|
{
|
|
time_ext(sess.time_passes(), Some(sess), what, f)
|
|
}
|
|
|
|
pub fn time_ext<T, F>(do_it: bool, sess: Option<&Session>, what: &str, f: F) -> T where
|
|
F: FnOnce() -> T,
|
|
{
|
|
if !do_it { return f(); }
|
|
|
|
let old = TIME_DEPTH.with(|slot| {
|
|
let r = slot.get();
|
|
slot.set(r + 1);
|
|
r
|
|
});
|
|
|
|
if let Some(sess) = sess {
|
|
if cfg!(debug_assertions) {
|
|
profq_msg(sess, ProfileQueriesMsg::TimeBegin(what.to_string()))
|
|
}
|
|
}
|
|
let start = Instant::now();
|
|
let rv = f();
|
|
let dur = start.elapsed();
|
|
if let Some(sess) = sess {
|
|
if cfg!(debug_assertions) {
|
|
profq_msg(sess, ProfileQueriesMsg::TimeEnd)
|
|
}
|
|
}
|
|
|
|
print_time_passes_entry(true, what, dur);
|
|
|
|
TIME_DEPTH.with(|slot| slot.set(old));
|
|
|
|
rv
|
|
}
|
|
|
|
pub fn print_time_passes_entry(do_it: bool, what: &str, dur: Duration) {
|
|
if !do_it {
|
|
return
|
|
}
|
|
|
|
let indentation = TIME_DEPTH.with(|slot| slot.get());
|
|
|
|
let mem_string = match get_resident() {
|
|
Some(n) => {
|
|
let mb = n as f64 / 1_000_000.0;
|
|
format!("; rss: {}MB", mb.round() as usize)
|
|
}
|
|
None => String::new(),
|
|
};
|
|
println!("{}time: {}{}\t{}",
|
|
" ".repeat(indentation),
|
|
duration_to_secs_str(dur),
|
|
mem_string,
|
|
what);
|
|
}
|
|
|
|
// Hack up our own formatting for the duration to make it easier for scripts
|
|
// to parse (always use the same number of decimal places and the same unit).
|
|
pub fn duration_to_secs_str(dur: Duration) -> String {
|
|
const NANOS_PER_SEC: f64 = 1_000_000_000.0;
|
|
let secs = dur.as_secs() as f64 +
|
|
dur.subsec_nanos() as f64 / NANOS_PER_SEC;
|
|
|
|
format!("{:.3}", secs)
|
|
}
|
|
|
|
pub fn to_readable_str(mut val: usize) -> String {
|
|
let mut groups = vec![];
|
|
loop {
|
|
let group = val % 1000;
|
|
|
|
val /= 1000;
|
|
|
|
if val == 0 {
|
|
groups.push(group.to_string());
|
|
break;
|
|
} else {
|
|
groups.push(format!("{:03}", group));
|
|
}
|
|
}
|
|
|
|
groups.reverse();
|
|
|
|
groups.join("_")
|
|
}
|
|
|
|
pub fn record_time<T, F>(accu: &Lock<Duration>, f: F) -> T where
|
|
F: FnOnce() -> T,
|
|
{
|
|
let start = Instant::now();
|
|
let rv = f();
|
|
let duration = start.elapsed();
|
|
let mut accu = accu.lock();
|
|
*accu = *accu + duration;
|
|
rv
|
|
}
|
|
|
|
// Memory reporting
|
|
#[cfg(unix)]
|
|
fn get_resident() -> Option<usize> {
|
|
use std::fs;
|
|
|
|
let field = 1;
|
|
let contents = fs::read("/proc/self/statm").ok()?;
|
|
let contents = String::from_utf8(contents).ok()?;
|
|
let s = contents.split_whitespace().nth(field)?;
|
|
let npages = s.parse::<usize>().ok()?;
|
|
Some(npages * 4096)
|
|
}
|
|
|
|
#[cfg(windows)]
|
|
fn get_resident() -> Option<usize> {
|
|
type BOOL = i32;
|
|
type DWORD = u32;
|
|
type HANDLE = *mut u8;
|
|
use libc::size_t;
|
|
use std::mem;
|
|
#[repr(C)]
|
|
#[allow(non_snake_case)]
|
|
struct PROCESS_MEMORY_COUNTERS {
|
|
cb: DWORD,
|
|
PageFaultCount: DWORD,
|
|
PeakWorkingSetSize: size_t,
|
|
WorkingSetSize: size_t,
|
|
QuotaPeakPagedPoolUsage: size_t,
|
|
QuotaPagedPoolUsage: size_t,
|
|
QuotaPeakNonPagedPoolUsage: size_t,
|
|
QuotaNonPagedPoolUsage: size_t,
|
|
PagefileUsage: size_t,
|
|
PeakPagefileUsage: size_t,
|
|
}
|
|
type PPROCESS_MEMORY_COUNTERS = *mut PROCESS_MEMORY_COUNTERS;
|
|
#[link(name = "psapi")]
|
|
extern "system" {
|
|
fn GetCurrentProcess() -> HANDLE;
|
|
fn GetProcessMemoryInfo(Process: HANDLE,
|
|
ppsmemCounters: PPROCESS_MEMORY_COUNTERS,
|
|
cb: DWORD) -> BOOL;
|
|
}
|
|
let mut pmc: PROCESS_MEMORY_COUNTERS = unsafe { mem::zeroed() };
|
|
pmc.cb = mem::size_of_val(&pmc) as DWORD;
|
|
match unsafe { GetProcessMemoryInfo(GetCurrentProcess(), &mut pmc, pmc.cb) } {
|
|
0 => None,
|
|
_ => Some(pmc.WorkingSetSize as usize),
|
|
}
|
|
}
|
|
|
|
pub fn indent<R, F>(op: F) -> R where
|
|
R: Debug,
|
|
F: FnOnce() -> R,
|
|
{
|
|
// Use in conjunction with the log post-processor like `src/etc/indenter`
|
|
// to make debug output more readable.
|
|
debug!(">>");
|
|
let r = op();
|
|
debug!("<< (Result = {:?})", r);
|
|
r
|
|
}
|
|
|
|
pub struct Indenter {
|
|
_cannot_construct_outside_of_this_module: (),
|
|
}
|
|
|
|
impl Drop for Indenter {
|
|
fn drop(&mut self) { debug!("<<"); }
|
|
}
|
|
|
|
pub fn indenter() -> Indenter {
|
|
debug!(">>");
|
|
Indenter { _cannot_construct_outside_of_this_module: () }
|
|
}
|
|
|
|
pub trait MemoizationMap {
|
|
type Key: Clone;
|
|
type Value: Clone;
|
|
|
|
/// If `key` is present in the map, return the value,
|
|
/// otherwise invoke `op` and store the value in the map.
|
|
///
|
|
/// N.B., if the receiver is a `DepTrackingMap`, special care is
|
|
/// needed in the `op` to ensure that the correct edges are
|
|
/// added into the dep graph. See the `DepTrackingMap` impl for
|
|
/// more details!
|
|
fn memoize<OP>(&self, key: Self::Key, op: OP) -> Self::Value
|
|
where OP: FnOnce() -> Self::Value;
|
|
}
|
|
|
|
impl<K, V> MemoizationMap for RefCell<FxHashMap<K,V>>
|
|
where K: Hash+Eq+Clone, V: Clone
|
|
{
|
|
type Key = K;
|
|
type Value = V;
|
|
|
|
fn memoize<OP>(&self, key: K, op: OP) -> V
|
|
where OP: FnOnce() -> V
|
|
{
|
|
let result = self.borrow().get(&key).cloned();
|
|
match result {
|
|
Some(result) => result,
|
|
None => {
|
|
let result = op();
|
|
self.borrow_mut().insert(key, result.clone());
|
|
result
|
|
}
|
|
}
|
|
}
|
|
}
|