| 1 | #!/bin/bash |
| 2 | # π€ wedge-watcher β samples a ClickLab-instrumented Mac build while its main |
| 3 | # thread is wedged. ClickLab (ios/LChatMac/ClickLab.swift) rewrites a |
| 4 | # heartbeat file from the MAIN thread every ~1s; when the file goes stale |
| 5 | # while the process is alive, main is blocked (or the app is napping β idle |
| 6 | # stacks are discarded at analysis time), and `sample` captures the live |
| 7 | # stack. The in-app watchdog can only report a stall AFTER it drains β this |
| 8 | # is how you catch the blocked stack DURING one. |
| 9 | # |
| 10 | # Usage: tools/wedge-watcher.sh [bundle-id-suffix] [out-dir] [hours] |
| 11 | # e.g. tools/wedge-watcher.sh alt-clicklab ~/Desktop/wedges 8 |
| 12 | # The build must be sample-able: Debug builds are (get-task-allow); Release |
| 13 | # rig builds need CODE_SIGN_INJECT_BASE_ENTITLEMENTS=YES at build time. |
| 14 | # Analysis: read the sample's main-thread block β an idle run loop |
| 15 | # (__CFRunLoopRun β mach_msg) means App Nap, anything else names the wedge. |
| 16 | SUFFIX="${1:-alt-clicklab}" |
| 17 | HB="$HOME/Library/Containers/com.elevenmessenger.messenger.mac.$SUFFIX/Data/Library/Logs/ClickLab.heartbeat" |
| 18 | OUT="${2:-./wedges}" |
| 19 | HOURS="${3:-8}" |
| 20 | mkdir -p "$OUT" |
| 21 | END=$((SECONDS + HOURS * 3600)) |
| 22 | LAST=0 |
| 23 | echo "watching $HB β $OUT for ${HOURS}h" |
| 24 | while [ $SECONDS -lt $END ]; do |
| 25 | sleep 2 |
| 26 | PID=$(pgrep -f "$SUFFIX" | head -1) |
| 27 | [ -z "$PID" ] && continue |
| 28 | [ -f "$HB" ] || continue |
| 29 | AGE=$(( $(date +%s) - $(stat -f %m "$HB") )) |
| 30 | if [ "$AGE" -ge 4 ] && [ $((SECONDS - LAST)) -ge 30 ]; then |
| 31 | TS=$(date +%H%M%S) |
| 32 | sample "$PID" 3 -file "$OUT/wedge-$TS.txt" >/dev/null 2>&1 |
| 33 | echo "$(date +%H:%M:%S) heartbeat ${AGE}s stale β sampled pid $PID β wedge-$TS.txt" |
| 34 | LAST=$SECONDS |
| 35 | fi |
| 36 | done |
| 37 | echo "wedge-watcher: ${HOURS}h window elapsed, exiting" |
| 38 | |