1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use anyhow::bail;
use anyhow::{anyhow, Context, Result};
use std::convert::{TryFrom, TryInto};
use std::fs;
use std::process;
use std::time::{self, Duration};
use process::Command;
pub struct ProcessUptime {
pub pid: u32,
pub uptime: time::Duration,
}
const PROC_UPTIME: &str = "/proc/uptime";
impl ProcessUptime {
pub fn new() -> Result<Self> {
let pid = process::id();
let uptime = match Self::get_ps_etime(pid) {
Ok(etime) => time::Duration::from_secs(etime),
Err(_) => {
Self::proc_stat_uptime(pid)?
}
};
Ok(Self { pid, uptime })
}
pub(crate) fn proc_stat_uptime(pid: u32) -> Result<Duration> {
let content = fs::read_to_string(PROC_UPTIME)?;
let system_uptime = Self::system_uptime(&content)?;
let process_uptime = Self::process_uptime(pid)?;
let sc_clk_tck: u64 = unsafe { libc::sysconf(libc::_SC_CLK_TCK).try_into()? };
let process_uptime = Duration::from_secs(system_uptime - process_uptime / sc_clk_tck);
Ok(process_uptime)
}
fn process_uptime(pid: u32) -> Result<u64> {
let content = fs::read_to_string(format!("/proc/{}/stat", pid))?;
let process_uptime = content
.split_whitespace()
.nth(21)
.ok_or_else(|| anyhow!("/proc/{}/stat 22nd column not found", pid))?;
let process_uptime = process_uptime.parse::<f32>()? as u64;
Ok(process_uptime)
}
pub(crate) fn system_uptime(content: &str) -> Result<u64> {
let uptime = content.split_whitespace().next();
let uptime = match uptime {
Some(uptime) => uptime,
None => bail!("`/proc/uptime` 0th field not present"),
};
Ok(uptime.parse::<f32>()? as u64)
}
fn get_ps_etime(pid: u32) -> Result<u64> {
let output = Command::new("sh")
.arg("-c")
.arg(format!("ps -o etimes -p {} --no-headers", pid).as_str())
.output()?;
let mut uptime_string = std::str::from_utf8(output.stdout.as_slice())?.to_string();
if uptime_string.ends_with('\n') {
uptime_string.pop();
if uptime_string.ends_with('\r') {
uptime_string.pop();
}
}
uptime_string
.parse::<u64>()
.with_context(|| anyhow!("Failed to parse {} to u64", uptime_string))
}
}
impl TryFrom<u32> for ProcessUptime {
type Error = anyhow::Error;
fn try_from(pid: u32) -> Result<Self, Self::Error> {
let metadata = fs::metadata(format!("/proc/{}", pid))?;
let uptime = metadata.modified()?.elapsed()?;
Ok(Self { pid, uptime })
}
}
#[cfg(test)]
mod test {
use std::{process, time::Duration};
use crate::ProcessUptime;
#[test]
fn test_system_uptime() {
assert_eq!(
ProcessUptime::system_uptime("609773.79 19153047.14\n").unwrap(),
609773
)
}
#[ignore]
#[test]
fn test_this_process_uptime() {
std::thread::sleep(Duration::from_secs(3));
let pid = process::id();
assert_eq!(
ProcessUptime::proc_stat_uptime(pid).unwrap(),
Duration::from_secs(3)
);
}
}