我需要从 shell 脚本中以百分比形式报告每个核心的 CPU 负载,但是我无法运行例如 mpstat 一秒钟。基本上我认为top
按下后显示的信息1
是我想要的,但我无法配置顶部以批处理模式显示此信息(至少我不知道如何)。我可以~/.toprc
使用配置创建一个文件,但我必须希望用户不要弄乱它。
我查看mpstat
并解析了输出,但这仅支持秒作为间隔时间。我的脚本通过 SNMP 调用,等待 1 秒响应将产生超时,因此这不是一个选项。
还有其他方法可以获取每个核心 cpu 负载吗?我读过有关解析的内容/proc/stat
,但我认为这更像是最后的手段。
答案1
有多种方法可以完成 CPU 负载的次秒轮询,可以使用 dstat 等实用程序(下面的示例),也可以直接轮询 /proc/stat(下面的示例)。
在继续技术示例之前,让我们先回顾一下两者的优缺点。
要使用 dstat,您需要运行快速 crontab( */1 * * * * ) 并将结果通过管道传输到您可以检查的统计文件。优点是您的 SNMP 超时不会成为问题,缺点是它不是真正瞬时的,并且当您实际上没有查找此数据时运行 crontab 会产生影响。影响可能可以忽略不计,但仍然存在。
要使用 /proc/stat,您必须轮询 /proc/stat 的内容两次。 /proc/stat 的内容从启动开始累积。所以需要将第一次轮询和第二次轮询的结果相减,然后才能计算出当前的负载。缺点是必须有某种形式的延迟才能进行此计算。在下面的示例中,我已将延迟降低到亚秒级。这可以满足您的需求,但是数据样本非常接近,我不确定准确性有多绝对。
使用 dstat; 将此行添加到 /etc/crontab:
*/1 * * * * root echo $((100-`dstat -c -C0 --noheaders --nocolor 1 1 | grep -v "\-\|u" | awk 'NR == 2' | tr -s " " | cut -d \ -f 4`)) > /tmp/cpuload
每分钟仅更新一次。如果您想要更频繁的更新,请添加第二行并在命令前面加上 sleep 30,例如
*/1 * * * * root sleep 30; echo $((100-`dstat -c -C0 --noheaders --nocolor 1 1 | grep -v "\-\|u" | awk 'NR == 2' | tr -s " " | cut -d \ -f 4`)) > /tmp/cpuload
可以进一步使用(滥用) cron 并获得亚秒结果,但这完全是另一个话题。
解释:
dstat -c -C 0 --noheaders --nocolor 1 0
-c 只显示cpu数据
-C 选择cpu0。更改编号以选择其他 cpu
--noheaders --nocolor (隐含 --noupdate)简化我们所看到的内容
1 统计数据读取延迟一秒
1 第二次读取统计数据后退出。调用后给它时间安定下来。
grep -v“-\|u”
删除非数据线
awk 'NR == 2'
选择第二行。
tr-s“”
修剪掉在屏幕上看起来不错但不适合系统使用的额外空间
切-d \ -f 4
-d \(\(转义)空格后面有一个空格 -f 4 选择空闲。是的,它在视觉上是 3,但行开头的空格算作一个字段,从而忽略了字段计数。
$(())
bash 算术运算,从 100 减去系统空闲。
使用/proc/stat;
保存为cpuload.sh;
#!/bin/bash
#Calculation delay. Without a delay, there is no way to determine current
#values. The content or /proc/stat is cumulitative from last boot.
# in seconds; sleep must be able to support float values
dly=3
function calculate {
#load arrays
IFS=' ' read -r -a firstarr <<< "$1"
IFS=' ' read -r -a secondarr <<< "$2"
#clear name fields in array so that calculations don't get messy
firstarr[0]=0 ;
secondarr[0]=0 ;
#clear values
firsttotcpu=0
secondtotcpu=0
#calculate the begining interrupt counts
for f in ${firstarr[@]};
do
let firsttotcpu+=$f;
done
firstidle=$((${firstarr[4]}+${firstarr[5]}));
#calculate the ending interrupt counts
for l in ${secondarr[@]};
do
let secondtotcpu+=$l;
done;
secondidle=$((${secondarr[4]}+${secondarr[5]}));
#calculate the relative change counts
insttotcpu=$(( secondtotcpu - firsttotcpu ))
instidle=$(( secondidle - firstidle ))
#calculate the utilization percentage. must be done external to bash as it's a
#floating calculation
cpu_load=$( echo | awk -v tot=$insttotcpu -v idl=$instidle ' { print ( ( ( tot - idl ) / tot ) * 100 ) } ' )
echo -n $cpu_load " "
}
export -f calculate
#main execution
oldIFS=$IFS
IFS=$'\n' cpu_start=( $( grep cpu /proc/stat ) );
#must delay to get difference
sleep $dly
IFS=$'\n' cpu_end=( $( grep cpu /proc/stat ) );
cpucount=${#cpu_start[@]}
#uncomment this for loop to enable printing the cpu name above the percentages
#for i in ${cpu_start[@]};
# do
# IFS=' ' read -r -a name <<< "$i"
# echo -n ${name[0]} " "
#done
#echo ""
for (( i=0; i<$cpucount; i++ ))
do
calculate "${cpu_start[$i]}" "${cpu_end[$i]}"
done
echo ""
IFS=$oldIFS
答案2
获取原始值的另一种方法是grep cpu0 /proc/stat
.在那里您可以看到每个状态的刻度数。请man proc
做详细解释。如果你想要一个百分比,你必须将它们加在一起并除以,例如沿着什么线约翰·吉尔建议。
答案3
这里有一个基于 bash示例脚本(使用/proc/stat)并附有解释。它可以按照您需要的速度运行。保存为 /tmp/cpuLoad.sh,然后“chmod +x /tmp/cpuLoad.sh”并最后运行:/tmp/cpuLoad.sh
#!/bin/bash
interval=0.25; ##loop interval in seconds
##so settings below
lCpus=(); ##store last readings
lCount=0; ## loop counter
while :; do {
cCpu=(); ##current cpu
cCpus=(); ##all cpus
values=$(grep -E "cpu[0-9]+\s" /proc/stat);
for value in $values; do {
if [[ $value =~ ^cpu[0-9]+ ]]; then
if [[ ${#cCpu[@]} > 0 ]]; then
cCpus[${cCpu[1]}]="${cCpu[@]}"
fi
cCpu[0]=$value; ##name
cCpu[1]=${#cCpus[@]}; ##cpu index
cCpu[2]=0; ##cpu idle ticks
cCpu[3]=0; ##cpu busy ticks
i=0; ## column index
else
((i=i+1));
if ([ $i == 4 ] || [ $i == 5 ]); then
# position 4 is the idle, position 5 is the i/o wait (also idle introduced 2.5.41) src https://www.idnt.net/en-US/kb/941772
((cCpu[2]=cCpu[2] + value));
else
((cCpu[3]=cCpu[3] + value));
fi
fi
} done
##include the last cpu
cCpus[${cCpu[1]}]="${cCpu[@]}"
output="Loop $lCount";
x=0;
for cpu in "${cCpus[@]}"; do {
if [[ $lCount > 0 ]]; then
cCpu=($cpu);
lCpu=(${lCpus[$x]});
dTotal=$(((${cCpu[2]} + ${cCpu[3]}) - (${lCpu[2]} + ${lCpu[3]})));
dUsed=$((dTotal - (${cCpu[2]} - ${lCpu[2]})));
if [[ $dTotal == 0 ]]; then
dTotal=1; ##dividing by 0 is never a good idea
fi
output="$output, ${cCpu[0]}: $((100 * dUsed / dTotal))%";
fi
##store the reading so we can do a delta next round
lCpus[$x]=$cpu;
((x=x+1));
} done
if [[ $lCount > 0 ]]; then
echo $output;
fi
sleep $interval;
((lCount=lCount+1));
} done
答案4
事实证明,RedHat 上安装的一些 MIB 提供了此处所需的所有信息。由于我的目标是通过 SNMP 在 OID 下提供这些值,因此我可以利用 SNMP 并处理信息。
所有 cpu 平均值计算如下100-idle
:
function allCpuLoad {
# get system idle value from
# snmpget -v2c -cmdaf localhost UCD-SNMP-MIB::ssCpuIdle.0
# UCD-SNMP-MIB::ssCpuIdle.0 = INTEGER: 93
# and compute load by substracting it from 100.0
snmpget -v2c -cmdaf localhost UCD-SNMP-MIB::ssCpuIdle.0|cut -f4 -d' '| awk '{printf "%d", 100 - $1}'
}
我们可以使用 snmpwalk 获取所有单个 cpu 的负载,然后提取最大值:
function maxCpuLoad {
# get load of all cpus
# snmpwalk -v2c -cmdaf localhost HOST-RESOURCES-MIB::hrProcessorLoad
# HOST-RESOURCES-MIB::hrProcessorLoad.196608 = INTEGER: 1
# HOST-RESOURCES-MIB::hrProcessorLoad.196609 = INTEGER: 1
# HOST-RESOURCES-MIB::hrProcessorLoad.196610 = INTEGER: 1
# HOST-RESOURCES-MIB::hrProcessorLoad.196611 = INTEGER: 1
# HOST-RESOURCES-MIB::hrProcessorLoad.196612 = INTEGER: 6
# HOST-RESOURCES-MIB::hrProcessorLoad.196613 = INTEGER: 1
# HOST-RESOURCES-MIB::hrProcessorLoad.196614 = INTEGER: 1
# HOST-RESOURCES-MIB::hrProcessorLoad.196615 = INTEGER: 1
# HOST-RESOURCES-MIB::hrProcessorLoad.196616 = INTEGER: 1
# HOST-RESOURCES-MIB::hrProcessorLoad.196617 = INTEGER: 27
# HOST-RESOURCES-MIB::hrProcessorLoad.196618 = INTEGER: 4
# HOST-RESOURCES-MIB::hrProcessorLoad.196619 = INTEGER: 0
# HOST-RESOURCES-MIB::hrProcessorLoad.196620 = INTEGER: 1
# HOST-RESOURCES-MIB::hrProcessorLoad.196621 = INTEGER: 0
# HOST-RESOURCES-MIB::hrProcessorLoad.196622 = INTEGER: 0
# HOST-RESOURCES-MIB::hrProcessorLoad.196623 = INTEGER: 1
# and get maximum value only
snmpwalk -v2c -cmdaf localhost HOST-RESOURCES-MIB::hrProcessorLoad|cut -f 4 -d' '|sort -n -r|head -n1
}