-
Snapshot system state — run these in parallel:
ps -eo pid,pcpu,pmem,comm -r | head -25
top -l 1 -n 1 -stats pid,command,cpu | head -10
sysctl vm.swapusage && memory_pressure
-
Triage into CPU-bound vs memory-bound:
- CPU-bound: One or more processes above 30% CPU. Identify and investigate.
- Memory-bound: Swap used > 2 GB, compressor > 4 GB, or "Pages free" under 10,000. Even if no single process is hot, the system spends CPU on compression/decompression and swap I/O.
- Both: Common. A runaway daemon often causes memory pressure too.
-
For CPU hogs — identify the category:
| Process type | Normal range | Investigate if | Common fix |
|---|
| WindowServer | 10-20% | >35% sustained | Usually driven by another app doing excessive redraws. Fix the other app. |
| contactsd / AddressBookManager | <2% | >10% | Stuck sync loop. Check error logs, disable problematic account sync (Gmail CardDAV is a known offender). |
| mds / mds_stores / mdworker | <5% each | >20% or many workers | Spotlight indexing. Usually transient after updates. sudo mdutil -i off / to pause if urgent. |
| kernel_task | varies | >200% | Thermal throttling. Not fixable via software — machine needs cooling. |
| nsurlsessiond / cloudd | <5% | >15% | iCloud sync stuck. Check System Settings > Apple ID > iCloud. |
| trustd | <1% | >10% | Certificate validation loop. Often clears on reboot. |
| Electron apps (Discord, Slack, VS Code, Windsurf) | 2-5% each | >15% | Restart the app. Electron apps leak memory over time. |
-
For memory pressure — assess severity:
sysctl vm.swapusage
memory_pressure
| Indicator | Healthy | Warning | Critical |
|---|
| Swap used | <500 MB | 500 MB - 2 GB | >2 GB |
| Compressor | <2 GB | 2-5 GB | >5 GB |
| Pages free | >20,000 | 5,000-20,000 | <5,000 |
| Free percentage | >30% | 10-30% | <10% |
If critical: recommend quitting the heaviest apps. List them with memory percentages so the user can make an informed choice.
-
For runaway system daemons — pull error logs:
/usr/bin/log show --predicate 'process == "<daemon_name>" AND (messageType == 16 OR messageType == 17)' --last 5m --style compact 2>&1 | head -50
messageType == 16 = Error, messageType == 17 = Fault
- Use
/usr/bin/log (full path) because shell aliases can interfere
- Look for repeated errors in tight loops — that's the signature of a stuck daemon
-
Report findings using this structure:
Top resource consumers:
| Process | CPU % | RAM % | Status |
|---|
| contactsd | 124% | 0.9% | Stuck — sync loop (see below) |
| WindowServer | 47% | 0.7% | Elevated — driven by contactsd churn |
Memory pressure: Critical — 15 GB / 16 GB used, 4.3 GB swap, 6.5 GB compressor
Diagnosis: contactsd is in a sync error loop caused by [root cause]. This is stuck, not transient.
Recommended fixes (least to most disruptive):
- [First option]
- [Second option]
- [Nuclear option, if applicable]
Adapt the structure to fit — skip sections that don't apply, add detail where it matters. The key is: show the data, state the diagnosis, order the fixes.