16
\$\begingroup\$

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 "$CPUUSAGE\n"

Would this be considered a reasonably accurate way of doing this? Any suggestions to increase efficiency/accuracy would be greatly appreciated.

\$\endgroup\$
1
  • \$\begingroup\$ PS. good for you in figuring in the # cores...ala "Irix" cpu timekeeping... which helps know # of cores and multithreading. I.e. if a process only ever uses 100% cpu, it is likely 1 thread; if >100%, likely more than one thread. Doesn't equate 50%cpu on a single core machine with 50% on a 12 core (like many windows utils do). \$\endgroup\$
    – Astara
    Sep 9, 2014 at 19:04

2 Answers 2

8
\$\begingroup\$

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 %"
\$\endgroup\$
2
  • \$\begingroup\$ Nice! If you post that code for code review, maybe I can improve on it a bit ;-) \$\endgroup\$
    – janos
    Sep 9, 2014 at 15:19
  • \$\begingroup\$ @janos - good idea, and done \$\endgroup\$
    – rolfl
    Sep 9, 2014 at 15:33
5
\$\begingroup\$

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.)

\$\endgroup\$
2
  • \$\begingroup\$ Re: comment #4 above -- echo is not portable. printf is the posix recommended way of printing for portability (primarily because of echo's behavior with expanding inline codes ("\n") w/r/t the -e/-E switches and how it works with suppressing newlines (-n), and whether or not -- suppresses further argument processing... I.e. those items aren't the same everywhere... so printf ended up getting the standards vote. (I probably more often use echo...) \$\endgroup\$
    – Astara
    Sep 9, 2014 at 18:57
  • \$\begingroup\$ I know. I wouldn't recommend echo for anything involving any of its switches, they are a complete mess. But in this particular case, using printf just for a simple printing with a newline, that's just tedious. \$\endgroup\$
    – janos
    Sep 9, 2014 at 19:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.