Busybox包含了很多小工具,这些工具短小精悍有效。bootchartd就是其中之一。
Bootchart is a tool for performance analysis and virtualization of the GNU/Linux boot process.
Resource utilization and process information are collected during the boot process and are later rendered in a PNG, SVG or EPS encoded chart.
将bootchartd使能
要使能bootchartd首先需要配置Busybox:
修改project/prj_phone/config/normal/common/config.vendor:
CONFIG_USER_BUSYBOX_BOOTCHARTD=y (使能bootchartd) CONFIG_USER_BUSYBOX_TAR=y (bootchard需要使用tar工具) CONFIG_USER_BUSYBOX_FEATURE_SEAMLESS_GZ=y (tar子命令,-x选项) |
然后还需要修改UBoot设置,将rdinit设置为/bin/bootchartd:
drivers/mtd/partition/partition.c
add_partition_to_bootargs ->sprintf((char *)bootargs_cmd, "rdinit=/bin/bootchartd console=ttyS1,115200 no_console_suspend");
|
内核会去解析Kernel command line:
static int __init rdinit_setup(char *str) { unsigned int i;
ramdisk_execute_command = str; /* See "auto" comment in init_setup */ for (i = 1; i < MAX_INIT_ARGS; i++) argv_init[i] = NULL; return 1; } __setup("rdinit=", rdinit_setup);
|
在start_kernel->rest_init->kernel_init->init_post中会执行ramdisk_execute_command:
static noinline int init_post(void) { /* need to finish all async __init code before freeing the memory */ … if (ramdisk_execute_command) { run_init_process(ramdisk_execute_command); printk(KERN_WARNING "Failed to execute %s\n", ramdisk_execute_command); } …
}
|
在生成的ramdisk中确保bootchartd连接到busybox。
在project/prj_phone/rootfs/normal/etc/rc中添加启动bootchart的脚本,然后在adb shell进入后停止bootchartd。
--- rc (revision 1173) +++ rc (working copy) @@ -1,5 +1,5 @@ #!/bin/sh
/bin/mount -t proc proc /proc echo "Starting mdevd..." @@ -8,6 +7,7 @@ echo /sbin/mdev > /proc/sys/kernel/hotplug /sbin/mdev -s +/bin/bootchartd start (将bootchartd放在proc挂载之后,因为bootchartd需要依赖proc节点。)
|
最后结果生成在/var/log/bootlog.tgz。
PS:默认情况下执行bootchartd错误信息没有吐出,可以bootchart start 2>&1这样子输出。
bootchart分析工具pybootchartgui
使用Ubuntu默认的bootchart解析会失败,在这里下载工具。然后开始编译bootchart:
编译后使用pybootchartgui.py处理,然后查看结果bootchart.png。
./bootchart/pybootchartgui.py bootlog.tgz --show-all |
生成bootchart.png文件:
这里可以清晰地看到CPU(user+sys)、I/O(wait)、Disk的使用情况,以及每个进程的Running、Unint.sleep、Sleeping、Zombie情况。
如果阅读输出结果
如下可以将Bootchart输出结果分为三部分:系统信息、系统资源概览、进程信息。
系统信息
这些信息主要保存在bootlog.tgz的header中,包括title、uname、kernel options等。还有整个bootchart分析时间段。
系统资源概览
CPU资源的占用率,可以看出统计只有在bootchart start之后才开始。可以看出整个bootchart期间的CPU占用率变化情况,在cfgnv_init.sh到internet.sh期间,CPU占用率非常高。
PS:I/O、Disk相关的显示可能存在问题。
磁盘信息对应proc_diskstats.log。
进程信息
对应proc_ps.log和proc_stat.log。
1.通过下图可以看出进程telnetd的开始时间和结束时间;结合可以知道今晨执行期间不同阶段的状态。
2.通过下图可以看出进程间的父子关系,父进程引出虚线指向子进程。
使用Bootchart帮助文档。
pybootchartgui简单分析
入口文件是pybootchartgui.py,实体在pybootchartgui文件夹中。
pybootchartgui.py->main.py-|->parsing.py-|->process_tree.py
|->batch.py-|->draw.py
|->gui.py
Bootchart代码分析
Busybox的官方代码在github.com的镜像。
在applet_tables.h定义了bootchartd的入口:
const char applet_names[] ALIGN1 = "" … "bootchartd" "\0" … ;
int (*const applet_main[])(int argc, char **argv) = { … bootchartd_main, … };
|
bootchartd_main
int bootchartd_main(int argc UNUSED_PARAM, char **argv) { unsigned sample_period_us; pid_t parent_pid, logger_pid; smallint cmd; int process_accounting; enum { CMD_STOP = 0, CMD_START, CMD_INIT, CMD_PID1, /* used to mark pid 1 case */ };
INIT_G();
parent_pid = getpid(); if (argv[1]) { cmd = index_in_strings("stop\0""start\0""init\0", argv[1]); if (cmd < 0) bb_show_usage(); if (cmd == CMD_STOP) { pid_t *pidList = find_pid_by_name("bootchartd"); (stop时,找到bootchartd这个进程,并关闭) while (*pidList != 0) { if (*pidList != parent_pid) kill(*pidList, SIGUSR1); pidList++; } return EXIT_SUCCESS; } } else { if (parent_pid != 1) (检查是否是init进程,如果不是,则说明是在命令行调用不带参数的bootchartd,显示usage) bb_show_usage(); cmd = CMD_PID1; } /* Here we are in START, INIT or CMD_PID1 state */
/* Read config file: */ (确认bootchartd的配置信息,采样间隔、采样数。) sample_period_us = 200 * 1000; process_accounting = 0; if (ENABLE_FEATURE_BOOTCHARTD_CONFIG_FILE) { char* token[2]; parser_t *parser = config_open2("/etc/bootchartd.conf" + 5, fopen_for_read); if (!parser) parser = config_open2("/etc/bootchartd.conf", fopen_for_read); while (config_read(parser, token, 2, 0, "#=", PARSE_NORMAL & ~PARSE_COLLAPSE)) { if (strcmp(token[0], "SAMPLE_PERIOD") == 0 && token[1]) sample_period_us = atof(token[1]) * 1000000; if (strcmp(token[0], "PROCESS_ACCOUNTING") == 0 && token[1] && (strcmp(token[1], "on") == 0 || strcmp(token[1], "yes") == 0) ) { process_accounting = 1; } } config_close(parser); if ((int)sample_period_us <= 0) sample_period_us = 1; /* prevent division by 0 */ } /* Create logger child: */ (创建用于采样子进程) logger_pid = fork_or_rexec(argv); if (logger_pid == 0) { /* child */ char *tempdir;
bb_signals(0 + (1 << SIGUSR1) + (1 << SIGUSR2) + (1 << SIGTERM) + (1 << SIGQUIT) + (1 << SIGINT) + (1 << SIGHUP) , record_signo);
if (DO_SIGNAL_SYNC) /* Inform parent that we are ready */ raise(SIGSTOP);
/* If we are started by kernel, PATH might be unset. * In order to find "tar", let's set some sane PATH: */ if (cmd == CMD_PID1 && !getenv("PATH")) putenv((char*)bb_PATH_root_path);
tempdir = make_tempdir(); (生成存放采样数据的临时目录) do_logging(sample_period_us, process_accounting); finalize(tempdir, cmd == CMD_START ? argv[2] : NULL, process_accounting); (打包log文件,并清理中间文件) return EXIT_SUCCESS; }
/* parent */
USE_FOR_NOMMU(argv[0][0] &= 0x7f); /* undo fork_or_rexec() damage */
if (DO_SIGNAL_SYNC) { /* Wait for logger child to set handlers, then unpause it. * Otherwise with short-lived PROG (e.g. "bootchartd start true") * we might send SIGUSR1 before logger sets its handler. */ waitpid(logger_pid, NULL, WUNTRACED); kill(logger_pid, SIGCONT); }
if (cmd == CMD_PID1) { (非init/start/stop三者之一的子命令) char *bootchart_init = getenv("bootchart_init"); if (bootchart_init) execl(bootchart_init, bootchart_init, NULL); execl("/init", "init", NULL); execl("/sbin/init", "init", NULL); bb_perror_msg_and_die("can't execute '%s'", "/sbin/init"); }
if (cmd == CMD_START && argv[2]) { /* "start PROG ARGS" */ pid_t pid = xvfork(); if (pid == 0) { /* child */ argv += 2; BB_EXECVP_or_die(argv); } /* parent */ waitpid(pid, NULL, 0); kill(logger_pid, SIGUSR1); } return EXIT_SUCCESS; }
|
do_logging
作为bootchartd的主题,将需要记录的log保存到文件中。
static void do_logging(unsigned sample_period_us, int process_accounting) { FILE *proc_stat = xfopen("proc_stat.log", "w"); FILE *proc_diskstats = xfopen("proc_diskstats.log", "w"); FILE *proc_meminfo = xfopen("proc_meminfo.log", "w"); //FILE *proc_netdev = xfopen("proc_netdev.log", "w"); FILE *proc_ps = xfopen("proc_ps.log", "w"); int look_for_login_process = (getppid() == 1); unsigned count = 60*1000*1000 / sample_period_us; /* ~1 minute */
if (process_accounting) { close(xopen("kernel_pacct", O_WRONLY | O_CREAT | O_TRUNC)); acct("kernel_pacct"); }
while (--count && !bb_got_signal) { char *p; int len = open_read_close("/proc/uptime", G.jiffy_line, sizeof(G.jiffy_line)-2); if (len < 0) goto wait_more; /* /proc/uptime has format "NNNNNN.MM NNNNNNN.MM" */ /* we convert it to "NNNNNNMM\n" (using first value) */ G.jiffy_line[len] = '\0'; p = strchr(G.jiffy_line, '.'); if (!p) goto wait_more; while (isdigit(*++p)) p[-1] = *p; p[-1] = '\n'; p[0] = '\0';
dump_file(proc_stat, "/proc/stat"); dump_file(proc_diskstats, "/proc/diskstats"); dump_file(proc_meminfo, "/proc/meminfo"); //dump_file(proc_netdev, "/proc/net/dev"); if (dump_procs(proc_ps, look_for_login_process)) { /* dump_procs saw a getty or {g,k,x}dm (在从/proc/xxx/stat读取到getty、gdm、kdm、xdm后,在2s内停止记录log。) * stop logging in 2 seconds: */ if (count > 2*1000*1000 / sample_period_us) count = 2*1000*1000 / sample_period_us; } fflush_all(); (fflush(NULL)会将所有打开输出流刷出) wait_more: usleep(sample_period_us); (每次读取后睡眠sample_period_us段时间。) } }
|
从这里可知,bootchartd从启动之后每个sample_period_us段时间,就从proc中读取信息,保存在相应的log中。
|
/proc/uptime |
16091.87 16040.75 |
proc_stat.log |
/proc/stat |
cpu 550 0 1958 1583767 0 0 84 0 0 0 cpu0 550 0 1958 1583767 0 0 84 0 0 0 intr 1337973 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 29 0 0 0 0 0 0 1 64 0 0 0 43 0 885974 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 25943 0 13160 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 339767 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 72990 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ctxt 1774228 btime 65536 processes 1541 procs_running 1 procs_blocked 0 softirq 72046 0 29501 1 639 0 0 29498 0 5045 7362
|
proc_diskstats.log |
/proc/diskstats |
1 0 ram0 0 0 0 0 0 0 0 0 0 0 0 1 1 ram1 0 0 0 0 0 0 0 0 0 0 0 1 2 ram2 0 0 0 0 0 0 0 0 0 0 0 1 3 ram3 0 0 0 0 0 0 0 0 0 0 0 1 4 ram4 0 0 0 0 0 0 0 0 0 0 0 1 5 ram5 0 0 0 0 0 0 0 0 0 0 0 1 6 ram6 0 0 0 0 0 0 0 0 0 0 0 1 7 ram7 0 0 0 0 0 0 0 0 0 0 0 1 8 ram8 0 0 0 0 0 0 0 0 0 0 0 1 9 ram9 0 0 0 0 0 0 0 0 0 0 0 1 10 ram10 0 0 0 0 0 0 0 0 0 0 0 1 11 ram11 0 0 0 0 0 0 0 0 0 0 0 1 12 ram12 0 0 0 0 0 0 0 0 0 0 0 1 13 ram13 0 0 0 0 0 0 0 0 0 0 0 1 14 ram14 0 0 0 0 0 0 0 0 0 0 0 1 15 ram15 0 0 0 0 0 0 0 0 0 0 0 7 0 loop0 0 0 0 0 0 0 0 0 0 0 0 7 1 loop1 0 0 0 0 0 0 0 0 0 0 0 7 2 loop2 0 0 0 0 0 0 0 0 0 0 0 7 3 loop3 0 0 0 0 0 0 0 0 0 0 0 7 4 loop4 0 0 0 0 0 0 0 0 0 0 0 7 5 loop5 0 0 0 0 0 0 0 0 0 0 0 7 6 loop6 0 0 0 0 0 0 0 0 0 0 0 7 7 loop7 0 0 0 0 0 0 0 0 0 0 0 31 0 mtdblock0 0 0 0 0 0 0 0 0 0 0 0 31 1 mtdblock1 0 0 0 0 0 0 0 0 0 0 0 31 2 mtdblock2 0 0 0 0 0 0 0 0 0 0 0 35 3 zftl3 0 0 0 0 0 0 0 0 0 0 0 31 3 mtdblock3 0 0 0 0 0 0 0 0 0 0 0 35 4 zftl4 0 0 0 0 0 0 0 0 0 0 0 31 4 mtdblock4 0 0 0 0 0 0 0 0 0 0 0 35 5 zftl5 0 0 0 0 0 0 0 0 0 0 0 31 5 mtdblock5 0 0 0 0 0 0 0 0 0 0 0 35 6 zftl6 2 6 64 0 2 0 16 75 0 75 75 31 6 mtdblock6 0 0 0 0 0 0 0 0 0 0 0 31 7 mtdblock7 0 0 0 0 0 0 0 0 0 0 0 35 8 zftl8 0 0 0 0 0 0 0 0 0 0 0 31 8 mtdblock8 0 0 0 0 0 0 0 0 0 0 0 31 9 mtdblock9 0 0 0 0 0 0 0 0 0 0 0 31 10 mtdblock10 0 0 0 0 0 0 0 0 0 0 0 31 11 mtdblock11 0 0 0 0 0 0 0 0 0 0 0 31 12 mtdblock12 0 0 0 0 0 0 0 0 0 0 0 31 13 mtdblock13 0 0 0 0 0 0 0 0 0 0 0 35 14 zftl14 0 0 0 0 0 0 0 0 0 0 0 31 14 mtdblock14 0 0 0 0 0 0 0 0 0 0 0 31 15 mtdblock15 0 0 0 0 0 0 0 0 0 0 0 31 16 mtdblock16 0 0 0 0 0 0 0 0 0 0 0 31 17 mtdblock17 0 0 0 0 0 0 0 0 0 0 0
|
proc_meminfo.log |
/proc/meminfo |
MemTotal: 23940 kB MemFree: 8240 kB Buffers: 0 kB Cached: 3644 kB SwapCached: 0 kB Active: 3216 kB Inactive: 2444 kB Active(anon): 2016 kB Inactive(anon): 0 kB Active(file): 1200 kB Inactive(file): 2444 kB Unevictable: 0 kB Mlocked: 0 kB SwapTotal: 0 kB SwapFree: 0 kB Dirty: 0 kB Writeback: 0 kB AnonPages: 2032 kB Mapped: 1540 kB Shmem: 0 kB Slab: 7980 kB SReclaimable: 1796 kB SUnreclaim: 6184 kB KernelStack: 728 kB PageTables: 244 kB NFS_Unstable: 0 kB Bounce: 0 kB WritebackTmp: 0 kB CommitLimit: 11968 kB Committed_AS: 6640 kB VmallocTotal: 999424 kB VmallocUsed: 130280 kB VmallocChunk: 732668 kB
|
proc_ps.log |
/proc/xxx/stat |
1 (init) S 0 1 1 0 -1 4194560 92 111177 6 7 0 135 223 301 20 0 1 0 3 1167360 91 4294967295 32768 461096 3201900288 32018 99704 3069296632 0 0 0 543239 3221332596 0 0 17 0 0 0 0 0 0 495616 497086 503808
|
|
|
|
dump_procs
static int dump_procs(FILE *fp, int look_for_login_process) { struct dirent *entry; DIR *dir = opendir("/proc"); int found_login_process = 0;
fputs(G.jiffy_line, fp); while ((entry = readdir(dir)) != NULL) { char name[sizeof("/proc/%u/cmdline") + sizeof(int)*3]; int stat_fd; unsigned pid = bb_strtou(entry->d_name, NULL, 10); if (errno) continue;
/* Android's version reads /proc/PID/cmdline and extracts * non-truncated process name. Do we want to do that? */
sprintf(name, "/proc/%u/stat", pid); (通过readdir遍历,然后读取此pid下的stat) stat_fd = open(name, O_RDONLY); if (stat_fd >= 0) { char *p; char stat_line[4*1024]; int rd = safe_read(stat_fd, stat_line, sizeof(stat_line)-2);
close(stat_fd); if (rd < 0) continue; stat_line[rd] = '\0'; p = strchrnul(stat_line, '\n'); *p++ = '\n'; *p = '\0'; fputs(stat_line, fp); (将读取stat的内容写入文件) if (!look_for_login_process) continue; p = strchr(stat_line, '('); if (!p) continue; p++; strchrnul(p, ')')[0] = '\0'; /* Is it gdm, kdm or a getty? */ if (((p[0] == 'g' || p[0] == 'k' || p[0] == 'x') && p[1] == 'd' && p[2] == 'm') || strstr(p, "getty") ) { found_login_process = 1; } } } closedir(dir); fputc('\n', fp); return found_login_process; }
|
finalize
写header文件,然后和之前保存的log文件一起打包,到/var/log/bootlog.tgz中。
优化提升
增加系统内存的监控
修改bootchartd的do_logging增加输出内容,比如增加meminfo。
FILE *proc_stat = xfopen("proc_stat.log", "w"); FILE *proc_diskstats = xfopen("proc_diskstats.log", "w"); + FILE *proc_meminfo = xfopen("proc_meminfo.log", "w"); //FILE *proc_netdev = xfopen("proc_netdev.log", "w"); FILE *proc_ps = xfopen("proc_ps.log", "w"); int look_for_login_process = (getppid() == 1); @@ -240,6 +241,7 @@ dump_file(proc_stat, "/proc/stat"); dump_file(proc_diskstats, "/proc/diskstats"); + dump_file(proc_meminfo, "/proc/meminfo"); //dump_file(proc_netdev, "/proc/net/dev"); if (dump_procs(proc_ps, look_for_login_process)) { /* dump_procs saw a getty or {g,k,x}dm
|
在bootlog.tgz压缩包中会增加proc_meminfo.log文件。分析结果如下,可以看出系统的内存使用情况。
修改pybootchartgui/draw.py的render_charts内存使用部分,mem_scale使用MemTotal,从上到下按照Free+Buffers+Cached+Used来显示。
可以清晰地看出整个内存的消耗情况,由于Buffers为0,所以MemTotal=MemFree+Cached+Used.
内核log分析
之前的内核启动和用户空间的启动都是分开处理的,如果能将两者整合到一张图表中,使用一根Timeline那么就能更好的进行度量启动时间。
pybootchartgui可以将dmesg的内容作为一个进程进行分析。
在busybox中使能busybox:
-# CONFIG_USER_BUSYBOX_DMESG is not set + CONFIG_USER_BUSYBOX_DMESG=y
|
修改bootchard.c,执行dmesg命令将内核log存放到dmesg中,然后打包给pybootchartgui分析。
+ system(xasprintf("dmesg >dmesg")); + /* Package log files */ - system(xasprintf("tar -zcf /var/log/bootlog.tgz header %s *.log", process_accounting ? "kernel_pacct" : "")); + system(xasprintf("tar -zcf /var/log/bootlog.tgz header dmesg %s *.log", process_accounting ? "kernel_pacct" : "")); /* Clean up (if we are not in detached tmpfs) */ if (tempdir) { unlink("header"); @@ -315,6 +319,8 @@ unlink("proc_diskstats.log"); //unlink("proc_netdev.log"); unlink("proc_ps.log"); + unlink("proc_meminfo.log"); + unlink("dmesg"); if (process_accounting) unlink("kernel_pacct"); rmdir(tempdir);
|
由于bootchart的单位是秒级,所以很多initcall细节被忽视了。只能看到一个大概的情况,其中k-boot是整个内核启动时间段。可以看出内核使用了多长时间。
内核和用户空间的同步
从上面可知内核和用户空间的Timeline图表都可以使用了,那么能否将这两者放到一个Timeline里面呢。
就来看看init_post这个函数。
static noinline int init_post(void) { /* need to finish all async __init code before freeing the memory */ async_synchronize_full(); free_initmem(); 内核的终点 mark_rodata_ro(); system_state = SYSTEM_RUNNING; numa_default_policy();
KERNEL_START_END=1;
current->signal->flags |= SIGNAL_UNKILLABLE;
printk(KERN_ALERT "%s ramdisk_execute_command=%s\n", __func__, ramdisk_execute_command); if (ramdisk_execute_command) { run_init_process(ramdisk_execute_command); printk(KERN_WARNING "Failed to execute %s\n", ramdisk_execute_command); }
/* * We try each of these until one succeeds. * * The Bourne shell can be used instead of init if we are * trying to recover a really broken machine. */ printk(KERN_ALERT "%s execute_command=%s\n", __func__, execute_command); if (execute_command) { run_init_process(execute_command); printk(KERN_WARNING "Failed to execute %s. Attempting " "defaults...\n", execute_command); } run_init_process("/sbin/init"); 用户空间的起点 run_init_process("/etc/init"); run_init_process("/bin/init"); run_init_process("/bin/sh");
panic("No init found. Try passing init= option to kernel. " "See Linux Documentation/init.txt for guidance."); }
|
从上面可知,可以将k-boot的终点作为init的起点。
pybootchartgui增强功能
./bootchart/pybootchartgui.py bootlog --show-all |
能显示多显示进程的pid。
很方便强大的工功能interactive模式
如下的图可以看出,可以进行矢量缩放,很方便可总体、细节。右上角的Show more可以看到进程pid。
Kernel boot视图则更形象的得出时间先后的瀑布形式结构图。
绿红+/-号适用于修改时间轴粒度,白色+/-使用缩放整体图像。
实战优化
从下图可以看出从init开始的进程之间的关系,init作为所有用户空间进程的父进程。
观察上面的图表,重点查看init分支出来的进程。
1.由于是单核CPU,所以他们应该是顺序执行。init的子进程,rc占用了最多的运行时间。
2.这个图形就像一个一个倒楼梯下来,那个楼梯距离长就表示独占时间长。
结合/etc/rc文件分析,更能看出问题所在。
1.nvserver
2.cfgnv_init.sh
3.internet.sh
4.sleep 7,有一个睡眠硬生生睡了7秒。
在进行简单的修改rc之后,效果明显提升。
-cfgnv_init.sh +cfgnv_init.sh & zte_usbCfgMng & sbin/at_ctl & @@ -175,8 +175,8 @@ # apps start #####sunquan start -netdog_init_set.sh -internet.sh +netdog_init_set.sh & +internet.sh & #####sunquan end @@ -188,7 +188,7 @@ #pc_server & ## please confirm your app should place after or before ## for dial up and wifi go faster -sleep 7 +sleep 2
|
结果如下,可以看出整个rc进程的执行时间从19s降低到7s。
小结:
1.如果daemon的内容可以推迟的话,尽量推迟。
2.如果可以并行执行,尽量并行。只留下必须串行的在rc中。
3.不能有长延时,sleep 7很恐怖。
扩展阅读
/proc/stat
bootchart主要读取第一行的数据。
cpu 433 0 1276 63007 0 1 92 0 0 0 cpu0 433 0 1276 63007 0 1 92 0 0 0 intr 458145 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 25 0 0 0 0 0 0 1 63 0 0 0 38 0 41926 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 27851 0 867 1 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 372841 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 14531 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ctxt 97452 btime 65536 processes 1437 procs_running 1 procs_blocked 0 softirq 41439 0 6091 1 147 0 0 28275 0 247 6678
|
第一行的数据表示的是CPU总的使用情况,依次是:user nice system idle iowait irq softirq steal guest guest_nice。
这些数值的单位是jiffies,jiffies是内核中的一个全局变量,用来记录系统以来产生的节拍数。在Linux中,一个节拍大致可理解为操作系统进程调度的最小时间片。
user(433) 从系统开始累计到当前时刻,处于用户态的运行时间,不包含nice值为负进程。
nice(0) 从系统启动开始累计到当前时刻,nice值为负的进程所占用的CPU时间。
system(1276) 从系统启动开始累计到当前时刻,处于核心态的运行时间。
idle(63007) 从系统启动开始累计到当前时刻,除IO等待时间以外的其它等待时间
iowait(0) 从系统启动开始累计到当前时刻,IO等待时间
irq(1) 从系统启动开始累计到当前时刻,硬中断时间
softirq(92) 从系统启动开始累计到当前时刻,软中断时间
steal(0)
guest(0)
guest_nice(0)
总的CPU时间=user+nice+system+idle+iowait+irq+softirq+steal+guest+guest_nice
/proc/xxx/stat
包含了某一进程的所有活动信息,该文件都是从进程启动开始累计到当前时刻。
proc下pid节点信息在fs/proc/base.c中定义:
static const struct pid_entry tid_base_stuff[] = { … INF("cmdline", S_IRUGO, proc_pid_cmdline), ONE("stat", S_IRUGO, proc_tid_stat), ONE("statm", S_IRUGO, proc_pid_statm), … }
|
proc_tid_stat->do_task_stat,这里面有打印这个节点的代码。和读取的信息对照一下,可以知道每个指的含义。
1(pid) (init) S(state) 0 1 1 0 -1 4194560 92 111177 6 7 0(utime) 145(stime) 326(cutime) 623(cstime) 20(priority) 0(nice) 1 0 3(start_time) 1167360 91 4294967295 32768 461096 3198336768 31983 36184 3069464568 0 0 0 543239 3221332596 0 0 17 0 0(rt_priority) 0(policy) 0 0 0 495616 497086 503808
|
pid:6873 进程号
stae:
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"Z (zombie)", /* 16 */
"X (dead)", /* 32 */
"x (dead)", /* 64 */
"K (wakekill)", /* 128 */
"W (waking)", /* 256 */
utime:0该任务在用户态运行的时间,单位为jiffies
stime:145该任务在核心态运行的时间,单位为jiffies
cutime:326所有已死线程在用户态运行的时间,单位为jiffies
cstime:623 所有已死在核心态运行的时间,单位为jiffies
待研究
- 使用taskstats.log,需要内核使能taskstats,通过CONFIG_TASKSTATS来打开。