Skip to content

Commit 46c2a0a

Browse files
w: implement idle_time and elapsed time formatting (#361)
* w: implement idle_time and elapsed time formatting * w: fix CI and clippy warnings * w: fix CI again * w: fix typo * w: add unit test for format_time_elapsed * w: misc test and code quality fixes --------- Co-authored-by: Krysztal Huang <[email protected]>
1 parent f635788 commit 46c2a0a

File tree

1 file changed

+71
-8
lines changed

1 file changed

+71
-8
lines changed

src/uu/w/src/w.rs

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@ use clap::crate_version;
99
use clap::{Arg, ArgAction, Command};
1010
#[cfg(target_os = "linux")]
1111
use libc::{sysconf, _SC_CLK_TCK};
12-
use std::process;
1312
#[cfg(target_os = "linux")]
14-
use std::{collections::HashMap, fs, path::Path};
13+
use std::{collections::HashMap, fs, path::Path, time::SystemTime};
14+
use std::{process, time::Duration};
1515
#[cfg(target_os = "linux")]
1616
use uucore::utmpx::Utmpx;
1717
use uucore::{error::UResult, format_usage, help_about, help_usage};
@@ -23,7 +23,7 @@ struct UserInfo {
2323
user: String,
2424
terminal: String,
2525
login_time: String,
26-
idle_time: String,
26+
idle_time: Duration, // for better compatibility with old-style outputs
2727
jcpu: String,
2828
pcpu: String,
2929
command: String,
@@ -83,6 +83,51 @@ fn fetch_pcpu_time(pid: i32) -> Result<f64, std::io::Error> {
8383
Ok((utime + stime) / get_clock_tick() as f64)
8484
}
8585

86+
#[cfg(target_os = "linux")]
87+
fn fetch_idle_time(tty: String) -> Result<Duration, std::io::Error> {
88+
let path = Path::new("/dev/").join(tty);
89+
let stat = fs::metadata(path)?;
90+
if let Ok(time) = stat.accessed() {
91+
Ok(SystemTime::now().duration_since(time).unwrap_or_default())
92+
} else {
93+
Ok(Duration::ZERO)
94+
}
95+
}
96+
97+
#[cfg(not(target_os = "linux"))]
98+
fn _fetch_idle_time(_tty: String) -> Result<Duration, std::io::Error> {
99+
Ok(Duration::ZERO)
100+
}
101+
102+
fn format_time_elapsed(time: Duration, old_style: bool) -> Result<String, chrono::OutOfRangeError> {
103+
let t = chrono::Duration::from_std(time)?;
104+
if t.num_days() >= 2 {
105+
Ok(format!("{}days", t.num_days()))
106+
} else if t.num_hours() >= 1 {
107+
Ok(format!(
108+
"{}:{:02}{}",
109+
t.num_hours(),
110+
t.num_minutes() % 60,
111+
if old_style { "" } else { "m" }
112+
))
113+
} else if t.num_minutes() >= 1 {
114+
Ok(format!(
115+
"{}:{:02}{}",
116+
t.num_minutes() % 60,
117+
t.num_seconds() % 60,
118+
if old_style { "m" } else { "" }
119+
))
120+
} else if old_style {
121+
Ok(String::new())
122+
} else {
123+
Ok(format!(
124+
"{}.{:02}s",
125+
t.num_seconds() % 60,
126+
(t.num_milliseconds() % 1000) / 10
127+
))
128+
}
129+
}
130+
86131
#[cfg(target_os = "linux")]
87132
fn format_time(time: String) -> Result<String, chrono::format::ParseError> {
88133
let mut t: String = time;
@@ -127,7 +172,7 @@ fn fetch_user_info() -> Result<Vec<UserInfo>, std::io::Error> {
127172
user: entry.user(),
128173
terminal: entry.tty_device(),
129174
login_time: format_time(entry.login_time().to_string()).unwrap_or_default(),
130-
idle_time: "TODO".into(), // Placeholder, needs actual implementation
175+
idle_time: fetch_idle_time(entry.tty_device())?,
131176
jcpu: format!("{:.2}", jcpu),
132177
pcpu: fetch_pcpu_time(entry.pid()).unwrap_or_default().to_string(),
133178
command: fetch_cmdline(entry.pid()).unwrap_or_default(),
@@ -150,6 +195,7 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
150195

151196
let no_header = matches.get_flag("no-header");
152197
let short = matches.get_flag("short");
198+
let old_style = matches.get_flag("old-style");
153199

154200
match fetch_user_info() {
155201
Ok(user_info) => {
@@ -167,15 +213,18 @@ pub fn uumain(args: impl uucore::Args) -> UResult<()> {
167213
if short {
168214
println!(
169215
"{:<9}{:<9}{:<7}{:<}",
170-
user.user, user.terminal, user.idle_time, user.command
216+
user.user,
217+
user.terminal,
218+
format_time_elapsed(user.idle_time, old_style).unwrap_or_default(),
219+
user.command
171220
);
172221
} else {
173222
println!(
174223
"{:<9}{:<9}{:<9}{:<6} {:<7}{:<5}{:<}",
175224
user.user,
176225
user.terminal,
177226
user.login_time,
178-
user.idle_time,
227+
format_time_elapsed(user.idle_time, old_style).unwrap_or_default(),
179228
user.jcpu,
180229
user.pcpu,
181230
user.command
@@ -260,9 +309,10 @@ pub fn uu_app() -> Command {
260309
#[cfg(target_os = "linux")]
261310
mod tests {
262311
use crate::{
263-
fetch_cmdline, fetch_pcpu_time, fetch_terminal_number, format_time, get_clock_tick,
312+
fetch_cmdline, fetch_pcpu_time, fetch_terminal_number, format_time, format_time_elapsed,
313+
get_clock_tick,
264314
};
265-
use std::{fs, path::Path, process};
315+
use std::{fs, path::Path, process, time::Duration};
266316

267317
#[test]
268318
fn test_format_time() {
@@ -283,6 +333,19 @@ mod tests {
283333
)
284334
}
285335

336+
#[test]
337+
fn test_format_time_elapsed() {
338+
let td = Duration::new(60 * 60 * 18 + 60 * 18, 0);
339+
assert_eq!(
340+
format_time_elapsed(td, false).unwrap(),
341+
String::from("18:18m")
342+
);
343+
assert_eq!(
344+
format_time_elapsed(td, true).unwrap(),
345+
String::from("18:18")
346+
);
347+
}
348+
286349
#[test]
287350
// Get PID of current process and use that for cmdline testing
288351
fn test_fetch_cmdline() {

0 commit comments

Comments
 (0)