Commit 2e33608
authored
Refactor temperature reading, improve error handling, and optimize CPU usage (#3)
* sketchybar: read die temperature from a helper, not macmon
macmon's temp.cpu_temp_avg is not trustworthy. Measured inside a SINGLE
invocation with CPU load flat at 1.5-3.7% and cpu_power at 0.06W:
t+0s cpu=37.74 gpu=35.33
t+2s cpu=31.79 gpu=35.27
t+4s cpu=20.19 gpu=35.27
A die cannot cool 17C in two seconds and reheat. Across trials the value
is bimodal, landing on ~31.9 or ~38.1 and never between, which is the
signature of a mean over a varying sensor set rather than a temperature.
helpers/thermal.swift reads the PMU tdie sensors directly through
IOHIDEventSystemClient (no sudo). Spread 0.30C over 20 reads, and it
tracks real load: 34.9 idle -> 37.6 under full load, monotonic.
The helper was untracked until now, while both install and system.sh
depended on it. Both guarded defensively, so a fresh clone produced a bar
with no temperature and no error at all.
* install: make a missing helper source loud instead of silent
The two helper build blocks tested `[[ -r $src ]] && command -v swiftc`
in one condition, so a MISSING SOURCE and a MISSING TOOLCHAIN were
indistinguishable and both exited without printing anything -- no ok, no
warn. thermal.swift was untracked in git, so a fresh clone hit exactly
that path and produced a bar with no temperature and no explanation.
Replace both with one build_sb_helper function carrying a three-way
guard, so each outcome reports itself.
* sketchybar: document weather's IP geolocation, and fail closed on HTTP errors
sketchybarrc pointed at "the privacy note in weather.sh"; no such note
existed. With LOCATION empty, wttr.in geolocates by SOURCE IP, so every
poll discloses the host's public IP to a third party. That is a fair
trade for zero configuration but it is not visible from the URL.
Write the note, and add SKETCHYBAR_WEATHER_LOCATION so the behaviour can
be pinned to a place or turned off.
Also curl -sf: without -f, curl exits 0 on a 5xx and the error page is
written to the cache, which is then served as the last-known-good reading
indefinitely.
* aerospace: float Raycast; the "not installed" comment had gone stale
The rule was commented out on the belief that Raycast was in the Brewfile
but not installed. /Applications/Raycast.app exists and its Info.plist
gives com.raycast.macos -- exactly the id the comment declined to guess.
Caveat recorded inline: the id came from Info.plist, not from
`aerospace list-apps` with the Settings window open, since Raycast was
not running.
* sketchybar: fix volume and brightness reporting values they cannot know
volume showed "missing value%". osascript prints that literal string and
exits 0 for a device with no software volume, so the `[ -z "$vol" ]` guard
sailed past it. The output here is a Focusrite Scarlett 2i2, which exposes
neither kAudioDevicePropertyVolumeScalar nor Mute on any scope or element
-- confirmed against CoreAudio. There is genuinely no level to show.
brightness showed a constant 100%. It looped display ids 1..16 and took
the first DisplayServicesGetBrightness that answered; on this clamshell
desk that is the external, and the call SUCCEEDS for a non-Apple external
and returns a hardcoded 1.0. The 100% was fabricated, not stale.
Both now read the right source (brightness: gamma ramp for an external,
which is what MonitorControl's software dimming manipulates -- verified
0.75/0.50/0.25 -> 75/50/25%) and hide rather than invent a number.
colors.sh gains the compositing note left over from a frosted-bar
experiment that was reverted.
* docs: record the silent-failure rules, and add the production audit
Three failure classes that are invisible when they occur, all found the
hard way:
- a self-hiding item under the config-wide updates=when_shown default
stops being updated entirely, so it can never un-hide. Eight items
were affected; each worked after a reload and then went quiet.
- SketchyBar fork_execs plugins and reports NOTHING when the execute
bit is missing. The item simply never updates.
- a plugin runs with less TCC access than a terminal, so ~/Library
reads succeed by hand and fail with EPERM in the bar, at correct
Unix permissions.
docs/audit-2026-08-02.md is a full production-readiness review with 20
findings, measured costs per plugin, and a four-phase roadmap.
* sketchybar: cut idle CPU by raising two intervals that were set on bad data
mic was polled every 2s on the strength of a comment in mic.swift saying
the helper "runs in single-digit ms". Measured min/median/max over 10
consecutive runs: 60/63/65ms -- wrong by a factor of 8. At 2s that item
alone cost 1890ms of CPU per MINUTE, 36% of the whole bar's budget and
the single most expensive thing in the config. Now 5s.
The cost is inherent, not a Swift artefact: the helper enumerates every
CoreAudio device and queries two properties each, so it is O(devices) IPC
into coreaudiod per tick. Making it free means a property listener, not a
faster loop; noted in the source.
system was 30s at ~930ms per invocation (macmon alone measured 854ms,
because it samples a full SoC telemetry frame to extract three numbers).
Now 60s. CPU%, RAM and fan RPM are not acted on faster than that.
Together: 3750 -> 1686 ms/min, a 55% reduction in the bar's idle CPU.
* sketchybar: repaint all workspace pills from one query, not nine
Each of the nine workspace items ran its own copy of aerospace.sh, and
each copy shelled out to `aerospace list-workspaces` to ask a GLOBAL
question -- which workspaces hold windows -- whose answer is identical for
all nine. Measured 25ms per call, 104ms for the nine, plus nine greps.
Worse, the items were subscribed to front_app_switched as well as
aerospace_workspace_change, so all of that ran on every application
switch, one of the highest-frequency actions there is.
Replace with a zero-width driver item that owns the subscriptions and
rewrites every pill in a single sketchybar call: 2 aerospace calls and 1
sketchybar call regardless of workspace count. Full repaint now 54ms.
The occupancy test is parameter expansion instead of a grep per item, with
sentinel spaces so a future workspace 10 cannot match workspace 1.
* portability: fall back to /usr/local for Intel Homebrew
Both PATH exports hardcoded /opt/homebrew, the Apple Silicon prefix. On
an Intel Mac every plugin depending on aerospace, macmon, gh or icalBuddy
would fail to find its binary -- and they all hide rather than error when
that happens, so the bar would come up merely looking quiet.
Prepending a directory that does not exist is harmless, and this avoids a
`brew --prefix` subprocess on every bar start.
* sketchybar: extract lib.sh and collapse six duplication sites
hide() was defined identically in three plugins, the truncate idiom was
copy-pasted into three more, the helper build-on-demand block existed
twice, and amphetamine.sh had a six-line osascript block duplicated
verbatim inside itself.
lib.sh provides hide, truncate_label, state_file, ensure_helper and
require. Notes on two of them:
hide() now closes any popup unconditionally. Two of the three local
definitions did that and the third has no popup; popup.drawing=off on a
popup-less item is accepted silently (verified). Doing it always removes
a footgun -- a bare hide() that left a popup on screen would be a rare
and subtle bug.
truncate_label replaces `printf | cut -c1-N`, a two-subprocess pipeline
run on every invocation of every plugin that shows a name. cut -c counts
BYTES in some implementations while ${#s} and ${s:0:n} count characters,
so the old form could cut a UTF-8 name mid-codepoint.
require() gives missing dependencies a voice: plugins previously failed
into empty output and then hid, which is indistinguishable from "nothing
to report".
* sketchybar: updates=on for music -- the ninth self-hiding item
Found by scripts/lint-sketchybar.sh, not by the hand audit that claimed to
have found all of them. music.sh hides via a hide() call rather than a
literal `--set "$NAME" drawing=off`, so the grep used to sweep for this
never saw it.
The item was live-broken: Apple Music is usually not running, so the item
was sitting hidden with updates=when_shown, meaning starting playback
would never have brought the pill back.
* ci: lint the failure classes that are invisible at runtime
Three SketchyBar behaviours produce no error and no log line, so the only
symptom is an item that quietly stops updating:
- a plugin without its execute bit; sketchybar fork_execs it and says
nothing
- a self-hiding item without updates=on, which can then never un-hide
- a comment referencing a section that no longer exists, in a config
where the comments carry the measurements
scripts/lint-sketchybar.sh checks all three, and earned its place
immediately by finding the music item bug above.
It is deliberately precise about two things it must NOT flag:
label.drawing=off and background.drawing=off hide a COMPONENT, leaving the
item drawn and updating (amphetamine and pomodoro both do this correctly);
and passive items like cpu and memory carry no script of their own, so
they are un-hidden by whichever item's script writes them.
* Update1 parent b378f6f commit 2e33608
33 files changed
Lines changed: 1083 additions & 940 deletions
File tree
- .github/workflows
- aerospace/.config/aerospace
- claude/.claude
- raycast/.config/raycast/scripts
- scripts
- sketchybar/.config/sketchybar
- helpers
- plugins
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| 19 | + | |
19 | 20 | | |
20 | 21 | | |
21 | 22 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
| 36 | + | |
36 | 37 | | |
37 | 38 | | |
38 | 39 | | |
| |||
48 | 49 | | |
49 | 50 | | |
50 | 51 | | |
| 52 | + | |
51 | 53 | | |
52 | 54 | | |
53 | 55 | | |
| |||
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
70 | 70 | | |
71 | 71 | | |
72 | 72 | | |
73 | | - | |
| 73 | + | |
74 | 74 | | |
75 | 75 | | |
76 | 76 | | |
| |||
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
7 | 11 | | |
8 | 12 | | |
9 | 13 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
444 | 444 | | |
445 | 445 | | |
446 | 446 | | |
447 | | - | |
| 447 | + | |
448 | 448 | | |
449 | | - | |
| 449 | + | |
450 | 450 | | |
451 | | - | |
452 | | - | |
453 | | - | |
454 | | - | |
455 | | - | |
456 | | - | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
457 | 471 | | |
458 | | - | |
| 472 | + | |
459 | 473 | | |
460 | | - | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
461 | 478 | | |
462 | 479 | | |
463 | 480 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
23 | | - | |
| 23 | + | |
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
0 commit comments