在 shell 腳本中取得每個核心的 CPU 負載

在 shell 腳本中取得每個核心的 CPU 負載

我需要從 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
}

相關內容