linux/procfs: add read_proc_stat
For now we just parse the CPU "time-spent" counters and calculate the CPU usage of the whole system. As we do not want a total average we use a state-full implementation, which calculates the usage since the last request (if older than 1 second). Somewhat modeled after our Perl version of this. But for the "total" we do not use the: > ($ctime - $last_proc_stat->{ctime}) * $clock_ticks * $cpucount; calculation, but just add _all_ fields together, as that is the total of CPU time. Use a Reader-Writer lock to sync updating. First check under the reader lock if the data is recent enough, else update it under the writer lock. Signed-off-by: Thomas Lamprecht <t.lamprecht@proxmox.com>
This commit is contained in:
parent
1e7467ee11
commit
cfae8ae3d2
@ -3,6 +3,8 @@ use std::fs::OpenOptions;
|
||||
use std::io::{BufRead, BufReader};
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
use std::str::FromStr;
|
||||
use std::sync::RwLock;
|
||||
use std::time::Instant;
|
||||
|
||||
use failure::*;
|
||||
use lazy_static::lazy_static;
|
||||
@ -164,6 +166,196 @@ pub fn read_proc_uptime_ticks() -> Result<(u64, u64), Error> {
|
||||
Ok((up as u64, idle as u64))
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
/// The CPU fields from `/proc/stat` with their native time value. Multiply
|
||||
/// with CLOCK_TICKS to get the real value.
|
||||
pub struct ProcFsStat {
|
||||
/// Time spent in user mode.
|
||||
pub user: u64,
|
||||
/// Time spent in user mode with low priority (nice).
|
||||
pub nice: u64,
|
||||
/// Time spent in system mode.
|
||||
pub system: u64,
|
||||
/// Time spent in the idle task.
|
||||
pub idle: u64,
|
||||
/// Time waiting for I/O to complete. This value is not reiable, see `man 5 proc`
|
||||
pub iowait: u64,
|
||||
/// Time servicing interrupts.
|
||||
pub irq: u64,
|
||||
/// Time servicing softirqs.
|
||||
pub softirq: u64,
|
||||
/// Stolen time, which is the time spent in other operating systems when running
|
||||
/// in a virtualized environment.
|
||||
pub steal: u64,
|
||||
/// Time spent running a virtual CPU for guest operating systems under the control of the
|
||||
/// Linux kernel.
|
||||
pub guest: u64,
|
||||
/// Time spent running a niced guest (virtual CPU for guest operating systems under the control
|
||||
/// of the Linux kernel).
|
||||
pub guest_nice: u64,
|
||||
/// The sum of all other u64 fields
|
||||
pub total: u64,
|
||||
/// The percentage (0 - 1.0) of cpu utilization from the whole system, basica underlying calculation
|
||||
/// `1 - (idle / total)` but with delta values between now and the last call to `read_proc_stat` (min. 1s interval)
|
||||
pub cpu: f64,
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
static ref PROC_LAST_STAT: RwLock<(ProcFsStat, Instant, bool)> =
|
||||
RwLock::new((ProcFsStat::default(), Instant::now(), true));
|
||||
}
|
||||
|
||||
/// reads `/proc/stat`. For now only total host CPU usage is handled as the
|
||||
/// other metrics are not really interesting
|
||||
pub fn read_proc_stat() -> Result<ProcFsStat, Error> {
|
||||
let sample_time = Instant::now();
|
||||
let mut stat =
|
||||
parse_proc_stat(unsafe { std::str::from_utf8_unchecked(&std::fs::read("/proc/stat")?) })
|
||||
.unwrap();
|
||||
|
||||
{
|
||||
// read lock scope
|
||||
let prev_read_guarded = PROC_LAST_STAT.read().unwrap();
|
||||
let (prev_stat, prev_time, first_time) = &*prev_read_guarded;
|
||||
let last_update = sample_time
|
||||
.saturating_duration_since(*prev_time)
|
||||
.as_millis();
|
||||
// only update if data is old
|
||||
if last_update < 1000 && !first_time {
|
||||
stat.cpu = prev_stat.cpu;
|
||||
return Ok(stat);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// write lock scope
|
||||
let mut prev_write_guarded = PROC_LAST_STAT.write().unwrap();
|
||||
// we do not expect much lock contention, so sample_time should be
|
||||
// recent. Else, we'd need to reread & parse here to get current data
|
||||
let (prev_stat, prev_time, first_time) = &mut *prev_write_guarded;
|
||||
|
||||
let delta_total = stat.total - prev_stat.total;
|
||||
let delta_idle = stat.idle - prev_stat.idle;
|
||||
|
||||
stat.cpu = 1. - (delta_idle as f64) / (delta_total as f64);
|
||||
|
||||
*prev_stat = ProcFsStat { ..stat };
|
||||
*prev_time = sample_time;
|
||||
*first_time = false;
|
||||
}
|
||||
|
||||
Ok(stat)
|
||||
}
|
||||
|
||||
fn parse_proc_stat(statstr: &str) -> Result<ProcFsStat, Error> {
|
||||
// for now we just use the first accumulated "cpu" line
|
||||
|
||||
let mut parts = statstr.trim_start().split_ascii_whitespace();
|
||||
parts.next(); // swallow initial cpu string
|
||||
|
||||
// helpers:
|
||||
fn required<'a>(value: Option<&'a str>, what: &'static str) -> Result<&'a str, Error> {
|
||||
value.ok_or_else(|| format_err!("missing '{}' in /proc/stat", what))
|
||||
}
|
||||
|
||||
fn req_num<T>(value: Option<&str>, what: &'static str) -> Result<T, Error>
|
||||
where
|
||||
T: FromStr,
|
||||
<T as FromStr>::Err: std::fmt::Display,
|
||||
{
|
||||
required(value, what)?
|
||||
.parse::<T>()
|
||||
.map_err(|e| format_err!("error parsing {}: {}", what, e))
|
||||
}
|
||||
|
||||
let mut stat = ProcFsStat {
|
||||
user: req_num::<u64>(parts.next(), "user")?,
|
||||
nice: req_num::<u64>(parts.next(), "nice")?,
|
||||
system: req_num::<u64>(parts.next(), "system")?,
|
||||
idle: req_num::<u64>(parts.next(), "idle")?,
|
||||
iowait: req_num::<u64>(parts.next(), "iowait")?,
|
||||
irq: req_num::<u64>(parts.next(), "irq")?,
|
||||
softirq: req_num::<u64>(parts.next(), "softirq")?,
|
||||
steal: req_num::<u64>(parts.next(), "steal")?,
|
||||
guest: req_num::<u64>(parts.next(), "guest")?,
|
||||
guest_nice: req_num::<u64>(parts.next(), "guest_nice")?,
|
||||
total: 0,
|
||||
cpu: 0.0,
|
||||
};
|
||||
stat.total = stat.user
|
||||
+ stat.nice
|
||||
+ stat.system
|
||||
+ stat.iowait
|
||||
+ stat.irq
|
||||
+ stat.softirq
|
||||
+ stat.steal
|
||||
+ stat.guest
|
||||
+ stat.guest_nice
|
||||
+ stat.idle;
|
||||
|
||||
// returns avg. heuristic for the first request
|
||||
stat.cpu = 1. - (stat.idle as f64) / (stat.total as f64);
|
||||
|
||||
Ok(stat)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_proc_stat() {
|
||||
let stat = parse_proc_stat(
|
||||
"cpu 2845612 241 173179 264715515 93366 0 7925 141017 0 0\n\
|
||||
cpu0 174375 9 11367 16548335 5741 0 2394 8500 0 0\n\
|
||||
cpu1 183367 11 11423 16540656 4644 0 1235 8888 0 0\n\
|
||||
cpu2 179636 21 20463 16534540 4802 0 456 9270 0 0\n\
|
||||
cpu3 184560 9 11379 16532136 7113 0 225 8967 0 0\n\
|
||||
cpu4 182341 17 10277 16542865 3274 0 181 8461 0 0\n\
|
||||
cpu5 179771 22 9910 16548859 2259 0 112 8328 0 0\n\
|
||||
cpu6 181185 14 8933 16548550 2057 0 78 8313 0 0\n\
|
||||
cpu7 176326 12 8514 16553428 2246 0 76 8812 0 0\n\
|
||||
cpu8 177341 13 7942 16553880 1576 0 56 8565 0 0\n\
|
||||
cpu9 176883 10 8648 16547886 3067 0 103 8788 0 0\n\
|
||||
cpu10 169446 4 7993 16561700 1584 0 39 8797 0 0\n\
|
||||
cpu11 170878 7 7783 16560870 1526 0 23 8444 0 0\n\
|
||||
cpu12 164062 12 7839 16567155 1686 0 43 8794 0 0\n\
|
||||
cpu13 164303 4 7661 16567497 1528 0 41 8525 0 0\n\
|
||||
cpu14 173709 2 11478 16546352 3965 0 571 9414 0 0\n\
|
||||
cpu15 207422 67 21561 16460798 46292 0 2283 10142 0 0\n\
|
||||
intr 29200426 1 9 0 0 0 0 3 0 1 0 275744 40 16 0 0 166292 0 0 0 0 0 0 0 0 0 0 \
|
||||
0 1463843 0 1048751 328317 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 \
|
||||
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0\n\
|
||||
ctxt 27543372\n\
|
||||
btime 1576502436\n\
|
||||
processes 701089\n\
|
||||
procs_running 2\n\
|
||||
procs_blocked 0\n\
|
||||
softirq 36227960 0 16653965 39 1305264 1500573 0 38330 5024204 356 11705229",
|
||||
)
|
||||
.expect("successful parsed a sample /proc/stat entry");
|
||||
assert_eq!(stat.user, 2845612);
|
||||
assert_eq!(stat.nice, 241);
|
||||
assert_eq!(stat.system, 173179);
|
||||
assert_eq!(stat.idle, 264715515);
|
||||
assert_eq!(stat.iowait, 93366);
|
||||
assert_eq!(stat.irq, 0);
|
||||
assert_eq!(stat.softirq, 7925);
|
||||
assert_eq!(stat.steal, 141017);
|
||||
assert_eq!(stat.guest, 0);
|
||||
assert_eq!(stat.guest_nice, 0);
|
||||
assert_eq!(stat.total, 267976855);
|
||||
assert_eq!(stat.cpu, 0.012170230149167183);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ProcFsMemInfo {
|
||||
pub memtotal: u64,
|
||||
|
Loading…
x
Reference in New Issue
Block a user