1*434283fdSDevin Teske# -*- tab-width: 4 -*- ;; Emacs 2*434283fdSDevin Teske# vi: set filetype=sh tabstop=8 shiftwidth=8 noexpandtab :: Vi/ViM 3*434283fdSDevin Teske############################################################ IDENT(1) 4*434283fdSDevin Teske# 5*434283fdSDevin Teske# $Title: dwatch(8) module for long sleeps and hung threads $ 6*434283fdSDevin Teske# $Copyright: 2026 Devin Teske. All rights reserved. $ 7*434283fdSDevin Teske# 8*434283fdSDevin Teske############################################################ DESCRIPTION 9*434283fdSDevin Teske# 10*434283fdSDevin Teske# Print threads that were asleep for a long time, as they wake, naming 11*434283fdSDevin Teske# the sleeper, how long it slept, and (in the standard event tag) the 12*434283fdSDevin Teske# process that woke it. Sleeps shorter than a threshold are suppressed 13*434283fdSDevin Teske# (default 1000 ms; tunable via DWATCH_HANG_MS in the environment, 0 to 14*434283fdSDevin Teske# show everything). Answers "what was my process stuck on?" -- the 15*434283fdSDevin Teske# blocking that the slow profile cannot see, because a syscall that 16*434283fdSDevin Teske# never returns never reports its latency. NB: the report fires at 17*434283fdSDevin Teske# wakeup with the full duration; a thread still asleep has not yet 18*434283fdSDevin Teske# been reported. 19*434283fdSDevin Teske# The hang-top profile maintains a running catalog, updated every 20*434283fdSDevin Teske# 3 seconds, of long sleeps by process. Combine with `-O cmd' to 21*434283fdSDevin Teske# capture state as each event occurs. 22*434283fdSDevin Teske# 23*434283fdSDevin Teske############################################################ PRAGMAS 24*434283fdSDevin Teske 25*434283fdSDevin Teskecase "$PROFILE" in 26*434283fdSDevin Teskehang-top) 27*434283fdSDevin Teske DTRACE_PRAGMA=" 28*434283fdSDevin Teske option quiet 29*434283fdSDevin Teske option aggsortrev 30*434283fdSDevin Teske " # END-QUOTE 31*434283fdSDevin Teske ;; 32*434283fdSDevin Teskeesac 33*434283fdSDevin Teske 34*434283fdSDevin Teske############################################################ PROBE 35*434283fdSDevin Teske 36*434283fdSDevin Teskecase "$PROFILE" in 37*434283fdSDevin Teskehang-top) 38*434283fdSDevin Teske : ${PROBE:=profile:::tick-3s} ;; 39*434283fdSDevin Teske*) 40*434283fdSDevin Teske : ${PROBE:=sched:::wakeup} 41*434283fdSDevin Teskeesac 42*434283fdSDevin Teske 43*434283fdSDevin Teske############################################################ EVENT ACTION 44*434283fdSDevin Teske 45*434283fdSDevin Teske: ${DWATCH_HANG_MS:=1000} 46*434283fdSDevin Teske 47*434283fdSDevin Teskecase "$DWATCH_HANG_MS" in 48*434283fdSDevin Teske""|*[!0-9]*) die "DWATCH_HANG_MS must be a number" ;; # NOTREACHED 49*434283fdSDevin Teskeesac 50*434283fdSDevin Teske 51*434283fdSDevin Teske[ "$CUSTOM_TEST" ] || case "$PROFILE" in 52*434283fdSDevin Teskehang-top) ;; 53*434283fdSDevin Teske*) EVENT_TEST="this->hang_ns >= (int64_t)$DWATCH_HANG_MS * 1000000" 54*434283fdSDevin Teskeesac 55*434283fdSDevin Teske 56*434283fdSDevin Teske############################################################ ACTIONS 57*434283fdSDevin Teske 58*434283fdSDevin Teskeif [ "$PROFILE" = "hang-top" ]; then 59*434283fdSDevin Teskeexec 9<<EOF 60*434283fdSDevin Teskethis int64_t hang_ns; 61*434283fdSDevin Teskeint64_t hang_ts[int]; 62*434283fdSDevin Teske 63*434283fdSDevin TeskeBEGIN { printf("Cataloging long sleeps ...") } /* probe ID $ID */ 64*434283fdSDevin Teske 65*434283fdSDevin Teskesched:::sleep /* probe ID $(( $ID + 1 )) */ 66*434283fdSDevin Teske{ 67*434283fdSDevin Teske hang_ts[curthread->td_tid] = timestamp; 68*434283fdSDevin Teske} 69*434283fdSDevin Teske 70*434283fdSDevin Teskesched:::wakeup /* probe ID $(( $ID + 2 )) */ 71*434283fdSDevin Teske{ 72*434283fdSDevin Teske /* NB: -1 if we did not see the sleep (enabled mid-sleep) */ 73*434283fdSDevin Teske this->hang_ns = 74*434283fdSDevin Teske hang_ts[((struct thread *)args[0])->td_tid] ? timestamp - 75*434283fdSDevin Teske hang_ts[((struct thread *)args[0])->td_tid] : -1; 76*434283fdSDevin Teske hang_ts[((struct thread *)args[0])->td_tid] = 0; 77*434283fdSDevin Teske} 78*434283fdSDevin Teske 79*434283fdSDevin Teskesched:::wakeup /this->hang_ns >= 80*434283fdSDevin Teske (int64_t)$DWATCH_HANG_MS * 1000000/ /* probe ID $(( $ID + 3 )) */ 81*434283fdSDevin Teske{ 82*434283fdSDevin Teske @hang_cnt[stringof(((struct proc *)args[1])->p_comm)] = count(); 83*434283fdSDevin Teske @hang_max[stringof(((struct proc *)args[1])->p_comm)] = 84*434283fdSDevin Teske max(this->hang_ns / 1000000); 85*434283fdSDevin Teske} 86*434283fdSDevin TeskeEOF 87*434283fdSDevin TeskeACTIONS=$( cat <&9 ) 88*434283fdSDevin TeskeID=$(( $ID + 4 )) 89*434283fdSDevin Teskeelse 90*434283fdSDevin Teskeexec 9<<EOF 91*434283fdSDevin Teskethis int64_t hang_ns; 92*434283fdSDevin Teskeint64_t hang_ts[int]; 93*434283fdSDevin Teske 94*434283fdSDevin Teskesched:::sleep /* probe ID $ID */ 95*434283fdSDevin Teske{${TRACE:+ 96*434283fdSDevin Teske printf("<$ID>"); 97*434283fdSDevin Teske} 98*434283fdSDevin Teske hang_ts[curthread->td_tid] = timestamp; 99*434283fdSDevin Teske} 100*434283fdSDevin Teske 101*434283fdSDevin Teske$PROBE /* probe ID $(( $ID + 1 )) */ 102*434283fdSDevin Teske{${TRACE:+ 103*434283fdSDevin Teske printf("<$(( $ID + 1 ))>"); 104*434283fdSDevin Teske} 105*434283fdSDevin Teske /* NB: -1 if we did not see the sleep (enabled mid-sleep) */ 106*434283fdSDevin Teske this->hang_ns = 107*434283fdSDevin Teske hang_ts[((struct thread *)args[0])->td_tid] ? timestamp - 108*434283fdSDevin Teske hang_ts[((struct thread *)args[0])->td_tid] : -1; 109*434283fdSDevin Teske hang_ts[((struct thread *)args[0])->td_tid] = 0; 110*434283fdSDevin Teske 111*434283fdSDevin Teske $( pproc -P _hang "(struct proc *)args[1]" ) 112*434283fdSDevin Teske} 113*434283fdSDevin TeskeEOF 114*434283fdSDevin TeskeACTIONS=$( cat <&9 ) 115*434283fdSDevin TeskeID=$(( $ID + 2 )) 116*434283fdSDevin Teskefi 117*434283fdSDevin Teske 118*434283fdSDevin Teske############################################################ EVENT TAG 119*434283fdSDevin Teske 120*434283fdSDevin Teske# For the running catalog, override the default `UID.GID CMD[PID]: ' tag 121*434283fdSDevin Teske# with ANSI cursor-homing and screen-clearing codes plus column headers. 122*434283fdSDevin Teske 123*434283fdSDevin Teskeif [ "$PROFILE" = "hang-top" ]; then 124*434283fdSDevin Teskesize=$( stty size 2> /dev/null ) 125*434283fdSDevin Teskerows="${size%% *}" 126*434283fdSDevin Teskecols="${size#* }" 127*434283fdSDevin Teske 128*434283fdSDevin Teskeexec 9<<EOF 129*434283fdSDevin Teske printf("\033[H"); /* Position the cursor at top-left */ 130*434283fdSDevin Teske printf("\033[J"); /* Clear display from cursor to end */ 131*434283fdSDevin Teske 132*434283fdSDevin Teske /* Header line containing probe (left) and date (right) */ 133*434283fdSDevin Teske printf("%-*s%s%Y%s\n", 134*434283fdSDevin Teske $(( ${cols:-80} - 20 )), "$PROBE", 135*434283fdSDevin Teske console ? "\033[32m" : "", 136*434283fdSDevin Teske walltimestamp, 137*434283fdSDevin Teske console ? "\033[39m" : ""); 138*434283fdSDevin Teske 139*434283fdSDevin Teske /* Column headers */ 140*434283fdSDevin Teske printf("%s%8s %10s %s%s\n", 141*434283fdSDevin Teske console ? "\033[1m" : "", 142*434283fdSDevin Teske "COUNT", 143*434283fdSDevin Teske "MAX(ms)", 144*434283fdSDevin Teske "EXECNAME", 145*434283fdSDevin Teske console ? "\033[22m" : ""); 146*434283fdSDevin TeskeEOF 147*434283fdSDevin TeskeEVENT_TAG=$( cat <&9 ) 148*434283fdSDevin Teskefi 149*434283fdSDevin Teske 150*434283fdSDevin Teske############################################################ EVENT DETAILS 151*434283fdSDevin Teske 152*434283fdSDevin Teskeif [ "$PROFILE" = "hang-top" ]; then 153*434283fdSDevin Teskeexec 9<<EOF 154*434283fdSDevin Teske /* NB: Cumulative; not truncated between updates */ 155*434283fdSDevin Teske printa("%@8u %@10d %s\n", @hang_cnt, @hang_max); 156*434283fdSDevin TeskeEOF 157*434283fdSDevin TeskeEVENT_DETAILS=$( cat <&9 ) 158*434283fdSDevin Teskeelif [ ! "$CUSTOM_DETAILS" ]; then 159*434283fdSDevin Teskeexec 9<<EOF 160*434283fdSDevin Teske /* 161*434283fdSDevin Teske * Print long sleep details (tag shows the waker) 162*434283fdSDevin Teske */ 163*434283fdSDevin Teske printf("pid %d slept %d.%03d ms -- %s", 164*434283fdSDevin Teske this->pid_hang, 165*434283fdSDevin Teske this->hang_ns / 1000000, 166*434283fdSDevin Teske (this->hang_ns % 1000000) / 1000, 167*434283fdSDevin Teske this->args_hang); 168*434283fdSDevin TeskeEOF 169*434283fdSDevin TeskeEVENT_DETAILS=$( cat <&9 ) 170*434283fdSDevin Teskefi 171*434283fdSDevin Teske 172*434283fdSDevin Teske################################################################################ 173*434283fdSDevin Teske# END 174*434283fdSDevin Teske################################################################################ 175