use alloc::borrow::ToOwned;
use alloc::boxed::Box;
use alloc::format;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use alloc::borrow::Borrow;
use core::fmt::Display;
use core::marker::PhantomData;
use core::time::Duration;
use eko::file as fs;
use eko::time::Instant;
bitflags::bitflags! {
#[allow(dead_code)]
#[derive(Clone, Copy)]
struct EventFilter: u16 {
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 QUERY_KEYS = 1 << 5;
const FUNCTION_ARGS = 1 << 6;
const LLVM = 1 << 7;
const INCR_RESULT_HASHING = 1 << 8;
const ARTIFACT_SIZES = 1 << 9;
const QUERY_CACHE_HIT_COUNTS = 1 << 10;
const DEFAULT = Self::GENERIC_ACTIVITIES.bits() |
Self::QUERY_PROVIDERS.bits() |
Self::QUERY_BLOCKED.bits() |
Self::INCR_CACHE_LOADS.bits() |
Self::INCR_RESULT_HASHING.bits() |
Self::ARTIFACT_SIZES.bits() |
Self::QUERY_CACHE_HIT_COUNTS.bits();
const ARGS = Self::QUERY_KEYS.bits() | Self::FUNCTION_ARGS.bits();
const QUERY_CACHE_HIT_COMBINED = Self::QUERY_CACHE_HITS.bits() | Self::QUERY_CACHE_HIT_COUNTS.bits();
}
}
pub struct QueryInvocationId(pub u32);
#[derive(Clone, Copy, PartialEq, Hash, Debug)]
pub enum TimePassesFormat {
Text,
Json,
}
#[derive(Clone)]
pub struct SelfProfilerRef {
print_verbose_generic_activities: Option<TimePassesFormat>,
}
impl SelfProfilerRef {
pub fn new(print_verbose_generic_activities: Option<TimePassesFormat>) -> SelfProfilerRef {
SelfProfilerRef { print_verbose_generic_activities }
}
#[inline(always)]
fn exec(&self, _event_filter: EventFilter) -> TimingGuard<'_> {
TimingGuard::none()
}
pub fn verbose_generic_activity(&self, event_label: &'static str) -> VerboseTimingGuard<'_> {
let message_and_format =
self.print_verbose_generic_activities.map(|format| (event_label.to_owned(), format));
VerboseTimingGuard::start(message_and_format, self.generic_activity(event_label))
}
pub fn verbose_generic_activity_with_arg<A>(
&self,
event_label: &'static str,
event_arg: A,
) -> VerboseTimingGuard<'_>
where
A: Borrow<str> + Into<String>,
{
let message_and_format = self
.print_verbose_generic_activities
.map(|format| (format!("{}({})", event_label, event_arg.borrow()), format));
VerboseTimingGuard::start(
message_and_format,
self.generic_activity_with_arg(event_label, event_arg),
)
}
#[inline(always)]
pub fn generic_activity(&self, _event_label: &'static str) -> TimingGuard<'_> {
self.exec(EventFilter::GENERIC_ACTIVITIES)
}
#[inline(always)]
pub fn generic_activity_with_arg<A>(
&self,
_event_label: &'static str,
_event_arg: A,
) -> TimingGuard<'_>
where
A: Borrow<str> + Into<String>,
{
self.exec(EventFilter::GENERIC_ACTIVITIES)
}
#[inline(always)]
pub fn generic_activity_with_arg_recorder<F>(
&self,
_event_label: &'static str,
_f: F,
) -> TimingGuard<'_>
where
F: FnMut(&mut EventArgRecorder<'_>),
{
self.exec(EventFilter::GENERIC_ACTIVITIES)
}
#[inline(always)]
pub fn artifact_size<A>(&self, _artifact_kind: &str, _artifact_name: A, _size: u64)
where
A: Borrow<str> + Into<String>,
{
drop(self.exec(EventFilter::ARTIFACT_SIZES))
}
#[inline(always)]
pub fn generic_activity_with_args(
&self,
_event_label: &'static str,
_event_args: &[String],
) -> TimingGuard<'_> {
self.exec(EventFilter::GENERIC_ACTIVITIES)
}
#[inline(always)]
pub fn query_provider(&self) -> TimingGuard<'_> {
self.exec(EventFilter::QUERY_PROVIDERS)
}
#[inline(always)]
pub fn query_cache_hit(&self, _query_invocation_id: QueryInvocationId) {}
#[inline(always)]
pub fn query_blocked(&self) -> TimingGuard<'_> {
self.exec(EventFilter::QUERY_BLOCKED)
}
#[inline(always)]
pub fn incr_cache_loading(&self) -> TimingGuard<'_> {
self.exec(EventFilter::INCR_CACHE_LOADS)
}
#[inline(always)]
pub fn incr_result_hashing(&self) -> TimingGuard<'_> {
self.exec(EventFilter::INCR_RESULT_HASHING)
}
#[inline]
pub fn enabled(&self) -> bool {
false
}
#[inline]
pub fn llvm_recording_enabled(&self) -> bool {
false
}
pub fn is_args_recording_enabled(&self) -> bool {
false
}
}
pub struct EventArgRecorder<'p> {
_profiler: PhantomData<&'p ()>,
}
impl EventArgRecorder<'_> {
pub fn record_arg<A>(&mut self, _event_arg: A)
where
A: Borrow<str> + Into<String>,
{
}
}
#[must_use]
pub struct TimingGuard<'a>(PhantomData<&'a ()>);
impl<'a> TimingGuard<'a> {
#[inline]
pub fn finish_with_query_invocation_id(self, _query_invocation_id: QueryInvocationId) {}
#[inline]
pub fn none() -> TimingGuard<'a> {
TimingGuard(PhantomData)
}
#[inline(always)]
pub fn run<R>(self, f: impl FnOnce() -> R) -> R {
let _timer = self;
f()
}
}
struct VerboseInfo {
start_time: Instant,
start_rss: Option<usize>,
message: String,
format: TimePassesFormat,
}
#[must_use]
pub struct VerboseTimingGuard<'a> {
info: Option<VerboseInfo>,
_guard: TimingGuard<'a>,
}
impl<'a> VerboseTimingGuard<'a> {
pub fn start(
message_and_format: Option<(String, TimePassesFormat)>,
_guard: TimingGuard<'a>,
) -> Self {
VerboseTimingGuard {
_guard,
info: message_and_format.map(|(message, format)| VerboseInfo {
start_time: Instant::now(),
start_rss: get_resident_set_size(),
message,
format,
}),
}
}
#[inline(always)]
pub fn run<R>(self, f: impl FnOnce() -> R) -> R {
let _timer = self;
f()
}
}
impl Drop for VerboseTimingGuard<'_> {
fn drop(&mut self) {
if let Some(info) = &self.info {
let end_rss = get_resident_set_size();
let dur = info.start_time.elapsed();
print_time_passes_entry(&info.message, dur, info.start_rss, end_rss, info.format);
}
}
}
struct JsonTimePassesEntry<'a> {
pass: &'a str,
time: f64,
start_rss: Option<usize>,
end_rss: Option<usize>,
}
impl Display for JsonTimePassesEntry<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let Self { pass: what, time, start_rss, end_rss } = self;
write!(f, r#"{{"pass":"{what}","time":{time},"rss_start":"#).unwrap();
match start_rss {
Some(rss) => write!(f, "{rss}")?,
None => write!(f, "null")?,
}
write!(f, r#","rss_end":"#)?;
match end_rss {
Some(rss) => write!(f, "{rss}")?,
None => write!(f, "null")?,
}
write!(f, "}}")?;
Ok(())
}
}
pub fn print_time_passes_entry(
what: &str,
dur: Duration,
start_rss: Option<usize>,
end_rss: Option<usize>,
format: TimePassesFormat,
) {
match format {
TimePassesFormat::Json => {
let entry =
JsonTimePassesEntry { pass: what, time: dur.as_secs_f64(), start_rss, end_rss };
eko::eprintln!(r#"time: {entry}"#);
return;
}
TimePassesFormat::Text => (),
}
let is_notable = || {
if dur.as_millis() > 5 {
return true;
}
if let (Some(start_rss), Some(end_rss)) = (start_rss, end_rss) {
let change_rss = end_rss.abs_diff(start_rss);
if change_rss > 0 {
return true;
}
}
false
};
if !is_notable() {
return;
}
let rss_to_mb = |rss| libm::round(rss as f64 / 1_000_000.0) as usize;
let rss_change_to_mb = |rss| libm::round(rss as f64 / 1_000_000.0) as i128;
let mem_string = match (start_rss, end_rss) {
(Some(start_rss), Some(end_rss)) => {
let change_rss = end_rss as i128 - start_rss as i128;
format!(
"; rss: {:>4}MB -> {:>4}MB ({:>+5}MB)",
rss_to_mb(start_rss),
rss_to_mb(end_rss),
rss_change_to_mb(change_rss),
)
}
(Some(start_rss), None) => format!("; rss start: {:>4}MB", rss_to_mb(start_rss)),
(None, Some(end_rss)) => format!("; rss end: {:>4}MB", rss_to_mb(end_rss)),
(None, None) => String::new(),
};
eko::eprintln!("time: {:>7}{}\t{}", duration_to_secs_str(dur), mem_string, what);
}
pub fn duration_to_secs_str(dur: core::time::Duration) -> String {
format!("{:.3}", dur.as_secs_f64())
}
cfg_select! {
windows => {
pub fn get_resident_set_size() -> Option<usize> {
use windows::Win32::System::ProcessStatus::{
K32GetProcessMemoryInfo, PROCESS_MEMORY_COUNTERS,
};
use windows::Win32::System::Threading::GetCurrentProcess;
let mut pmc = PROCESS_MEMORY_COUNTERS::default();
let pmc_size = size_of_val(&pmc);
unsafe { K32GetProcessMemoryInfo(GetCurrentProcess(), &mut pmc, pmc_size as u32) }
.ok()
.ok()?;
Some(pmc.WorkingSetSize)
}
}
target_os = "macos" => {
pub fn get_resident_set_size() -> Option<usize> {
use core::mem;
use libc::{PROC_PIDTASKINFO, c_int, c_void, getpid, proc_pidinfo, proc_taskinfo};
const PROC_TASKINFO_SIZE: c_int = size_of::<proc_taskinfo>() as c_int;
unsafe {
let mut info: proc_taskinfo = mem::zeroed();
let info_ptr = &mut info as *mut proc_taskinfo as *mut c_void;
let pid = getpid() as c_int;
let ret = proc_pidinfo(pid, PROC_PIDTASKINFO, 0, info_ptr, PROC_TASKINFO_SIZE);
if ret == PROC_TASKINFO_SIZE { Some(info.pti_resident_size as usize) } else { None }
}
}
}
unix => {
pub fn get_resident_set_size() -> Option<usize> {
use libc::{_SC_PAGESIZE, sysconf};
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 * unsafe { sysconf(_SC_PAGESIZE) } as usize)
}
}
_ => {
pub fn get_resident_set_size() -> Option<usize> {
None
}
}
}
#[cfg(test)]
mod tests;