7
\$\begingroup\$

This script converts a server load value into percentage, compares it with non desired state ($warning and $critical) and returns all information in NRPE (Nagios Remote Plugin Executor) friendly output.

Desired output:

CPU_CORES=1 load=0.30 | load_in_percent=30;95;100 (And the main thing exit code.)

Goal: Use so much less external commands how is possible. Why? I would like to have the script independent. The second thing is just training purpose of bash scripting.

#!/usr/bin/env bash

set -o errexit -o pipefail

warning=95
critical=100

# 1. Find out a load was before 15 minutes.
# citation: "Frankly, if your box spikes above 1.0 on the one-minute average, you're still fine. It's when the 15-minute average goes north of 1.0 and stays there that you need to snap to."
# source: Andre Lewis, https://scoutapm.com/blog/understanding-load-averages
read -r _ _ quarter_load_average _ _ < /proc/loadavg


# 2. Count all cores.
# citation: "How the cor es are spread out over CPUs doesn't matter. Two quad-cores == four dual-cores == eight single-cores. It's all eight cores for these purposes."
# source: Andre Lewis, https://scoutapm.com/blog/understanding-load-averages
while read -a row; do
  if [[ ${row[0]} = "cpu" ]] && [[ ${row[1]} = "cores" ]]; then
    cpu_cores=$(( cpu_cores + "${row[3]}" ))
  fi
done < /proc/cpuinfo


# 3. Convert load value into percentage.
# citation: "Sometimes it's helpful to consider load as percentage of the available resources (the load value divided by the number or cores).
# source: https://access.redhat.com/solutions/30554
load_mlt=$(echo "$quarter_load_average*100/1" | bc)
load_prc=$(( "$load_mlt" / "$cpu_cores" ))


# 4. Compare result with desired status and prepare returned value.
if [[ -z "$load_prc" ]]; then
  returned_text="LOAD UNKNOWN - check script"
  returned_code=3
else
  returned_text="CPU_CORES=$cpu_cores load=$quarter_load_average | load_in_percent=$load_prc;$warning;$critical"
  if [[ $load_prc -gt $critical ]]; then
    returned_code=2
  elif [[ $load_prc -gt $warning ]]; then
    returned_code=1
  else
    returned_code=0
  fi
fi

echo "$returned_text"
exit $returned_code
New contributor
David Mosler is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
\$\endgroup\$
  • \$\begingroup\$ Welcome to Code Review. I have rolled back your edit. Please do not update the code in your question to incorporate feedback from answers, doing so goes against the Question + Answer style of Code Review. This is not a forum where you should keep the most updated version in your question. Please see what you may and may not do after receiving answers. \$\endgroup\$ – Heslacher 2 days ago
6
\$\begingroup\$

This looks pretty good. Some suggestions:

  1. [[ is preferred over [ in Bash.
  2. This script could benefit from set -o errexit -o nounset -o pipefail. You'll need to set cpu_cores=0 before referring to it in the loop, but that's just best practice.
  3. Dividing by one isn't going to do much :)
  4. The quotes are a cute feature, but I'd much rather see a summary of the point in them than the full sentences. Something like

    Treat 15 minute load average above 1 as critical.

    A useful tactic I've found when dealing with comments is to think how you could name things to avoid comments altogether. Code can't lie, and names are very much like comments enshrined in code, so if you for example rename minute15 to quarter_load_average or something else human readable you should be able to remove the

    1. Find out a load was before 15 minutes.

    comment above it.

  5. Since you're only interested in one row from /proc/cpuinfo I would grep for it. Your goal of making the script stand-alone should be weighed against at least
    • maintainability, which will suffer greatly if you only use builtin commands,
    • speed, which is already not great for read, and
    • availability of grep, which is pretty much universal by now.
  6. You can redirect a line to standard input using some_command <<< "$line" to avoid redundant echos. So echo "$quarter_load_average*100/1" | bc could instead be bc <<< "$quarter_load_average*100/1".
\$\endgroup\$
  • \$\begingroup\$ 3. Well, there is a purpose. Maybe is there a better way of that. :) $ echo 0.5*100 | bc = 50.0 $ echo 0.5*100/1 | bc = 50. \$\endgroup\$ – David Mosler 2 days ago
3
\$\begingroup\$

I’d agree with all of l0b0’s answer – including the suggestion of using grep to process /proc/cpuinfo. An alternate way to count all CPU cores using an AWK one-liner would be:

cpu_cores=$(awk '/cpu cores/ { num_cores += $4} END { print num_cores}' /proc/cpuinfo)

This would also remove the usage of Bash arrays, resulting in the script being POSIX-compatible – if that’s something you’re interested in. (dash performs better than bash for running scripts but any performance gain would be negated by the time it takes to run awk).

\$\endgroup\$
0
\$\begingroup\$

There are syntax issues in artimetic operations:

Instead:

cpu_cores=$(( cpu_cores + "${row[3]}" ))
load_prc=$(( "$load_mlt" / "$cpu_cores" ))

Should be:

cpu_cores=$(( cpu_cores + ${row[3]} ))
load_prc=$(( "$load_mlt" / $cpu_cores ))
New contributor
David Mosler is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct.
\$\endgroup\$

Your Answer

David Mosler is a new contributor. Be nice, and check out our Code of Conduct.

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.