Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 65

Threshold-Based Monitoring Script Enhancement

Recommended resources

Hello! Welcome back to our "Practical Bash Automation Projects" module.

In the last lesson, you built a great system_monitor.sh script that acts as a real-time dashboard for CPU, memory, and disk usage. It's a fantastic starting point, but right now, it requires a human to watch it and decide if the numbers are good or bad.

Today, we're going to make our script "smarter." We'll give it the ability to make decisions on its own.

Today's Learning Outcome: By the end of this lesson, you will be able to modify a monitoring script to include threshold checks and print a warning message if usage is high.

This is a critical step in moving from simple information display to genuine, automated monitoring. Instead of just showing data, our script will soon be able to actively flag potential problems.


1. The if Statement: Making Decisions in Bash

At the heart of any "smart" script is the ability to make decisions. In Bash, as in most programming languages, this is done with an if statement. The logic is simple: if a certain condition is true, then perform an action.

Flowchart of an If Statement
This flowchart illustrates the core concept of conditional logic. The script checks a condition. If it's true, a command is executed. If it's false, the script moves on.

The basic syntax in Bash looks like this:

if [ condition ]; then
  # Commands to execute if the condition is true
  echo "The condition was met!"
fi
  • if: Starts the conditional block.
  • [ condition ]: The test. Note the spaces around the brackets!
  • then: Indicates the beginning of the commands to run if the test is true.
  • fi: Ends the if block (which is if spelled backward).

For comparing numbers (integers), we use special operators:

  • -gt: greater than
  • -lt: less than
  • -ge: greater than or equal to
  • -le: less than or equal to
  • -eq: equal to
  • -ne: not equal to

Example:

USED_SPACE=95
THRESHOLD=90

if [ "$USED_SPACE" -gt "$THRESHOLD" ]; then
  echo "Warning: Disk space is critically high!"
fi

2. Refactoring Our Script for Checks

Before we can add checks, we need to make a small but important change to the functions in our system_monitor.sh script. In the previous lesson, our functions like get_memory_usage both calculated the value and printed the formatted output.

# Old version - prints directly
get_memory_usage() {
  free -m | awk 'NR==2{printf "Memory Usage: %.2f%%\n", $3*100/$2 }'
}

To check the value, we need the function to return only the raw number. The main part of our script will then be responsible for both checking it and printing it. This is a better design principle called separation of concerns: one function gathers data, another part of the code uses it.

Let's refactor the get_memory_usage function:

# New version - returns only the number
get_memory_usage() {
  free -m | awk 'NR==2{printf "%.2f", $3*100/$2 }'
}

Now, in our main loop, we can capture this value into a variable:

# In the main while loop
MEM_USAGE=$(get_memory_usage)
echo "Memory Usage: ${MEM_USAGE}%" # We now format the output here

This simple change makes our script much more flexible.


3. Implementing Threshold Checks

With our refactored functions, we can now add the if statements. Let's start by defining our thresholds at the top of the script. This makes them easy to find and change later.

#!/usr/bin/env bash

# --- Thresholds ---
CPU_THRESHOLD=80
MEM_THRESHOLD=80
DISK_THRESHOLD=85

Now, let's add a check for each metric.

A. Disk Usage Check (Integer Comparison)

The df command we used gives a percentage without a decimal, so we can use a simple integer comparison. After capturing the disk usage, we add an if block.

# In the main while loop...
DISK_USAGE=$(get_disk_usage) # Assumes get_disk_usage now returns just the number from the 'Use%' column
echo "Disk Usage: ${DISK_USAGE}%"

if [ "$DISK_USAGE" -gt "$DISK_THRESHOLD" ]; then
  echo "WARNING: Disk usage has exceeded the threshold of ${DISK_THRESHOLD}%!"
fi

This is straightforward: if the captured value in DISK_USAGE is greater than our DISK_THRESHOLD, it prints the warning.

B. CPU and Memory Checks (Floating-Point Comparison)

Our CPU and memory functions return floating-point numbers (e.g., 15.73). The standard Bash test [ ... ] can't handle decimals and will cause an error.

We need a different tool for this: bc, the basic calculator utility. We can pipe a comparison to bc, which will output 1 (for true) or 0 (for false).

$ echo "15.73 > 80.0" | bc -l
0
$ echo "85.1 > 80.0" | bc -l
1

Note: The -l flag loads the math library, which is necessary for these comparisons.

We can use this inside an if statement with a special arithmetic evaluation syntax ((...)).

The resource below provides a complete script that uses this technique effectively. Let's look at how it implements these checks.

Linux System Health Monitoring Using Bash Scripting

The article 'Linux System Health Monitoring Using Bash Scripting' by Abdullah shows a complete monitoring script. Pay close attention to how it performs the checks for CPU, memory, and disk usage.

Read 'Step 2: Writing the Bash Script' and 'Step 3: Script Explanation', focusing on the section 'Threshold Alerts'. Notice how it combines echo and bc within an if statement to handle floating-point numbers for CPU and memory, and a standard test for disk usage.

As you saw in the article, the structure for checking a floating-point number is:

# In the main while loop...
CPU_USAGE=$(get_cpu_usage)
echo "CPU Usage: ${CPU_USAGE}%"

if (( $(echo "$CPU_USAGE > $CPU_THRESHOLD" | bc -l) )); then
  echo "WARNING: CPU usage has exceeded the threshold of ${CPU_THRESHOLD}%!"
fi

Let's break down that if condition:

  1. echo "$CPU_USAGE > $CPU_THRESHOLD": This creates the comparison string, like "15.73 > 80".
  2. | bc -l: This pipes the string to bc, which evaluates it and outputs 1 or 0.
  3. $(...): This is command substitution, so the 1 or 0 replaces this entire part.
  4. (( ... )): This is Bash's arithmetic context. Inside it, ((1)) evaluates to true, and ((0)) evaluates to false.

This is the standard and most reliable way to compare decimal numbers in Bash scripts.


Test your understanding!

It's time to upgrade your system_monitor.sh script. Open the file you created in the last lesson and make the following modifications:

  1. Define Thresholds: At the top of your script, add variables for CPU_THRESHOLD, MEM_THRESHOLD, and DISK_THRESHOLD and set them to reasonable values (e.g., 80 or 85).
  2. Refactor Functions: Change get_cpu_usage, get_memory_usage, and get_disk_usage so they only echo the raw numeric value, without any labels or percentage signs.
  3. Update Main Loop:
    • In your while loop, capture the return value from each function into a variable (e.g., CPU_USAGE=$(get_cpu_usage)).
    • Update your echo or printf statements to display the formatted output using these new variables.
  4. Add Conditional Checks: After displaying each metric, add an if block to check if the current usage exceeds the corresponding threshold. If it does, print a clear warning message.
    • Use if [ ... -gt ... ] for the disk usage check.
    • Use if (( $(echo "... > ..." | bc -l) )) for the CPU and memory checks.

Note: If bc is not installed on your system, you can install it with sudo apt install bc or sudo yum install bc.

Show answer

Here is a complete example of the modified script. Your implementation might vary slightly, but it should follow this structure.

#!/usr/bin/env bash

#
# A system monitoring script that displays usage and warns if thresholds are exceeded.
#

# --- Thresholds ---
CPU_THRESHOLD=80
MEM_THRESHOLD=80
DISK_THRESHOLD=85

# --- Functions ---

get_cpu_usage() {
  # Subtract idle percentage from 100 to get total usage.
  awk '{print 100 - $1}' <(grep 'cpu ' /proc/stat | awk '{usage=($2+$4)*100/($2+$4+$5)} END {print usage ""}')
  # NOTE: The above is a more robust way to get CPU that avoids the slowness of `top`.
  # The original `top` command from the last lesson also works fine:
  # top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1}'
}

get_memory_usage() {
  # Returns memory usage as a percentage, e.g., 45.32
  free | awk '/Mem/ {printf "%.2f", $3/$2 * 100}'
}

get_disk_usage() {
  # Returns disk usage for '/' as an integer, e.g., 19
  df / | awk 'NR==2 {print $5}' | sed 's/%//'
}

# --- Main Loop ---
while true; do
  clear
  echo "--- System Resource Monitor ---"
  echo "Last updated: $(date)"
  echo "-------------------------------"

  # --- CPU Check ---
  CPU_USAGE=$(get_cpu_usage)
  printf "CPU Usage: %.2f%%\n" "$CPU_USAGE"
  if (( $(echo "$CPU_USAGE > $CPU_THRESHOLD" | bc -l) )); then
    echo "  -> WARNING: High CPU Usage!"
  fi

  # --- Memory Check ---
  MEM_USAGE=$(get_memory_usage)
  printf "Memory Usage: %.2f%%\n" "$MEM_USAGE"
  if (( $(echo "$MEM_USAGE > $MEM_THRESHOLD" | bc -l) )); then
    echo "  -> WARNING: High Memory Usage!"
  fi

  # --- Disk Check ---
  DISK_USAGE=$(get_disk_usage)
  printf "Disk Usage: %s%%\n" "$DISK_USAGE"
  if [ "$DISK_USAGE" -gt "$DISK_THRESHOLD" ]; then
    echo "  -> WARNING: Disk Space Critical!"
  fi

  echo "-------------------------------"
  sleep 5
done

Conclusion

Excellent work! You've transformed your monitoring script from a passive display into an active detection tool. This is a fundamental skill in automation and a core concept in DevOps. A system that can flag its own problems is the first step toward a system that can fix its own problems.

Key Takeaways:

  • Conditional Logic: You learned to use the if ... then ... fi construct to make decisions in your script.
  • Integer vs. Floating-Point Comparison: You can now handle both integer checks (with -gt, -lt, etc.) and floating-point checks using the bc utility.
  • Script Refactoring: You improved your script's design by separating data gathering (in functions) from data processing and display (in the main loop).
  • Threshold-Based Alerting: You implemented a basic alerting mechanism by defining thresholds and printing warnings when they are breached.

Next Up

Printing a warning to the console is great, but what if no one is looking? A true monitoring system needs to notify the right people. In the next lesson, you will extend this script to send an email alert when a threshold is exceeded, making your automated monitor truly useful.