1

I am trying to write a BASH script which will evaluate and show in the terminal a list of all cores and their current load. I am using the output of the /proc/stat. For example:

cat /proc/stat
     user nice system idle iowait  irq  softirq steal guest guest_nice
cpu  4705 356  584    3699   23    23     0       0     0          0

and evaluating the used CPU time by summing the user, nice, system, irq, softirq, steal and the CPU idle time by summing the idle, iowait. Then I am adding the used CPU time + CPU idle time to obtain the total CPU time and dividing the CPU use time to total CPU time.

The problem with this methodology is that this is the average CPU usage since the system was last booted. In order to get the current usage, I need to check two times the /proc/stat and use the differences between the total CPU time and used CPU time between the two checks and then to divide the result by the difference in time in seconds between them. For this I am using a while infinite loop complimented with a sleep command. I want to have the output in the following format:

CPU: 10%
CPU0: 15%
CPU1: 5%
CPU2: 7%
CPU3: 13%

And I want the total CPU usage across all cores and the CPU usage per core to update after every sleep automatically. This is my code so far:

#!/bin/bash

PREV_CPU_USE=0
PREV_CPU_IDLE=0
PREV_EPOCH_TIME=0

# Setting the delimiter
IFS=$'\n'

while true; do
  # Getting the total CPU usage
  CPU_USAGE=$(head -n 1 /proc/stat)

  # Getting the Linux Epoch time in seconds
  EPOCH_TIME=$(date +%s)

  # Splitting the /proc/stat output
  IFS=" " read -ra USAGE_ARRAY <<< "$CPU_USAGE"

  # Calculating the used CPU time, CPU idle time and CPU total time
  CPU_USE=$((USAGE_ARRAY[1] + USAGE_ARRAY[2] + USAGE_ARRAY[3] + USAGE_ARRAY[6] + USAGE_ARRAY[7] + USAGE_ARRAY[8] ))
  CPU_IDLE=$((USAGE_ARRAY[4] + USAGE_ARRAY[5]))

  # Calculating the differences
  DIFF_USE=$((CPU_USE - PREV_CPU_USE))
  DIFF_IDLE=$((CPU_IDLE - PREV_CPU_IDLE))
  DIFF_TOTAL=$((DIFF_USE + DIFF_IDLE))
  DIFF_TIME=$((EPOCH_TIME - PREV_EPOCH_TIME))

  #Printing the line and ommiting the trailing new line and using carrier trailer to go to the beginning of the line
  echo -en "${USAGE_ARRAY[0]} Usage: $((DIFF_USE*100/(DIFF_TOTAL*DIFF_TIME)))%                          \\r\\n"
  echo -en "${USAGE_ARRAY[0]} Idle: $((DIFF_IDLE*100/(DIFF_TOTAL*DIFF_TIME)))%                         \\r"

  # Assigning the old values to the PREV_* values
  PREV_CPU_USE=$CPU_USE
  PREV_CPU_IDLE=$CPU_IDLE
  PREV_EPOCH_TIME=$EPOCH_TIME

  # Sleep for one second 
  sleep 1
done

Here I have simplified the script and I am actually printing only the current CPU usage and Idle CPU time on two different lines but even though the cpu Idle is remaining on one line the cpu Usage is adding new lines like:

cpu Usage: 0%                         
cpu Usage: 0%                         
cpu Usage: 0%                         
cpu Idle: 99%

Is there an option to have the cpu Usage on one line only for the whole duration of the script?

1
  • This would be easier with Awk using getline < "/proc/stat" and system("sleep 1").
    – Wildcard
    Aug 22, 2019 at 18:37

1 Answer 1

1

I think you want to have cpu Usage string to be shown on the same line each time and to be followed by the cpu Idle line immediately. To achieve that you can use tput and el (clr_eol) terminal capability to remove line and cuu (parm_up_cursor) to move n lines up. You can read about terminal capabilities in man terminfo. Your script would look like this:

#!/bin/bash

PREV_CPU_USE=0
PREV_CPU_IDLE=0
PREV_EPOCH_TIME=0

# Setting the delimiter
IFS=$'\n'
counter=0

while true; do
  # Getting the total CPU usage
  CPU_USAGE=$(head -n 1 /proc/stat)

  # Getting the Linux Epoch time in seconds
  EPOCH_TIME=$(date +%s)

  # Splitting the /proc/stat output
  IFS=" " read -ra USAGE_ARRAY <<< "$CPU_USAGE"

  # Calculating the used CPU time, CPU idle time and CPU total time
  CPU_USE=$((USAGE_ARRAY[1] + USAGE_ARRAY[2] + USAGE_ARRAY[3] + USAGE_ARRAY[6] + USAGE_ARRAY[7] + USAGE_ARRAY[8] ))
  CPU_IDLE=$((USAGE_ARRAY[4] + USAGE_ARRAY[5]))

  # Calculating the differences
  DIFF_USE=$((CPU_USE - PREV_CPU_USE))
  DIFF_IDLE=$((CPU_IDLE - PREV_CPU_IDLE))
  DIFF_TOTAL=$((DIFF_USE + DIFF_IDLE))
  DIFF_TIME=$((EPOCH_TIME - PREV_EPOCH_TIME))

  printf "\r%s%s Usage: %d (counter = %d)\n" "$(tput el)" "${USAGE_ARRAY[0]}" "$((DIFF_USE*100/(DIFF_TOTAL*DIFF_TIME)))" "$counter"
  printf "\r%s%s Idle: %d (counter = %d)" "$(tput el)" "${USAGE_ARRAY[0]}" "$((DIFF_IDLE*100/(DIFF_TOTAL*DIFF_TIME)))" "$counter"
  counter=$((counter + 1))

  tput cuu 1

  # Assigning the old values to the PREV_* values
  PREV_CPU_USE=$CPU_USE
  PREV_CPU_IDLE=$CPU_IDLE
  PREV_EPOCH_TIME=$EPOCH_TIME

  # Sleep for one second 
  sleep 1
done

I added an extra counter variable for debugging purposes. It's incremented after each print to inform user that the old line is replaced by the new line on the screen.

I've also replaced your calls to echo with printf as it's more portable.

1

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .