17

In a bash script I need to wait until CPU usage gets below a threshold.

In other words, I'd need a command wait_until_cpu_low which I would use like this:

# Trigger some background CPU-heavy command wait_until_cpu_low 40 # Some other commands executed when CPU usage is below 40% 

How could I do that?

Edit:

  • target OS is: Red Hat Enterprise Linux Server release 6.5
  • I'm considering the average CPU usage (across all cores)
10
  • There are many posts that can help to get the current CPU usage. My approach would be to keep checking the usage after certain intervals and if the usage in not below threshold keep checking it and once its below the threshold, just to proceed with the rest of the code. I am not a bash coder. Please make me aware if I missed something important? unix.stackexchange.com/questions/152988/… Commented Aug 31, 2015 at 13:05
  • 2
    You could narrow this question down to a particular environment (eg, Linux or Solaris), which will drive the specific tool to use. There is no standard POSIX way to get the CPU utilization. Further, you should clarify what you mean by CPU: since modern CPU have multiple cores, do you consider these each CPU? Do you want an average of the cores? Do you want average across all CPUs' cores? Commented Aug 31, 2015 at 13:06
  • 1
    yum install sysstat, then mpstat -P ALL 1 | grep all | awk '{print $9}' should get you in the ballpark of the idle % on all CPU. That said, you could also clarify the question by specifying whether the threshold is percentage "idle" or "used". Commented Aug 31, 2015 at 13:14
  • 2
    Adding a small code sample outlining the context you want to use this in, however contrived, might help to dissuade down voters (just something I've noticed with this type of question) Commented Aug 31, 2015 at 13:16
  • 1
    Question: what do you mean by 40% CPU usage? On most Unix systems, having more that the one CPU means the maximum CPU usage is 100% * NumCPUs. So is that 40% meaning "40% of maximum CPU usage" or "40% as the system reports it"? Commented Aug 31, 2015 at 14:50

4 Answers 4

12

A much more efficient version just calls mpstat and awk once each, and keeps them both running until done; no need to explicitly sleep and restart both processes every second (which, on an embedded platform, could add up to measurable overhead):

wait_until_cpu_low() { awk -v target="$1" ' $13 ~ /^[0-9.]+$/ { current = 100 - $13 if(current <= target) { exit(0); } }' < <(LC_ALL=C mpstat 1) } 

I'm using $13 here because that's where idle % is for my version of mpstat; substitute appropriately if yours differs.

This has the extra advantage of doing floating point math correctly, rather than needing to round to integers for shell-native math.

Sign up to request clarification or add additional context in comments.

3 Comments

This script worked perfectly, until under certain mysterious circumstances involving gitlab-runner, mpstat appears to ignore the Broken Pipe when awk exits, and keeps running. Using strace showed every write is returning EPIPE. Just commenting as a warning in case someone else has this problem.
@szmoore, I'd suggest filing a ticket to get this fixed in mpstat; it really should consider that error fatal. That said, if you need a workaround in bash for this, I think it should be possible to build one if you have a version of the shell new enough for process substitutions to store their PID in $! (I think that feature was introduced in 4.3, maybe?) -- if you ask a new question on the subject, feel free to @ me in.
@CharlesDuffy, I had a really hard time reproducing the issue outside of a gitlab-runner environment. I suspected gitlab-runner blocked the SIGPIPE signal somehow but didn't have time to prove who I should be filing the bug report against. For my use case I rewrote the script in Python using psutil instead, so it's unfortunately no longer a bash question.
6
wait_for_cpu_usage() { current=$(mpstat 1 1 | awk '$12 ~ /[0-9.]+/ { print int(100 - $12 + 0.5) }') while [[ "$current" -ge "$1" ]]; do current=$(mpstat 1 1 | awk '$12 ~ /[0-9.]+/ { print int(100 - $12 + 0.5) }') sleep 1 done } 

Notice it requires sysstat package installed.

12 Comments

current is evaluated only once, right? Shouldn't it be recomputed inside the loop?
Actually, current is not evaluated only once, the problem is that I forgot to update current status, so you are right
line 4: [[: 6.67: syntax error: invalid arithmetic operator (error token is ".67")
Oops, I've forgotten to round the output. Check now
Value of current is always 7 (I added an echo in the script), even when doing some CPU-intensive stuff.
|
4

You might use a function based on the top utility. But note, that doing so is not very reliable because the CPU utilization might - rapidly - change at any time. Meaning that just because the check succeeded, it is not guaranteed that the CPU utilization will stay low as long the following code runs. You have been warned.

The function:

function wait_for_cpu_usage { threshold=$1 while true ; do # Get the current CPU usage usage=$(top -n1 | awk 'NR==3{print $2}' | tr ',' '.') # Compared the current usage against the threshold result=$(bc -l <<< "$usage <= $threshold") [ $result == "1" ] && break # Feel free to sleep less than a second. (with GNU sleep) sleep 1 done return 0 } # Example call wait_for_cpu_usage 25 

Note that I'm using bc -l for the comparison since top prints the CPU utilization as a float value.

6 Comments

I get an error. top -n1 | awk 'NR==3{print $2}' returns 6.2%us,
Which version of top and awk are you using?
top: 3.2.8, awk: 3.1.7
Hard to reproduce for me. You basically will need to remove the %us - which should not be there at all. Find out what's causing this (missing whitespace in top's output etc..) and fix the awk command according to this. (hint: -F[ %]+ should do the trick). My top-3.3.9 and GNU awk-4.1.1 are simply working.
Maybe try uptime? uptime | awk '{ gsub(/,/, ""); print $10; }'
|
1

As noted by "Mr. Llama" in a comment above, I've used uptime to write my simple function:

function wait_cpu_low() { threshold=$1 while true; do current=$(uptime | awk '{ gsub(/,/, ""); print $10 * 100; }') if [ $current -lt $threshold ]; then break; else sleep 5 fi done } 

In awk expression:

  • $10 is to get average CPU usage in last minute
  • $11 is to get average CPU usage in last 5 minutes
  • $12 is to get average CPU usage in last 15 minutes

And here is an usage example:

wait_cpu_low 20 

It waits one minute average CPU usage is below 20% of one core of CPU.

2 Comments

I like this script the most. One fix: print $11 needs to be used instead of print $10 which always throws zero on Ubuntu 20.04.
Another fix: You may want to put some sleep 60 as the first command in the function; otherwise the function would end immediately if launched simultaneously with some CPU-intensive task (due to load value being averaged).

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.