about summary refs log tree commit diff
diff options
context:
space:
mode:
authorWesley Wiser <wwiser@gmail.com>2018-05-19 13:50:58 -0400
committerWesley Wiser <wwiser@gmail.com>2018-08-02 18:57:24 -0400
commit45482c6f994b5fd3b9a10c4d463dd80aa1f72d5c (patch)
tree97c40eb4861231746461ae8ea47ed5c261f0e92a
parent40cb4478a3f550bf12d81cdcf08cc9ef3985ed41 (diff)
downloadrust-45482c6f994b5fd3b9a10c4d463dd80aa1f72d5c.tar.gz
rust-45482c6f994b5fd3b9a10c4d463dd80aa1f72d5c.zip
Basic profiling
-rw-r--r--src/librustc/lib.rs1
-rw-r--r--src/librustc/session/config.rs4
-rw-r--r--src/librustc/session/mod.rs15
-rw-r--r--src/librustc/ty/query/config.rs2
-rw-r--r--src/librustc/ty/query/mod.rs1
-rw-r--r--src/librustc/ty/query/plumbing.rs8
-rw-r--r--src/librustc/util/profiling.rs207
-rw-r--r--src/librustc_codegen_llvm/base.rs3
-rw-r--r--src/librustc_codegen_llvm/lib.rs3
-rw-r--r--src/librustc_driver/driver.rs11
-rw-r--r--src/librustc_typeck/lib.rs5
11 files changed, 259 insertions, 1 deletions
diff --git a/src/librustc/lib.rs b/src/librustc/lib.rs
index 8e71df3c34b..bac511aac1f 100644
--- a/src/librustc/lib.rs
+++ b/src/librustc/lib.rs
@@ -165,6 +165,7 @@ pub mod util {
     pub mod nodemap;
     pub mod fs;
     pub mod time_graph;
+    pub mod profiling;
 }
 
 // A private module so that macro-expanded idents like
diff --git a/src/librustc/session/config.rs b/src/librustc/session/config.rs
index e0532a3320b..cc29bad9cb5 100644
--- a/src/librustc/session/config.rs
+++ b/src/librustc/session/config.rs
@@ -65,7 +65,7 @@ pub enum Sanitizer {
     Thread,
 }
 
-#[derive(Clone, Copy, PartialEq, Hash)]
+#[derive(Clone, Copy, Debug, PartialEq, Hash)]
 pub enum OptLevel {
     No,         // -O0
     Less,       // -O1
@@ -1367,6 +1367,8 @@ options! {DebuggingOptions, DebuggingSetter, basic_debugging_options,
         "disables the 'leak check' for subtyping; unsound, but useful for tests"),
     crate_attr: Vec<String> = (Vec::new(), parse_string_push, [TRACKED],
         "inject the given attribute in the crate"),
+    self_profile: bool = (false, parse_bool, [UNTRACKED],
+          "run the self profiler"),
 }
 
 pub fn default_lib_output() -> CrateType {
diff --git a/src/librustc/session/mod.rs b/src/librustc/session/mod.rs
index 7b8bbbf4a10..a321728f749 100644
--- a/src/librustc/session/mod.rs
+++ b/src/librustc/session/mod.rs
@@ -40,6 +40,7 @@ use syntax::parse::ParseSess;
 use syntax::{ast, codemap};
 use syntax::feature_gate::AttributeType;
 use syntax_pos::{MultiSpan, Span};
+use util::profiling::SelfProfiler;
 
 use rustc_target::spec::{LinkerFlavor, PanicStrategy};
 use rustc_target::spec::{Target, TargetTriple};
@@ -133,6 +134,9 @@ pub struct Session {
     /// Used by -Z profile-queries in util::common
     pub profile_channel: Lock<Option<mpsc::Sender<ProfileQueriesMsg>>>,
 
+    /// Used by -Z self-profile
+    pub self_profiling: Lock<SelfProfiler>,
+
     /// Some measurements that are being gathered during compilation.
     pub perf_stats: PerfStats,
 
@@ -825,6 +829,16 @@ impl Session {
         }
     }
 
+    pub fn profiler<F: FnOnce(&mut SelfProfiler) -> ()>(&self, f: F) {
+        let mut profiler = self.self_profiling.borrow_mut();
+        f(&mut profiler);
+    }
+
+    pub fn print_profiler_results(&self) {
+        let mut profiler = self.self_profiling.borrow_mut();
+        profiler.print_results(&self.opts);
+    }
+
     pub fn print_perf_stats(&self) {
         println!(
             "Total time spent computing symbol hashes:      {}",
@@ -1125,6 +1139,7 @@ pub fn build_session_(
         imported_macro_spans: OneThread::new(RefCell::new(HashMap::new())),
         incr_comp_session: OneThread::new(RefCell::new(IncrCompSession::NotInitialized)),
         ignored_attr_names: ich::compute_ignored_attr_names(),
+        self_profiling: Lock::new(SelfProfiler::new()),
         profile_channel: Lock::new(None),
         perf_stats: PerfStats {
             symbol_hash_time: Lock::new(Duration::from_secs(0)),
diff --git a/src/librustc/ty/query/config.rs b/src/librustc/ty/query/config.rs
index fedf090b9e9..d2648cad55e 100644
--- a/src/librustc/ty/query/config.rs
+++ b/src/librustc/ty/query/config.rs
@@ -21,6 +21,7 @@ use ty::subst::Substs;
 use ty::query::queries;
 use ty::query::Query;
 use ty::query::QueryCache;
+use util::profiling::ProfileCategory;
 
 use std::hash::Hash;
 use std::fmt::Debug;
@@ -33,6 +34,7 @@ use ich::StableHashingContext;
 
 pub trait QueryConfig<'tcx> {
     const NAME: &'static str;
+    const CATEGORY: ProfileCategory;
 
     type Key: Eq + Hash + Clone + Debug;
     type Value: Clone + for<'a> HashStable<StableHashingContext<'a>>;
diff --git a/src/librustc/ty/query/mod.rs b/src/librustc/ty/query/mod.rs
index 1b1020c9bd8..ab9bdd82e01 100644
--- a/src/librustc/ty/query/mod.rs
+++ b/src/librustc/ty/query/mod.rs
@@ -46,6 +46,7 @@ use ty::steal::Steal;
 use ty::subst::Substs;
 use util::nodemap::{DefIdSet, DefIdMap, ItemLocalSet};
 use util::common::{ErrorReported};
+use util::profiling::ProfileCategory::*;
 
 use rustc_data_structures::indexed_set::IdxSetBuf;
 use rustc_target::spec::PanicStrategy;
diff --git a/src/librustc/ty/query/plumbing.rs b/src/librustc/ty/query/plumbing.rs
index 7a9827b50a1..62e69ffee67 100644
--- a/src/librustc/ty/query/plumbing.rs
+++ b/src/librustc/ty/query/plumbing.rs
@@ -379,6 +379,7 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
 
         if dep_node.kind.is_anon() {
             profq_msg!(self, ProfileQueriesMsg::ProviderBegin);
+            self.sess.profiler(|p| p.start_activity(Q::CATEGORY));
 
             let res = job.start(self, |tcx| {
                 tcx.dep_graph.with_anon_task(dep_node.kind, || {
@@ -386,6 +387,7 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
                 })
             });
 
+            self.sess.profiler(|p| p.end_activity(Q::CATEGORY));
             profq_msg!(self, ProfileQueriesMsg::ProviderEnd);
             let ((result, dep_node_index), diagnostics) = res;
 
@@ -523,6 +525,8 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
                 key, dep_node);
 
         profq_msg!(self, ProfileQueriesMsg::ProviderBegin);
+        self.sess.profiler(|p| p.start_activity(Q::CATEGORY));
+
         let res = job.start(self, |tcx| {
             if dep_node.kind.is_eval_always() {
                 tcx.dep_graph.with_eval_always_task(dep_node,
@@ -536,6 +540,8 @@ impl<'a, 'gcx, 'tcx> TyCtxt<'a, 'gcx, 'tcx> {
                                         Q::compute)
             }
         });
+
+        self.sess.profiler(|p| p.end_activity(Q::CATEGORY));
         profq_msg!(self, ProfileQueriesMsg::ProviderEnd);
 
         let ((result, dep_node_index), diagnostics) = res;
@@ -655,6 +661,7 @@ macro_rules! define_queries_inner {
             rustc_data_structures::stable_hasher::StableHasher,
             ich::StableHashingContext
         };
+        use util::profiling::ProfileCategory;
 
         define_queries_struct! {
             tcx: $tcx,
@@ -768,6 +775,7 @@ macro_rules! define_queries_inner {
             type Value = $V;
 
             const NAME: &'static str = stringify!($name);
+            const CATEGORY: ProfileCategory = $category;
         }
 
         impl<$tcx> QueryAccessors<$tcx> for queries::$name<$tcx> {
diff --git a/src/librustc/util/profiling.rs b/src/librustc/util/profiling.rs
new file mode 100644
index 00000000000..eb20a85ca69
--- /dev/null
+++ b/src/librustc/util/profiling.rs
@@ -0,0 +1,207 @@
+// Copyright 2018 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 session::config::Options;
+
+use std::io::{self, StdoutLock, Write};
+use std::time::Instant;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub enum ProfileCategory {
+    Parsing,
+    Expansion,
+    TypeChecking,
+    BorrowChecking,
+    Codegen,
+    Linking,
+    Other,
+}
+
+struct Categories<T> {
+    parsing: T,
+    expansion: T,
+    type_checking: T,
+    borrow_checking: T,
+    codegen: T,
+    linking: T,
+    other: T,
+}
+
+impl<T: Default> Categories<T> {
+    fn new() -> Categories<T> {
+        Categories {
+            parsing: T::default(),
+            expansion: T::default(),
+            type_checking: T::default(),
+            borrow_checking: T::default(),
+            codegen: T::default(),
+            linking: T::default(),
+            other: T::default(),
+        }
+    }
+}
+
+impl<T> Categories<T> {
+    fn get(&self, category: ProfileCategory) -> &T {
+        match category {
+            ProfileCategory::Parsing => &self.parsing,
+            ProfileCategory::Expansion => &self.expansion,
+            ProfileCategory::TypeChecking => &self.type_checking,
+            ProfileCategory::BorrowChecking => &self.borrow_checking,
+            ProfileCategory::Codegen => &self.codegen,
+            ProfileCategory::Linking => &self.linking,
+            ProfileCategory::Other => &self.other,
+        }
+    }
+
+    fn set(&mut self, category: ProfileCategory, value: T) {
+        match category {
+            ProfileCategory::Parsing => self.parsing = value,
+            ProfileCategory::Expansion => self.expansion = value,
+            ProfileCategory::TypeChecking => self.type_checking = value,
+            ProfileCategory::BorrowChecking => self.borrow_checking = value,
+            ProfileCategory::Codegen => self.codegen = value,
+            ProfileCategory::Linking => self.linking = value,
+            ProfileCategory::Other => self.other = value,
+        }
+    }
+}
+
+struct CategoryData {
+    times: Categories<u64>,
+}
+
+impl CategoryData {
+    fn new() -> CategoryData {
+        CategoryData {
+            times: Categories::new(),
+        }
+    }
+
+    fn print(&self, lock: &mut StdoutLock) {
+        writeln!(lock, "{0: <15} \t\t {1: <15}", "Parsing", self.times.parsing / 1_000_000).unwrap();
+        writeln!(lock, "{0: <15} \t\t {1: <15}", "Expansion", self.times.expansion / 1_000_000).unwrap();
+        writeln!(lock, "{0: <15} \t\t {1: <15}", "TypeChecking", self.times.type_checking / 1_000_000).unwrap();
+        writeln!(lock, "{0: <15} \t\t {1: <15}", "BorrowChecking", self.times.borrow_checking / 1_000_000).unwrap();
+        writeln!(lock, "{0: <15} \t\t {1: <15}", "Codegen", self.times.codegen / 1_000_000).unwrap();
+        writeln!(lock, "{0: <15} \t\t {1: <15}", "Linking", self.times.linking / 1_000_000).unwrap();
+        writeln!(lock, "{0: <15} \t\t {1: <15}", "Other", self.times.other / 1_000_000).unwrap();
+    }
+}
+
+pub struct SelfProfiler {
+    timer_stack: Vec<ProfileCategory>,
+    data: CategoryData,
+    current_timer: Instant,
+}
+
+pub struct ProfilerActivity<'a>(ProfileCategory, &'a mut SelfProfiler);
+
+impl<'a> Drop for ProfilerActivity<'a> {
+    fn drop(&mut self) {
+        let ProfilerActivity (category, profiler) = self;
+
+        profiler.end_activity(*category);
+    }
+}
+
+impl SelfProfiler {
+    pub fn new() -> SelfProfiler {
+        let mut profiler = SelfProfiler {
+            timer_stack: Vec::new(),
+            data: CategoryData::new(),
+            current_timer: Instant::now(),
+        };
+
+        profiler.start_activity(ProfileCategory::Other);
+
+        profiler
+    }
+
+    pub fn start_activity(&mut self, category: ProfileCategory) {
+        match self.timer_stack.last().cloned() {
+            None => {
+                self.current_timer = Instant::now();
+            },
+            Some(current_category) if current_category == category => {
+                //since the current category is the same as the new activity's category,
+                //we don't need to do anything with the timer, we just need to push it on the stack
+            }
+            Some(current_category) => {
+                let elapsed = self.stop_timer();
+
+                //record the current category's time
+                let new_time = self.data.times.get(current_category) + elapsed;
+                self.data.times.set(current_category, new_time);
+            }
+        }
+
+        //push the new category
+        self.timer_stack.push(category);
+    }
+
+    pub fn end_activity(&mut self, category: ProfileCategory) {
+        match self.timer_stack.pop() {
+            None => bug!("end_activity() was called but there was no running activity"),
+            Some(c) => 
+                assert!(
+                    c == category, 
+                    "end_activity() was called but a different activity was running"),
+        }
+
+        //check if the new running timer is in the same category as this one
+        //if it is, we don't need to do anything
+        if let Some(c) = self.timer_stack.last() {
+            if *c == category {
+                return;
+            }
+        }
+
+        //the new timer is different than the previous, so record the elapsed time and start a new timer
+        let elapsed = self.stop_timer();
+        let new_time = self.data.times.get(category) + elapsed;
+        self.data.times.set(category, new_time);
+    }
+
+    fn stop_timer(&mut self) -> u64 {
+        let elapsed = self.current_timer.elapsed();
+
+        self.current_timer = Instant::now();
+
+        (elapsed.as_secs() * 1_000_000_000) + (elapsed.subsec_nanos() as u64)
+    }
+
+    pub fn print_results(&mut self, opts: &Options) {
+        self.end_activity(ProfileCategory::Other);
+
+        assert!(self.timer_stack.is_empty(), "there were timers running when print_results() was called");
+
+        let out = io::stdout();
+        let mut lock = out.lock();
+
+        let crate_name = opts.crate_name.as_ref().map(|n| format!(" for {}", n)).unwrap_or_default();
+
+        writeln!(lock, "Self profiling results{}:", crate_name).unwrap();
+
+        self.data.print(&mut lock);
+
+        writeln!(lock).unwrap();
+        writeln!(lock, "Optimization level: {:?}", opts.optimize).unwrap();
+
+        let incremental = if opts.incremental.is_some() { "on" } else { "off" };
+        writeln!(lock, "Incremental: {}", incremental).unwrap();
+    }
+
+    pub fn record_activity<'a>(&'a mut self, category: ProfileCategory) -> ProfilerActivity<'a> {
+        self.start_activity(category);
+
+        ProfilerActivity(category, self)
+    }
+}
\ No newline at end of file
diff --git a/src/librustc_codegen_llvm/base.rs b/src/librustc_codegen_llvm/base.rs
index 8278b443a4c..34cd3998f83 100644
--- a/src/librustc_codegen_llvm/base.rs
+++ b/src/librustc_codegen_llvm/base.rs
@@ -45,6 +45,7 @@ use rustc::dep_graph::{DepNode, DepConstructor};
 use rustc::middle::cstore::{self, LinkMeta, LinkagePreference};
 use rustc::middle::exported_symbols;
 use rustc::util::common::{time, print_time_passes_entry};
+use rustc::util::profiling::ProfileCategory;
 use rustc::session::config::{self, NoDebugInfo};
 use rustc::session::Session;
 use rustc_incremental;
@@ -741,11 +742,13 @@ pub fn codegen_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
     let link_meta = link::build_link_meta(crate_hash);
 
     // Codegen the metadata.
+    tcx.sess.profiler(|p| p.start_activity(ProfileCategory::Codegen));
     let llmod_id = "metadata";
     let metadata_llvm_module = ModuleLlvm::new(tcx.sess, llmod_id);
     let metadata = time(tcx.sess, "write metadata", || {
         write_metadata(tcx, &metadata_llvm_module, &link_meta)
     });
+    tcx.sess.profiler(|p| p.end_activity(ProfileCategory::Codegen));
 
     let metadata_module = ModuleCodegen {
         name: link::METADATA_MODULE_NAME.to_string(),
diff --git a/src/librustc_codegen_llvm/lib.rs b/src/librustc_codegen_llvm/lib.rs
index 724a2e3e65f..c01ef37d1b8 100644
--- a/src/librustc_codegen_llvm/lib.rs
+++ b/src/librustc_codegen_llvm/lib.rs
@@ -84,6 +84,7 @@ use rustc::session::config::{OutputFilenames, OutputType, PrintRequest};
 use rustc::ty::{self, TyCtxt};
 use rustc::util::time_graph;
 use rustc::util::nodemap::{FxHashSet, FxHashMap};
+use rustc::util::profiling::ProfileCategory;
 use rustc_mir::monomorphize;
 use rustc_codegen_utils::codegen_backend::CodegenBackend;
 
@@ -240,10 +241,12 @@ impl CodegenBackend for LlvmCodegenBackend {
 
         // Run the linker on any artifacts that resulted from the LLVM run.
         // This should produce either a finished executable or library.
+        sess.profiler(|p| p.start_activity(ProfileCategory::Linking));
         time(sess, "linking", || {
             back::link::link_binary(sess, &ongoing_codegen,
                                     outputs, &ongoing_codegen.crate_name.as_str());
         });
+        sess.profiler(|p| p.end_activity(ProfileCategory::Linking));
 
         // Now that we won't touch anything in the incremental compilation directory
         // any more, we can finalize it (which involves renaming it)
diff --git a/src/librustc_driver/driver.rs b/src/librustc_driver/driver.rs
index 24a2354775c..d862741cda5 100644
--- a/src/librustc_driver/driver.rs
+++ b/src/librustc_driver/driver.rs
@@ -25,6 +25,7 @@ use rustc::middle::privacy::AccessLevels;
 use rustc::ty::{self, AllArenas, Resolutions, TyCtxt};
 use rustc::traits;
 use rustc::util::common::{install_panic_hook, time, ErrorReported};
+use rustc::util::profiling::ProfileCategory;
 use rustc_allocator as allocator;
 use rustc_borrowck as borrowck;
 use rustc_incremental;
@@ -352,6 +353,10 @@ pub fn compile_input(
         sess.print_perf_stats();
     }
 
+    if sess.opts.debugging_opts.self_profile {
+        sess.print_profiler_results();
+    }
+
     controller_entry_point!(
         compilation_done,
         sess,
@@ -667,6 +672,7 @@ pub fn phase_1_parse_input<'a>(
         profile::begin(sess);
     }
 
+    sess.profiler(|p| p.start_activity(ProfileCategory::Parsing));
     let krate = time(sess, "parsing", || match *input {
         Input::File(ref file) => parse::parse_crate_from_file(file, &sess.parse_sess),
         Input::Str {
@@ -674,6 +680,7 @@ pub fn phase_1_parse_input<'a>(
             ref name,
         } => parse::parse_crate_from_source_str(name.clone(), input.clone(), &sess.parse_sess),
     })?;
+    sess.profiler(|p| p.end_activity(ProfileCategory::Parsing));
 
     sess.diagnostic().set_continue_after_error(true);
 
@@ -944,6 +951,7 @@ where
     syntax_ext::register_builtins(&mut resolver, syntax_exts, sess.features_untracked().quote);
 
     // Expand all macros
+    sess.profiler(|p| p.start_activity(ProfileCategory::Expansion));
     krate = time(sess, "expansion", || {
         // Windows dlls do not have rpaths, so they don't know how to find their
         // dependencies. It's up to us to tell the system where to find all the
@@ -1021,6 +1029,7 @@ where
         }
         krate
     });
+    sess.profiler(|p| p.end_activity(ProfileCategory::Expansion));
 
     krate = time(sess, "maybe building test harness", || {
         syntax::test::modify_for_testing(
@@ -1350,7 +1359,9 @@ pub fn phase_4_codegen<'a, 'tcx>(
         ::rustc::middle::dependency_format::calculate(tcx)
     });
 
+    tcx.sess.profiler(|p| p.start_activity(ProfileCategory::Codegen));
     let codegen = time(tcx.sess, "codegen", move || codegen_backend.codegen_crate(tcx, rx));
+    tcx.sess.profiler(|p| p.end_activity(ProfileCategory::Codegen));
     if tcx.sess.profile_queries() {
         profile::dump(&tcx.sess, "profile_queries".to_string())
     }
diff --git a/src/librustc_typeck/lib.rs b/src/librustc_typeck/lib.rs
index 0cc1f6333af..4f160945517 100644
--- a/src/librustc_typeck/lib.rs
+++ b/src/librustc_typeck/lib.rs
@@ -109,6 +109,7 @@ use rustc::ty::subst::Substs;
 use rustc::ty::{self, Ty, TyCtxt};
 use rustc::ty::query::Providers;
 use rustc::traits::{ObligationCause, ObligationCauseCode, TraitEngine, TraitEngineExt};
+use rustc::util::profiling::ProfileCategory;
 use session::{CompileIncomplete, config};
 use util::common::time;
 
@@ -334,6 +335,8 @@ pub fn provide(providers: &mut Providers) {
 pub fn check_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>)
                              -> Result<(), CompileIncomplete>
 {
+    tcx.sess.profiler(|p| p.start_activity(ProfileCategory::TypeChecking));
+
     // this ensures that later parts of type checking can assume that items
     // have valid types and not error
     tcx.sess.track_errors(|| {
@@ -371,6 +374,8 @@ pub fn check_crate<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>)
     check_unused::check_crate(tcx);
     check_for_entry_fn(tcx);
 
+    tcx.sess.profiler(|p| p.end_activity(ProfileCategory::TypeChecking));
+
     tcx.sess.compile_status()
 }