DEV Community

Cover image for My Laptop Woke 76 Times This Week; 13 of Them Were Me
Mustafa ERBAY
Mustafa ERBAY

Posted on Originally published at mustafaerbay.com.tr

My Laptop Woke 76 Times This Week; 13 of Them Were Me

Two days ago, writing here about what it cost to move CI onto my own laptop,
I opened a section called "the machine's right to sleep". I pasted in the output of
pmset -g custom and wrote this sentence:

Idle sleep is off while plugged in — the machine has been up for ten days and five
hours.

The first half is correct. For the second half I was reading the wrong counter: the
uptime counter doesn't stop during sleep, so that number showed only that the machine
hadn't been rebooted, not that it had stayed awake. The same piece repeated the claim
at its close as "ten days of uninterrupted wakefulness", which asserts even more. Two
days later, for a completely
unrelated reason, I ran pmset -g log. Fifty-four thousand one hundred seventy-nine
lines scrolled past, and I realised I had no idea what the machine I had just written
about actually does.

The log, and two counting traps

macOS keeps power management events under /var/log/powermanagement; pmset -g log
renders them readable. The log covers exactly 168 hours, from 22 September 08:34 to
29 September 08:34. The machine is a MacBook Pro (Mac16,7), macOS 26.6.2, the
battery at 263 cycles and 92% of its maximum capacity.

Counting the events looks easy. I tripped twice, and I'll describe both, because every
other number in this piece depends on it.

My first count gave 245 for Wake. The figure felt suspiciously round, so I went
through the lines: the log contains two distinct types beginning with Wake. Actual
wakes, and Wake Requests, meaning wake requests. A naive text search drops both
into the same bucket. 76 real wakes, 169 request lines; 245 together.

The second trap was subtler, and on my first pass I fell into it. Not all 223 lines
tagged Sleep are sleeps:

$ pmset -g log | grep -oE 'Entering (Sleep|DarkWake) state' | sort | uniq -c
  54 Entering DarkWake state
 169 Entering Sleep state
Enter fullscreen mode Exit fullscreen mode

Fifty-four of those lines don't say "going to sleep", they say "entering dark wake".
Same tag, two different transitions. The number for my title isn't 223, it's 169.
The way I caught it was mundane: the figure was larger than I expected, and I have a
habit of checking large figures.

Counted properly:

Entering Sleep state      169
Entering DarkWake state    54
DarkWake                  160
Wake (full wake)           76
Wake Requests             169
Enter fullscreen mode Exit fullscreen mode

Sleep doesn't have one reason

Here is the backbone of the whole thing. On the kernel side of macOS, inside
IOPMPrivate.h
in the xnu sources, there is an enum called SystemSleepReasons:

kIOPMSleepReasonClamshell                   = 101,
kIOPMSleepReasonPowerButton                 = 102,
kIOPMSleepReasonSoftware                    = 103,
kIOPMSleepReasonOSSwitchHibernate           = 104,
kIOPMSleepReasonIdle                        = 105,
kIOPMSleepReasonLowPower                    = 106,
kIOPMSleepReasonThermalEmergency            = 107,
kIOPMSleepReasonMaintenance                 = 108,
kIOPMSleepReasonSleepServiceExit            = 109,
kIOPMSleepReasonDarkWakeThermalEmergency    = 110,
kIOPMSleepReasonNotificationWakeExit        = 111
Enter fullscreen mode Exit fullscreen mode

Right below it come the text strings these values write into the log: "Clamshell Sleep",
"Idle Sleep", "Low Power Sleep", "Sleep Service Back to Sleep" and the rest.
Every quoted phrase I see in pmset -g log is the name of a kernel constant. Reading
the log isn't guesswork, it's looking things up in a table.

Let me not oversell the list: 109 and 111 aren't independent sleep reasons but
transitions back to sleep after a dark wake, and 110 is a thermal emergency specific to
dark wake. Even so, what the pmset sleep setting governs is a single line of that
list: 105, idle sleep. The name Apple shows users says as much. In the
sleep and wake settings guide
the option reads "Prevent automatic sleeping on power adapter when the display is off".
Automatic. It prevents the automatic one.

Here is how my own 169 sleeps break down:

Reason Count
Sleep Service Back to Sleep 72
Maintenance Sleep 51
Clamshell Sleep 43
Notification Wake Back to Sleep 2
Low Power Sleep 1
Idle Sleep 0

What actually produced the zero

The first conclusion this table invites is "the setting worked flawlessly". Looking at
my own pmset output again dissolved it:

Battery Power:
 sleep                1
AC Power:
 sleep                0
Enter fullscreen mode Exit fullscreen mode

sleep 0 applies only on mains. On the battery profile, idle sleep is on, with a
one-minute timer. And 150 of the 169 sleeps happened on battery. So even in the profile
where idle sleep is fully enabled, the Idle Sleep count is zero.

The zero comes from how sleep arrives, not from my setting. It enters by a different
door every time: the
lid closes, a maintenance window opens, or a background job finishes and puts the
machine back down. The idle timer never gets its turn. That also means nothing would
change if I removed the setting. Two days ago I looked at that setting and drew a
conclusion about the machine's behaviour, roughly like assuming there's no fire in the
house because the thermostat is off.

What happens when the lid closes on mains

This is the one place my intuition turned out partly right, and for a reason I hadn't
guessed. When I close the lid while plugged in, the log shows 41 events:

Lid closed on mains Count
Transition to DarkWake 30
Actual sleep 11

Most of the time, closing the lid on mains doesn't put the machine to sleep; it moves
into dark wake, does its work, and carries on from there. "It doesn't sleep while
plugged in" wasn't entirely wrong. It just wasn't because of sleep 0; a lid closure
on mains lands on a different transition. Eleven of the 19 real sleeps on mains were
still lid-driven. In Apple's open-source PowerManagement,
pmconfigd.m
has a display-blanking block that branches on the sleep reason, where
kIOPMClamshellSleepKey sits on its own branch alongside kIOPMSoftwareSleepKey.

Not sleep, naps

Then I computed the durations. I paired every Sleep line with the first wake that
followed it, then verified the same figure a second way: the Sleep line already ends
with a field called N secs, and for all 169 records the two methods agree.

sleep segments     169
total sleep        37.10 hours  (22.1% of 168 hours)
median segment     900 seconds (15.0 minutes)
shorter than 30m   168 / 169
longest segment    459 minutes
Enter fullscreen mode Exit fullscreen mode

A median of fifteen minutes. A hundred and sixty-eight of a hundred and sixty-nine
segments under half an hour. This isn't a sleep pattern, it's a napping pattern. And
the time a dark wake stays awake is absurdly short: a median of 2 seconds, a mean
of 20, totalling 53 minutes across seven days.

That single 459-minute sleep was not my doing:

2026-09-28 00:23:44 Sleep  due to 'Low Power Sleep' Using Batt (Charge:1%)
2026-09-28 08:02:49 Wake   from Hibernate : due to lid rtc/UserActivity Assertion
                           Using AC (Charge:3%)      (lines abbreviated)
Enter fullscreen mode Exit fullscreen mode

The battery hit 1%. hibernatemode 3 writes a copy of memory to disk but normally
still wakes from memory; returning from the disk image requires power to actually run
out. The Wake from Hibernate line in the log says exactly that: in the morning it
came back from the image, not from memory. There isn't a single sleep event in
the whole of 27 September. The only long sleep in my seven-day log was a blackout.

It sets an alarm before going to sleep

This is the part that surprised me most. Just below the Sleep lines there is a line
called Wake Requests:

Wake Requests [*process=dasd request=SleepService deltaSecs=1017
               wakeAt=2026-09-26 07:40:44 info="com.apple.…"]   (abbreviated)
Enter fullscreen mode Exit fullscreen mode

As it goes to sleep, the machine schedules the moment it will wake. The log holds 169
sleeps and 169 such blocks, though the two don't line up one to one: 158 of the blocks
follow a sleep directly, the rest follow a full wake or a dark wake transition. Between
them the blocks hold 851 requests. The thing that matters is
that small asterisk: * marks the request in that block that was actually armed.
There are exactly 169 asterisks across 169 blocks, and the starred request is always
the nearest one. The rest are requests waiting in the queue.

Queued by Request type In queue Actually armed
dasd SleepService 169 139
powerd CSPNEvaluation 169 16
powerd UserWake 169 2
mDNSResponder Maintenance 160 0
powerd TCPKATurnOff 148 0
dasd TimerPlugin 19 4
PowerUIAgent Maintenance 17 8

The median distance of the armed alarm is 960 seconds, 16.0 minutes. The median sleep
segment was 15.0 minutes. The machine isn't falling asleep; it's setting a
sixteen-minute timer and waking to it.

Diagram

The loudest name in the queue isn't the one that rings

When I got to counting the info labels on the requests, the top of the list was this:

130  …calaccessd.travelEngine.periodicRefreshTimer
104  DHCP lease renewal
 94  …searchd.heartbeat
 56  upkeep wake
Enter fullscreen mode Exit fullscreen mode

The calendar's travel-time engine, queued 130 times. That ranking invites you to write
"this is what woke my computer most", and it would be wrong. travelEngine sits in 130
blocks as a request, but it was the armed alarm in only 2 of them; its median distance is 6.3
hours, so the machine is up on some other alarm long before its turn.

The labels on the alarms that actually fired are a completely different list:

66  …searchd.heartbeat
16  (untagged — all powerd/CSPNEvaluation)
10  …dataaccessd.fetch
 9  …IntelligencePlatformCore.ViewEvery21Minutes
 9  …chronod.nextScheduledTimelineRefresh
 8  com.apple.obc
Enter fullscreen mode Exit fullscreen mode

What rings the bell is the search index heartbeat: 66 of 169 alarms are Spotlight
refreshing itself. The loudest name in the queue and the name that actually lifts the
machine are not the same, and if I'd confused them the action item at the end of this
piece would have been wrong too.

It stays on the network even while asleep

Next to the reason on every Sleep line there is a small tag: TCPKeepAlive=active.
It is enabled in 168 of the 169 sleeps. The machine keeps open TCP sessions alive while
sleeping; to the other end, it still looks connected.

That has an expiry date too, and powerd writes it into the queue before sleeping. The
median distance on request=TCPKATurnOff records is 312,174 seconds, roughly 86.7
hours. The machine notes to itself: if I'm still asleep three and a half days from now,
I'll let go of my presence on the network. It took that note 148 times in seven days,
and never once stayed asleep long enough to act on it.

Which one was the single inactive? The moment the battery hit 1%. In low power sleep
there is no budget left to sustain keepalive.

Seventy-six full wakes, and thirteen of them

Now for the arithmetic behind the title. I looked at what initiated each of the 76 full
wakes:

Initiator Count
Notification 63
UserActivity Assertion 7
HID Activity 6

Sixty-three were caused by a notification. In thirteen the initiator on record was me;
in nine of those the line says lid outright. Not one of the sixty-three notification
wakes mentions lid.

A caveat is needed: the log records who initiated the wake, not who was in the room at
the time. I may well have been sitting right there when the notification arrived. But a
number is a number. Across these seven days the machine went to full power seven times
more often because the notification system nudged it than because I lifted its lid.
Apple's own code spells the behaviour out; inside
PMAssertions.c
there is a branch that, after a notification wake, blanks the display if the user isn't
active and puts the machine back down with the reason "Notification Wake Back to Sleep".
In my log that branch ran seven times: two into sleep, five into dark wake. The
remaining fifty-six notification wakes ended some other way, and I couldn't determine
which from the log, so I won't invent one.

The reason for the setting is gone; the setting isn't

I put sleep 0 on this machine for CI: GitHub Actions runs had moved here, and if the
machine slept, queued jobs would wait. That reason disappeared this week; the runs moved
to gitsrv. Here is what I see today:

$ launchctl print-disabled gui/501 | grep itwise
  "com.itwise.ci-runners-repo" => disabled
  "com.itwise.ci-runners"      => disabled

$ limactl list
NAME  STATUS   CPUS  MEMORY  DISK          (columns abbreviated)
ci    Stopped    12    14GiB  60GiB
Enter fullscreen mode Exit fullscreen mode

The runners are off, the virtual machine is stopped, the setting is still in place. When
the reason behind a decision disappears, the decision doesn't lift itself. On servers
that work is on a schedule; what I learned this week is that I have no such habit for my
own laptop.

So what will I do now? I won't rush to remove the setting, because the log already
showed it isn't doing anything. The place I'll actually intervene is the dasd side:
the search index heartbeat behind 66 of the alarms, and notification previews on the
lock screen. Both are measurable targets named in the log. Throttling the travel-time
engine would have bought me two wakes; without counting the asterisks I'd have done that
wrong job.

Questions you can ask your own machine

If this piece has one practical takeaway, let it be this: don't guess what your machine
is doing, ask it. Three commands are enough, and all of them are read-only.

  • How much did it sleep? Get the filter right; this is where I tripped twice:
  pmset -g log | awk '$4=="Sleep" || $4=="DarkWake" || ($4=="Wake" && $5!="Requests")'
Enter fullscreen mode Exit fullscreen mode

To isolate sleeps, search for Entering Sleep state, not the Sleep tag.

  • Why did it sleep? Extract the quoted phrases. If you don't see Idle Sleep, fiddling with your sleep settings won't help you.
  • Who woke it? Find the starred request on the Wake Requests lines. The unstarred ones are requests waiting in the queue; counting those led me to the wrong answer.

There's a fourth: pmset -g assertions shows who is holding sleep off right now. None
of these change a setting. They only look.

Two sentences, two days apart

I started writing this with "my machine doesn't sleep" and finished it with "my machine
sleeps every fifteen minutes and gets up every two seconds". The only thing that changed
in between was that I read the log.

There's something here larger than a laptop. We mostly learn how the systems we work on
behave from the decisions we made about them. A decision is an intention; a log is what
happened. Two days ago I looked at a setting and wrote a sentence; this week the same
machine told me, across 54,179 lines, which half of that sentence was right and which
was wrong. And asking cost nothing.

Official Sources

Top comments (0)