Monitoring our environment is crucial especially when deploying a new application. Nowadays, companies use open source solutions to monitor system resources on a daily basis. However, when it comes to monitor for a certain amount of time for testing purposes, bash scripting comes handy.

In this tutorial, we are going to write a bash/shell script that is going to output a table with three columns showing the percentages of Memory, Disk and CPU used on our machine. The script is going to run for a certain amount of time every 1 second and save the date to a log file in a table form. Later on, the data file can then be imported into an Microsoft Excel sheet for analysis and graphing.

Let’s get started!

The script is basically made up of three main parts:

  1. Memory:
free -m | awk 'NR==2{printf "%.2f%%\t\t", $3*100/$2 }'

free -m is a command used to show the memory used and free and it gives the below output:

[root@localhost tmp]# free -m
             total       used       free     shared    buffers     cached
Mem:           996         92        904          0         11         31
-/+ buffers/cache:         49        947
Swap:         1583          0       1583

However, what we need from the above output is the total and used memory from the second line. A way to extract data from a given output is by using AWK.

AWK is an programming language used for text processing and data extraction. It is a standard feature of most UNIX systems. awk ‘NR==2’ extracts data form the second line. the $3 and $2 act as holders for the used and total values respectively.

2. Disk

df -h | awk '$NF=="/"{printf "%s\t\t", $5}'

The second command outputs the percentage of disk used. df -h outputs data related to disk usage and partitions.

[root@localhost tmp]# df -h
Filesystem                    Size  Used Avail Use% Mounted on
/dev/mapper/VolGroup-lv_root   14G  814M   12G   7% /
tmpfs                         499M     0  499M   0% /dev/shm
/dev/sda1                     485M   32M  428M   7% /boot

awk ‘$NF outputs the number of fields. However,  df -h | awk ‘$NF==”/” will go to that line which contains the character “/”. The $5 will select the field number five from that line. This ensures that the command extracts the correct percentage of disk used (which is %7 in our case).

3. CPU

top -bn1 | grep load | awk '{printf "%.2f%%\t\t\n", $(NF-2)}'

The top -bn1 command will execute the top command only once (n1 = one iteration) the “b” is used when we want to use “top” in a bash script or to output its data to a file.

“grep load” will output the line which contains the string “load”. $(NF-2) will count the number of fields on that line and decrement 2, thus outputs field #10, or the first 0.00 in the below output.

[root@localhost tmp]# top -bn1 | grep load
top - 19:31:25 up  1:47,  1 user,  load average: 0.00, 0.00, 0.00

After going through the basic parts of the bash scripts, we need to save these commands into variables MEMORY, DISK, and CPU:

MEMORY=$(free -m | awk 'NR==2{printf "%.2f%%\t\t", $3*100/$2 }')
DISK=$(df -h | awk '$NF=="/"{printf "%s\t\t", $5}')
CPU=$(top -bn1 | grep load | awk '{printf "%.2f%%\t\t\n", $(NF-2)}')

We need the script to run for a certain amount of time, let’s say for an hour. In order to do this, we need to use the “while do” loop, with a delay of x seconds after each loop since or depending on your testing:

while [ $SECONDS -lt $end ]; do
sleep 5

In order to run a loop for a certain amount of time, we can define a variable $end which takes the  current number of seconds from the time at which the bash script started, hence SECONDS, and adds a number to the current seconds. So an hour would be 3600 seconds.

The second line of the above code snippet states that the while loop will keep executing as long as the current number of seconds is less than the (current number of seconds + 3600). So, we have defined a starting time and an ending time for the loop, in addition to a sleep time that will pause each loop for 5 seconds. Inside the loop is the three variables being assigned every 5 seconds new values, and the echo “$MEMORY$DISK$CPU” which will output the three resource usages.

The complete code is below:

#! /bin/bash
printf "Memory\t\tDisk\t\tCPU\n"
while [ $SECONDS -lt $end ]; do
MEMORY=$(free -m | awk 'NR==2{printf "%.2f%%\t\t", $3*100/$2 }')
DISK=$(df -h | awk '$NF=="/"{printf "%s\t\t", $5}')
CPU=$(top -bn1 | grep load | awk '{printf "%.2f%%\t\t\n", $(NF-2)}')
sleep 5

The above code will output the following:

[root@localhost tmp]# ./
Memory Disk CPU
9.34% 7% 0.00%
9.34% 7% 0.00%
9.34% 7% 0.00%
9.34% 7% 0.00%
^C[root@localhost tmp]#

You can always output the data to a log file:

[root@localhost tmp]# ./ >> log.txt

Stress Test

Since we barley have any load on the machine, we can use a stress tester to cause the CPU and Memory to load for some time.

Issue the below command (on CentOS):

root@localhost tmp]# yum install stress
Failed to set locale, defaulting to C
Loaded plugins: fastestmirror
Determining fastest mirrors
epel/metalink                                                               | 4.2 kB     00:00     
 * base:
 * epel:
 * extras:
 * updates:
base                                                                        | 3.7 kB     00:00     
epel                                                                        | 4.3 kB     00:00     
epel/primary_db                                                             | 5.9 MB     00:37     
extras                                                                      | 3.4 kB     00:00     
extras/primary_db                                                           |  37 kB     00:00     
updates                                                                     | 3.4 kB     00:00     
updates/primary_db                                                          | 5.2 MB     00:30     
Setting up Install Process
Resolving Dependencies
--> Running transaction check
---> Package stress.x86_64 0:1.0.4-4.el6 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

 Package               Arch                  Version                     Repository           Size
 stress                x86_64                1.0.4-4.el6                 epel                 36 k

Transaction Summary
Install       1 Package(s)

Total download size: 36 k
Installed size: 89 k
Is this ok [y/N]: y
Downloading Packages:
stress-1.0.4-4.el6.x86_64.rpm                                               |  36 kB     00:01     
Running rpm_check_debug
Running Transaction Test
Transaction Test Succeeded
Running Transaction
  Installing : stress-1.0.4-4.el6.x86_64                                                       1/1 
  Verifying  : stress-1.0.4-4.el6.x86_64                                                       1/1 

  stress.x86_64 0:1.0.4-4.el6                                                                      


Now we can use the command “stress” to load our machine. For example, a load average of four is imposed on the system by specifying two CPU-bound processes, one I/O-bound process, and one memory allocator process as follows. The below stress test will run for 1 hour.

[root@localhost tmp]# stress -c 2 -i 1 -m 1 --vm-bytes 128M -t 3600s
stress: info: [1574] dispatching hogs: 2 cpu, 1 io, 1 vm, 0 hdd
stress: info: [1574] successful run completed in 3600s
[root@localhost tmp]# ./ 
Memory		Disk		CPU
20.48%		7%		1.21%		
20.48%		7%		1.02%		
20.48%		7%		0.94%		
21.89%		7%		1.18%		
20.68%		7%		1.41%		
22.09%		7%		1.62%		
24.10%		7%		1.81%		
24.90%		7%		1.98%		
32.93%		7%		2.14%		
30.32%		7%		2.29%		
20.58%		7%		2.63%		
27.91%		7%		2.82%		
20.48%		7%		2.59%		
20.48%		7%		2.38%		
20.48%		7%		2.19%		
20.48%		7%		2.02%		
20.48%		7%		1.86%		

This concludes our tutorial for how to create a very basic and useful bash script for all the System Administrators out there.