获取系统中各应用的运行时间
通过增加动态库获取应用的运行时间
同事提出一个问题:如何获取嵌入式设备系统中各个应用已运行的时间?这个问题的解决方案有多种,其中一种是使用功能较强的软件作为系统的init进程和服务管理,例如systemd;以它启动各应用软件服务后,可通过systemctl status XXXXX.service获取某个服务的运行时间:
root@ubuntu:~# systemctl status dbus.service
● dbus.service - D-Bus System Message BusLoaded: loaded (/lib/systemd/system/dbus.service; static)Active: active (running) since Sat 2023-05-13 09:20:21 CST; 11h ago
TriggeredBy: ● dbus.socketDocs: man:dbus-daemon(1)
如果使用这种方案,那么就要求在嵌入式设备上安装systemd;之后编写脚本遍历正在运行的各个系统和应用服务,获取并汇总各个进程的运行时间。然而,笔者很少见到有嵌入式设备选择systemd作为系统的init进程管理软件。
另一种可行的方案是,增加一个动态库,当动态库被加载时会自动执行一个函数,记录应用开始运行时的系统时间。以下是动态库的全部代码:
#include
#include unsigned long start_time;static void auto_run_func(void) __attribute__((used, constructor));unsigned long app_run_duration(void)
{struct timespec tspec;(void) clock_gettime(CLOCK_BOOTTIME, &tspec);return ((unsigned long) tspec.tv_sec) - start_time;
}static void auto_run_func(void)
{struct timespec tspec;(void) clock_gettime(CLOCK_BOOTTIME, &tspec);start_time = (unsigned long) tspec.tv_sec;
}
不过该方案的缺陷是需要修改各个应用的代码,并增加进程间通信的机制(通过上面的app_run_duration函数将应用的运行时间发送出去),才能够一次性地获得所有应用已运行的时间。为避免不必要的代码改动,最后笔者放弃了这一实现方案(不过该方案也有可取之处,后面会提到)。
通过读取应用的状态信息计算应用的运行时间
Linux内核官方文档为这个问题提供了一种解决方案。在proc文档的Contents of the stat files表格中,指出/proc/文件包含了进程的启动时间start_time:
Table 1-4: Contents of the stat files (as of 2.6.30-rc7)============= ===============================================================Field Content============= ===============================================================pid process idtcomm filename of the executablestate state (R is running, S is sleeping, D is sleeping in anuninterruptible wait, Z is zombie, T is traced or stopped)ppid process id of the parent processpgrp pgrp of the processsid session idtty_nr tty the process usestty_pgrp pgrp of the ttyflags task flagsmin_flt number of minor faultscmin_flt number of minor faults with child'smaj_flt number of major faultscmaj_flt number of major faults with child'sutime user mode jiffiesstime kernel mode jiffiescutime user mode jiffies with child'scstime kernel mode jiffies with child'spriority priority levelnice nice levelnum_threads number of threadsit_real_value (obsolete, always 0)start_time time the process started after system boot
上面的文档并未给出start_time的单位或含义。经查看相关Linux内核源码可知,其对应应用刚创建时的内核启动时间start_boottime,该值的单位为纳秒。之后通过nsec_to_clock_t转换为一个tick的单位。
/* linux-6.2/fs/proc/array.c */static int do_task_stat(struct seq_file *m, struct pid_namespace *ns,struct pid *pid, struct task_struct *task, int whole)
{....../* apply timens offset for boottime and convert nsec -> ticks */start_time =nsec_to_clock_t(timens_add_boottime_ns(task->start_boottime));seq_put_decimal_ull(m, "", pid_nr_ns(pid, ns));seq_puts(m, " (");proc_task_name(m, task, false);seq_puts(m, ") ");......seq_put_decimal_ull(m, " ", 0);seq_put_decimal_ull(m, " ", start_time);
}
不过,nsec_to_clock_t函数转换为tick的单位,并不是内核内核配置选项CONFIG_HZ指定的频率;这是因为,当内核处理空闲状态时,内核会调度tick定时器的触发周期,以进一步降底CPU的功耗:
/* kernel/time/time.c */
u64 nsec_to_clock_t(u64 x)
{
#if (NSEC_PER_SEC % USER_HZ) == 0return div_u64(x, NSEC_PER_SEC / USER_HZ);
#elif (USER_HZ % 512) == 0return div_u64(x * USER_HZ / 512, NSEC_PER_SEC / 512);
#else/* * max relative error 5.7e-8 (1.8s per year) for USER_HZ <= 1024,* overflow after 64.99 years.* exact for HZ=60, 72, 90, 120, 144, 180, 300, 600, 900, ...*/return div_u64(x * 9, (9ull * NSEC_PER_SEC + (USER_HZ / 2)) / USER_HZ);
#endif
}
对于大多数Linux支持的芯片架构,宏定义USER_HZ为100,即对应0.01秒;函数nsec_to_clock_t的作用是将单位为纳秒的x转换为单位为1/USER_HZ(即0.01秒)的值。那么该函数的作用是用10000000除x,得到的值通过进程的/proc/self/stat文件向应用层提供。将该值再除以USER_HZ,就是应用开始运行时系统的启动时间,单位为秒。那么其与当前系统已运行的时间之差,就是应用进程已持续运行的时间。该方案简单可行,笔者在工作中就依此实现了一个名为apptime的Lua脚本,可遍历嵌入式设备中正在运行的所有应用进程,根据CONFIG_HZ值计算各进程已运行的时间。因该Lua脚本“太过浅陋,不便展示”源码。
用Rust语言实现各进程运行时间的计算
笔者使用Rust语言重新实现了工作中编写的apptime Lua脚本,基本的处理逻辑与Lua脚本保持一致。从代码量上看,Lua版本的apptime接近200行代码,而Rust版本只有105行左右的代码;此外,Rust编译生成的二进制apptime应用大小超过1MB(Lua脚本文件大小不足10KB)。apptime Rust工程的Cargo.toml文件内容为:
# Cargo.toml
[package]
name = "apptime"
version = "0.1.0"
edition = "2021"[dependencies]
glob = { "version" = "0.3.1" }
regex = { "version" = "1.7.3" }
libc = { "version" = "0.2.141" }
lazy_static = { "version" = "1.4" }
apptime的主代码文件内容如下:
use glob::glob;
use regex::Regex;
use lazy_static::lazy_static;
use std::path::{Path, PathBuf};
use libc::{timespec, clock_gettime, CLOCK_BOOTTIME};struct Appinfo {exe_name: String,app_name: String,app_pid: u64,start_tick: u64,
}impl Appinfo {fn new() -> Self {Self { exe_name: String::new(), app_name: String::new(), app_pid: 0, start_tick: 0 }}
}fn uptime() -> u64 {let mut nowt = timespec { tv_sec: 0, tv_nsec: 0 };let _ = unsafe { clock_gettime(CLOCK_BOOTTIME, &mut nowt as *mut timespec) };nowt.tv_sec as u64
}fn parse_appinfo(mut app: PathBuf, maxlen: &mut usize) -> Option<Appinfo> {lazy_static! {static ref REGEX_APP: Regex = Regex::new(r"^\d+\s+\(([^)]+)\)").unwrap();}// only interested in directoriesif !app.is_dir() {return None;}/* determine process PID */let apppid: u64 = {let pidstr: &str = app.file_name().unwrap().to_str().unwrap();str::parse::<u64>(pidstr).ok()?};app.push("exe");let realexe = std::fs::read_link(AsRef::<Path>::as_ref(&app)).ok()?;let exename = realexe.file_name().unwrap();/* check the length of executable file name */match exename.len() {0 => return None,elen if elen > *maxlen => *maxlen = elen,_ => { },}/* read /proc/XXX/stat */let proc_stat = format!("/proc/{}/stat", apppid);let appst = std::fs::read_to_string(AsRef::<Path>::as_ref(&proc_stat)).ok()?;/* extract application name */let cappname = REGEX_APP.captures(&appst)?;let appmatch = cappname.get(1).unwrap();let matchend = appmatch.end();if (matchend + 0x2) >= appst.len() {eprintln!("Error, invalid /proc/XXX/stat for {}", exename.to_string_lossy());return None;}let mut fields = appst[matchend + 0x2 ..].split_whitespace();let apptick: u64 = str::parse(fields.nth(19).unwrap_or("0")).unwrap();let mut appi = Appinfo::new();appi.exe_name = exename.to_string_lossy().into_owned();appi.app_name = appmatch.as_str().to_string();appi.app_pid = apppid;appi.start_tick = apptick;Some(appi)
}fn main() {let cfghz: u64 = if let Ok(confighz) = std::env::var("USER_HZ") {str::parse(confighz.as_str()).unwrap_or(100u64) } else { 100u64 };if cfghz == 0 {eprintln!("Error, invalid zero USER_HZ value.\n\tUSER_HZ is usually defined as 100 in the linux kernel.");std::process::exit(1);}let mut maxlen: usize = 1;let apps = glob("/proc/*").expect("Error, failed to glob running process.");let mut app_info: Vec<Appinfo> = apps.into_iter().filter_map(|papp| if papp.is_err() { None } else { parse_appinfo(papp.unwrap(), &mut maxlen) }).collect();maxlen += 1;if app_info.len() == 0 {eprintln!("Error, no running applications found.");std::process::exit(1);}app_info.sort_by(|pa, pb| pa.start_tick.partial_cmp(&pb.start_tick).unwrap());let uptim = uptime();for appi in app_info.iter() {let startim: u64 = appi.start_tick / cfghz;let duration = uptim - startim;let hour: u64 = duration / 3600;let mins: u64 = (duration % 3600) / 60;println!("{:,appi.exe_name, appi.app_name, hour, mins, duration, appi.app_pid);}
}
编译完成后,在笔者的树莓派设备上运行效果如下:
root@debian:~# uname -a
Linux debian 5.10.63-v8+ #3 SMP PREEMPT Sat Jan 15 23:11:15 CST 2022 aarch64 GNU/Linux
root@debian:~# ls -lh apptime
-rwxr-xr-x 1 pi pi 1.4M May 13 12:23 apptime
root@debian:~# ./apptime
systemd (systemd ), duration: 0 hours, 16 minutes (995 seconds), PID: 1
systemd-journald (systemd-journal ), duration: 0 hours, 16 minutes (989 seconds), PID: 130
udevadm (systemd-udevd ), duration: 0 hours, 16 minutes (987 seconds), PID: 176
avahi-daemon (avahi-daemon ), duration: 0 hours, 16 minutes (983 seconds), PID: 360
cron (cron ), duration: 0 hours, 16 minutes (983 seconds), PID: 362
dbus-daemon (dbus-daemon ), duration: 0 hours, 16 minutes (983 seconds), PID: 363
lttng-sessiond (lttng-sessiond ), duration: 0 hours, 16 minutes (983 seconds), PID: 373
avahi-daemon (avahi-daemon ), duration: 0 hours, 16 minutes (983 seconds), PID: 376
rsyslogd (rsyslogd ), duration: 0 hours, 16 minutes (983 seconds), PID: 383
systemd-logind (systemd-logind ), duration: 0 hours, 16 minutes (983 seconds), PID: 402
thd (thd ), duration: 0 hours, 16 minutes (983 seconds), PID: 403
wpa_supplicant (wpa_supplicant ), duration: 0 hours, 16 minutes (983 seconds), PID: 404
dhcpcd (dhcpcd ), duration: 0 hours, 16 minutes (982 seconds), PID: 443
rngd (rngd ), duration: 0 hours, 16 minutes (982 seconds), PID: 462
lttng-sessiond (lttng-runas ), duration: 0 hours, 16 minutes (982 seconds), PID: 470
cupsd (cupsd ), duration: 0 hours, 16 minutes (982 seconds), PID: 478
agetty (agetty ), duration: 0 hours, 16 minutes (981 seconds), PID: 495
agetty (agetty ), duration: 0 hours, 16 minutes (981 seconds), PID: 496
wpa_supplicant (wpa_supplicant ), duration: 0 hours, 16 minutes (981 seconds), PID: 497
sshd (sshd ), duration: 0 hours, 16 minutes (981 seconds), PID: 507
cups-browsed (cups-browsed ), duration: 0 hours, 16 minutes (981 seconds), PID: 509
hciattach (hciattach ), duration: 0 hours, 16 minutes (974 seconds), PID: 521
bluetoothd (bluetoothd ), duration: 0 hours, 16 minutes (974 seconds), PID: 536
systemd-timesyncd (systemd-timesyn ), duration: 0 hours, 16 minutes (971 seconds), PID: 733
sshd (sshd ), duration: 0 hours, 14 minutes (884 seconds), PID: 747
systemd (systemd ), duration: 0 hours, 14 minutes (883 seconds), PID: 750
systemd ((sd-pam ), duration: 0 hours, 14 minutes (883 seconds), PID: 751
pipewire (pipewire ), duration: 0 hours, 14 minutes (882 seconds), PID: 770
pulseaudio (pulseaudio ), duration: 0 hours, 14 minutes (882 seconds), PID: 771
sshd (sshd ), duration: 0 hours, 14 minutes (882 seconds), PID: 776
rtkit-daemon (rtkit-daemon ), duration: 0 hours, 14 minutes (882 seconds), PID: 778
polkitd (polkitd ), duration: 0 hours, 14 minutes (882 seconds), PID: 781
dbus-daemon (dbus-daemon ), duration: 0 hours, 14 minutes (882 seconds), PID: 785
pipewire-media-session (pipewire-media- ), duration: 0 hours, 14 minutes (882 seconds), PID: 786
bash (bash ), duration: 0 hours, 14 minutes (880 seconds), PID: 792
sudo (sudo ), duration: 0 hours, 1 minutes (105 seconds), PID: 1009
bash (bash ), duration: 0 hours, 1 minutes (105 seconds), PID: 1010
apptime (apptime ), duration: 0 hours, 0 minutes (0 seconds), PID: 1041
root@debian:~# uptime22:33:38 up 16 min, 1 user, load average: 0.18, 0.71, 0.61
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
