Skip to content

Commit 3ee8834

Browse files
authored
bug: fix uptime calculation for Linux (#1410)
* bug: fix uptime for linux Use another calculation to determine the uptime of a process on Linux. * appease clippy * changelog * edit add
1 parent 0068f8a commit 3ee8834

File tree

5 files changed

+49
-21
lines changed

5 files changed

+49
-21
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2121

2222
- [#1314](https://github.com/ClementTsang/bottom/pull/1314): Fix fat32 mounts not showing up in macOS.
2323
- [#1355](https://github.com/ClementTsang/bottom/pull/1355): Reduce chances of non-D0 devices waking up due to temperature checks on Linux.
24+
- [#1410](https://github.com/ClementTsang/bottom/pull/1410): Fix uptime calculation for Linux.
2425

2526
## [0.9.6] - 2023-08-26
2627

src/data_collection/processes.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ pub struct ProcessHarvest {
7474
/// The current state of the process (e.g. zombie, asleep).
7575
pub process_state: (String, char),
7676

77-
/// Cumulative total CPU time used.
77+
/// Cumulative process uptime.
7878
pub time: Duration,
7979

8080
/// This is the *effective* user ID of the process. This is only used on Unix platforms.
@@ -109,7 +109,7 @@ impl ProcessHarvest {
109109
self.write_bytes_per_sec += rhs.write_bytes_per_sec;
110110
self.total_read_bytes += rhs.total_read_bytes;
111111
self.total_write_bytes += rhs.total_write_bytes;
112-
self.time += rhs.time;
112+
self.time = self.time.max(rhs.time);
113113
#[cfg(feature = "gpu")]
114114
{
115115
self.gpu_mem += rhs.gpu_mem;

src/data_collection/processes/linux.rs

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -132,9 +132,7 @@ fn get_linux_cpu_usage(
132132
}
133133

134134
fn read_proc(
135-
prev_proc: &PrevProcDetails, process: Process, cpu_usage: f64, cpu_fraction: f64,
136-
use_current_cpu_total: bool, time_difference_in_secs: u64, total_memory: u64,
137-
user_table: &mut UserTable,
135+
prev_proc: &PrevProcDetails, process: Process, args: ReadProcArgs, user_table: &mut UserTable,
138136
) -> error::Result<(ProcessHarvest, u64)> {
139137
let Process {
140138
pid: _,
@@ -144,6 +142,15 @@ fn read_proc(
144142
cmdline,
145143
} = process;
146144

145+
let ReadProcArgs {
146+
use_current_cpu_total,
147+
cpu_usage,
148+
cpu_fraction,
149+
total_memory,
150+
time_difference_in_secs,
151+
uptime,
152+
} = args;
153+
147154
let (command, name) = {
148155
let truncated_name = stat.comm.as_str();
149156
if let Ok(cmdline) = cmdline {
@@ -231,7 +238,7 @@ fn read_proc(
231238
if ticks_per_sec == 0 {
232239
Duration::ZERO
233240
} else {
234-
Duration::from_secs(stat.utime + stat.stime) / ticks_per_sec
241+
Duration::from_secs(uptime.saturating_sub(stat.start_time / ticks_per_sec as u64))
235242
}
236243
} else {
237244
Duration::ZERO
@@ -279,6 +286,17 @@ fn is_str_numeric(s: &str) -> bool {
279286
s.chars().all(|c| c.is_ascii_digit())
280287
}
281288

289+
/// General args to keep around for reading proc data.
290+
#[derive(Copy, Clone)]
291+
pub(crate) struct ReadProcArgs {
292+
pub(crate) use_current_cpu_total: bool,
293+
pub(crate) cpu_usage: f64,
294+
pub(crate) cpu_fraction: f64,
295+
pub(crate) total_memory: u64,
296+
pub(crate) time_difference_in_secs: u64,
297+
pub(crate) uptime: u64,
298+
}
299+
282300
pub(crate) fn linux_process_data(
283301
collector: &mut DataCollector, time_difference_in_secs: u64,
284302
) -> error::Result<Vec<ProcessHarvest>> {
@@ -329,23 +347,25 @@ pub(crate) fn linux_process_data(
329347
}
330348
});
331349

350+
let args = ReadProcArgs {
351+
use_current_cpu_total,
352+
cpu_usage,
353+
cpu_fraction,
354+
total_memory,
355+
time_difference_in_secs,
356+
uptime: sysinfo::System::uptime(),
357+
};
358+
332359
let process_vector: Vec<ProcessHarvest> = pids
333360
.filter_map(|pid_path| {
334361
if let Ok(process) = Process::from_path(pid_path) {
335362
let pid = process.pid;
336363
let prev_proc_details = pid_mapping.entry(pid).or_default();
337364

338365
#[allow(unused_mut)]
339-
if let Ok((mut process_harvest, new_process_times)) = read_proc(
340-
prev_proc_details,
341-
process,
342-
cpu_usage,
343-
cpu_fraction,
344-
use_current_cpu_total,
345-
time_difference_in_secs,
346-
total_memory,
347-
user_table,
348-
) {
366+
if let Ok((mut process_harvest, new_process_times)) =
367+
read_proc(prev_proc_details, process, args, user_table)
368+
{
349369
#[cfg(feature = "gpu")]
350370
if let Some(gpus) = &collector.gpu_pids {
351371
gpus.iter().for_each(|gpu| {

src/data_collection/processes/linux/process.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ fn next_part<'a>(iter: &mut impl Iterator<Item = &'a str>) -> Result<&'a str, io
2626
.ok_or_else(|| io::Error::from(io::ErrorKind::InvalidData))
2727
}
2828

29-
/// A wrapper around the data in `/proc/<PID>/stat`. For documentation, see [here](https://man7.org/linux/man-pages/man5/proc.5.html).
29+
/// A wrapper around the data in `/proc/<PID>/stat`. For documentation, see
30+
/// [here](https://man7.org/linux/man-pages/man5/proc.5.html).
3031
///
3132
/// Note this does not necessarily get all fields, only the ones we use in bottom.
3233
pub(crate) struct Stat {
@@ -47,6 +48,9 @@ pub(crate) struct Stat {
4748

4849
/// The resident set size, or the number of pages the process has in real memory.
4950
pub rss: u64,
51+
52+
/// The start time of the process, represented in clock ticks.
53+
pub start_time: u64,
5054
}
5155

5256
impl Stat {
@@ -76,18 +80,19 @@ impl Stat {
7680
.chars()
7781
.next()
7882
.ok_or_else(|| anyhow!("missing state"))?;
79-
8083
let ppid: Pid = next_part(&mut rest)?.parse()?;
8184

8285
// Skip 9 fields until utime (pgrp, session, tty_nr, tpgid, flags, minflt, cminflt, majflt, cmajflt).
8386
let mut rest = rest.skip(9);
84-
8587
let utime: u64 = next_part(&mut rest)?.parse()?;
8688
let stime: u64 = next_part(&mut rest)?.parse()?;
8789

88-
// Skip 8 fields until rss (cutime, cstime, priority, nice, num_threads, itrealvalue, starttime, vsize).
89-
let mut rest = rest.skip(8);
90+
// Skip 6 fields until starttime (cutime, cstime, priority, nice, num_threads, itrealvalue).
91+
let mut rest = rest.skip(6);
92+
let start_time: u64 = next_part(&mut rest)?.parse()?;
9093

94+
// Skip one field until rss (vsize)
95+
let mut rest = rest.skip(1);
9196
let rss: u64 = next_part(&mut rest)?.parse()?;
9297

9398
Ok(Stat {
@@ -97,6 +102,7 @@ impl Stat {
97102
utime,
98103
stime,
99104
rss,
105+
start_time,
100106
})
101107
}
102108

src/widgets/process_table/proc_widget_data.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ impl ProcWidgetData {
263263
self.wps += other.wps;
264264
self.total_read += other.total_read;
265265
self.total_write += other.total_write;
266+
self.time = self.time.max(other.time);
266267
#[cfg(feature = "gpu")]
267268
{
268269
self.gpu_mem_usage = match (&self.gpu_mem_usage, &other.gpu_mem_usage) {

0 commit comments

Comments
 (0)