After reading the answer by MelBurslan and various comments, I decided to try (inspired by their suggestions) to do a version in Lua. This was done in Lua 5.1.5 - I'm not sure if it will work with the latest Lua.
The general idea is to use Lua's popen
(open a pipe) to execute top
and then process the resulting data using a regular expression (or pattern, as it is called in Lua). Matching lines (which would be most of them) are then considered for crossing the threshold percentage. If they do, they are added to a table.
If the table is not empty, then zenity
is called to display a message to the user. A few "gotchas" I found during development:
- I added a timeout of 60 seconds to zenity so that, if you were not at the PC at the time, you didn't fill the screen with warning dialogs.
- I added
--display=:0.0
so that a display screen was found when running under cron
.
I simplified the test for "every 15 minutes" in the crontab, like this:
*/15 * * * * /home/nick/check_cpu_usage.lua
The regular expression captures everything from top
in case you want to do other tests (eg. using too much memory).
I think this would be faster than firing off lots of processes and subshells. It seems to work OK. Test by reducing the threshold (eg. to 5) and change the crontab entry to check every minutes.
check_cpu_usage.lua
#! /usr/local/bin/lua
THRESHOLD = 90 -- percent
-- pipe output of top through a file "f"
f = assert (io.popen ("top -b -n 1 -w 512"))
t = { }
-- check each line
for line in f:lines() do
-- match top output, eg.
-- PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
-- 30734 nick 20 0 6233848 3.833g 3.731g S 8.6 12.2 3:11.75 VirtualBox
local pid, user, priority, nice, virt, res, shr,
status, cpu, mem, time, command =
string.match (line,
"^%s*(%d+)%s+(%a+)%s+(%-?%d+)%s+(%-?%d+)" ..
-- pid user priority nice
"%s+([%d.]+[g]?)%s+([%d.]+[g]?)%s+([%d.]+[g]?)%s+([DRSTZ])%s+(%d+%.%d+)%s+(%d+%.%d+)" ..
-- virtual res shr status %cpu %mem
"%s+([0-9:.]+)%s+(.*)$")
-- time command
-- if a match (first few lines won't) check for CPU threshold
if pid then
cpu = tonumber (cpu)
if cpu >= THRESHOLD then
table.insert (t, string.format ("%s (%.1f%%)", command, cpu))
end -- if
end -- if
end -- for loop
f:close()
-- if any over the limit, alert us
if #t > 0 then
os.execute ('zenity --title="CPU usage warning!" --info ' ..
'--text="These processes are using more than ' ..
THRESHOLD .. '% CPU:\n' ..
table.concat (t, ", ") ..
'" --ok-label="OK" ' ..
'--timeout=60 ' .. -- close dialog after one minute in case we aren't around
'--display=:0.0 ' -- ensure visible when running under cron
)
end -- if
taskset
uses a mask to specify which CPUs a process can run on. Each bit maps to one CPU; if a bit is set to 1, the process can run on that CPU, if it’s set to 0, it can’t. Thus a mask of FF means any CPU from 0 to 7 (not one specific CPU), and a mask of 2 means only CPU 1.
Best Answer
Update: Newer versions of taskset have a
-a
/--all-tasks
option that "operates on all the tasks (threads) for a given pid" and should solve the behavior I show below.I wrote a Python script that simply spins up some threads and burns CPU cycles. The idea is to test taskset against it, as it's quite simple.
Just running the Python script eats up about 150% CPU usage.
Launching my Python script with taskset works as expected. Watching top shows the Python process pegged at 100% usage.
Interestingly, launching the Python script and then immediately using taskset to set the just-started process' affinity caps the process at 100%. Note from the output that the Linux scheduler finished executing the Bash commands before spawning the Python threads. So, the Python process was started, then it was set to run on CPU 0, then it spawned its threads, which inherited the proper affinity.
That result contrasts with this method, which is exactly the same but allows the Python threads to spawn before setting the affinity of the Python process. This replicates the "taskset does nothing" results I described above.
Apparently threads spawned before the parent process' affinity is changed don't inherit the affinity of their parent. If someone could edit in a link to documentation that explains this, that would be helpful.