Calculating current CPU usage on Linux

  • linux
  • bash

tl;dr: The code

I use a tool called i3blocks to script my own status bar, which includes a CPU usage indicator. Recently, I noticed something odd about it. The output would change for a while, but eventually stick right around 41%. I had previously just copied this script from Stack Overflow, so I decided it was time to dig in and learn about this in order to fix it.

I was able to relocate the Stack Overflow question I had taken the script from: How to get overall CPU Usage (e.g. 57%) on Linux The answer I had chosen is the highest rated, and relies on /proc/stat.

grep 'cpu ' /proc/stat | awk '{usage=($2+$4)*100/($2+$4+$5)} END {print usage "%"}'

It reads in the fields from /proc/stat, which breaks down the time spent on each task by your CPUs. In particular, this grep looks for the aggregate of all your cores. The numbered inputs correspond as $2 = user tasks, $4 = system tasks, and $5 = idling. Awk then prints the division of combined time spend working (user + system) by the total time (working + idling). This percentage is the answer I was looking for.

The problem with this approach is that /proc/stat is a record of everything since startup. That’s why the output kept leveling off — the more inputs you have, the harder it is to move the average. With this approach out, I moved to the next answer.

top -bn1 | grep "Cpu(s)" | \
           sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | \
           awk '{print 100 - $1"%"}'

This answer uses the popular tool, top, which pulls together a lot of statistics about activity on one’s computer. This script generates a single page of top, looks for the line about CPU usage, parses out the idle time, then returns the inverse (100 - $1).

This script suffers from the same problem as the previous answer — the idle time is an accumulated amount since boot time. That combined with the fact that it generates a lot of other statistics, this seems like a worse option. However, there is more to this than meets the eye.

Using top in this manner will always give you a “since boot” measurement first, but if you generate 2 pages by changing the source to top -bn2, the second page will include a measurement since the first page was generated, which is the time period we’re looking for.

top -bn2 | grep "Cpu(s)" | \
           sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | \
           awk '{print 100 - $1"%"}' | \
           tail -1

This now gives the answer I wanted, but it takes a while, and is a bit heavy. It generates two full pages of top statistics and has a waiting period in-between. I decided to press further.

mpstat | awk '$12 ~ /[0-9.]+/ { print 100 - $12"%" }'

The several remaining answers use mpstat, and none of them worked for me — they all report 100%. This is due to a column mismatch. The awk command is trying to return 100 - idle time like the top command, but the number of column in my version differ, to it’s pulling %gnice instead of %idle. As far as I can tell, this mpstat approach is practically identical to the top approach. ¯\(ツ)

With all these contenders not meeting my standards, I realized I was going to need to use more than ctrl+c, ctrl+v. I like that top -bn2 gives the delta between when the two samples were taken, but there’s not really a way to get around the wait period. I like that /proc/stat is just a file to read from and not a command to run extra cycles to compute. It also has the strength of providing the source data. Using this, I realized I can calculate the value I want quickly by just using a temp file. 

# Write the current state to a temp file
save_current() {
    grep 'cpu ' /proc/stat > /tmp/cpustat

# If the temp file doesn't exist, save it now.
# This means the current reading will be pretty inaccurate, usually too high,
# because it measures CPU usage from now until 2 lines later in this program.
[ ! -e /tmp/cpustat ] && save_current

# Load the previous state from the temp file, and capture the current output.
previous=$(cat /tmp/cpustat)
current=$(grep 'cpu ' /proc/stat)

# Define the awk script to parse the two lines of input.
# For the first line (NR == 1), just save the working and idle values.
# For subsequent rows (NR > 1), calculate the difference between this line and
# the first line and calculate the total average percentage.
awkscript='NR == 1 {
           NR > 1 {
             printf "%.1f%", 100 * work / (work + idle)

# Execute the awk script against the two lines of input and save the string.
usage=$(echo -e "$previous\n$current" | awk "$awkscript")

# Save the current value. The next time you run this script will calculate
# average usage since this line was run.

echo "$usage"

This solution is very fast, and gives a pretty accurate measurement of the current CPU usage.

Lenovo Thinkpad Carbon X1, 4th gen
Ubuntu 17.04
Kernel 4.10.0-35-generic
sysstat 11.4.3