xref: /freebsd/cddl/usr.sbin/dwatch/libexec/hang (revision 434283fda99e89af20c3fe95fde427624ae96d04)
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