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?

Joshua Goldberg
  • 5,059
  • 2
  • 34
  • 39
thibmaek
  • 1,171
  • 3
  • 13
  • 21
  • See also on AskDifferent, [Is there an app that will notify me (in notifications center) if a process is using up a high percentage of CPU for a specified amount of time?](https://apple.stackexchange.com/q/90270/19289) – Joshua Goldberg Jan 20 '21 at 21:30

2 Answers2

5

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).
Community
  • 1
  • 1
mklement0
  • 382,024
  • 64
  • 607
  • 775
  • 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. Thanks – thibmaek Jun 10 '14 at 18:37
  • @Thibmaekelbergh: Glad to hear it. How did you achieve the _periodic_ invocation? Did you just put a loop into the `bash` script? – mklement0 Jun 10 '14 at 19:06
  • 1
    @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. – mklement0 Jun 11 '14 at 05:04
  • thanks! The Automator flow I created still got some issues, will see what yours does. – thibmaek Jun 11 '14 at 20:24
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.

jweaks
  • 3,674
  • 1
  • 19
  • 32
  • 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 number_ – thibmaek Jun 10 '14 at 17:32
  • 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. – jweaks Jun 10 '14 at 18:40
  • 1
    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. – mklement0 Jun 10 '14 at 19:05
  • 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). – mklement0 Jun 10 '14 at 19:07
  • 1
    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. – jweaks Jun 10 '14 at 19:10
  • 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 `). Curiously, though, `top` waits for an _additional_ interval _after_ outputting the last sample - no idea why. – mklement0 Jun 10 '14 at 19:11
  • 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. – jweaks Jun 10 '14 at 19:14
  • 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. – mklement0 Jun 10 '14 at 19:17
  • 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' – mklement0 Jun 10 '14 at 19:24
  • 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. – jweaks Jun 10 '14 at 19:30
  • 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. – mklement0 Jun 10 '14 at 19:52
  • 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. – jweaks Jun 10 '14 at 19:55
  • 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. – mklement0 Jun 10 '14 at 20:00