]> git.lizzy.rs Git - rust.git/commitdiff
Move self-profile infrastructure to data structures
authorMark Rousskov <mark.simulacrum@gmail.com>
Mon, 11 Nov 2019 22:15:36 +0000 (17:15 -0500)
committerMark Rousskov <mark.simulacrum@gmail.com>
Tue, 12 Nov 2019 17:48:04 +0000 (12:48 -0500)
The single dependency on queries (QueryName) can be fairly easily
abstracted via a trait and this further decouples Session from librustc
(the primary goal).

13 files changed:
Cargo.lock
src/librustc/Cargo.toml
src/librustc/lib.rs
src/librustc/session/mod.rs
src/librustc/ty/context.rs
src/librustc/ty/query/config.rs
src/librustc/ty/query/mod.rs
src/librustc/ty/query/plumbing.rs
src/librustc/util/profiling.rs [deleted file]
src/librustc_codegen_ssa/back/write.rs
src/librustc_data_structures/Cargo.toml
src/librustc_data_structures/lib.rs
src/librustc_data_structures/profiling.rs [new file with mode: 0644]

index 16f2ffc28150f6c8507c4779df227c32315e0afd..7c074fb18a6f16b62f584de5fcfcab6df0caf159 100644 (file)
@@ -3120,7 +3120,6 @@ dependencies = [
  "graphviz",
  "jobserver",
  "log",
- "measureme",
  "num_cpus",
  "parking_lot 0.9.0",
  "polonius-engine",
@@ -3470,6 +3469,7 @@ dependencies = [
 name = "rustc_data_structures"
 version = "0.0.0"
 dependencies = [
+ "bitflags",
  "cfg-if",
  "crossbeam-utils 0.6.5",
  "ena",
@@ -3478,6 +3478,7 @@ dependencies = [
  "jobserver",
  "lazy_static 1.3.0",
  "log",
+ "measureme",
  "parking_lot 0.9.0",
  "rustc-hash",
  "rustc-rayon 0.3.0",
index 92b94af75d75019502843ba9fbeeac0d591fb979..bcbe765b85073cef280a035248e9939cc341a7dd 100644 (file)
@@ -40,4 +40,3 @@ byteorder = { version = "1.3" }
 chalk-engine = { version = "0.9.0", default-features=false }
 rustc_fs_util = { path = "../librustc_fs_util" }
 smallvec = { version = "1.0", features = ["union", "may_dangle"] }
-measureme = "0.4"
index 996f5b1241263e7397e29ee93cc2c95c0e5a9aac..1fd5bcb4915b8e62f4cc1717be258fac1af5e275 100644 (file)
@@ -126,7 +126,6 @@ pub mod util {
     pub mod captures;
     pub mod common;
     pub mod nodemap;
-    pub mod profiling;
     pub mod bug;
 }
 
index 9792223ea15063b2893b553bb84c293635a118f4..bba377392b43306eaa3d2406114c1d28d4553d7b 100644 (file)
 use syntax::sess::{ParseSess, ProcessCfgMod};
 use syntax::symbol::Symbol;
 use syntax_pos::{MultiSpan, Span};
-use crate::util::profiling::{SelfProfiler, SelfProfilerRef};
 
 use rustc_target::spec::{PanicStrategy, RelroLevel, Target, TargetTriple};
 use rustc_data_structures::flock;
 use rustc_data_structures::jobserver;
+use rustc_data_structures::profiling::{SelfProfiler, SelfProfilerRef};
 use ::jobserver::Client;
 
 use std;
index 04e0f6f4b56d71856434c6e71f905b07f306d4fd..2ba670f509f80d75c78762e5ec395a552487cdba 100644 (file)
 use crate::util::common::ErrorReported;
 use crate::util::nodemap::{DefIdMap, DefIdSet, ItemLocalMap, ItemLocalSet, NodeMap};
 use crate::util::nodemap::{FxHashMap, FxHashSet};
-use crate::util::profiling::SelfProfilerRef;
 
 use errors::DiagnosticBuilder;
 use arena::SyncDroplessArena;
 use smallvec::SmallVec;
+use rustc_data_structures::profiling::SelfProfilerRef;
 use rustc_data_structures::stable_hasher::{
     HashStable, StableHasher, StableVec, hash_stable_hashmap,
 };
index c1c6a655d96a9132fe1e366f24b356a2582e1681..7e126459dcc731aa8d8d690d6d30158edc68735b 100644 (file)
@@ -6,7 +6,7 @@
 use crate::ty::query::{Query, QueryName};
 use crate::ty::query::QueryCache;
 use crate::ty::query::plumbing::CycleError;
-use crate::util::profiling::ProfileCategory;
+use rustc_data_structures::profiling::ProfileCategory;
 
 use std::borrow::Cow;
 use std::hash::Hash;
index 0615004125b3ca94df1720fb94ca0241b1f96eb9..a1eb1c43335b1caac4c3f31a4bde61f0ed77147d 100644 (file)
@@ -39,7 +39,7 @@
 use crate::ty::subst::SubstsRef;
 use crate::util::nodemap::{DefIdSet, DefIdMap};
 use crate::util::common::ErrorReported;
-use crate::util::profiling::ProfileCategory::*;
+use rustc_data_structures::profiling::ProfileCategory::*;
 
 use rustc_data_structures::svh::Svh;
 use rustc_index::vec::IndexVec;
index 538154b035ac6567e1e2cf01574a161537445579..0f77a637b4d03253ed803e311ecea9d18810c240 100644 (file)
@@ -672,7 +672,7 @@ macro_rules! define_queries_inner {
             rustc_data_structures::stable_hasher::StableHasher,
             ich::StableHashingContext
         };
-        use crate::util::profiling::ProfileCategory;
+        use rustc_data_structures::profiling::ProfileCategory;
 
         define_queries_struct! {
             tcx: $tcx,
@@ -816,8 +816,18 @@ pub enum QueryName {
             $($name),*
         }
 
+        impl rustc_data_structures::profiling::QueryName for QueryName {
+            fn discriminant(self) -> std::mem::Discriminant<QueryName> {
+                std::mem::discriminant(&self)
+            }
+
+            fn as_str(self) -> &'static str {
+                QueryName::as_str(&self)
+            }
+        }
+
         impl QueryName {
-            pub fn register_with_profiler(profiler: &crate::util::profiling::SelfProfiler) {
+            pub fn register_with_profiler(profiler: &rustc_data_structures::profiling::SelfProfiler) {
                 $(profiler.register_query_name(QueryName::$name);)*
             }
 
diff --git a/src/librustc/util/profiling.rs b/src/librustc/util/profiling.rs
deleted file mode 100644 (file)
index 5a1b7f3..0000000
+++ /dev/null
@@ -1,313 +0,0 @@
-use std::error::Error;
-use std::fs;
-use std::mem::{self, Discriminant};
-use std::path::Path;
-use std::process;
-use std::sync::Arc;
-use std::thread::ThreadId;
-use std::u32;
-
-use crate::ty::query::QueryName;
-
-use measureme::{StringId, TimestampKind};
-
-/// MmapSerializatioSink is faster on macOS and Linux
-/// but FileSerializationSink is faster on Windows
-#[cfg(not(windows))]
-type SerializationSink = measureme::MmapSerializationSink;
-#[cfg(windows)]
-type SerializationSink = measureme::FileSerializationSink;
-
-type Profiler = measureme::Profiler<SerializationSink>;
-
-
-#[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)]
-pub enum ProfileCategory {
-    Parsing,
-    Expansion,
-    TypeChecking,
-    BorrowChecking,
-    Codegen,
-    Linking,
-    Other,
-}
-
-bitflags! {
-    struct EventFilter: u32 {
-        const GENERIC_ACTIVITIES = 1 << 0;
-        const QUERY_PROVIDERS    = 1 << 1;
-        const QUERY_CACHE_HITS   = 1 << 2;
-        const QUERY_BLOCKED      = 1 << 3;
-        const INCR_CACHE_LOADS   = 1 << 4;
-
-        const DEFAULT = Self::GENERIC_ACTIVITIES.bits |
-                        Self::QUERY_PROVIDERS.bits |
-                        Self::QUERY_BLOCKED.bits |
-                        Self::INCR_CACHE_LOADS.bits;
-
-        // empty() and none() aren't const-fns unfortunately
-        const NONE = 0;
-        const ALL  = !Self::NONE.bits;
-    }
-}
-
-const EVENT_FILTERS_BY_NAME: &[(&str, EventFilter)] = &[
-    ("none", EventFilter::NONE),
-    ("all", EventFilter::ALL),
-    ("generic-activity", EventFilter::GENERIC_ACTIVITIES),
-    ("query-provider", EventFilter::QUERY_PROVIDERS),
-    ("query-cache-hit", EventFilter::QUERY_CACHE_HITS),
-    ("query-blocked" , EventFilter::QUERY_BLOCKED),
-    ("incr-cache-load", EventFilter::INCR_CACHE_LOADS),
-];
-
-fn thread_id_to_u64(tid: ThreadId) -> u64 {
-    unsafe { mem::transmute::<ThreadId, u64>(tid) }
-}
-
-
-/// A reference to the SelfProfiler. It can be cloned and sent across thread
-/// boundaries at will.
-#[derive(Clone)]
-pub struct SelfProfilerRef {
-    // This field is `None` if self-profiling is disabled for the current
-    // compilation session.
-    profiler: Option<Arc<SelfProfiler>>,
-
-    // We store the filter mask directly in the reference because that doesn't
-    // cost anything and allows for filtering with checking if the profiler is
-    // actually enabled.
-    event_filter_mask: EventFilter,
-}
-
-impl SelfProfilerRef {
-
-    pub fn new(profiler: Option<Arc<SelfProfiler>>) -> SelfProfilerRef {
-        // If there is no SelfProfiler then the filter mask is set to NONE,
-        // ensuring that nothing ever tries to actually access it.
-        let event_filter_mask = profiler
-            .as_ref()
-            .map(|p| p.event_filter_mask)
-            .unwrap_or(EventFilter::NONE);
-
-        SelfProfilerRef {
-            profiler,
-            event_filter_mask,
-        }
-    }
-
-    // This shim makes sure that calls only get executed if the filter mask
-    // lets them pass. It also contains some trickery to make sure that
-    // code is optimized for non-profiling compilation sessions, i.e. anything
-    // past the filter check is never inlined so it doesn't clutter the fast
-    // path.
-    #[inline(always)]
-    fn exec<F>(&self, event_filter: EventFilter, f: F) -> TimingGuard<'_>
-        where F: for<'a> FnOnce(&'a SelfProfiler) -> TimingGuard<'a>
-    {
-        #[inline(never)]
-        fn cold_call<F>(profiler_ref: &SelfProfilerRef, f: F) -> TimingGuard<'_>
-            where F: for<'a> FnOnce(&'a SelfProfiler) -> TimingGuard<'a>
-        {
-            let profiler = profiler_ref.profiler.as_ref().unwrap();
-            f(&**profiler)
-        }
-
-        if unlikely!(self.event_filter_mask.contains(event_filter)) {
-            cold_call(self, f)
-        } else {
-            TimingGuard::none()
-        }
-    }
-
-    /// Start profiling a generic activity. Profiling continues until the
-    /// TimingGuard returned from this call is dropped.
-    #[inline(always)]
-    pub fn generic_activity(&self, event_id: &str) -> TimingGuard<'_> {
-        self.exec(EventFilter::GENERIC_ACTIVITIES, |profiler| {
-            let event_id = profiler.profiler.alloc_string(event_id);
-            TimingGuard::start(
-                profiler,
-                profiler.generic_activity_event_kind,
-                event_id
-            )
-        })
-    }
-
-    /// Start profiling a query provider. Profiling continues until the
-    /// TimingGuard returned from this call is dropped.
-    #[inline(always)]
-    pub fn query_provider(&self, query_name: QueryName) -> TimingGuard<'_> {
-        self.exec(EventFilter::QUERY_PROVIDERS, |profiler| {
-            let event_id = SelfProfiler::get_query_name_string_id(query_name);
-            TimingGuard::start(profiler, profiler.query_event_kind, event_id)
-        })
-    }
-
-    /// Record a query in-memory cache hit.
-    #[inline(always)]
-    pub fn query_cache_hit(&self, query_name: QueryName) {
-        self.non_guard_query_event(
-            |profiler| profiler.query_cache_hit_event_kind,
-            query_name,
-            EventFilter::QUERY_CACHE_HITS,
-            TimestampKind::Instant,
-        );
-    }
-
-    /// Start profiling a query being blocked on a concurrent execution.
-    /// Profiling continues until the TimingGuard returned from this call is
-    /// dropped.
-    #[inline(always)]
-    pub fn query_blocked(&self, query_name: QueryName) -> TimingGuard<'_> {
-        self.exec(EventFilter::QUERY_BLOCKED, |profiler| {
-            let event_id = SelfProfiler::get_query_name_string_id(query_name);
-            TimingGuard::start(profiler, profiler.query_blocked_event_kind, event_id)
-        })
-    }
-
-    /// Start profiling how long it takes to load a query result from the
-    /// incremental compilation on-disk cache. Profiling continues until the
-    /// TimingGuard returned from this call is dropped.
-    #[inline(always)]
-    pub fn incr_cache_loading(&self, query_name: QueryName) -> TimingGuard<'_> {
-        self.exec(EventFilter::INCR_CACHE_LOADS, |profiler| {
-            let event_id = SelfProfiler::get_query_name_string_id(query_name);
-            TimingGuard::start(
-                profiler,
-                profiler.incremental_load_result_event_kind,
-                event_id
-            )
-        })
-    }
-
-    #[inline(always)]
-    fn non_guard_query_event(
-        &self,
-        event_kind: fn(&SelfProfiler) -> StringId,
-        query_name: QueryName,
-        event_filter: EventFilter,
-        timestamp_kind: TimestampKind
-    ) {
-        drop(self.exec(event_filter, |profiler| {
-            let event_id = SelfProfiler::get_query_name_string_id(query_name);
-            let thread_id = thread_id_to_u64(std::thread::current().id());
-
-            profiler.profiler.record_event(
-                event_kind(profiler),
-                event_id,
-                thread_id,
-                timestamp_kind,
-            );
-
-            TimingGuard::none()
-        }));
-    }
-}
-
-pub struct SelfProfiler {
-    profiler: Profiler,
-    event_filter_mask: EventFilter,
-    query_event_kind: StringId,
-    generic_activity_event_kind: StringId,
-    incremental_load_result_event_kind: StringId,
-    query_blocked_event_kind: StringId,
-    query_cache_hit_event_kind: StringId,
-}
-
-impl SelfProfiler {
-    pub fn new(
-        output_directory: &Path,
-        crate_name: Option<&str>,
-        event_filters: &Option<Vec<String>>
-    ) -> Result<SelfProfiler, Box<dyn Error>> {
-        fs::create_dir_all(output_directory)?;
-
-        let crate_name = crate_name.unwrap_or("unknown-crate");
-        let filename = format!("{}-{}.rustc_profile", crate_name, process::id());
-        let path = output_directory.join(&filename);
-        let profiler = Profiler::new(&path)?;
-
-        let query_event_kind = profiler.alloc_string("Query");
-        let generic_activity_event_kind = profiler.alloc_string("GenericActivity");
-        let incremental_load_result_event_kind = profiler.alloc_string("IncrementalLoadResult");
-        let query_blocked_event_kind = profiler.alloc_string("QueryBlocked");
-        let query_cache_hit_event_kind = profiler.alloc_string("QueryCacheHit");
-
-        let mut event_filter_mask = EventFilter::empty();
-
-        if let Some(ref event_filters) = *event_filters {
-            let mut unknown_events = vec![];
-            for item in event_filters {
-                if let Some(&(_, mask)) = EVENT_FILTERS_BY_NAME.iter()
-                                                               .find(|&(name, _)| name == item) {
-                    event_filter_mask |= mask;
-                } else {
-                    unknown_events.push(item.clone());
-                }
-            }
-
-            // Warn about any unknown event names
-            if unknown_events.len() > 0 {
-                unknown_events.sort();
-                unknown_events.dedup();
-
-                warn!("Unknown self-profiler events specified: {}. Available options are: {}.",
-                    unknown_events.join(", "),
-                    EVENT_FILTERS_BY_NAME.iter()
-                                         .map(|&(name, _)| name.to_string())
-                                         .collect::<Vec<_>>()
-                                         .join(", "));
-            }
-        } else {
-            event_filter_mask = EventFilter::DEFAULT;
-        }
-
-        Ok(SelfProfiler {
-            profiler,
-            event_filter_mask,
-            query_event_kind,
-            generic_activity_event_kind,
-            incremental_load_result_event_kind,
-            query_blocked_event_kind,
-            query_cache_hit_event_kind,
-        })
-    }
-
-    fn get_query_name_string_id(query_name: QueryName) -> StringId {
-        let discriminant = unsafe {
-            mem::transmute::<Discriminant<QueryName>, u64>(mem::discriminant(&query_name))
-        };
-
-        StringId::reserved(discriminant as u32)
-    }
-
-    pub fn register_query_name(&self, query_name: QueryName) {
-        let id = SelfProfiler::get_query_name_string_id(query_name);
-        self.profiler.alloc_string_with_reserved_id(id, query_name.as_str());
-    }
-}
-
-#[must_use]
-pub struct TimingGuard<'a>(Option<measureme::TimingGuard<'a, SerializationSink>>);
-
-impl<'a> TimingGuard<'a> {
-    #[inline]
-    pub fn start(
-        profiler: &'a SelfProfiler,
-        event_kind: StringId,
-        event_id: StringId,
-    ) -> TimingGuard<'a> {
-        let thread_id = thread_id_to_u64(std::thread::current().id());
-        let raw_profiler = &profiler.profiler;
-        let timing_guard = raw_profiler.start_recording_interval_event(event_kind,
-                                                                       event_id,
-                                                                       thread_id);
-        TimingGuard(Some(timing_guard))
-    }
-
-    #[inline]
-    pub fn none() -> TimingGuard<'a> {
-        TimingGuard(None)
-    }
-}
index b302b9ae7f0e4a4b9997cf10dcb6234c25e4e5b2..ed901fa064a4e3e7064fabb5908cff5240afeb83 100644 (file)
@@ -19,7 +19,7 @@
 use rustc::hir::def_id::{CrateNum, LOCAL_CRATE};
 use rustc::ty::TyCtxt;
 use rustc::util::common::{time_depth, set_time_depth, print_time_passes_entry};
-use rustc::util::profiling::SelfProfilerRef;
+use rustc_data_structures::profiling::SelfProfilerRef;
 use rustc_fs_util::link_or_copy;
 use rustc_data_structures::svh::Svh;
 use rustc_data_structures::sync::Lrc;
index e79b3a81b9654331cf589b780c69dc8a8dd9c301..0fd47115022c2ce63d74f0cf6df7c1d076bdfdb4 100644 (file)
@@ -25,6 +25,8 @@ rayon-core = { version = "0.3.0", package = "rustc-rayon-core" }
 rustc-hash = "1.0.1"
 smallvec = { version = "1.0", features = ["union", "may_dangle"] }
 rustc_index = { path = "../librustc_index", package = "rustc_index" }
+bitflags = "1.2.1"
+measureme = "0.4"
 
 [dependencies.parking_lot]
 version = "0.9"
index 474a42644d9150700bf2e47ce673ef3e04b04c7d..fb541637e5f79da6c267ea39fc4b67493ee26e3c 100644 (file)
@@ -94,6 +94,7 @@ macro_rules! unlikely {
 pub mod vec_linked_list;
 pub mod work_queue;
 pub mod fingerprint;
+pub mod profiling;
 
 pub struct OnDrop<F: Fn()>(pub F);
 
diff --git a/src/librustc_data_structures/profiling.rs b/src/librustc_data_structures/profiling.rs
new file mode 100644 (file)
index 0000000..b89170c
--- /dev/null
@@ -0,0 +1,315 @@
+use std::error::Error;
+use std::fs;
+use std::mem::{self, Discriminant};
+use std::path::Path;
+use std::process;
+use std::sync::Arc;
+use std::thread::ThreadId;
+use std::u32;
+
+use measureme::{StringId, TimestampKind};
+
+/// MmapSerializatioSink is faster on macOS and Linux
+/// but FileSerializationSink is faster on Windows
+#[cfg(not(windows))]
+type SerializationSink = measureme::MmapSerializationSink;
+#[cfg(windows)]
+type SerializationSink = measureme::FileSerializationSink;
+
+type Profiler = measureme::Profiler<SerializationSink>;
+
+pub trait QueryName: Sized + Copy {
+    fn discriminant(self) -> Discriminant<Self>;
+    fn as_str(self) -> &'static str;
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Ord, PartialOrd)]
+pub enum ProfileCategory {
+    Parsing,
+    Expansion,
+    TypeChecking,
+    BorrowChecking,
+    Codegen,
+    Linking,
+    Other,
+}
+
+bitflags::bitflags! {
+    struct EventFilter: u32 {
+        const GENERIC_ACTIVITIES = 1 << 0;
+        const QUERY_PROVIDERS    = 1 << 1;
+        const QUERY_CACHE_HITS   = 1 << 2;
+        const QUERY_BLOCKED      = 1 << 3;
+        const INCR_CACHE_LOADS   = 1 << 4;
+
+        const DEFAULT = Self::GENERIC_ACTIVITIES.bits |
+                        Self::QUERY_PROVIDERS.bits |
+                        Self::QUERY_BLOCKED.bits |
+                        Self::INCR_CACHE_LOADS.bits;
+
+        // empty() and none() aren't const-fns unfortunately
+        const NONE = 0;
+        const ALL  = !Self::NONE.bits;
+    }
+}
+
+const EVENT_FILTERS_BY_NAME: &[(&str, EventFilter)] = &[
+    ("none", EventFilter::NONE),
+    ("all", EventFilter::ALL),
+    ("generic-activity", EventFilter::GENERIC_ACTIVITIES),
+    ("query-provider", EventFilter::QUERY_PROVIDERS),
+    ("query-cache-hit", EventFilter::QUERY_CACHE_HITS),
+    ("query-blocked" , EventFilter::QUERY_BLOCKED),
+    ("incr-cache-load", EventFilter::INCR_CACHE_LOADS),
+];
+
+fn thread_id_to_u64(tid: ThreadId) -> u64 {
+    unsafe { mem::transmute::<ThreadId, u64>(tid) }
+}
+
+
+/// A reference to the SelfProfiler. It can be cloned and sent across thread
+/// boundaries at will.
+#[derive(Clone)]
+pub struct SelfProfilerRef {
+    // This field is `None` if self-profiling is disabled for the current
+    // compilation session.
+    profiler: Option<Arc<SelfProfiler>>,
+
+    // We store the filter mask directly in the reference because that doesn't
+    // cost anything and allows for filtering with checking if the profiler is
+    // actually enabled.
+    event_filter_mask: EventFilter,
+}
+
+impl SelfProfilerRef {
+
+    pub fn new(profiler: Option<Arc<SelfProfiler>>) -> SelfProfilerRef {
+        // If there is no SelfProfiler then the filter mask is set to NONE,
+        // ensuring that nothing ever tries to actually access it.
+        let event_filter_mask = profiler
+            .as_ref()
+            .map(|p| p.event_filter_mask)
+            .unwrap_or(EventFilter::NONE);
+
+        SelfProfilerRef {
+            profiler,
+            event_filter_mask,
+        }
+    }
+
+    // This shim makes sure that calls only get executed if the filter mask
+    // lets them pass. It also contains some trickery to make sure that
+    // code is optimized for non-profiling compilation sessions, i.e. anything
+    // past the filter check is never inlined so it doesn't clutter the fast
+    // path.
+    #[inline(always)]
+    fn exec<F>(&self, event_filter: EventFilter, f: F) -> TimingGuard<'_>
+        where F: for<'a> FnOnce(&'a SelfProfiler) -> TimingGuard<'a>
+    {
+        #[inline(never)]
+        fn cold_call<F>(profiler_ref: &SelfProfilerRef, f: F) -> TimingGuard<'_>
+            where F: for<'a> FnOnce(&'a SelfProfiler) -> TimingGuard<'a>
+        {
+            let profiler = profiler_ref.profiler.as_ref().unwrap();
+            f(&**profiler)
+        }
+
+        if unlikely!(self.event_filter_mask.contains(event_filter)) {
+            cold_call(self, f)
+        } else {
+            TimingGuard::none()
+        }
+    }
+
+    /// Start profiling a generic activity. Profiling continues until the
+    /// TimingGuard returned from this call is dropped.
+    #[inline(always)]
+    pub fn generic_activity(&self, event_id: &str) -> TimingGuard<'_> {
+        self.exec(EventFilter::GENERIC_ACTIVITIES, |profiler| {
+            let event_id = profiler.profiler.alloc_string(event_id);
+            TimingGuard::start(
+                profiler,
+                profiler.generic_activity_event_kind,
+                event_id
+            )
+        })
+    }
+
+    /// Start profiling a query provider. Profiling continues until the
+    /// TimingGuard returned from this call is dropped.
+    #[inline(always)]
+    pub fn query_provider(&self, query_name: impl QueryName) -> TimingGuard<'_> {
+        self.exec(EventFilter::QUERY_PROVIDERS, |profiler| {
+            let event_id = SelfProfiler::get_query_name_string_id(query_name);
+            TimingGuard::start(profiler, profiler.query_event_kind, event_id)
+        })
+    }
+
+    /// Record a query in-memory cache hit.
+    #[inline(always)]
+    pub fn query_cache_hit(&self, query_name: impl QueryName) {
+        self.non_guard_query_event(
+            |profiler| profiler.query_cache_hit_event_kind,
+            query_name,
+            EventFilter::QUERY_CACHE_HITS,
+            TimestampKind::Instant,
+        );
+    }
+
+    /// Start profiling a query being blocked on a concurrent execution.
+    /// Profiling continues until the TimingGuard returned from this call is
+    /// dropped.
+    #[inline(always)]
+    pub fn query_blocked(&self, query_name: impl QueryName) -> TimingGuard<'_> {
+        self.exec(EventFilter::QUERY_BLOCKED, |profiler| {
+            let event_id = SelfProfiler::get_query_name_string_id(query_name);
+            TimingGuard::start(profiler, profiler.query_blocked_event_kind, event_id)
+        })
+    }
+
+    /// Start profiling how long it takes to load a query result from the
+    /// incremental compilation on-disk cache. Profiling continues until the
+    /// TimingGuard returned from this call is dropped.
+    #[inline(always)]
+    pub fn incr_cache_loading(&self, query_name: impl QueryName) -> TimingGuard<'_> {
+        self.exec(EventFilter::INCR_CACHE_LOADS, |profiler| {
+            let event_id = SelfProfiler::get_query_name_string_id(query_name);
+            TimingGuard::start(
+                profiler,
+                profiler.incremental_load_result_event_kind,
+                event_id
+            )
+        })
+    }
+
+    #[inline(always)]
+    fn non_guard_query_event(
+        &self,
+        event_kind: fn(&SelfProfiler) -> StringId,
+        query_name: impl QueryName,
+        event_filter: EventFilter,
+        timestamp_kind: TimestampKind
+    ) {
+        drop(self.exec(event_filter, |profiler| {
+            let event_id = SelfProfiler::get_query_name_string_id(query_name);
+            let thread_id = thread_id_to_u64(std::thread::current().id());
+
+            profiler.profiler.record_event(
+                event_kind(profiler),
+                event_id,
+                thread_id,
+                timestamp_kind,
+            );
+
+            TimingGuard::none()
+        }));
+    }
+}
+
+pub struct SelfProfiler {
+    profiler: Profiler,
+    event_filter_mask: EventFilter,
+    query_event_kind: StringId,
+    generic_activity_event_kind: StringId,
+    incremental_load_result_event_kind: StringId,
+    query_blocked_event_kind: StringId,
+    query_cache_hit_event_kind: StringId,
+}
+
+impl SelfProfiler {
+    pub fn new(
+        output_directory: &Path,
+        crate_name: Option<&str>,
+        event_filters: &Option<Vec<String>>
+    ) -> Result<SelfProfiler, Box<dyn Error>> {
+        fs::create_dir_all(output_directory)?;
+
+        let crate_name = crate_name.unwrap_or("unknown-crate");
+        let filename = format!("{}-{}.rustc_profile", crate_name, process::id());
+        let path = output_directory.join(&filename);
+        let profiler = Profiler::new(&path)?;
+
+        let query_event_kind = profiler.alloc_string("Query");
+        let generic_activity_event_kind = profiler.alloc_string("GenericActivity");
+        let incremental_load_result_event_kind = profiler.alloc_string("IncrementalLoadResult");
+        let query_blocked_event_kind = profiler.alloc_string("QueryBlocked");
+        let query_cache_hit_event_kind = profiler.alloc_string("QueryCacheHit");
+
+        let mut event_filter_mask = EventFilter::empty();
+
+        if let Some(ref event_filters) = *event_filters {
+            let mut unknown_events = vec![];
+            for item in event_filters {
+                if let Some(&(_, mask)) = EVENT_FILTERS_BY_NAME.iter()
+                                                               .find(|&(name, _)| name == item) {
+                    event_filter_mask |= mask;
+                } else {
+                    unknown_events.push(item.clone());
+                }
+            }
+
+            // Warn about any unknown event names
+            if unknown_events.len() > 0 {
+                unknown_events.sort();
+                unknown_events.dedup();
+
+                warn!("Unknown self-profiler events specified: {}. Available options are: {}.",
+                    unknown_events.join(", "),
+                    EVENT_FILTERS_BY_NAME.iter()
+                                         .map(|&(name, _)| name.to_string())
+                                         .collect::<Vec<_>>()
+                                         .join(", "));
+            }
+        } else {
+            event_filter_mask = EventFilter::DEFAULT;
+        }
+
+        Ok(SelfProfiler {
+            profiler,
+            event_filter_mask,
+            query_event_kind,
+            generic_activity_event_kind,
+            incremental_load_result_event_kind,
+            query_blocked_event_kind,
+            query_cache_hit_event_kind,
+        })
+    }
+
+    fn get_query_name_string_id(query_name: impl QueryName) -> StringId {
+        let discriminant = unsafe {
+            mem::transmute::<Discriminant<_>, u64>(query_name.discriminant())
+        };
+
+        StringId::reserved(discriminant as u32)
+    }
+
+    pub fn register_query_name(&self, query_name: impl QueryName) {
+        let id = SelfProfiler::get_query_name_string_id(query_name);
+        self.profiler.alloc_string_with_reserved_id(id, query_name.as_str());
+    }
+}
+
+#[must_use]
+pub struct TimingGuard<'a>(Option<measureme::TimingGuard<'a, SerializationSink>>);
+
+impl<'a> TimingGuard<'a> {
+    #[inline]
+    pub fn start(
+        profiler: &'a SelfProfiler,
+        event_kind: StringId,
+        event_id: StringId,
+    ) -> TimingGuard<'a> {
+        let thread_id = thread_id_to_u64(std::thread::current().id());
+        let raw_profiler = &profiler.profiler;
+        let timing_guard = raw_profiler.start_recording_interval_event(event_kind,
+                                                                       event_id,
+                                                                       thread_id);
+        TimingGuard(Some(timing_guard))
+    }
+
+    #[inline]
+    pub fn none() -> TimingGuard<'a> {
+        TimingGuard(None)
+    }
+}