获取系统中各应用的运行时间

通过增加动态库获取应用的运行时间

同事提出一个问题:如何获取嵌入式设备系统中各个应用已运行的时间?这个问题的解决方案有多种,其中一种是使用功能较强的软件作为系统的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//stat文件包含了进程的启动时间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


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部