Open In App

Shell Scripting to check System Performance

Last Updated : 23 Sep, 2022
Improve
Improve
Like Article
Like
Save
Share
Report

Prerequisites: Bash Scripting, Shell Function Library

We will build a bash script to check :

  • memory utilization
  • CPU utilisation
  • disk utilisation

And analyze the report and print messages or error warnings accordingly.

Creating Shell Script

The name of the script will be sysperform.sh. Make it an executable file.

$ touch sysperform.sh
$ chmod +x sysperform.sh

Step 1: Handling Argument Errors

$ ./sysperform.sh <args> # invalid syntax (the script doesn’t take any argument)

$ ./sysperform.sh # correct syntax

Open the file and add the following script :

#!/bin/bash

# if args have been specified, then
# show the correct syntax to the user
if [ $# -gt 0 ] ; then
  echo "Invalid Syntax!"
  echo "The valid syntax is ./$(basename $0)"
  exit 1
fi

Step 2: Create Main Function

function main ()
{
  memory_util
  cpu_util
  disk_util
}
main

The main function will call three functions as follows :

  • memory_util  will give information about Available memory, Free memory, Memory Used Percentage, etc., as well as reports on whether the memory is healthy or not.
  • cpu_util  will give information about CPU load and CPU usage and tell whether the CPU is performing well or not.
  • disk_util will tell the disk used and disk available of the root partition and home partition. 

Step 3: Create memory_util function

function memory_util ()
{
  BUFFCACHE_MEM=$(free -m | awk '/Mem/ {print $6}')
  FREE_MEM=$(free -m | awk '/Mem/ {print $4}')
  YIELD_MEM=$(( $BUFFCACHE_MEM + $FREE_MEM ))
  
  AVAILABLE_MEM=$(free -m | awk '/Mem/ {print $7}')
  TOTAL_MEM=$(free -m | awk '/Mem/ {print $2}')

  TOTAL_USED_MEM=$(( $TOTAL_MEM - $AVAILABLE_MEM ))

  #Total memory usage is Total Memory - Available Memory
  MEM_USAGE_PERCENT=$(bc <<<"scale=2; $TOTAL_USED_MEM * 100 / $TOTAL_MEM")

  echo -e "........................................\nMEMORY UTILIZATION\n"
  echo -e "Total Memory\t\t:$TOTAL_MEM MB"
  echo -e "Available Memory\t:$AVAILABLE_MEM MB"
  echo -e "Buffer+Cache Memory\t:$BUFFCACHE_MEM MB"
  echo -e "Free Memory\t\t:$FREE_MEM MB"
  echo -e "Memory Usage Percentage\t:$MEM_USAGE_PERCENT %"

  #if available or (free+buffer+cache) is close to 0
  #We will warn the user if it's below 100 MiB
  if [ $AVAILABLE_MEM -lt 100 -o $YIELD_MEM -lt 100 ] ; then
    echo "Available Memory or the free and buffer+cache Memory is too low!"
    echo "Unhealthy Memory!"
  #if kernel employed OOM Killer process
  elif dmesg | grep oom-killer > /dev/null ; then
    echo "System is critically low on memory!"
  else
    echo -e "\nMEMORY OK\n........................................"
  fi
}

We will use the free -m command coupled with awk to filter our unnecessary details so as to view the free memory, available memory, buffer, and cached memory and finally calculate Memory used percentage.

We will use bc to handle Floating-point arithmetic operations in bash.

Some important facts :

  • Linux tries to use unused RAM for disk caching to fasten up the operations. Do not be worried if free memory is close to 0.
  • Free memory is basically unused wasted memory.
  • Buffer and Cache memory contain metadata and actual contents from the disk so that the operations could be done quickly. Together we will call it YIELD_MEM(yield memory) since it can yield memory whenever a new process is started.
  • Available memory is the actual memory that is available to the user.
  • So actual memory used(which includes buffer & cache) = total memory – available memory

Memory is unhealthy if

  • If available memory or the free+buffer+cache memory is too low.
  • If kernel employs OOM(Out of Memory)-Killer process.

Step 4: Create cpu_util function

function cpu_util ()
{
  #number of cpu cores
  CORES=$(nproc)

  #cpu load average of 15 minutes
  LOAD_AVERAGE=$(uptime | awk '{print $10}')
  LOAD_PERCENT=$(bc <<<"scale=0; $LOAD_AVERAGE * 100")

  echo -e "........................................\nCPU UTILIZATION\n"
  echo -e "Number of Cores\t:$CORES\n"

  echo -e "Total CPU Load Average for the past 15 minutes\t:$LOAD_AVERAGE"
  echo -e "CPU Load %\t\t\t\t\t:$LOAD_PERCENT"
  echo -e "\nThe load average reading takes into account all the core(s) present in the system"

  #if load average is equal to the number of cores, print warning
  if [[ $(echo "if (${LOAD_AVERAGE} == ${CORES}) 1 else 0" | bc) -eq 1 ]] ; then
    echo "Load average not ideal."
  elif [[ $(echo "if (${LOAD_AVERAGE} > ${CORES}) 1 else 0" | bc) -eq 1 ]] ; then
    echo "Critical! Load average is too high!"
  else
    echo -e "\nCPU LOAD OK"
  fi
  
  # capturing the second iteration of top and storing the CPU% id.
  IDLE=$(top -b -n2 | awk '/Cpu/ {print $8}' | tail -1)
  CPU_USAGE_PERCENT=$(bc <<<"scale=2; 100 - $IDLE/$CORES")

  echo -e "\nCPU Usage %\t:$CPU_USAGE_PERCENT\n........................................"  
} 

We will use the bc command to evaluate the floating point operations in if-else statements.

  • The healthy Load Average of each core is less than 1. top command displays the total load average of all the cores combined.
  • Load Percentage = Lode Average * 100
  • To calculate CPU Usage, we will divide the CPU time spent idle by the number of cores and subtract it by 100.
    • CPU Usage % = 100 – (Idle time spent/number of cores)
  • We will use the second iteration of the top command to view the correct idle time. -b helps to redirect output and -n2 refers to 2 iterations of the top command. tail -1 prints the last recorded idle time.

CPU is unhealthy if

  • Load Average is greater than or equal to the number of cores

Step 5: Create disk_util function

function disk_util ()
{
  ROOT_DISK_USED=$(df -h | grep -w '/' | awk '{print $5}')
  ROOT_DISK_USED=$(printf %s "$ROOT_DISK_USED" | tr -d [="%"=])
  ROOT_DISK_AVAIL=$(( 100 - $ROOT_DISK_USED ))

  HOME_DISK_USED=$(df -h | grep -w '/home' | awk '{print $5}')
  HOME_DISK_USED=$(printf %s "$HOME_DISK_USED" | tr -d [="%"=])
  HOME_DISK_AVAIL=$(( 100 - $HOME_DISK_USED ))

  echo -e "........................................\nDISK UTILIZATION\n"
  echo -e "Root(/) Used\t\t:$ROOT_DISK_USED%"
  echo -e "Root(/) Available\t:$ROOT_DISK_AVAIL%\n"

  echo -e "Home(/home) Used\t:$HOME_DISK_USED%"
  echo -e "Home(/home) Available\t:$HOME_DISK_AVAIL%"

  #print warning if any of the disk is used above 95%
  if [ $ROOT_DISK_USED -ge 95 -o $HOME_DISK_USED -ge 95 ] ; then
    echo -e "\nDisk is almost full! Free up some space!"
  else
    echo -e "\nDISK OK"
  fi
}

We will filter out unnecessary details from df -h with the help of grep and awk.

  • We will check the used space and available space of root( / ) and home partition( /home ).

The disk is unhealthy if

  • Disk space available is very less

Step 6: Putting together everything

#!/bin/bash

function memory_util ()
{
  BUFFCACHE_MEM=$(free -m | awk '/Mem/ {print $6}')
  FREE_MEM=$(free -m | awk '/Mem/ {print $4}')
  YIELD_MEM=$(( $BUFFCACHE_MEM + $FREE_MEM ))
  
  AVAILABLE_MEM=$(free -m | awk '/Mem/ {print $7}')
  TOTAL_MEM=$(free -m | awk '/Mem/ {print $2}')

  TOTAL_USED_MEM=$(( $TOTAL_MEM - $AVAILABLE_MEM ))

  # Total memory usage is Total Memory - Available Memory
  MEM_USAGE_PERCENT=$(bc <<<"scale=2; $TOTAL_USED_MEM * 100 / $TOTAL_MEM")

  echo -e "........................................\nMEMORY UTILIZATION\n"
  echo -e "Total Memory\t\t:$TOTAL_MEM MB"
  echo -e "Available Memory\t:$AVAILABLE_MEM MB"
  echo -e "Buffer+Cache Memory\t:$BUFFCACHE_MEM MB"
  echo -e "Free Memory\t\t:$FREE_MEM MB"
  echo -e "Memory Usage Percentage\t:$MEM_USAGE_PERCENT %"

  # if available or (free+buffer+cache) is close to 0
  # We will warn the user if it's below 100 MiB
  if [ $AVAILABLE_MEM -lt 100 -o $YIELD_MEM -lt 100 ] ; then
    echo "Available Memory or the free and buffer+cache Memory is too low!"
    echo "Unhealthy Memory!"
  
  # if kernel employed OOM Killer process
  elif dmesg | grep oom-killer > /dev/null ; then
    echo "System is critically low on memory!"
  else
    echo -e "\nMEMORY OK\n........................................"
  fi
}

function cpu_util ()
{
  # number of cpu cores
  CORES=$(nproc)

  # cpu load average of 15 minutes
  LOAD_AVERAGE=$(uptime | awk '{print $10}')
  LOAD_PERCENT=$(bc <<<"scale=0; $LOAD_AVERAGE * 100")

  echo -e "........................................\nCPU UTILIZATION\n"
  echo -e "Number of Cores\t:$CORES\n"

  echo -e "Total CPU Load Average for the past 15 minutes\t:$LOAD_AVERAGE"
  echo -e "CPU Load %\t\t\t\t\t:$LOAD_PERCENT"
  echo -e "\nThe load average reading takes into account all the core(s) present in the system"

  # if load average is equal to the number 
  # of cores, print warning
  if [[ $(echo "if (${LOAD_AVERAGE} == ${CORES}) 1 else 0" | bc) -eq 1 ]] ; then
    echo "Load average not ideal."
  elif [[ $(echo "if (${LOAD_AVERAGE} > ${CORES}) 1 else 0" | bc) -eq 1 ]] ; then
    echo "Critical! Load average is too high!"
  else
    echo -e "\nCPU LOAD OK"
  fi

  # capturing the second iteration of top and storing the CPU% id.
  IDLE=$(top -b -n2 | awk '/Cpu/ {print $8}' | tail -1)
  CPU_USAGE_PERCENT=$(bc <<<"scale=2; 100 - $IDLE/$CORES")

  echo -e "\nCPU Usage %\t:$CPU_USAGE_PERCENT\n........................................"  
} 

function disk_util ()
{
  ROOT_DISK_USED=$(df -h | grep -w '/' | awk '{print $5}')
  ROOT_DISK_USED=$(printf %s "$ROOT_DISK_USED" | tr -d [="%"=])
  ROOT_DISK_AVAIL=$(( 100 - $ROOT_DISK_USED ))

  HOME_DISK_USED=$(df -h | grep -w '/home' | awk '{print $5}')
  HOME_DISK_USED=$(printf %s "$HOME_DISK_USED" | tr -d [="%"=])
  HOME_DISK_AVAIL=$(( 100 - $HOME_DISK_USED ))

  echo -e "........................................\nDISK UTILIZATION\n"
  echo -e "Root(/) Used\t\t:$ROOT_DISK_USED%"
  echo -e "Root(/) Available\t:$ROOT_DISK_AVAIL%\n"

  echo -e "Home(/home) Used\t:$HOME_DISK_USED%"
  echo -e "Home(/home) Available\t:$HOME_DISK_AVAIL%"

  # print warning if any of the disk is used above 95%
  if [ $ROOT_DISK_USED -ge 95 -o $HOME_DISK_USED -ge 95 ] ; then
    echo -e "\nDisk is almost full! Free up some space!"
  else
    echo -e "\nDISK OK"
  fi
}

# if args have been specified, then show the
# correct syntax to the user
if [ $# -gt 0 ] ; then
  echo "Invalid Syntax!"
  echo "The valid syntax is ./$(basename $0)"
  exit 1
fi

function main ()
{
  memory_util
  cpu_util
  disk_util
}
main

Run the Script

$ ./sysperform.sh

Displaying the system performance report and analyzing it

Handles invalid cases as well

The script doesn’t take any argument



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads