Problem
I have been developing the below method to calculate the CPU usage of a Linux system at a point in time, as a percentage of the total number of cores available in the system.
#!/bin/bash
# Get number of cores from proc cpuinfo
CORECOUNT=`grep -c ^processor /proc/cpuinfo`
# Use top, skip the first 7 rows, count the sum of the values in column 9 - the CPU column, do some simple rounding at the end.
CPUUSAGE=`top -bn 1 | awk 'NR>7{s+=$9} END {print s/'$CORECOUNT'}' | awk '{print int($1+0.5)}'`
printf "$CPUUSAGEn"
Would this be considered a reasonably accurate way of doing this? Any suggestions to increase efficiency/accuracy would be greatly appreciated.
Solution
There are a few problems you have here. The first, is the concept of getting the usage at a point in time, which is not possible. But, you can get it for a span of time. You are using top -bn 1
which will get the usage for the span of a second.
Since you are accessing the /proc
system to get the number of CPU’s, you may as well use the right /proc
file for what you want:
cat /proc/stat
:
~ $ cat /proc/stat
cpu 177206 161827 1274669 3582291272 144435 154779 0 0 0 0
cpu0 32170 48533 914773 894903107 30650 69784 0 0 0 0
cpu1 52790 41088 180295 895685563 76035 14410 0 0 0 0
cpu2 18745 40955 49422 895959636 11295 2234 0 0 0 0
cpu3 73500 31249 130178 895742965 26454 68350 0 0 0 0
intr 339571688 48 10 0 0 0 0 2 0 1 0 0 0 152 0 0 8750527 0 322394 40428015 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
......
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
ctxt 660707018
btime 1401123121
processes 196075
procs_running 1
procs_blocked 0
softirq 322202763 0 136092603 1792784 40737199 4701209 0 6 133008518 51951 5818493
With the above file, I would calculate it manually…. (also using the documentation here: http://www.mjmwired.net/kernel/Documentation/filesystems/proc.txt#1212 )
I have taken the liberty of programming it myself…. since the recommendation is sucha deviation from your solution, it makes sense:
#!/bin/bash
#CORECOUNT=$(grep cpu /proc/stat | grep -v 'cpu ' | wc -l)
#echo $CORECOUNT cores not needed for calculation though
DELAY=${1:-1}
function getstat() {
grep 'cpu ' /proc/stat | sed -e 's/ */x/g' -e 's/^cpux//'
}
function extract() {
echo $1 | cut -d 'x' -f $2
}
function change() {
local e=$(extract $ENDSTAT $1)
local b=$(extract $STARTSTAT $1)
local diff=$(( $e - $b ))
echo $diff
}
#Record the start statistics
STARTSTAT=$(getstat)
sleep $DELAY
#Record the end statistics
ENDSTAT=$(getstat)
#http://www.mjmwired.net/kernel/Documentation/filesystems/proc.txt#1236
#echo "From $STARTSTAT"
#echo "TO $ENDSTAT"
# usr nice sys idle iowait irq guest
#From 177834 168085 1276260 3584351494 144468 154895 0 0 0 0
#TO 177834 168085 1276261 3584351895 144468 154895 0 0 0 0
USR=$(change 1)
SYS=$(change 3)
IDLE=$(change 4)
IOW=$(change 5)
#echo USR $USR SYS $SYS IDLE $IDLE IOW $IOW
ACTIVE=$(( $USR + $SYS + $IOW ))
TOTAL=$(($ACTIVE + $IDLE))
PCT=$(( $ACTIVE * 100 / $TOTAL ))
echo "BUSY $ACTIVE TOTAL $TOTAL $PCT %"
Let’s assume that column 9 in the output of the top
command is a good measure of the CPU usage. I don’t really know, but let’s assume.
Your script could be written cleaner and better:
- Don’t use
`...`
style process substitution, use$(...)
instead - Usually, there’s no point piping the output of one
awk
command to another. You can do what you did in a singleawk
, which is better, because you save one unnecessary process - Better to pass variables to
awk
using the-v
parameter than trying to embed in the command line - Don’t use
printf
when anecho
is enough and simpler - Avoid extremely long lines. The mouse wheel scrolls up-down fine, left-right is a PITA. You could break that long comment to 2 lines.
Suggested implementation:
#!/bin/bash
# Get number of cores
CORECOUNT=$(grep -c ^processor /proc/cpuinfo)
# Use top, skip the first 7 rows, count the sum of the values
# in column 9 - the CPU column, do some simple rounding at the end
CPUUSAGE=$(top -bn 1 | awk -v n=$CORECOUNT 'NR > 7 { s += $9 } END { print int(s / n + .5); }')
echo $CPUUSAGE
Btw, do you really need $CPUUSAGE
? If not, just omit the variable, change the last line:
top -bn 1 | awk -v n=$CORECOUNT 'NR > 7 { s += $9 } END { print int(s / n + .5); }'
(I used n
as the Awk variable name mainly to make my answer fit on the page without scrolling 😉 Feel free to name it corecount
or as you like.)