Notifying when using high CPU. Via AppleScript or Automator?
Asked Answered
B

2

7

I'd like to automate that whenever a process is using more than 50% CPU
it sends a notification to my Notification Center

I'm using terminal-notifier for sending trough notifications
but I'm a bit stuck on what the best method is for creating this automation.

Should I use Automator.app or create a custom AppleScript and if so,
how do I make it to always be on?

Buckhound answered 9/6, 2014 at 22:13 Comment(1)
H
6

If this is for interactive use, let me suggest a pragmatic alternative:

  • Run Activity Monitor.
  • Control-click its dock icon.
  • Select Dock Icon > Show CPU Usage - or, for a floating window, Monitors > Show CPU Usage.

You'll get a per-core display of current CPU usage - clicking on it will show the full Activity Monitor window, where you can sort by CPU usage.


If you do need an automated solution, I suggest:

  • writing a bash script that uses top to find the highest-CPU-percentage task and invokes terminal-notifier, if above the threshold.
  • scheduling that script as a launchd task for periodic invocation.

Automator and AppleScript are probably too heavy for such - presumably frequent - background activity.

Even running top itself uses quite a bit of CPU.


Here's a simple bash script that roughly does what you want:

#!/usr/bin/env bash

read pct name < <(top -l 2 -n 1 -F -o cpu -stats cpu,command | tail -1)
if (( ${pct%.*} >= 50 )); then
  /Applications/terminal-notifier.app/Contents/MacOS/terminal-notifier \
    -message "Process > 50%: $name ($pct%)"
fi

Note that this takes at least 2 seconds to run, because 2 samples (1 second apart) must be collected to calculate CPU-usage percentages, so consider that when determining how frequently to invoke the command.

Update - see below for step-by-step implementation instructions.

References:


Step-by-step instructions for implementing the automated solution:

  • Create the bash script:
    • Create plain-text file ~/watchcpu (i.e., file watchcpu in your home folder), paste the above bash script into it, and save it.
  • Create the per-user launch agent for invocation at login and periodic invocation thereafter:
    • Create plain-text file ~/Library/LaunchAgents/WatchCPU.plist, paste the following XML document into it, and save it:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>KeepAlive</key>
    <false/>
    <key>Label</key>
    <string>WatchCPU</string>
    <key>ProgramArguments</key>
    <array>
        <string>bash</string>
        <string>-c</string>
        <string>. ~/watchcpu</string>
    </array>
    <key>RunAtLoad</key>
    <true/>
    <key>StartInterval</key>
    <integer>15</integer>
</dict>
</plist>
  • Load the per-user launch agent to activate it:
    • Run the following command in Terminal (only needed once; from then on, the file will auto-load on every login):
launchctl load ~/Library/LaunchAgents/WatchCPU.plist

Note:

  • You're free to choose your own filenames and a different location for your bash script, but the launch agent .plist file MUST reside in ~/Library/LaunchAgents in order to be loaded automatically at login.
  • The interval (key StartInterval) is chosen at 15 seconds; again, you're free to change that, but note that choosing more frequent invocations doesn't make much sense, because launchd (the service that invokes launch agents) throttles agents whose execution time is too close to the invocation interval; I'm unclear on the details, but in the solution at hand an interval of 10 seconds results in frequent throttling notices in system.log (check via Console.app).
Habitude answered 10/6, 2014 at 5:33 Comment(4)
This worked better than the above option for me. Created a new Automator app and made it run shell script, and added it to login items. ThanksBuckhound
@Thibmaekelbergh: Glad to hear it. How did you achieve the periodic invocation? Did you just put a loop into the bash script?Habitude
@Thibmaekelbergh: Because I was curious, I've implemented the bash script / launchd (per-user launch agent) solution - see my update with step-by-step instructions.Habitude
thanks! The Automator flow I created still got some issues, will see what yours does.Buckhound
V
0

You can easily pull the CPU usage to use either with a script or in an Automator workflow. Here's a script that you can schedule to run on a schedule and will notify if usage is over 50%:

set theDelay to 3 -- number of seconds to sample the CPU
set CPUusage to do shell script "top -F -l " & theDelay & " -n 1 -stats cpu | grep 'CPU usage:' | tail -1 | cut -d. -f1"
set idlePercent to word -2 of CPUusage as number
if idlePercent < 50 then display notification ("CPU usage is at " & (100 - idlePercent) & "%.") with title "CPU Usage"

See the comments to follow the editing of the do shell script command to allow for getting the integer naturally from the shell command to work better with non-English systems.

Vyner answered 10/6, 2014 at 6:52 Comment(13)
Thanks but it didn't seem to work, I stress tested my cpu by running yes > /dev/null & yes > /dev/null & yes > /dev/null & yes > /dev/null & and got the following error: error "\"44.22\" can't be converted to type number." number -1700 from "44.22" to numberBuckhound
Are you non-English system. You can add an explicit number coercion and that might fix it. I added it above. You might need a different method to pull the idle % number but that would be a piece of cake to figure out. By the way, to test, just change the < to a >. You don't have to load the system. Capture the CPUusage string and post here if you need help parsing the number.Vyner
You're solving a different problem in determining the overall CPU-use percentage, whereas @Thibmaekelbergh - the way I read the question - is looking for when a single process uses more than 50% of the CPU.Habitude
While invoking grep as GREP will work on the case-sensitive OSX filesystem, I suggest using the case as in the actual executable filename, grep (just to form a more portable habit).Habitude
Good points, mklement. I changed the grep. I see now the distinction regarding single vs overall CPU. Would just need to adjust the line used from top, similar to what you did. If he's happy with a bash script, I'm happy.Vyner
Just to clarify number of seconds to sample the CPU: strictly speaking you're specifying the number of sampling runs, spaced by the interval at which to take samples (defaults to 1 second, can be changed with -s <numIntegralSecs>). Curiously, though, top waits for an additional interval after outputting the last sample - no idea why.Habitude
Right again. My guess was that it: samples - waits - samples - waits - returns. That's why I just short handed to "number of seconds". I set that up with a property in case he wanted to save the AS as a keep running applescript app, and then could use the theDelay as the delay interval.Vyner
I see (any idea why it waits again at the end?). Note that since the sampling itself takes time and the overall execution time is delay + delay * processing-time-per-sample, the interval on the AppleScript side should be greater to avoid overlapping invocations.Habitude
Re potential locale issues: top behaves the same in all locales (is not locale-aware; but even if it didn't, do shell script without explicitly setting the locale actually defaults to the generic "C" locale). Thus, your command always returns a number with . as the decimal separator, irrespective of locale. AppleScript itself, by contrast, does respect the system locale, and in the case at hand - Belgian locale, if I were to guess - breaks when trying to convert the .-based string to a number. A simple fix is to have the shell command return an integer by adding `| cut -d. -f1'Habitude
I'll be curious to see if s/he posts the CPUusage string from his/her locale. The explicit coercion in AS should also fix. I suspect we're done hearing from them tho.Vyner
No, it is precisely the explicit number conversion on the AppleScript side that breaks in locales that use something other than . as the decimal separator: AppleScript, being generally locale-aware (with the curious exception of do shell script), expects a locale-appropriate number string, while your shell command always returns a number with "." as the decimal separator. And, as I said, invoking do shell script does NOT respect the system locale - unless you include an explicit export LANG=... statement.Habitude
Thanks for that clarification... I completely understand now. I didn't understand what you were saying in the earlier explanation, but now I get it. I'll update the answer for any future reader.Vyner
I appreciate the update; perhaps you could also add a brief explanation (or at least explicitly point to the comments), as not everyone will necessarily read through this lengthy exchange.Habitude

© 2022 - 2024 — McMap. All rights reserved.