To calculate the total CPU usage as a percentage

Posted on

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:

  1. Don’t use `...` style process substitution, use $(...) instead
  2. Usually, there’s no point piping the output of one awk command to another. You can do what you did in a single awk, which is better, because you save one unnecessary process
  3. Better to pass variables to awk using the -v parameter than trying to embed in the command line
  4. Don’t use printf when an echo is enough and simpler
  5. 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.)

Leave a Reply

Your email address will not be published. Required fields are marked *