在Linux中,cpustat定期转储正在运行的进程的当前CPU利用率统计信息。cpustat已被优化为具有最小的CPU开销,与top相比,通常使用约35%的CPU。cpustat还包括一些简单的统计分析选项,可以帮助描述CPU的加载方式。
cpustat介绍
cpustat是一个转储当前运行任务(即进程或内核线程)的CPU利用率的程序。cpustat用于监视系统中长期存在的进程的活动,例如守护进程、内核线程以及典型的用户进程。
cpustat [ options ] [delay [count]]
cpustat命令行选项:
-h帮助
-a 基于所有CPU节拍而不是一个CPU来计算CPU利用率
-c 从进程命令字段获取命令名(CPU成本较低)
-d 删除目录basename命令信息
-D 显示运行结束时CPU利用率统计数据的分布
-g 显示运行结束时CPU利用率统计的总计
-i 忽略了统计数据中的cpustat
-l 显示长(完整)命令信息
-n 指定要显示的任务数
-q 安静运行,使用选项-r非常有用
-r 指定要将样本转储到的逗号分隔值输出文件。
-s 显示简短命令信息
-S 时间戳输出
-t 指定忽略小于此值的样本的任务刻度计数阈值。
-T 显示总CPU利用率统计数据
-x 显示额外的统计数据(平均负载、平均cpu频率等)
对于在采样时间内消耗了一些CPU的每个正在运行的任务,将显示以下信息:
标题说明
%CPU 使用的CPU总数(百分比)
%USR 空间中使用的USR CPU(百分比)
%SYS (内核)空间中使用的SYS CPU(百分比)
PID 进程ID
S进程状态,R(运行),S(休眠),D(等待,磁盘休眠),T(停止),T(跟踪停止),W(寻呼),X(死),X(死)、K(唤醒),W),P(停止)。
CPU采样时进程使用的CPU。
Time 自进程启动以来使用的CPU总时间。
Task 进程命令行信息(来自进程命令行或命令字段)
cpustat定期报告正在运行的任务的当前CPU利用率,并且可以在运行结束时报告每个CPU和每个任务的利用率统计信息。
cpustat工具实现的一些知识点
- Linux 当前线程数查询
/proc/sys/kernel/pid_max #查系统支持的最大线程数,一般会很大,相当于理论值。
- /proc/pid/cmdline :进程的命令行参数
该文件保存了进程的完整命令行. 如果该进程已经 被交换出内存, 或者该进程已经僵死, 那么就没有 任何东西在该文件里, 这时候对该文件的读操作将返回零 个字符. 该文件以空字符null 而不是换行符作为结 束标志.
- /proc/stat: 计算cpu的利用率
stat 进程状态信息, 被命令 ps(1) 使用.
现将该文件里各域, 以及他们的 scanf(3) 格式说明符, 按顺序分述如下:
pid %d 进程标识.
comm %s
可执行文件的文件名, 包括路径. 该文件是否可 见取决于该文件是否已被交换出内存.
state %c
";RSDZT"; 中的一个, R 是正在运行, S 是 在可中断的就绪态中睡眠, D 是在不可中 断的等待或交换态中睡眠, Z 是僵死, T 是被跟踪或被停止(由于收到信号).
ppid %d
父进程 PID.
pgrp %d
进程的进程组 ID.
session %d
进程的会话 ID.
tty %d 进程所使用终端.
tpgid %d
当前拥有该进程所连接终端的进程所在的进程 组 ID.
flags %u
进程标志. 目前每个标志都设了数学位, 所以输出里就不包括该位. crt0.s 检查数学仿真 这可能是一个臭虫, 因为不是每个进 程都是用 c 编译的程序. 数学位应该是十 进制的
4, 而跟踪位应该是十进制的 10.
minflt %u
进程所导致的小错误(minor faults)数目, 这样的 小错误(minor faults)不需要从磁盘重新载入一个 内存页.
cminflt %u
进程及其子进程所导致的小错误(minor faults)数目.
majflt %u
进程所导致的大错误(major faults)数目, 这样的 大错误(major faults)需要重新载入内存页.
cmajflt %u
进程及其子进程所导致的大错误(major faults)数目.
utime %d
进程被调度进用户态的时间(以 jiffy 为单 位, 1 jiffy=1/100 秒,另外不同硬件体系略有不同).
stime %d
进程被调度进内核态的时间, 以 jiffy 为 单位.
cutime %d
进程及其子进程被调度进用户态的时间, 以 jiffy 为单位.
cstime %d
进程及其子进程被调度进内核态的时间, 以 jiffy 为单位.
counter %d
如果进程不是当前正在运行的进程, 就是 进程在下个时间片当前可以拥有的最大时 间, 以 jiffy 为单位. 如果进程是当前正 在运行的进程, 就是当前时间片中所剩下 jiffy
数目.
priority %d
标准优先数只再加上 15, 在内核里该值总 是正的.
timeout %u
当前至进程的下一次间歇时间, 以 jiffy 为单位.
itrealvalue %u
由于计时间隔导致的下一个 SIGALRM 发送进程的时延,以 jiffy 为单位.
starttime %d
进程自系统启动以来的开始时间, 以 jiffy 为单位.
vsize %u
虚拟内存大小.
rss %u Resident Set Size(驻留大小): 进程所占用的真实内 存大小, 以页为单位, 为便于管理而减去 了 3. rss 只包括正文, 数据以及堆栈的空间,
但不包括尚未要求装入内存的或已被交换出去的.
rlim %u
当前进程的 rss 限制, 以字节为单位, 通 常为 2,147,483,647.
startcode %u
正文部分地址下限.
endcode %u
正文部分地址上限.
startstack %u
堆栈开始地址.
kstkesp %u
esp(32 位堆栈指针) 的当前值, 与在进程 的内核堆栈页得到的一致.
kstkeip %u
EIP(32 位指令指针)的当前值.
signal %d
待处理信号的 bitmap(通常为 0).
blocked %d
被阻塞信号的 bitmap(对 shell 通常是 0, 2).
sigignore %d
被忽略信号的 bitmap.
sigcatch %d
被俘获信号的 bitmap.
wchan %u
进程在其中等待的通道, 实际是一个系统 调用的地址. 如果你需要文本格式的, 也 可以在名字列表中找到. (如果有最新版本的 /etc/psdatabase, 你 可以在 ps -l 的结果中的
WCHAN 域看到)
- /sys/devices/system/cpu/cpu:获取平均CPU频率
查看CPU核心的当前运行频率,使用如下的命令
其中cpu0指第一个核心,根据情况可以换成cpu1, cpu2,cpu3…,在online文件里描述cpu的状态,0代表下线,1代表上线
- /proc/loadavg : 获取当前负载平均状态
前三个数字是1、5、15分钟内的平均进程数(有人认为是系统负荷的百分比,其实不然,有些时候可以看到200甚至更多)。
第四个值的分子是正在运行的进程数,分母是进程总数,最后一个是最近运行的进程ID号。
这里的平均负载也就是可运行的进程的平均数。
- ncurses:顶部模式下的设置显示
static void cpustat_top_setup(void)
{
(void)initscr(); //屏幕就会初始化并进入curses 模式
(void)cbreak(); //cbreak()函数都可以禁止行缓冲
(void)noecho(); // 禁止输入的字符出现在屏幕上
(void)nodelay(stdscr, 1); // nodelay选项使getch成为非阻塞调用。如果没有输入就绪,getch将返回ERR。
(void)keypad(stdscr, 1); //允许使用功能键,例如:F1、F2、方向键等功能键。
(void)curs_set(0);// curs_set例程将光标状态设置为不可见、正常或非常可见,可见性分别为0、1或2。
}
Linux下C/C++实现cpustat
...
static int get_pid_max_digits(void)
{
ssize_t n;
int digits, fd;
const int default_digits = 6;
const int min_digits = 5;
char buf[32];
digits = default_digits;
fd = open("/proc/sys/kernel/pid_max", O_RDONLY);
if (fd < 0)
goto ret;
n = read(fd, buf, sizeof(buf) - 1);
(void)close(fd);
if (n < 0)
goto ret;
buf[n] = '\0';
digits = 0;
while (buf[digits] >= '0' && buf[digits] <= '9')
digits++;
if (digits < min_digits)
digits = min_digits;
ret:
return digits;
}
static void info_banner_dump(const double time_now,int umax_w,int smax_w,int usmax_w)
{
static char str[256];
char *ptr = str;
int i;
char tmp[256];
size_t n, sz = sizeof(str);
snprintf(tmp, sizeof(tmp), "%*s %*s %*s ",
usmax_w, "%CPU",
umax_w, "%USR",
smax_w, "%SYS");
n = strlen(tmp);
(void)strncpy(ptr, tmp, sizeof(str));
ptr += n;
sz -= n;
for (i = 0; i < pid_max_digits - 3; i++, ptr++)
*ptr = ' ';
sz -= i;
(void)strncpy(ptr, "PID S CPU Time Task", sz);
ptr += 23;
if (UNLIKELY(opt_flags & OPT_TIMESTAMP)) {
struct tm tm;
get_tm(time_now, &tm);
(void)strncpy(ptr, " (", 4);
ptr += 3;
ptr += putint(ptr, 2, tm.tm_hour, true);
*ptr = ':';
ptr++;
ptr += putint(ptr, 2, tm.tm_min, true);
*ptr = ':';
ptr++;
ptr += putint(ptr, 2, tm.tm_sec, true);
*ptr = ')';
ptr++;
}
*ptr = '\0';
df.df_putstrnl(str, ptr - str);
}
...
static inline void info_total_dump(const double u_total,const double s_total)
{
if (UNLIKELY(opt_flags & OPT_TOTAL)) {
char buffer[256], *ptr = buffer;
ptr += putdouble(ptr, u_total + s_total, 100, 6);
*(ptr++) = ' ';
ptr += putdouble(ptr, u_total, 100, 6);
*(ptr++) = ' ';
ptr += putdouble(ptr, s_total, 100, 6);
*(ptr++) = ' ';
ptr += putstr(ptr, 5, "Total");
df.df_putstrnl(buffer, ptr - buffer);
}
}
static char *load_average(void)
{
static char buffer[4096];
char *ptr = buffer;
ssize_t len;
int fd, skip = 3;
if (UNLIKELY((fd = open("/proc/loadavg", O_RDONLY)) < 0))
goto unknown;
len = read(fd, buffer, sizeof(buffer) - 1);
(void)close(fd);
if (UNLIKELY(len < 1))
goto unknown;
buffer[len] = '\0';
for (;;)
{
if (*ptr == '\0')
{
skip--;
break;
}
if (*ptr == ' ')
{
skip--;
if (skip == 0)
{
*ptr = '\0';
break;
}
}
ptr++;
}
if (skip != 0)
goto unknown;
return buffer;
unknown:
return "unknown";
}
...
int main(int argc, char **argv)
{
...
for (;;) {
int c = getopt(argc, argv, "acdDghiln:qr:sSt:Tp:xX");
if (c == -1)
break;
switch (c)
{
case 'a':
opt_flags |= OPT_TICKS_ALL;
break;
case 'c':
opt_flags |= OPT_CMD_COMM;
break;
case 'd':
opt_flags |= OPT_DIRNAME_STRIP;
break;
case 'D':
opt_flags |= (OPT_SAMPLES | OPT_DISTRIBUTION);
break;
case 'g':
opt_flags |= OPT_GRAND_TOTAL;
break;
case 'h':
show_usage();
exit(EXIT_SUCCESS);
case 'i':
opt_flags |= OPT_IGNORE_SELF;
break;
case 'l':
opt_flags |= OPT_CMD_LONG;
break;
case 'n':
errno = 0;
n_lines = (int32_t)strtol(optarg, NULL, 10);
if (errno)
{
(void)fprintf(stderr, "Invalid value for -n option\n");
exit(EXIT_FAILURE);
}
if (n_lines < 1)
{
(void)fprintf(stderr,
"-n option must be greater than 0\n");
exit(EXIT_FAILURE);
}
break;
case 'p':
errno = 0;
opt_pid = strtol(optarg, NULL, 10);
if (errno)
{
(void)fprintf(stderr,
"Invalid value for -o option\n");
exit(EXIT_FAILURE);
}
opt_flags |= OPT_MATCH_PID;
break;
case 's':
opt_flags |= OPT_CMD_SHORT;
break;
case 'S':
opt_flags |= OPT_TIMESTAMP;
break;
case 't':
opt_threshold = atof(optarg);
if (opt_threshold < 0.0) {
(void)fprintf(stderr,
"-t threshold must be 0 or more.\n");
exit(EXIT_FAILURE);
}
break;
case 'T':
opt_flags |= OPT_TOTAL;
break;
case 'q':
opt_flags |= OPT_QUIET;
break;
case 'r':
csv_results = optarg;
opt_flags |= OPT_SAMPLES;
break;
case 'x':
opt_flags |= OPT_EXTRA_STATS;
break;
case 'X':
opt_flags |= OPT_TOP;
break;
default:
show_usage();
exit(EXIT_FAILURE);
}
}
...
}
运行结果:
当不带任何参数运行时,cpustat 默认会显示以下信息:
cpustat
每秒转储CPU统计数据,直到停止。
cpustat -n 20 60
每60秒转储前20个CPU消耗任务,直到停止。
cpustat 10 5
每10秒转储一次CPU统计数据,仅5次。
cpustat -x -D -a 1 300
每隔5分钟收集一次统计数据,并显示额外的CPU统计数据运行结束时每个任务和每个CPU的利用率分布。此外,比例CPU利用率除以CPU数量,因此100%利用率意味着100%CPU而不是1个CPU的100%。
cpustat -s 100 -s 10 -n 20
这将每100毫秒对所有进程进行一次采样,并在10次采样后(即每5秒)汇总此数据。
cpustat 5 5 -gxDST
If you need the complete source code of cpustat, please add WeChat number (c17865354792)
总结
cpustat 是 Linux 下一个用于linux下的CPU使用率监控工具。
Welcome to follow WeChat official account【程序猿编码】
参考:man 5 proc