1
Fork 0
mirror of https://github.com/RGBCube/uutils-coreutils synced 2025-07-29 03:57:44 +00:00

uptime: refactor, move some codes to uucore (#7289)

This commit is contained in:
Bluemangoo 2025-02-20 19:09:06 +08:00 committed by GitHub
parent 9aa872327b
commit 047ec995f2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 494 additions and 240 deletions

4
Cargo.lock generated
View file

@ -1308,7 +1308,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [
"cfg-if",
"windows-targets 0.48.5",
"windows-targets 0.52.6",
]
[[package]]
@ -3470,6 +3470,7 @@ dependencies = [
"thiserror 2.0.11",
"utmp-classic",
"uucore",
"windows-sys 0.59.0",
]
[[package]]
@ -3566,6 +3567,7 @@ dependencies = [
"tempfile",
"thiserror 2.0.11",
"time",
"utmp-classic",
"uucore_procs",
"walkdir",
"wild",

View file

@ -19,12 +19,18 @@ path = "src/uptime.rs"
[dependencies]
chrono = { workspace = true }
clap = { workspace = true }
uucore = { workspace = true, features = ["libc", "utmpx"] }
thiserror = { workspace = true }
uucore = { workspace = true, features = ["libc", "utmpx", "uptime"] }
[target.'cfg(target_os = "openbsd")'.dependencies]
utmp-classic = { workspace = true }
[target.'cfg(target_os="windows")'.dependencies]
windows-sys = { workspace = true, features = [
"Win32_System_RemoteDesktop",
"Wdk_System_SystemInformation",
] }
[[bin]]
name = "uptime"
path = "src/main.rs"

View file

@ -3,30 +3,23 @@
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore getloadavg behaviour loadavg uptime upsecs updays upmins uphours boottime nusers utmpxname gettime clockid
// spell-checker:ignore getloadavg behaviour loadavg uptime upsecs updays upmins uphours boottime nusers utmpxname gettime clockid formated
use chrono::{Local, TimeZone, Utc};
use clap::ArgMatches;
use std::ffi::OsString;
use std::fs;
use std::io;
use std::os::unix::fs::FileTypeExt;
use thiserror::Error;
use uucore::error::set_exit_code;
use uucore::error::UError;
use uucore::show_error;
#[cfg(not(target_os = "openbsd"))]
use uucore::libc::time_t;
use uucore::uptime::*;
use uucore::error::{UResult, USimpleError};
use uucore::error::UResult;
use clap::{builder::ValueParser, crate_version, Arg, ArgAction, Command, ValueHint};
use uucore::{format_usage, help_about, help_usage};
#[cfg(target_os = "openbsd")]
use utmp_classic::{parse_from_path, UtmpEntry};
#[cfg(unix)]
#[cfg(not(target_os = "openbsd"))]
use uucore::utmpx::*;
@ -37,12 +30,9 @@ pub mod options {
pub static PATH: &str = "path";
}
#[cfg(unix)]
use uucore::libc::getloadavg;
#[cfg(windows)]
extern "C" {
fn GetTickCount() -> uucore::libc::uint32_t;
fn GetTickCount() -> u32;
}
#[derive(Debug, Error)]
@ -68,27 +58,38 @@ impl UError for UptimeError {
#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from(args)?;
let argument = matches.get_many::<OsString>(options::PATH);
// Switches to default uptime behaviour if there is no argument
if argument.is_none() {
return default_uptime(&matches);
#[cfg(windows)]
return default_uptime(&matches);
#[cfg(unix)]
{
use std::ffi::OsString;
use uucore::error::set_exit_code;
use uucore::show_error;
let argument = matches.get_many::<OsString>(options::PATH);
// Switches to default uptime behaviour if there is no argument
if argument.is_none() {
return default_uptime(&matches);
}
let mut arg_iter = argument.unwrap();
let file_path = arg_iter.next().unwrap();
if let Some(path) = arg_iter.next() {
// Uptime doesn't attempt to calculate boot time if there is extra arguments.
// Its a fatal error
show_error!(
"{}",
UptimeError::ExtraOperandError(path.to_owned().into_string().unwrap())
);
set_exit_code(1);
return Ok(());
}
uptime_with_file(file_path)
}
let mut arg_iter = argument.unwrap();
let file_path = arg_iter.next().unwrap();
if let Some(path) = arg_iter.next() {
// Uptime doesn't attempt to calculate boot time if there is extra arguments.
// Its a fatal error
show_error!(
"{}",
UptimeError::ExtraOperandError(path.to_owned().into_string().unwrap())
);
set_exit_code(1);
return Ok(());
}
uptime_with_file(file_path)
}
pub fn uu_app() -> Command {
@ -114,7 +115,12 @@ pub fn uu_app() -> Command {
}
#[cfg(unix)]
fn uptime_with_file(file_path: &OsString) -> UResult<()> {
fn uptime_with_file(file_path: &std::ffi::OsString) -> UResult<()> {
use std::fs;
use std::os::unix::fs::FileTypeExt;
use uucore::error::set_exit_code;
use uucore::show_error;
// Uptime will print loadavg and time to stderr unless we encounter an extra operand.
let mut non_fatal_error = false;
@ -149,7 +155,7 @@ fn uptime_with_file(file_path: &OsString) -> UResult<()> {
show_error!("couldn't get boot time");
print_time();
print!("up ???? days ??:??,");
print_nusers(0);
print_nusers(Some(0))?;
print_loadavg();
set_exit_code(1);
return Ok(());
@ -159,7 +165,7 @@ fn uptime_with_file(file_path: &OsString) -> UResult<()> {
if non_fatal_error {
print_time();
print!("up ???? days ??:??,");
print_nusers(0);
print_nusers(Some(0))?;
print_loadavg();
return Ok(());
}
@ -169,10 +175,9 @@ fn uptime_with_file(file_path: &OsString) -> UResult<()> {
#[cfg(not(target_os = "openbsd"))]
{
let (boot_time, count) = process_utmpx_from_file(file_path);
let (boot_time, count) = process_utmpx(Some(file_path));
if let Some(time) = boot_time {
let upsecs = get_uptime_from_boot_time(time);
print_uptime(upsecs);
print_uptime(Some(time))?;
} else {
show_error!("couldn't get boot time");
set_exit_code(1);
@ -184,20 +189,20 @@ fn uptime_with_file(file_path: &OsString) -> UResult<()> {
#[cfg(target_os = "openbsd")]
{
user_count = process_utmp_from_file(file_path.to_str().expect("invalid utmp path file"));
user_count = get_nusers(file_path.to_str().expect("invalid utmp path file"));
let upsecs = get_uptime();
let upsecs = get_uptime(None);
if upsecs < 0 {
show_error!("couldn't get boot time");
set_exit_code(1);
print!("up ???? days ??:??,");
} else {
print_uptime(upsecs);
print_uptime(Some(upsecs))?;
}
}
print_nusers(user_count);
print_nusers(Some(user_count))?;
print_loadavg();
Ok(())
@ -205,17 +210,18 @@ fn uptime_with_file(file_path: &OsString) -> UResult<()> {
/// Default uptime behaviour i.e. when no file argument is given.
fn default_uptime(matches: &ArgMatches) -> UResult<()> {
#[cfg(target_os = "openbsd")]
let user_count = process_utmp_from_file("/var/run/utmp");
#[cfg(not(target_os = "openbsd"))]
let (boot_time, user_count) = process_utmpx();
#[cfg(target_os = "openbsd")]
let uptime = get_uptime();
#[cfg(not(target_os = "openbsd"))]
let uptime = get_uptime(boot_time);
if matches.get_flag(options::SINCE) {
#[cfg(unix)]
#[cfg(not(target_os = "openbsd"))]
let (boot_time, _) = process_utmpx(None);
#[cfg(target_os = "openbsd")]
let uptime = get_uptime(None)?;
#[cfg(unix)]
#[cfg(not(target_os = "openbsd"))]
let uptime = get_uptime(boot_time)?;
#[cfg(target_os = "windows")]
let uptime = get_uptime(None)?;
let initial_date = Local
.timestamp_opt(Utc::now().timestamp() - uptime, 0)
.unwrap();
@ -223,209 +229,68 @@ fn default_uptime(matches: &ArgMatches) -> UResult<()> {
return Ok(());
}
if uptime < 0 {
return Err(USimpleError::new(1, "could not retrieve system uptime"));
}
print_time();
print_uptime(uptime);
print_nusers(user_count);
print_uptime(None)?;
print_nusers(None)?;
print_loadavg();
Ok(())
}
#[cfg(unix)]
#[inline]
fn print_loadavg() {
use uucore::libc::c_double;
let mut avg: [c_double; 3] = [0.0; 3];
let loads: i32 = unsafe { getloadavg(avg.as_mut_ptr(), 3) };
if loads == -1 {
println!();
} else {
print!("load average: ");
for n in 0..loads {
print!(
"{:.2}{}",
avg[n as usize],
if n == loads - 1 { "\n" } else { ", " }
);
}
match get_formatted_loadavg() {
Err(_) => {}
Ok(s) => println!("{}", s),
}
}
#[cfg(windows)]
fn print_loadavg() {
// XXX: currently this is a noop as Windows does not seem to have anything comparable to
// getloadavg()
}
#[cfg(unix)]
#[cfg(target_os = "openbsd")]
fn process_utmp_from_file(file: &str) -> usize {
let mut nusers = 0;
let entries = parse_from_path(file).unwrap_or_default();
for entry in entries {
if let UtmpEntry::UTMP {
line: _,
user,
host: _,
time: _,
} = entry
{
if !user.is_empty() {
nusers += 1;
}
}
}
nusers
}
#[cfg(unix)]
#[cfg(not(target_os = "openbsd"))]
fn process_utmpx() -> (Option<time_t>, usize) {
fn process_utmpx(file: Option<&std::ffi::OsString>) -> (Option<time_t>, usize) {
let mut nusers = 0;
let mut boot_time = None;
for line in Utmpx::iter_all_records() {
match line.record_type() {
USER_PROCESS => nusers += 1,
BOOT_TIME => {
let dt = line.login_time();
if dt.unix_timestamp() > 0 {
boot_time = Some(dt.unix_timestamp() as time_t);
}
}
_ => continue,
}
}
(boot_time, nusers)
}
#[cfg(unix)]
#[cfg(not(target_os = "openbsd"))]
fn process_utmpx_from_file(file: &OsString) -> (Option<time_t>, usize) {
let mut nusers = 0;
let mut boot_time = None;
for line in Utmpx::iter_all_records_from(file) {
match line.record_type() {
USER_PROCESS => nusers += 1,
BOOT_TIME => {
let dt = line.login_time();
if dt.unix_timestamp() > 0 {
boot_time = Some(dt.unix_timestamp() as time_t);
}
}
_ => continue,
}
}
(boot_time, nusers)
}
#[cfg(windows)]
fn process_utmpx() -> (Option<time_t>, usize) {
(None, 0) // TODO: change 0 to number of users
}
fn print_nusers(nusers: usize) {
match nusers.cmp(&1) {
std::cmp::Ordering::Less => print!(" 0 users, "),
std::cmp::Ordering::Equal => print!("1 user, "),
std::cmp::Ordering::Greater => print!("{nusers} users, "),
let records = match file {
Some(f) => Utmpx::iter_all_records_from(f),
None => Utmpx::iter_all_records(),
};
for line in records {
match line.record_type() {
USER_PROCESS => nusers += 1,
BOOT_TIME => {
let dt = line.login_time();
if dt.unix_timestamp() > 0 {
boot_time = Some(dt.unix_timestamp() as time_t);
}
}
_ => continue,
}
}
(boot_time, nusers)
}
fn print_nusers(nusers: Option<usize>) -> UResult<()> {
print!(
"{}, ",
match nusers {
None => {
get_formatted_nusers()
}
Some(nusers) => {
format_nusers(nusers)
}
}
);
Ok(())
}
fn print_time() {
let local_time = Local::now().time();
print!(" {} ", local_time.format("%H:%M:%S"));
print!(" {} ", get_formatted_time());
}
#[cfg(not(target_os = "openbsd"))]
fn get_uptime_from_boot_time(boot_time: time_t) -> i64 {
let now = Local::now().timestamp();
#[cfg(target_pointer_width = "64")]
let boottime: i64 = boot_time;
#[cfg(not(target_pointer_width = "64"))]
let boottime: i64 = boot_time.into();
now - boottime
}
#[cfg(unix)]
#[cfg(target_os = "openbsd")]
fn get_uptime() -> i64 {
use uucore::libc::clock_gettime;
use uucore::libc::CLOCK_BOOTTIME;
use uucore::libc::c_int;
use uucore::libc::timespec;
let mut tp: timespec = timespec {
tv_sec: 0,
tv_nsec: 0,
};
let raw_tp = &mut tp as *mut timespec;
// OpenBSD prototype: clock_gettime(clk_id: ::clockid_t, tp: *mut ::timespec) -> ::c_int;
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();
uptime
} else {
-1
}
}
#[cfg(unix)]
#[cfg(not(target_os = "openbsd"))]
fn get_uptime(boot_time: Option<time_t>) -> i64 {
use std::fs::File;
use std::io::Read;
let mut proc_uptime_s = String::new();
let proc_uptime = File::open("/proc/uptime")
.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().ok());
proc_uptime.unwrap_or_else(|| match boot_time {
Some(t) => {
let now = Local::now().timestamp();
#[cfg(target_pointer_width = "64")]
let boottime: i64 = t;
#[cfg(not(target_pointer_width = "64"))]
let boottime: i64 = t.into();
now - boottime
}
None => -1,
})
}
#[cfg(windows)]
fn get_uptime(_boot_time: Option<time_t>) -> i64 {
unsafe { GetTickCount() as i64 }
}
fn print_uptime(upsecs: i64) {
let updays = upsecs / 86400;
let uphours = (upsecs - (updays * 86400)) / 3600;
let upmins = (upsecs - (updays * 86400) - (uphours * 3600)) / 60;
match updays.cmp(&1) {
std::cmp::Ordering::Equal => print!("up {updays:1} day, {uphours:2}:{upmins:02}, "),
std::cmp::Ordering::Greater => {
print!("up {updays:1} days {uphours:2}:{upmins:02}, ");
}
_ => print!("up {uphours:2}:{upmins:02}, "),
};
fn print_uptime(boot_time: Option<time_t>) -> UResult<()> {
print!("up {}, ", get_formated_uptime(boot_time)?);
Ok(())
}

View file

@ -74,11 +74,16 @@ tempfile = { workspace = true }
[target.'cfg(target_os = "windows")'.dependencies]
winapi-util = { workspace = true, optional = true }
windows-sys = { workspace = true, optional = true, default-features = false, features = [
"Wdk_System_SystemInformation",
"Win32_Storage_FileSystem",
"Win32_Foundation",
"Win32_System_RemoteDesktop",
"Win32_System_WindowsProgramming",
] }
[target.'cfg(target_os = "openbsd")'.dependencies]
utmp-classic = { workspace = true, optional = true }
[features]
default = []
# * non-default features
@ -122,3 +127,4 @@ version-cmp = []
wide = []
custom-tz-fmt = []
tty = []
uptime = ["libc", "windows-sys", "utmpx", "utmp-classic", "thiserror"]

View file

@ -34,6 +34,8 @@ pub mod ringbuffer;
pub mod sum;
#[cfg(feature = "update-control")]
pub mod update_control;
#[cfg(feature = "uptime")]
pub mod uptime;
#[cfg(feature = "version-cmp")]
pub mod version_cmp;

View file

@ -0,0 +1,371 @@
// This file is part of the uutils coreutils package.
//
// For the full copyright and license information, please view the LICENSE
// file that was distributed with this source code.
// spell-checker:ignore gettime BOOTTIME clockid boottime formated nusers loadavg getloadavg
//! Provides functions to get system uptime, number of users and load average.
// The code was originally written in uu_uptime
// (https://github.com/uutils/coreutils/blob/main/src/uu/uptime/src/uptime.rs)
// but was eventually moved here.
// See https://github.com/uutils/coreutils/pull/7289 for discussion.
use crate::error::{UError, UResult};
use chrono::Local;
use libc::time_t;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum UptimeError {
#[error("could not retrieve system uptime")]
SystemUptime,
#[error("could not retrieve system load average")]
SystemLoadavg,
#[error("Windows does not have an equivalent to the load average on Unix-like systems")]
WindowsLoadavg,
#[error("boot time larger than current time")]
BootTime,
}
impl UError for UptimeError {
fn code(&self) -> i32 {
1
}
}
/// Returns the formatted time string, e.g. "12:34:56"
pub fn get_formatted_time() -> String {
Local::now().time().format("%H:%M:%S").to_string()
}
/// Get the system uptime
///
/// # Arguments
///
/// boot_time: Option<time_t> - Manually specify the boot time, or None to try to get it from the system.
///
/// # Returns
///
/// Returns a UResult with the uptime in seconds if successful, otherwise an UptimeError.
#[cfg(target_os = "openbsd")]
pub fn get_uptime(_boot_time: Option<time_t>) -> UResult<i64> {
use libc::clock_gettime;
use libc::CLOCK_BOOTTIME;
use libc::c_int;
use libc::timespec;
let mut tp: timespec = timespec {
tv_sec: 0,
tv_nsec: 0,
};
let raw_tp = &mut tp as *mut timespec;
// OpenBSD prototype: clock_gettime(clk_id: ::clockid_t, tp: *mut ::timespec) -> ::c_int;
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)
} else {
Err(UptimeError::SystemUptime)
}
}
/// Get the system uptime
///
/// # Arguments
///
/// boot_time: Option<time_t> - Manually specify the boot time, or None to try to get it from the system.
///
/// # Returns
///
/// Returns a UResult with the uptime in seconds if successful, otherwise an UptimeError.
#[cfg(unix)]
#[cfg(not(target_os = "openbsd"))]
pub fn get_uptime(boot_time: Option<time_t>) -> UResult<i64> {
use crate::utmpx::Utmpx;
use libc::BOOT_TIME;
use std::fs::File;
use std::io::Read;
let mut proc_uptime_s = String::new();
let proc_uptime = File::open("/proc/uptime")
.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());
if let Some(uptime) = proc_uptime {
return Ok(uptime);
}
let boot_time = boot_time.or_else(|| {
let records = Utmpx::iter_all_records();
for line in records {
match line.record_type() {
BOOT_TIME => {
let dt = line.login_time();
if dt.unix_timestamp() > 0 {
return Some(dt.unix_timestamp() as time_t);
}
}
_ => continue,
}
}
None
});
if let Some(t) = boot_time {
let now = Local::now().timestamp();
#[cfg(target_pointer_width = "64")]
let boottime: i64 = t;
#[cfg(not(target_pointer_width = "64"))]
let boottime: i64 = t.into();
if now < boottime {
Err(UptimeError::BootTime)?;
}
return Ok(now - boottime);
}
Err(UptimeError::SystemUptime)?
}
/// Get the system uptime
///
/// # Returns
///
/// Returns a UResult with the uptime in seconds if successful, otherwise an UptimeError.
#[cfg(windows)]
pub fn get_uptime(_boot_time: Option<time_t>) -> UResult<i64> {
use windows_sys::Win32::System::SystemInformation::GetTickCount;
let uptime = unsafe { GetTickCount() };
if uptime < 0 {
Err(UptimeError::SystemUptime)?;
}
Ok(uptime as i64)
}
/// 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.
///
/// # 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_formated_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}")),
}
}
/// Get the number of users currently logged in
///
/// # Returns
///
/// Returns the number of users currently logged in if successful, otherwise 0.
#[cfg(unix)]
#[cfg(not(target_os = "openbsd"))]
// see: https://gitlab.com/procps-ng/procps/-/blob/4740a0efa79cade867cfc7b32955fe0f75bf5173/library/uptime.c#L63-L115
pub fn get_nusers() -> usize {
use crate::utmpx::Utmpx;
use libc::USER_PROCESS;
let mut num_user = 0;
Utmpx::iter_all_records().for_each(|ut| {
if ut.record_type() == USER_PROCESS {
num_user += 1;
}
});
num_user
}
/// Get the number of users currently logged in
///
/// # Returns
///
/// Returns the number of users currently logged in if successful, otherwise 0
#[cfg(target_os = "openbsd")]
pub fn get_nusers(file: &str) -> usize {
use utmp_classic::{parse_from_path, UtmpEntry};
let mut nusers = 0;
let entries = match parse_from_path(file) {
Some(e) => e,
None => return 0,
};
for entry in entries {
if let UtmpEntry::UTMP {
line: _,
user,
host: _,
time: _,
} = entry
{
if !user.is_empty() {
nusers += 1;
}
}
}
nusers
}
/// Get the number of users currently logged in
///
/// # Returns
///
/// Returns the number of users currently logged in if successful, otherwise 0
#[cfg(target_os = "windows")]
pub fn get_nusers() -> usize {
use std::ptr;
use windows_sys::Win32::System::RemoteDesktop::*;
let mut num_user = 0;
unsafe {
let mut session_info_ptr = ptr::null_mut();
let mut session_count = 0;
let result = WTSEnumerateSessionsW(
WTS_CURRENT_SERVER_HANDLE,
0,
1,
&mut session_info_ptr,
&mut session_count,
);
if result == 0 {
return 0;
}
let sessions = std::slice::from_raw_parts(session_info_ptr, session_count as usize);
for session in sessions {
let mut buffer: *mut u16 = ptr::null_mut();
let mut bytes_returned = 0;
let result = WTSQuerySessionInformationW(
WTS_CURRENT_SERVER_HANDLE,
session.SessionId,
5,
&mut buffer,
&mut bytes_returned,
);
if result == 0 || buffer.is_null() {
continue;
}
let username = if !buffer.is_null() {
let cstr = std::ffi::CStr::from_ptr(buffer as *const i8);
cstr.to_string_lossy().to_string()
} else {
String::new()
};
if !username.is_empty() {
num_user += 1;
}
WTSFreeMemory(buffer as _);
}
WTSFreeMemory(session_info_ptr as _);
}
num_user
}
/// Format the number of users to a human-readable string
///
/// # Returns
///
/// e.g. "0 user", "1 user", "2 users"
#[inline]
pub fn format_nusers(nusers: usize) -> String {
match nusers {
0 => "0 user".to_string(),
1 => "1 user".to_string(),
_ => format!("{} users", nusers),
}
}
/// Get the number of users currently logged in in a human-readable format
///
/// # Returns
///
/// e.g. "0 user", "1 user", "2 users"
#[inline]
pub fn get_formatted_nusers() -> String {
#[cfg(not(target_os = "openbsd"))]
return format_nusers(get_nusers());
#[cfg(target_os = "openbsd")]
format_nusers(get_nusers("/var/run/utmp"))
}
/// Get the system load average
///
/// # Returns
///
/// Returns a UResult with the load average if successful, otherwise an UptimeError.
/// The load average is a tuple of three floating point numbers representing the 1-minute, 5-minute, and 15-minute load averages.
#[cfg(unix)]
pub fn get_loadavg() -> UResult<(f64, f64, f64)> {
use crate::libc::c_double;
use libc::getloadavg;
let mut avg: [c_double; 3] = [0.0; 3];
let loads: i32 = unsafe { getloadavg(avg.as_mut_ptr(), 3) };
if loads == -1 {
Err(UptimeError::SystemLoadavg)?
} else {
Ok((avg[0], avg[1], avg[2]))
}
}
/// Get the system load average
/// Windows does not have an equivalent to the load average on Unix-like systems.
///
/// # Returns
///
/// Returns a UResult with an UptimeError.
#[cfg(windows)]
pub fn get_loadavg() -> UResult<(f64, f64, f64)> {
Err(UptimeError::WindowsLoadavg)?
}
/// Get the system load average in a human-readable format
///
/// # Returns
///
/// Returns a UResult with the load average in a human-readable format if successful, otherwise an UptimeError.
/// e.g. "load average: 0.00, 0.00, 0.00"
#[inline]
pub fn get_formatted_loadavg() -> UResult<String> {
let loadavg = get_loadavg()?;
Ok(format!(
"load average: {:.2}, {:.2}, {:.2}",
loadavg.0, loadavg.1, loadavg.2
))
}

View file

@ -66,6 +66,8 @@ pub use crate::features::ringbuffer;
pub use crate::features::sum;
#[cfg(feature = "update-control")]
pub use crate::features::update_control;
#[cfg(feature = "uptime")]
pub use crate::features::uptime;
#[cfg(feature = "version-cmp")]
pub use crate::features::version_cmp;