在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

Logo

更多推荐