A script to do something when load average is above a specified value

I wanted to create a script that will do something when the 1 minute load average is above some specified value. The problem I have is that bash does not handle floating point numbers. How can I solve this ?

So far I have come up with something like this :

#!/bin/bash
load_average_1=$(uptime | awk '{print $12}' | cut -d "," -f 1)
echo $load_average_1

 if  "$load_average_1" -ge "1"  ]; then
   echo "Load average to high "
 else
   echo "Load average not to high "
 fi

Should I use a different shell for this ? I’ve also read somewhere that awk can convert to integers. Will this work here ?
Anyone has a ready solution for this and could share it with me ? :slight_smile:

Thanks a lot in advance,

Best regards,
Greg

glistwan wrote:
> I wanted to create a script that will do something when the 1 minute
> load average is above some specified value. The problem I have is that
> bash does not handle floating point numbers. How can I solve this ?
>
> So far I have come up with something like this :
>
> Code:
> --------------------
> #!/bin/bash
> load_average_1=$(uptime | awk ‘{print $12}’ | cut -d “,” -f 1)
> echo $load_average_1
>
> if “$load_average_1” -ge “1” ]; then
> echo "Load average to high "
> else
> echo "Load average not to high "
> fi
> --------------------
>
>
> Should I use a different shell for this ? I’ve also read somewhere that
> awk can convert to integers. Will this work here ?
> Anyone has a ready solution for this and could share it with me ? :slight_smile:

Why use a shell at all? Perl will do this easily, as will other languages.

#!/usr/bin/perl
use strict;
use warnings;

my $uptime = uptime;

my ($load_average_1) = $uptime =~ /load average:\s+(\d.]+)/;

print $load_average_1, "
";

if ($load_average_1 > 1) {
print "Load average to high
";
}
else {
print "Load average not to high
";
}

If you want to continue to use awk and shell, just rethink the problem. Pass in the limit to awk in its command and have it return two strings, the load average plus a flag indicating whether or not it’s too high.

For handling floating point numbers in bash use bc. Example:

#!/bin/bash
THRESHOLD="1.00"
LOAD=$(uptime | sed -e "s/^.*[a-z]: //; s/,.*//")
echo "One minute load average = $LOAD"
if test $(echo "$LOAD > $THRESHOLD" | bc -l) == 1 ; then
        echo "Load average too high "
else
        echo "Load average not too high "
fi
exit 0

On my system, the result of

uptime | awk '{print $12}'

would be an empty string since the output of uptime shows only 10 fields. (?)

I see 12 fields separated by spaces (ignoring leading space?) on 11.3:

 17:36pm  up 73 days  2:13,  6 users,  load average: 0.06, 0.18, 0.08

In any case OP is fetching the last one being a 15 minute average and not the 1 minute value as desired. My sed script shown above gets the right one.

Now I see 12 fields too. There is no “0 day”, so two fields are missing if the system is up for less than 1 day.

from my example below:

# uptime
20:29pm  up 13 days  9:51,  4 users,  load average: 0.00, 0.02, 0.05

OP’s command gives that (field #12):

uptime | awk '{print $12}' | cut -d "," -f 1
0.05

The last pipe has no effect in this case.
Your sed command gives that (field #10):


uptime | sed -e "s/^.*[a-z]: //; s/,.*//" 
0.00

So does this one - using a single replace:

uptime | sed 's/^.*: \(^,]*\),.*/\1/'
0.00

I don’t know which field is relevant, but to obtain the last field in awk, you can use:

uptime | awk '{print $NF}'

so the result won’t vary whether the output has 10 fields or 12. An if you need field #10, you could use:

uptime | awk '{T=$(NF-2) ; sub(/,/,"",T) ; print T}'

I f you want to compare the result with a threshold of 100 (1.00 * 100), you could also take this value:

uptime | awk '{T=$(NF-2) ; sub(/,/,"",T) ; sub(/\./, "", T) ; sub (/^00*/, "", T) ; print T}'

It might be less error prone and slightly more efficient to read the load averages from /proc/loadavg. E.g.

$ cat /proc/loadavg
0.41 0.50 0.29 1/326 3352

Sorry I didn’t mention that I want to run this script on Solaris 5.10, on which the uptime returns this :

 uptime
  8:29am  up 234 day(s),  7:17,  1 user,  load average: 0.07, 0.06, 0.06

So this would be 15 minutes load average :

 uptime | awk '{print $12}' | cut -d "," -f 1
0.06

5 minutes :

 uptime | awk '{print $11}' | cut -d "," -f 1
0.06

1 minute :

 uptime | awk '{print $10}' | cut -d "," -f 1
0.07

And this returns average : on the Solaris I’m using :

uptime | awk '{print $9}' | cut -d "," -f 1
average:

@djh-novel
I think your script should be without smilies :

#!/usr/bin/perl
 use strict;
 use warnings;

 my $uptime = `uptime`;

 my ($load_average_1) = $uptime =~ /load averages :/+(\d\.]+)/;

 print $load_average_1, "
";

 if ($load_average_1 > 1) {
 print "Load average to high
";
 }
 else {
 print "Load average not to high
";
 }

But this gives me an error under openSUSE and Solaris :

Backslash found where operator expected at ./pearl.sh line 7, near "d\"
syntax error at ./pearl.sh line 7, near "d\"
Execution of ./pearl.sh aborted due to compilation errors.

@vodoo
The script runs without errors on openSUSE but gives me some warning on Solaris :

./vodoo.sh
One minute load average = 0.06
syntax error on line 1, teletype
./vodoo.sh: line 5: test: ==: unary operator expected
Load average not too high

@ken_yap last post
This won’t work for Solaris :stuck_out_tongue:

# ls /proc/
0      13519  183    2198   25501  25905  29777  489    577    679    7882
1      13534  184    2199   25505  28768  29778  518    578    694    828
1024   13570  2      2200   25506  28783  29784  523    579    7      834
1055   15212  204    2201   25507  28789  29800  524    581    713    835
1056   17330  20548  233    25508  28792  29971  525    582    751    863
10565  17498  208    24887  25529  28810  3      537    583    756    9
1057   17502  2195   24889  25569  290    33     553    673    776
1298   17503  2196   253    25904  29629  358    559    678    787

@please_try_again
Thanks I’ll try out your suggestion.

Best regards,
Greg

Meh, too bad for Solaris. :stuck_out_tongue:

It’s easy enough to have a shell script that conditionally uses /proc/loadavg if available.

Yes I really hate this OS. Nothing works as expected :stuck_out_tongue: Most of the things due to lack of knowledge on my side but it’s very hard to find any useful help on the Internet if You don’t have Oracle support.

Best regards,
Greg

glistwan wrote:
> @djh-novel
> I think your script should be without smilies :
>
> Code:
> --------------------
> #!/usr/bin/perl
> use strict;
> use warnings;
>
> my $uptime = uptime;
>
> my ($load_average_1) = $uptime =~ /load averages :/+(\d.]+)/;
>
> print $load_average_1, "
";
>
> if ($load_average_1 > 1) {
> print "Load average to high
";
> }
> else {
> print "Load average not to high
";
> }
> --------------------
>
>
> But this gives me an error under openSUSE and Solaris :
>
> Code:
> --------------------
> Backslash found where operator expected at ./pearl.sh line 7, near “d”
> syntax error at ./pearl.sh line 7, near “d”
> Execution of ./pearl.sh aborted due to compilation errors.
> --------------------

That’s because you misquoted my script. You’ve mangled the regex.

I’m using NNTP, so there were no smilies in my script when I posted and
none in it when I received it back. I’ve no idea how you get to see what
I posted. Why can’t we all just use mailing lists like grownups. Hrmph!

The regex is:

/load average:\s+(\d.]+)/

That is slash load average colon backslash s plus left-parenthesis
left-bracket backslash d backslash dot right-bracket plus
right-parenthesis slash

And you’ll all note that it is portable and doesn’t care whether the
system has ben up for less than a day. :stuck_out_tongue: (that is a smiley!)

This works as expected although I’ve got no idea why :slight_smile:

Thank You.

uptime
  1:18pm  up 234 day(s), 12:05,  1 user,  load average: 1.21, 0.73, 0.36
 ./pearl.sh
1.18
Load average to high
 ./pearl.sh
1.14
Load average to high
 ./pearl.sh
1.12
Load average to high
 ./pearl.sh
1.11
Load average to high
 ./pearl.sh
1.07
Load average to high
 ./pearl.sh
1.06
Load average to high
 ./pearl.sh
1.04
Load average to high
 ./pearl.sh
1.03
Load average to high
 ./pearl.sh
1.00
Load average not to high
 ./pearl.sh
0.96
Load average not to high

This is the version without smilies that works :

#!/usr/bin/perl
 use strict;
 use warnings;

 my $uptime = `uptime`;

 my ($load_average_1) = $uptime =~ /load average:\s+(\d\.]+)/;

 print $load_average_1, "
";

 if ($load_average_1 > 1) {
 print "Load average to high
";
 }
 else {
 print "Load average not to high
";
 }

Best regards,
Greg

glistwan wrote:
> This works as expected although I’ve got no idea why :slight_smile:

That’s the magic of perl!

TMTOWTDI and none of them understandable. :slight_smile:

If you’re feeling masochistic, you could try:
http://perldoc.perl.org/perlre.html#Regular-Expressions

The script runs without errors on openSUSE but gives me some warning on Solaris :

Try:

#!/bin/bash
THRESHOLD="1.00"
LOAD=$(uptime | sed -e "s/^.*[a-z]: //; s/,.*//")
echo "One minute load average = $LOAD"
if test $(echo "$LOAD > $THRESHOLD" | bc -l) = 1 ; then
        echo "Load average too high "
else
        echo "Load average not too high "
fi
exit 0

this is just one single = comparison operator. I always write == for readability which works fine on linux, but obviously not everywhere.

You could even pass 1 | 2 | 3 as an argument to select which load you want. Then it is:

#!/bin/bash
THRESHOLD="1.00"
LOADS=$(uptime | sed -e "s/^.*: //; s/,//g")
if test -z "$1" ; then
        OPT=1
else
        case "$1" in
        1|2|3) OPT=$1
        ;;
        *) OPT=1
        ;;
        esac
fi
LOAD=$(echo $LOADS | cut -d ' ' -f $OPT)
echo "Load average($OPT) = $LOAD"
if test $(echo "$LOAD > $THRESHOLD" | bc -l) = 1 ; then
        echo "Load average too high"
else
        echo "Load average not too high"
fi
exit 0

More variants are up to you. openSUSE motto applies: Have fun!

Thanks vodoo. I’ll try out your suggestion tomorrow. For the time being I settled on pearl and enhanced the script to send emails.

This does everything I need :

#!/usr/bin/perl
 use strict;
 use warnings;
 
 my $sendmail = "/usr/sbin/sendmail -t";
 my $reply_to = "Reply-to: noreply\@no-mx.org
";
 my $subject = "Subject: Load average warning
";
 my $to = "To: email1\@testdomain.com,email2\@testdomain.com
";

 my $uptime = `uptime`;
 my $date = `date`;

 my ($load_average_1) = $uptime =~ /load average:\s+(\d\.]+)/;

 if ($load_average_1 > 2) {
  open(SENDMAIL, "|$sendmail") or die "Cannot open $sendmail: $!";
  print SENDMAIL $reply_to;
  print SENDMAIL $subject;
  print SENDMAIL $to;
  print SENDMAIL "Content-type: text/plain

";
  print SENDMAIL "1 minute load average is ".$load_average_1." as on ".$date."
";
  close(SENDMAIL);
 }
 else {
  print "1 minute load average is ".$load_average_1." as on ".$date."
";
 }

If this is just one of many things you want to monitor on the machine, perhaps you might look at a more elaborate framework like Nagios or Cacti which can monitor lots of things on many machines, send out notifications in various ways, keep logs and graph performance over time.

Maybe you should try to put your code in [noparse]

...

[/noparse], even in NNTP, so that webforum users don’t get smilies. (?)

I’m using opsview for this but I can’t install NRPE plugin on that Solaris not to lose support on some other software that’s running there.

Best regards,
Greg

please try again wrote:
> Maybe you should try to put your code in
> Code:
> --------------------
> …
> --------------------
> , even in NNTP, so that webforum users don’t get smilies. (?)

Or maybe the gateway simply shouldn’t translate text* into garbage! Why
should I be expected to follow magic incantations because of a forum
software bug. Fix the bug!

Does it even work?

Code:

/load average:\s+(\d.]+)/

Cheers, Dave

  • What I send is:
    Content-Type: text/plain; charset=UTF-8