use std::{
collections::{BTreeMap, HashMap},
ops::{AddAssign, Sub},
time::{Duration, Instant},
};
#[inline(always)]
fn tv_to_duration(tv: libc::timeval) -> Duration {
Duration::new(tv.tv_sec as u64, (tv.tv_usec * 1000) as u32)
}
#[derive(Clone, Copy)]
struct ResourceUsage(libc::rusage);
impl ResourceUsage {
pub fn now() -> Self {
unsafe {
let mut rusage = std::mem::MaybeUninit::<libc::rusage>::uninit();
assert!(0 == libc::getrusage(libc::RUSAGE_SELF, rusage.as_mut_ptr()));
Self(rusage.assume_init())
}
}
#[inline(always)]
pub fn utime(&self) -> Duration {
tv_to_duration(self.0.ru_utime)
}
#[inline(always)]
pub fn stime(&self) -> Duration {
tv_to_duration(self.0.ru_stime)
}
#[inline(always)]
pub fn cpu_time(&self) -> Duration {
self.utime() + self.stime()
}
}
impl std::fmt::Debug for ResourceUsage {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ResourceUsage")
.field("ru_utime.tv_sec", &self.0.ru_utime.tv_sec)
.field("ru_utime.tv_usec", &self.0.ru_utime.tv_usec)
.field("ru_stime.tv_sec", &self.0.ru_stime.tv_sec)
.field("ru_stime.tv_usec", &self.0.ru_stime.tv_usec)
.finish()
}
}
#[derive(Debug, Clone, Copy)]
struct UsageSnapshot {
pub time: Instant,
pub usage: ResourceUsage,
}
impl UsageSnapshot {
pub fn now() -> Self {
Self {
time: Instant::now(),
usage: ResourceUsage::now(),
}
}
}
impl Sub for UsageSnapshot {
type Output = UsageMeasurement;
fn sub(self, rhs: Self) -> Self::Output {
UsageMeasurement {
utime: self.usage.utime() - rhs.usage.utime(),
stime: self.usage.stime() - rhs.usage.stime(),
wtime: self.time.duration_since(rhs.time),
}
}
}
#[derive(Debug)]
struct ScopeFrame {
path: String,
start: UsageSnapshot,
}
#[derive(Debug, Default, Clone, Copy)]
pub struct UsageMeasurement {
utime: Duration,
stime: Duration,
wtime: Duration,
}
impl UsageMeasurement {}
impl AddAssign for UsageMeasurement {
fn add_assign(&mut self, rhs: Self) {
self.utime += rhs.utime;
self.stime += rhs.stime;
self.wtime += rhs.wtime;
}
}
#[derive(Debug)]
pub struct ResourceMeterStack {
store: HashMap<String, UsageMeasurement>,
stack: Vec<ScopeFrame>,
last_snapshot: UsageSnapshot,
}
impl ResourceMeterStack {
pub fn new() -> Self {
Self {
store: HashMap::new(),
stack: vec![ScopeFrame {
path: String::new(),
start: UsageSnapshot::now(),
}],
last_snapshot: UsageSnapshot::now(),
}
}
pub fn push<T: Into<String>>(&mut self, key: T) {
self.stack.push(ScopeFrame {
path: key.into(),
start: UsageSnapshot::now(),
});
}
pub fn pop(&mut self) {
let now = UsageSnapshot::now();
let curr = self.stack.pop().unwrap();
let slot = self.store.entry(curr.path).or_default();
*slot += now - curr.start;
self.last_snapshot = now;
}
pub fn into_report(mut self, format: ReportFormat) -> Report {
while !self.stack.is_empty() {
self.pop();
}
match format {
ReportFormat::Flat => Report::Flat(self.store),
ReportFormat::Tree => {
let mut root = TreeNode::default();
for (path, usage) in self.store {
root.insert(path, usage);
}
Report::Tree(root)
}
}
}
pub fn finish(mut self) -> TreeReport {
while !self.stack.is_empty() {
self.pop();
}
TreeReport::new(self.store)
}
}
pub enum ReportFormat {
Flat,
Tree,
}
pub enum Report {
Flat(HashMap<String, UsageMeasurement>),
Tree(TreeNode),
}
#[derive(Debug)]
pub struct TreeReport(TreeNode);
impl TreeReport {
pub fn new(measurements: HashMap<String, UsageMeasurement>) -> Self {
let mut root = TreeNode::default();
for (path, usage) in measurements {
root.insert(path, usage);
}
Self(root)
}
}
#[derive(Debug, Default)]
struct TreeNode {
usage: Option<UsageMeasurement>,
children: BTreeMap<String, TreeNode>,
}
impl TreeNode {
fn insert(&mut self, path: String, usage: UsageMeasurement) {
let mut current = self;
for part in path.split('/') {
current = current.children.entry(part.to_string()).or_default();
}
current.usage = Some(usage);
}
fn fmt_with_indent(&self, f: &mut std::fmt::Formatter<'_>, depth: usize) -> std::fmt::Result {
let width = 20;
for (name, child) in &self.children {
let indent = " ".repeat(depth);
if let Some(usage) = child.usage {
writeln!(
f,
"{indent}{:<} real: {:>6?}, user: {:>6?}, sys: {:?}",
name,
usage.wtime.as_millis(),
usage.utime.as_millis(),
usage.stime.as_millis(),
)?;
} else {
writeln!(f, "{indent}{name}")?;
}
child.fmt_with_indent(f, depth + 1)?;
}
Ok(())
}
}
impl std::fmt::Display for TreeReport {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "--- Resource Usage Report ---")?;
self.0.fmt_with_indent(f, 0)?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple() {
let mut stk = ResourceMeterStack::new();
let now = Instant::now();
std::thread::sleep(Duration::from_millis(200));
stk.push("sleep600");
stk.push("sleep600/300");
std::thread::sleep(Duration::from_millis(300));
stk.pop();
std::thread::sleep(Duration::from_millis(100));
stk.push("sleep600/200");
std::thread::sleep(Duration::from_millis(200));
stk.pop();
stk.pop();
std::thread::sleep(Duration::from_millis(200));
let report = stk.finish();
eprintln!("{}, took: {:?}", report, now.elapsed());
}
#[test]
fn test_resource_usage() {
let rusage = ResourceUsage::now();
eprintln!("{:#?}", rusage);
}
}