use crate::command::{Command, Response};
use std::sync::LazyLock;
use wasm_lite_std::time::{Instant, SystemTime, UNIX_EPOCH};
static STARTED: LazyLock<(Instant, SystemTime)> =
LazyLock::new(|| (Instant::now(), SystemTime::now()));
pub(crate) fn mark_start() {
LazyLock::force(&STARTED);
}
pub(crate) struct Uptime;
impl Command for Uptime {
fn name(&self) -> &'static str {
"uptime"
}
fn short_description(&self) -> &'static str {
"Reports how long the program has been running and what time it thinks it is. Use this to tell a hung program from a slow one."
}
fn full_description(&self) -> &'static str {
"Reports elapsed time since `exfiltrate::begin` and the current wall clock.
Elapsed time comes from a monotonic clock, so it is unaffected by clock steps or by
the machine sleeping. Comparing the wall clock reported here against your own is how
you notice that a remote or browser target disagrees with you about what time it is.
The most common use is telling a hung program from a slow one: run it twice."
}
fn execute(&self, _args: Vec<String>) -> Result<Response, Response> {
let (started_monotonic, started_wall) = *STARTED;
let elapsed = started_monotonic.elapsed();
let now = SystemTime::now();
let mut out = String::new();
out.push_str(&format!("uptime: {}\n", format_duration(elapsed)));
out.push_str(&format!(
"started: {}\n",
format_unix(started_wall.duration_since(UNIX_EPOCH).ok())
));
out.push_str(&format!(
"wall clock: {}\n",
format_unix(now.duration_since(UNIX_EPOCH).ok())
));
out.push_str(&format!("monotonic: {:?}\n", elapsed));
Ok(out.into())
}
}
fn format_duration(duration: std::time::Duration) -> String {
let total = duration.as_secs();
let (days, hours, minutes, seconds) = (
total / 86_400,
(total % 86_400) / 3600,
(total % 3600) / 60,
total % 60,
);
if days > 0 {
format!("{days}d {hours:02}h {minutes:02}m {seconds:02}s")
} else if hours > 0 {
format!("{hours}h {minutes:02}m {seconds:02}s")
} else if minutes > 0 {
format!("{minutes}m {seconds:02}s")
} else {
format!("{}.{:03}s", seconds, duration.subsec_millis())
}
}
fn format_unix(since_epoch: Option<std::time::Duration>) -> String {
let Some(since_epoch) = since_epoch else {
return "unknown".to_string();
};
let seconds = since_epoch.as_secs() as i64;
let days = seconds.div_euclid(86_400);
let time_of_day = seconds.rem_euclid(86_400);
let (year, month, day) = civil_from_days(days);
format!(
"{year:04}-{month:02}-{day:02}T{:02}:{:02}:{:02}Z",
time_of_day / 3600,
(time_of_day % 3600) / 60,
time_of_day % 60
)
}
fn civil_from_days(days: i64) -> (i64, u32, u32) {
let z = days + 719_468;
let era = z.div_euclid(146_097);
let day_of_era = z.rem_euclid(146_097);
let year_of_era =
(day_of_era - day_of_era / 1460 + day_of_era / 36_524 - day_of_era / 146_096) / 365;
let year = year_of_era + era * 400;
let day_of_year = day_of_era - (365 * year_of_era + year_of_era / 4 - year_of_era / 100);
let shifted_month = (5 * day_of_year + 2) / 153;
let day = (day_of_year - (153 * shifted_month + 2) / 5 + 1) as u32;
let month = if shifted_month < 10 {
shifted_month + 3
} else {
shifted_month - 9
} as u32;
(if month <= 2 { year + 1 } else { year }, month, day)
}
#[cfg(test)]
mod tests {
use super::*;
use std::time::Duration;
#[test]
fn durations_read_the_way_an_uptime_reads() {
assert_eq!(format_duration(Duration::from_millis(1500)), "1.500s");
assert_eq!(format_duration(Duration::from_secs(65)), "1m 05s");
assert_eq!(format_duration(Duration::from_secs(3725)), "1h 02m 05s");
assert_eq!(
format_duration(Duration::from_secs(90_061)),
"1d 01h 01m 01s"
);
}
#[test]
fn the_epoch_and_a_known_date_both_format_correctly() {
assert_eq!(
format_unix(Some(Duration::from_secs(0))),
"1970-01-01T00:00:00Z"
);
assert_eq!(
format_unix(Some(Duration::from_secs(1_787_056_549))),
"2026-08-18T12:35:49Z"
);
assert_eq!(
format_unix(Some(Duration::from_secs(1_709_164_800))),
"2024-02-29T00:00:00Z"
);
}
#[test]
fn a_clock_before_the_epoch_says_unknown_rather_than_guessing() {
assert_eq!(format_unix(None), "unknown");
}
#[test]
fn the_command_reports_all_three_clocks() {
let text = Uptime.execute(Vec::new()).unwrap().into_string();
assert!(text.contains("uptime:"), "{text}");
assert!(text.contains("started:"), "{text}");
assert!(text.contains("wall clock:"), "{text}");
}
}