8000 Use `Duration` based uptime across uucore by irbeam256 · Pull Request #7926 · uutils/coreutils · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

Use Duration based uptime across uucore #7926

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10000
3 changes: 2 additions & 1 deletion docs/src/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ also provides a `-v`/`--verbose` flag.

## `uptime`

Similar to the proc-ps implementation and unlike GNU/Coreutils, `uptime` provides `-s`/`--since` to show since when the system is up.
Similar to the proc-ps implementation and unlike GNU/Coreutils, `uptime` provides `-s`/`--since` to show since when the system is up
and `-p`/`--pretty` to display uptime in a pretty-printed format.

## `base32/base64/basenc`

Expand Down
32 changes: 27 additions & 5 deletions src/uu/uptime/src/uptime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const USAGE: &str = help_usage!("uptime.md");
pub mod options {
pub static SINCE: &str = "since";
pub static PATH: &str = "path";
pub static PRETTY: &str = "pretty";
}

#[derive(Debug, Error)]
Expand Down Expand Up @@ -68,6 +69,8 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {

if matches.get_flag(options::SINCE) {
uptime_since()
} else if matches.get_flag(options::PRETTY) {
pretty_print_uptime(None)
} else if let Some(path) = file_path {
uptime_with_file(path)
} else {
Expand All @@ -87,6 +90,13 @@ pub fn uu_app() -> Command {
.long(options::SINCE)
.help("system up since")
.action(ArgAction::SetTrue),
)
.arg(
Arg::new(options::PRETTY)
.short('p')
.long(options::PRETTY)
.help("show uptime in pretty format")
.action(ArgAction::SetTrue),
);
#[cfg(unix)]
cmd.arg(
Expand Down Expand Up @@ -174,9 +184,9 @@ fn uptime_with_file(file_path: &OsString) -> UResult<()> {

#[cfg(target_os = "openbsd")]
{
let upsecs = get_uptime(None);
if upsecs >= 0 {
print_uptime(Some(upsecs))?;
let uptime = get_uptime(None);
if uptime.is_ok() >= 0 {
print_uptime(uptime.unwrap())?;
} else {
show_error!("couldn't get boot time");
set_exit_code(1);
Expand Down Expand Up @@ -205,8 +215,9 @@ fn uptime_since() -> UResult<()> {
#[cfg(target_os = "windows")]
let uptime = get_uptime(None)?;

// It is safe to cast Unix timestamps from u64->i64 at least for several trillion centuries.
let initial_date = Local
.timestamp_opt(Utc::now().timestamp() - uptime, 0)
.timestamp_opt(Utc::now().timestamp() - uptime.as_secs() as i64, 0)
.unwrap();
println!("{}", initial_date.format("%Y-%m-%d %H:%M:%S"));

Expand Down Expand Up @@ -276,6 +287,17 @@ fn print_time() {
}

fn print_uptime(boot_time: Option<time_t>) -> UResult<()> {
print!("up {}, ", get_formatted_uptime(boot_time)?);
print!(
"up {}, ",
get_formatted_uptime(boot_time, OutputFormat::HumanReadable)?
);
Ok(())
}

fn pretty_print_uptime(boot_time: Option<time_t>) -> UResult<()> {
print!(
"up {}",
get_formatted_uptime(boot_time, OutputFormat::PrettyPrint)?
);
Ok(())
}
113 changes: 85 additions & 28 deletions src/uucore/src/lib/features/uptime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
use crate::error::{UError, UResult};
use chrono::Local;
use libc::time_t;
use std::time::Duration;
use thiserror::Error;

#[derive(Debug, Error)]
Expand Down Expand Up @@ -48,9 +49,9 @@ pub fn get_formatted_time() -> String {
///
/// # Returns
///
/// Returns a UResult with the uptime in seconds if successful, otherwise an UptimeError.
/// Returns a UResult with the uptime as a Duration if successful, otherwise an UptimeError.
#[cfg(target_os = "openbsd")]
pub fn get_uptime(_boot_time: Option<time_t>) -> UResult<i64> {
pub fn get_uptime(_boot_time: Option<time_t>) -> UResult<Duration> {
use libc::CLOCK_BOOTTIME;
use libc::clock_gettime;

Expand All @@ -67,12 +68,7 @@ pub fn get_uptime(_boot_time: Option<time_t>) -> UResult<i64> {
let ret: c_int = unsafe { clock_gettime(CLOCK_BOOTTIME, raw_tp) };

if ret == 0 {
#[cfg(target_pointer_width = "64")]
let uptime: i64 = tp.tv_sec;
#[cfg(not(target_pointer_width = "64"))]
let uptime: i64 = tp.tv_sec.into();

Ok(uptime)
Duration::new(tp.tv_sec, tp.tv_nsec)
} else {
Err(UptimeError::SystemUptime)
}
Expand All @@ -86,10 +82,10 @@ pub fn get_uptime(_boot_time: Option<time_t>) -> UResult<i64> {
///
/// # Returns
///
/// Returns a UResult with the uptime in seconds if successful, otherwise an UptimeError.
/// Returns a UResult with the uptime as a Duration if successful, otherwise an UptimeError.
#[cfg(unix)]
#[cfg(not(target_os = "openbsd"))]
pub fn get_uptime(boot_time: Option<time_t>) -> UResult<i64> {
pub fn get_uptime(boot_time: Option<time_t>) -> UResult<Duration> {
use crate::utmpx::Utmpx;
use libc::BOOT_TIME;
use std::fs::File;
Expand All @@ -101,7 +97,8 @@ pub fn get_uptime(boot_time: Option<time_t>) -> UResult<i64> {
.ok()
.and_then(|mut f| f.read_to_string(&mut proc_uptime_s).ok())
.and_then(|_| proc_uptime_s.split_whitespace().next())
.and_then(|s| s.split('.').next().unwrap_or("0").parse::<i64>().ok());
.and_then(|s| s.parse::<f64>().ok())
.and_then(|f| Duration::try_from_secs_f64(f).ok());

if let Some(uptime) = proc_uptime {
return Ok(uptime);
Expand Down Expand Up @@ -132,7 +129,7 @@ pub fn get_uptime(boot_time: Option<time_t>) -> UResult<i64> {
if now < boottime {
Err(UptimeError::BootTime)?;
}
return Ok(now - boottime);
return Ok(Duration::from_secs(u64::try_from(now - boottime).unwrap()));
}

Err(UptimeError::SystemUptime)?
Expand All @@ -146,38 +143,98 @@ pub fn get_uptime(boot_time: Option<time_t>) -> UResult<i64> {
///
/// # Returns
///
/// Returns a UResult with the uptime in seconds if successful, otherwise an UptimeError.
/// Returns a UResult with the uptime as a Duration if successful, otherwise an UptimeError.
#[cfg(windows)]
pub fn get_uptime(_boot_time: Option<time_t>) -> UResult<i64> {
pub fn get_uptime(_boot_time: Option<time_t>) -> UResult<Duration> {
use windows_sys::Win32::System::SystemInformation::GetTickCount;
// SAFETY: always return u32
let uptime = unsafe { GetTickCount() };
Ok(uptime as i64 / 1000)
Duration::from_secs(uptime)
}

/// The format used to display a FormattedUptime.
pub enum OutputFormat {
/// Typical `uptime` output (e.g. 2 days, 3:04).
HumanReadable,

/// Pretty printed output (e.g. 2 days, 3 hours, 04 minutes).
PrettyPrint,
}

struct FormattedUptime {
up_days: u64,
up_hours: u64,
up_mins: u64,
}

impl FormattedUptime {
fn new(uptime: Duration) -> Self {
let up_secs = uptime.as_secs();
let up_days = up_secs / 86400;
let up_hours = (up_secs - (up_days * 86400)) / 3600;
let up_mins = (up_secs - (up_days * 86400) - (up_hours * 3600)) / 60;

FormattedUptime {
up_days,
up_hours,
up_mins,
}
}

fn get_human_readable_uptime(&self) -> String {
match self.up_days.cmp(&1) {
std::cmp::Ordering::Equal => format!(
"{} day, {:2}:{:02}",
self.up_days, self.up_hours, self.up_mins
),
std::cmp::Ordering::Greater => format!(
"{} days, {:2}:{:02}",
self.up_days, self.up_hours, self.up_mins
),
_ => format!("{:2}:{:02}", self.up_hours, self.up_mins),
}
}

fn get_pretty_print_uptime(&self) -> String {
let day_string = match self.up_days.cmp(&1) {
std::cmp::Ordering::Equal => format!("{} day, ", self.up_days),
std::cmp::Ordering::Greater => format!("{} days, ", self.up_days),
_ => String::new(),
};
let hour_string = match self.up_hours.cmp(&1) {
std::cmp::Ordering::Equal => format!("{} hour, ", self.up_hours),
std::cmp::Ordering::Greater => format!("{} hours, ", self.up_hours),
_ => String::new(),
};
let min_string = match self.up_mins.cmp(&1) {
std::cmp::Ordering::Equal => format!("{} min", self.up_mins),
_ => format!("{} mins", self.up_mins),
};
format!("{}{}{}", day_string, hour_string, min_string)
}
}

/// Get the system uptime in a human-readable format
///
/// # Arguments
///
/// boot_time: Option<time_t> - Manually specify the boot time, or None to try to get it from the system.
/// output_format: OutputFormat - Selects the format of the output string.
///
/// # Returns
///
/// Returns a UResult with the uptime in a human-readable format(e.g. "1 day, 3:45") if successful, otherwise an UptimeError.
#[inline]
pub fn get_formatted_uptime(boot_time: Option<time_t>) -> UResult<String> {
let up_secs = get_uptime(boot_time)?;

if up_secs < 0 {
Err(UptimeError::SystemUptime)?;
}
let up_days = up_secs / 86400;
let up_hours = (up_secs - (up_days * 86400)) / 3600;
let up_mins = (up_secs - (up_days * 86400) - (up_hours * 3600)) / 60;
match up_days.cmp(&1) {
std::cmp::Ordering::Equal => Ok(format!("{up_days:1} day, {up_hours:2}:{up_mins:02}")),
std::cmp::Ordering::Greater => Ok(format!("{up_days:1} days {up_hours:2}:{up_mins:02}")),
_ => Ok(format!("{up_hours:2}:{up_mins:02}")),
pub fn get_formatted_uptime(
boot_time: Option<time_t>,
output_format: OutputFormat,
) -> UResult<String> {
let uptime = get_uptime(boot_time)?;
let formatted_uptime = FormattedUptime::new(uptime);

match output_format {
OutputFormat::HumanReadable => Ok(formatted_uptime.get_human_readable_uptime()),
OutputFormat::PrettyPrint => Ok(formatted_uptime.get_pretty_print_uptime()),
}
}

Expand Down
9 changes: 9 additions & 0 deletions tests/by-util/test_uptime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,3 +282,12 @@ fn test_uptime_since() {

new_ucmd!().arg("--since").succeeds().stdout_matches(&re);
}

#[test]
fn test_uptime_pretty_print() {
new_ucmd!()
.arg("-p")
.succeeds()
.stdout_contains("up")
.stdout_contains("min");
}
Loading
0