Repository navigation
Conversation
Lid-closed s2idle draws about 2 W on an M1 Pro, enough to empty the battery in about a day, and nothing told the owner. A system-sleep hook now records the battery's energy and both the wall and boot clocks around every sleep, rejecting intervals with a charger, charging, a gain, a missing reading or disagreeing clocks. A user unit shows what a counted sleep of 30 minutes or more used at the next unlock. omarchy power report gathers the draw, power sources, profiles, cpufreq limits, cpuidle states, PMP binding, mem_sleep, the last sleep and a CPU activity sample. The manual gains a Battery and sleep page.
A short sleep before the morning unlock replaced the overnight record, so the notice never showed it. The notice now adds up every counted sleep since the last unlock, waits at most two minutes on an unreadable lock state, and retries a failed send. The hook catches its own errors, bounds the platform check and keeps the pending reading until the log is written. The report stops counting guest time twice and asks to unplug only while a charger is online. The manual says how to turn the notice and the recording off.
A notice that failed every send is now tried again at the next resume instead of being marked shown. A battery that is discharging while a USB-C source is online shows its draw rather than asking to unplug. The opt-outs say to stop the running watcher and where NoExtract goes.
scottjones
left a comment
There was a problem hiding this comment.
Thanks Marcelo, this is really useful. Showing people what suspend costs is the right move while the kernel side gets sorted, and the power report is great to have.
Your "Needs a maintainer's yes" items: yes to all three: the notice on by default, the root hook in system-sleep, and the world-readable sleep log. We can revisit the notice once the sleep drain itself is fixed.
Tested on hardware: 14" M2 Max (J414c), on battery, with #25 staged by its own install script, the hook in system-sleep and the watcher running as a user unit.
- A 34.7-minute s2idle suspend (10:05:38 to 10:40:17 in the journal) was recorded as "used 0.8 Wh over 0.6 h (1.30 W average)".
- After I unlocked, the notice "Suspend used 0.8 Wh over 0.6 h." appeared.
- The boot clock and the RTC agreed (2101 s vs 2102 s).
omarchy-power-reportprinted every section, including the last suspend.
That covers the clock half of the item still open in your test plan. The energy half is where I'd look next.
The undercount, confirmed: my own energy_now readings around that suspend gave at least 1.22 Wh (my reading 8 s after resume was taken before the gauge settled) against the 0.75 Wh #25 recorded, so I ran a short test to see why. Same Mac, on battery, reading energy_now and power_now every 2 s across a 334 s (5.6 min) systemctl suspend.
- Before the suspend,
energy_nowfell in steady 11400 µWh steps at about 13 W. - The first reading after resume, right as the kernel logged "PM: suspend exit", was 18525000 µWh. That's about 0.50 Wh higher than the last reading before the suspend, so a reading taken then says the Mac gained energy while asleep.
- The gauge then caught up in big steps (148200 µWh twice, then about 80000 at a time) for about 35 s, before going back to normal 11400 steps. One more ~91000 step came at 86 s. Beyond what
power_nowsays was used while awake, it dropped an extra 0.71 Wh by 34 s and 0.80 Wh by 86 s. - From the settled reading minus the awake use, the sleep itself cost 0.29 Wh, about 3.1 W. That's higher than the longer run's rate, which fits a short suspend paying a fixed cost to go in and come out.
So the SMC gauge reports a stale, too-high energy_now right after resume and catches up over the next 30 to 90 s. record_post reads it from the system-sleep hook at the moment of resume, so it undercounts. On a short suspend it's worse: the reading comes out higher than before the sleep, so judge() returns energy-rose and the sleep is dropped without a record.
Suggestion: take the post-sleep reading once the gauge has settled. For example, keep sampling until energy_now has moved in normal steps for a while, or just wait about 90 s, then subtract the awake use: power_now integrated over that time, or the delay times the awake draw. Or take it at unlock and subtract what was used while awake since the resume.
Smaller things:
- The comment at
lib/sleep-cost:17-18claims more than the BOOTTIME/REALTIME cross-check can catch. Both clocks get the same sleep delta on resume, so it only catches a wall-clock step, not a boot clock that missed the sleep. My run shows it's fine in practice, so this is just a wording fix. - The platform gates have no inverse tests. Removing the gate in
omarchy-power-report(around line 195) or inlib/sleep-cost(around line 276) still passes both test files. - Under dbus-broker,
dbus-monitorlogs "unable to enable new-style monitoring ... Falling back to eavesdropping". It still gotPrepareForSleep, and I know the runtime's sleep monitor does the same. A plain signal subscriber such asgdbus monitor --system --dest org.freedesktop.login1needs no monitoring rights, though. Optional. - The PR body says the version "stays at 0.1.0". Once #30 lands no bump is needed at all, so that line can just go.
The post-resume reading is the one thing I'd want fixed before this merges. The rest is small.
|
Maintainer decisions on the three policy points:
Still open before merge: one real lid-closed suspend on battery to confirm the energy and boot-clock readings after resume. |
Lid-closed suspend on Apple Silicon is s2idle only and draws about 2 W on an M1 Pro, so a battery is flat in about a day, and nothing tells the owner. This makes the cost visible. It does not change how the Mac sleeps; the kernel side is separate work (maralcbr/omarchy-mx-mac#281, ticket 18).
What changes
omarchy power report(omarchy-mac/bin/omarchy-power-report) prints:placeholderdriver changes nothing)apple_pmpis bound,mem_sleep, and the last sleep's cost/usr/lib/systemd/system-sleep/omarchy-mac-sleep-cost→lib/sleep-cost pre|post). It recordsenergy_now, status, charger state, boot ID,CLOCK_REALTIMEandCLOCK_BOOTTIMEaround every sleep, and writes one line per sleep to/var/lib/omarchy-mac/sleep-cost.log(newest 200 kept).omarchy-sleep-cost.service, enabled for every user like the audio watchdog. It follows logind'sPrepareForSleep. After a resume it waits for the unlock, then shows "Suspend used X Wh over Y h. Overnight suspend can substantially drain this Mac." once. Every counted sleep since the last unlock is added up, and the notice needs 30 minutes or more.installstagesvendor/systemd/system-sleep/*as 0755.Needs a maintainer's yes
systemctl --user mask --now omarchy-sleep-cost.service./usr/lib/systemd/system-sleep, so an/etcmask is not possible; the manual documents pacmanNoExtract.omarchy-mac/versionstays at 0.1.0. Nothing is tagged yet, and no behaviour PR since 0.1.0 has bumped it.Test plan
omarchy-mac/test/power-report-test.sh: fixture M1 Pro sysfs and /proc. Covers charger-hidden draw, a bound and an unbound PMP, the ranking in the CPU sample, and guest time not counted twice.omarchy-mac/test/sleep-cost-test.sh: one valid sleep, plus each rejection: charging, Mains or USB online, energy rose, a missing reading, no pre-sleep reading or one from another boot, clock disagreement. Also short and stale sleeps, the wait for the unlock, adding up a sleep taken before the unlock, an unknown lock state, and retrying a failed send.omarchy-mac/test/alland shellcheck in an Arch container.platform-test.shrun natively on the M1 Pro, because under Rosetta it fails on cleanmaintoo (a.cache/rosettadirectory).sleep-cost pre/postrejects a real interval as charger-connecteddbus-monitorruns as the user (it falls back to eavesdropping, as the runtime's sleep monitor does)energy_nowandCLOCK_BOOTTIMEright after resume. The journal shows the monotonic clock stops during s2idle on this kernel, which is why the duration is cross-checked against the wall clock.