在使用 Linux 作为应用服务器时,若遇到 CPU 资源被占用的情况,如何查看是哪些进程占用了大量 CPU 资源呢?接下来我将讲述如何查看占用了 CPU 资源的进程列表,并且按照 CPU 已使用率从大到小的顺序排序,仅查看前 10 名
top 命令是最熟悉、最基础的查看进程对资源占用情况的命令## 参数解析## -c:展示COMMAND的值时,不展示进程名称,而是展示启动进程的启动命令## -o %CPU:按照CPU使用率从高到低排序top -c -o %CPU
top 命令会采集 CPU 资源信息、内存资源信息、进程信息等大量信息,底层需要读取的文件多、还需要维护状态,属于重量级操作,在初始化时会占用大量 CPU 资源
top 命令对进程 CPU 使用率的第一次采样数据是不准确的:top 命令在获取进程 CPU 使用率时,底层是通过读取 /proc/进程PID/stat 文件内容来计算的。但是这个文件是系统自身维护的,记录的是进程自启动以来的 CPU 信息,所以只有在 3 秒刷新后的第二次采样结果才是正确的
由于 top 命令会采集大量信息,属于重量级操作,在初始化时会占用大量 CPU。这种特性对应用服务产生影响相对较大,不适于进行实时监控、日志采集等的高频场景,但是现场排查问题还是很适合的,毕竟操作非常简单、获取到的信息量又大
在 top 命令的基础上加参数、加计算,过滤掉第一次结果,仅获取第二次开始的前 10 条进程信息## 参数解析## LC_ALL=C:强制使用C语言环境,确保程序输出的格式是英文且可预测## top:命令## -b:top命令的参数,表示非交互式## -n3:top命令的参数,表示执行2次采样输出结果后退出## -d1:top命令的参数,表示采样之间的间隔时间,单位为秒## -c:展示COMMAND的值时,不展示进程名称,而是展示启动进程的启动命令## -o %CPU:按照CPU使用率从高到低排序## awk '/^top -/ { frame++ ; count=0 } frame >= 2 { if (++count <= 17) print }'## awk:系统自带的一个强大的文本处理工具,专门用于按行分析、提取和处理结构化文本数据## 命令格式:awk '模式 {动作}'## 模式:对哪些行执行操作,可省略## 动作:对匹配行做什么## 命令结果:对匹配行将以空格进行分割,以格式 $num 可以获取到每一个分割部分,num从1开始## /^top -/:匹配以 top - 开头的行## { frame++ ; count=0 }:每匹配到一行时,对变量frame的值加一,将变量count的值重置为0## frame >= 2 { if (++count <= 17) print }:当变量frame的值大于等于2时,打印从当前行开始的17行LC_ALL=C top -bn3 -d1 -c -o %CPU | awk '/^top -/ { frame++ ; count=0 } frame >= 2 { if (++count <= 17) print }'
- 可仅获取最关心的 CPU 占用率最高的前 10 个进程
top 命令会采集 CPU 资源信息、内存资源信息、进程信息等大量信息,底层需要读取的文件多、还需要维护状态,属于重量级操作,在初始化时会占用大量 CPU 资源
由于 top 命令会采集大量信息,属于重量级操作,在初始化时会占用大量 CPU。这种特性对应用服务产生影响相对较大,不适于进行实时监控、日志采集等的高频场景,但是现场排查问题还是很适合的,毕竟操作非常简单、获取到的信息量又大
前面讲了 top 命令,该命令底层是通过获取 /proc/进程PID/stat 文件内容来计算 CPU 使用率的,那么 /proc/进程PID/stat 文件是什么呢
/proc/进程PID/stat 文件是每个进程的内核级统计信息文件,包含 CPU 时间、状态、内存等。超级管理员可以访问所有用户拥有的进程的这些文件,但是普通用户只能访问自身拥有的进程的对应的文件
- 文件内容的格式是固定的,以空格进行隔开。下面讲解以空格隔开后的一些关键位置的数据

#!/bin/bash# 采样间隔,设置为1秒INTERVAL=1# 声明一个关联数组,用来保存第一次采样结果declare -A cpu1# 循环# IFS=:临时清空内部字段分隔符# read:从输入中读取一行,这里是读取到NUL字符位置# -r:禁止对反斜杠转义# -d '':指定行分隔符为NUL字符# dir:将前面的结果存入变量dir中while IFS= read -r -d '' dir; do # 剔除掉/proc/的目录前缀,仅获取PID pid=${dir#/proc/} # 确保目录是以PID开头 # 获取进程的stat文件内容赋值给变量 # 2>/dev/null:遇到异常时丢弃,静默处理 if [[ $pid =~ ^[0-9]+$ ]] && stat_line=$(cat "/proc/$pid/stat" 2>/dev/null); then # 将变量的内容按照预定的IFS的值进行分割 set -- $stat_line # 记录用户态CPU时间、内核态CPU时间的总和,若某一个位置没有则默认为0 cpu1[$pid]=$((${14:-0} + ${15:-0})) fi# 遍历/proc目录下的目录文件,且只遍历第一层子目录、子文件,且只需要名称是数字的子目录、子文件# -print0:使用NUL字符、也就是\0来进行分割,而不是使用默认的换行符,因为Linux中允许在目录、文件的名称中使用换行符# 2>/dev/null:遇到异常时丢弃,静默处理done < <(find /proc -maxdepth 1 -name '[0-9]*' -print0 2>/dev/null)# 睡眠sleep "$INTERVAL"# 创建一个临时文件tmpfile=$(mktemp)# 循环# IFS=:临时清空内部字段分隔符# read:从输入中读取一行,这里是读取到NUL字符位置# -r:禁止对反斜杠转义# -d '':指定行分隔符为NUL字符# dir:将前面的结果存入变量dir中while IFS= read -r -d '' dir; do # 剔除掉/proc/的目录前缀,仅获取PID pid=${dir#/proc/} # 确保目录是以PID开头 # 获取进程的stat文件内容赋值给变量 # 2>/dev/null:遇到异常时丢弃,静默处理 if [[ $pid =~ ^[0-9]+$ ]] && stat_line=$(cat "/proc/$pid/stat" 2>/dev/null); then # 将变量的内容按照预定的IFS的值进行分割 set -- $stat_line # 记录用户态CPU时间、内核态CPU时间的总和,若某一个位置没有则默认为0 cpu2=$((${14:-0} + ${15:-0})) # 计算两次记录值之间的差值 delta=$((cpu2 - ${cpu1[$pid]:-0})) # 若是差值大于等于0 if [ $delta -gt 0 ]; then # 获取进程名称 cmd="" # 1. 优先获取启动进程的完整命令 if [ -f "/proc/$pid/cmdline" ]; then cmd=$(tr '\0' ' ' < "/proc/$pid/cmdline" 2>/dev/null) cmd=$(echo "$cmd" | sed 's/ $//') fi # 2. 若没有获取到启动进程的完整命令,则获取进程名称 if [ -z "$cmd" ] && [ -f "/proc/$pid/comm" ]; then cmd=$(cat "/proc/$pid/comm" 2>/dev/null | tr -d '\n') fi # 3. 若没有获取到启动进程的完整命令、也没有获取到进程名称,则直接使用PID if [ -z "$cmd" ]; then cmd="[$pid]" fi # 根据差值和间隔时间计算平均CPU使用率 cpu_pct=$(awk "BEGIN {printf \"%.1f\", $delta / $INTERVAL}") # 按照格式【CPU使用率[制表符]PID[制表符]进程命令/名称】将文本写入临时文件中 printf "%s\t%s\t%s\n" "$cpu_pct" "$pid" "$cmd" fi fi# 遍历/proc目录下的目录文件,且只遍历第一层子目录、子文件,且只需要名称是数字的子目录、子文件# -print0:使用NUL字符、也就是\0来进行分割,而不是使用默认的换行符,因为Linux中允许在目录、文件的名称中使用换行符# 2>/dev/null:遇到异常时丢弃,静默处理done < <(find /proc -maxdepth 1 -name '[0-9]*' -print0 2>/dev/null) > "$tmpfile"# 将临时文件中的内容按照第一列进行排序# -k1:按照第一列进行排序# -n:表示数值排序# -r:表示逆序排序# head -n 10:仅获取前10条# IFS=$'\t':以制表符作为字段分隔符# read:从输入中读取一行# -r:禁止对反斜杠转义# cpu_pct pid cmd:将读取且分割后的数据分别赋予给三个变量sort -k1 -nr "$tmpfile" | head -n 10 | while IFS=$'\t' read -r cpu_pct pid cmd; do # 格式化输出打印 printf "PID:%-8s CPU:%6s%% CMD:%s\n" "$pid" "$cpu_pct" "$cmd"done# 删除临时文件rm -f "$tmpfile"

直接从底层文件入手来计算 CPU 使用率,性能高、资源占用少、最灵活现场问题排查使用此种方式不合适、耗时太久,但是非常适用于实时监控、日志采集等高频场景
现场排查问题,推荐使用 top 命令;高效、高频获取 CPU 使用率时,推荐使用命令脚本