tracexec-core 1.0.0-rc.1

Core crate of tracexec [Internal implementation! DO NOT DEPEND ON!]
Documentation
use std::{
  borrow::Cow,
  sync::LazyLock,
};

use chrono::{
  DateTime,
  Local,
};
use nutype::nutype;

#[nutype(
  validate(with = validate_strftime, error = Cow<'static,str>),
  derive(Debug, Clone, Serialize, Deserialize, Deref, FromStr)
)]
pub struct TimestampFormat(String);

impl Default for TimestampFormat {
  #[allow(clippy::unwrap_used)]
  fn default() -> Self {
    Self::try_new("%H:%M:%S").unwrap()
  }
}

fn validate_strftime(fmt: &str) -> Result<(), Cow<'static, str>> {
  if fmt.contains("\n") {
    return Err("inline timestamp format string should not contain newline(s)".into());
  }
  Ok(())
}

pub type Timestamp = DateTime<Local>;

pub fn ts_from_boot_ns(boot_ns: u64) -> Timestamp {
  DateTime::from_timestamp_nanos((*BOOT_TIME + boot_ns) as i64).into()
}

static BOOT_TIME: LazyLock<u64> = LazyLock::new(|| {
  let content = std::fs::read_to_string("/proc/stat").expect("Failed to read /proc/stat");
  for line in content.lines() {
    if let Some(boot_time) = line
      .strip_prefix("btime ")
      .and_then(|value| value.parse::<u64>().ok())
    {
      return boot_time * 1_000_000_000;
    }
  }
  panic!("btime is not available in /proc/stat. Am I running on Linux?")
});

#[cfg(test)]
mod tests {
  use std::str::FromStr;

  use chrono::{
    DateTime,
    Local,
  };
  use test_that::prelude::*;

  use super::*;

  /* ---------------- TimestampFormat ---------------- */

  #[test]
  fn timestamp_format_accepts_valid_strftime() {
    let fmt = TimestampFormat::from_str("%Y-%m-%d %H:%M:%S");
    assert_that!(fmt, ok(anything()));
  }

  #[test]
  fn timestamp_format_rejects_newline() {
    let fmt = TimestampFormat::from_str("%Y-%m-%d\n%H:%M:%S");
    assert_that!(fmt, err(anything()));

    let err = fmt.unwrap_err();
    assert_that!(err, contains_substring("should not contain newline"));
  }

  #[test]
  fn timestamp_format_deref_works() {
    let fmt = TimestampFormat::from_str("%s").unwrap();
    assert_eq!(&*fmt, "%s");
  }

  /* ---------------- BOOT_TIME ---------------- */

  #[test]
  fn boot_time_is_non_zero() {
    assert_that!(*BOOT_TIME, gt(0));
  }

  #[test]
  fn boot_time_is_reasonable_unix_time() {
    // boot time should be after year 2000
    const YEAR_2000_NS: u64 = 946684800_u64 * 1_000_000_000;
    assert_that!(*BOOT_TIME, gt(YEAR_2000_NS));
  }

  /* ---------------- ts_from_boot_ns ---------------- */

  #[test]
  fn ts_from_boot_ns_zero_matches_boot_time() {
    let ts = ts_from_boot_ns(0);
    let expected: DateTime<Local> = DateTime::from_timestamp_nanos(*BOOT_TIME as i64).into();

    assert_eq!(ts, expected);
  }

  #[test]
  fn ts_from_boot_ns_is_monotonic() {
    let t1 = ts_from_boot_ns(1_000);
    let t2 = ts_from_boot_ns(2_000);

    assert_that!(t2, gt(t1));
  }

  #[test]
  fn ts_from_boot_ns_large_offset() {
    let one_sec = 1_000_000_000;
    let ts = ts_from_boot_ns(one_sec);

    let base: DateTime<Local> = DateTime::from_timestamp_nanos(*BOOT_TIME as i64).into();

    assert_eq!(ts.timestamp(), base.timestamp() + 1);
  }

  /* ---------------- serde (nutype derive) ---------------- */

  #[test]
  fn timestamp_format_serde_roundtrip() {
    let fmt = TimestampFormat::from_str("%H:%M:%S").unwrap();

    let json = serde_json::to_string(&fmt).unwrap();
    let de: TimestampFormat = serde_json::from_str(&json).unwrap();

    assert_eq!(&*fmt, &*de);
  }
}