1

I'm trying to run a command every 10 minutes while usage of CPU is under 80%. I'm not used to bash scripting. I've found a way to get the total use of CPU ``top -b -n1 | grep "Cpu(s)" | awk '{print $2 + $4}'` and then I want to compare with a maximum of 80%. But I'm not sure why I cannot compare both. Here the script:

#!/bin/bash

MAX=80
CPU=`top -b -n1 | grep "Cpu(s)" | awk '{print $2 + $4}'`

while true
  do
    if $CPU < $MAX
      then
        echo $CPU  # This is an example
        CPU=`top -b -n1 | grep "Cpu(s)" | awk '{print $2 + $4}'`
        sleep 10m
      fi
    done

The script fails because interpret the numbers as a command... (cannot open 80: No such file) Why I cannot compre $MAX and $CPU? and how would you tackle this?

Thanks in advance.

2
  • 2
    Not sure why you would want that. Not disturbing the system when it is under heavy load ? Your "top" will anyway. Not to say that, depending on the real time needed by your command, the cpu load could rise above 80%. What about taking care of scheduling policies instead? ( man7.org/linux/man-pages/man7/sched.7.html ) – MC68020 Sep 3 '20 at 9:57
  • @MC68020 Thanks, I'll take a look on that option. I quite new to this, and sometimes is not clear which would be the best approach. – Xbel Sep 3 '20 at 10:52
3

< is used for redirections. If you need to compare integers:

if [ "$MAX" -gt "$CPU" ]

But $CPU can also be float and bash cannot operate on floats directly so you have to use bc:

if [ "$(bc -l <<< "${CPU} > ${MAX}")" -eq 1 ]

You should also use $(..) form instead of legacy backticks and you could assign CPU only once within while loop:

#!/bin/bash

MAX=80

while true
do
    CPU="$(top -b -n1 | grep "Cpu(s)" | awk '{print $2 + $4}')"

    if [ "$(bc -l <<< "${CPU} > ${MAX}")" -eq 1 ]
    then
        echo "$CPU"  # This is an example
        sleep 10m
    fi
done
2
  • Thanks, that is what I was looking for. Just one more question for curiosity. I have to run the script now with bash script.sh, because sh script.sh does not accept <<<. If I would like to use sh instead of bash could I change the <<< for another operator? or I would have to write the script in a different way? – Xbel Sep 3 '20 at 10:44
  • 1
    @Xbel: use | like that: if [ "$(echo "$CPU > $MAX" | bc)" -eq 1 ] – Arkadiusz Drabczyk Sep 3 '20 at 14:41
2

top(1) is not the best way to solve this problem. It's a rather heavyweight measuring tool, scanning far more than you need. Besides, it's focused on instantaneous values, which isn't the right thing here.

Instead, you should use the system load averages because they're computed continuously over long periods in the kernel. This means they carry no extra cost, thus don't impact your measurement, and they aren't affected by instantaneous changes...like calling top(1)!

Here's your script rewritten in terms of load averages:

#!/bin/bash
HIGH_LOAD=6      # 80% of an 8-core system; see link above

while true
  do
    CURR_LOAD=$(cut -f2 -d' ' < /proc/loadavg)
    if [ $CURR_LOAD -lt $HIGH_LOAD ]
      then
        run-background-command
      fi
    sleep 10m
  done

Note that the sleep call is moved outside the if condition: you want to delay regardless. Maybe you want to add an else instead so the delay is different for the low and high load cases.

Also unlike your version, we don't repeat the CPU usage check inside the loop and outside.

2
  • Thanks, as you pointed I had the problem of the measurement being instantaneous. his is a perfect solution. – Xbel Sep 4 '20 at 7:37
  • @Xbel: The very act of starting top and having it gather dozens of statistics per process and assemble them, and only then pull out CPU usage will spike the CPU usage! Your process might well skip run intervals purely because your measurement process is disturbing the result. – Warren Young Sep 4 '20 at 9:19

Your Answer

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.