blob: 0fdf41727462bf145203df52e3327df3e29c4cf0 [file] [log] [blame] [edit]
use crate::counters::Counter;
use crate::file_header::{write_file_header, FILE_MAGIC_EVENT_STREAM, FILE_MAGIC_TOP_LEVEL};
use crate::raw_event::RawEvent;
use crate::serialization::{PageTag, SerializationSink, SerializationSinkBuilder};
use crate::stringtable::{SerializableString, StringId, StringTableBuilder};
use crate::{event_id::EventId, file_header::FILE_EXTENSION};
use std::error::Error;
use std::fs;
use std::path::Path;
use std::sync::Arc;
pub struct Profiler {
event_sink: Arc<SerializationSink>,
string_table: StringTableBuilder,
counter: Counter,
}
impl Profiler {
pub fn new<P: AsRef<Path>>(path_stem: P) -> Result<Profiler, Box<dyn Error + Send + Sync>> {
Self::with_counter(
path_stem,
Counter::WallTime(crate::counters::WallTime::new()),
)
}
pub fn with_counter<P: AsRef<Path>>(
path_stem: P,
counter: Counter,
) -> Result<Profiler, Box<dyn Error + Send + Sync>> {
let path = path_stem.as_ref().with_extension(FILE_EXTENSION);
fs::create_dir_all(path.parent().unwrap())?;
let mut file = fs::File::create(path)?;
// The first thing in the file must be the top-level file header.
write_file_header(&mut file, FILE_MAGIC_TOP_LEVEL)?;
let sink_builder = SerializationSinkBuilder::new_from_file(file)?;
let event_sink = Arc::new(sink_builder.new_sink(PageTag::Events));
// The first thing in every stream we generate must be the stream header.
write_file_header(&mut event_sink.as_std_write(), FILE_MAGIC_EVENT_STREAM)?;
let string_table = StringTableBuilder::new(
Arc::new(sink_builder.new_sink(PageTag::StringData)),
Arc::new(sink_builder.new_sink(PageTag::StringIndex)),
)?;
let profiler = Profiler {
event_sink,
string_table,
counter,
};
let mut args = String::new();
for arg in std::env::args() {
args.push_str(&arg.escape_default().to_string());
args.push(' ');
}
profiler.string_table.alloc_metadata(&*format!(
r#"{{ "start_time": {}, "process_id": {}, "cmd": "{}", "counter": {} }}"#,
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos(),
std::process::id(),
args,
profiler.counter.describe_as_json(),
));
Ok(profiler)
}
#[inline(always)]
pub fn map_virtual_to_concrete_string(&self, virtual_id: StringId, concrete_id: StringId) {
self.string_table
.map_virtual_to_concrete_string(virtual_id, concrete_id);
}
#[inline(always)]
pub fn bulk_map_virtual_to_single_concrete_string<I>(
&self,
virtual_ids: I,
concrete_id: StringId,
) where
I: Iterator<Item = StringId> + ExactSizeIterator,
{
self.string_table
.bulk_map_virtual_to_single_concrete_string(virtual_ids, concrete_id);
}
#[inline(always)]
pub fn alloc_string<STR: SerializableString + ?Sized>(&self, s: &STR) -> StringId {
self.string_table.alloc(s)
}
/// Records an event with the given parameters. The event time is computed
/// automatically.
pub fn record_instant_event(&self, event_kind: StringId, event_id: EventId, thread_id: u32) {
let raw_event =
RawEvent::new_instant(event_kind, event_id, thread_id, self.counter.since_start());
self.record_raw_event(&raw_event);
}
/// Records an event with the given parameters. The event time is computed
/// automatically.
pub fn record_integer_event(
&self,
event_kind: StringId,
event_id: EventId,
thread_id: u32,
value: u64,
) {
let raw_event = RawEvent::new_integer(event_kind, event_id, thread_id, value);
self.record_raw_event(&raw_event);
}
/// Creates a "start" event and returns a `TimingGuard` that will create
/// the corresponding "end" event when it is dropped.
#[inline]
pub fn start_recording_interval_event<'a>(
&'a self,
event_kind: StringId,
event_id: EventId,
thread_id: u32,
) -> TimingGuard<'a> {
TimingGuard {
profiler: self,
event_id,
event_kind,
thread_id,
start_count: self.counter.since_start(),
}
}
/// Creates a "start" event and returns a `DetachedTiming`.
/// To create the corresponding "event" event, you must call
/// `finish_recording_internal_event` with the returned
/// `DetachedTiming`.
/// Since `DetachedTiming` does not capture the lifetime of `&self`,
/// this method can sometimes be more convenient than
/// `start_recording_interval_event` - e.g. it can be stored
/// in a struct without the need to add a lifetime parameter.
#[inline]
pub fn start_recording_interval_event_detached(
&self,
event_kind: StringId,
event_id: EventId,
thread_id: u32,
) -> DetachedTiming {
DetachedTiming {
event_id,
event_kind,
thread_id,
start_count: self.counter.since_start(),
}
}
/// Creates the corresponding "end" event for
/// the "start" event represented by `timing`. You
/// must have obtained `timing` from the same `Profiler`
pub fn finish_recording_interval_event(&self, timing: DetachedTiming) {
drop(TimingGuard {
profiler: self,
event_id: timing.event_id,
event_kind: timing.event_kind,
thread_id: timing.thread_id,
start_count: timing.start_count,
});
}
fn record_raw_event(&self, raw_event: &RawEvent) {
self.event_sink
.write_atomic(std::mem::size_of::<RawEvent>(), |bytes| {
raw_event.serialize(bytes);
});
}
}
/// Created by `Profiler::start_recording_interval_event_detached`.
/// Must be passed to `finish_recording_interval_event` to record an
/// "end" event.
#[must_use]
pub struct DetachedTiming {
event_id: EventId,
event_kind: StringId,
thread_id: u32,
start_count: u64,
}
/// When dropped, this `TimingGuard` will record an "end" event in the
/// `Profiler` it was created by.
#[must_use]
pub struct TimingGuard<'a> {
profiler: &'a Profiler,
event_id: EventId,
event_kind: StringId,
thread_id: u32,
start_count: u64,
}
impl<'a> Drop for TimingGuard<'a> {
#[inline]
fn drop(&mut self) {
let raw_event = RawEvent::new_interval(
self.event_kind,
self.event_id,
self.thread_id,
self.start_count,
self.profiler.counter.since_start(),
);
self.profiler.record_raw_event(&raw_event);
}
}
impl<'a> TimingGuard<'a> {
/// This method set a new `event_id` right before actually recording the
/// event.
#[inline]
pub fn finish_with_override_event_id(mut self, event_id: EventId) {
self.event_id = event_id;
// Let's be explicit about it: Dropping the guard will record the event.
drop(self)
}
}
// Make sure that `Profiler` can be used in a multithreaded context
fn _assert_bounds() {
assert_bounds_inner(&Profiler::new(""));
fn assert_bounds_inner<S: Sized + Send + Sync + 'static>(_: &S) {}
}