xref: /freebsd/cddl/usr.sbin/dwatch/libexec/dtmalloc (revision d15f2551b25f79ddcbe289faa95e655100b952da)
1# -*- tab-width: 4 -*- ;; Emacs
2# vi: set filetype=sh tabstop=8 shiftwidth=8 noexpandtab :: Vi/ViM
3############################################################ IDENT(1)
4#
5# $Title: dwatch(8) module for malloc(9) type activity $
6# $Copyright: 2026 Devin Teske. All rights reserved. $
7#
8############################################################ DESCRIPTION
9#
10# Print kernel malloc(9) and free(9) activity by malloc type, via the
11# dtmalloc provider (one malloc and one free probe per type; see
12# vmstat -m for the types). The default profile prints allocations and
13# frees meeting a size threshold (default 65536 bytes; tunable via
14# DWATCH_MALLOC_MIN in the environment, 0 to show everything),
15# answering "who is allocating huge kernel buffers?" Use
16# dtmalloc-NAME to watch a single type (e.g., dtmalloc-devbuf).
17# The dtmalloc-top profile maintains a running catalog, updated every
18# 3 seconds, of net bytes and outstanding allocation balance by type:
19# a type whose net bytes climb without bound while the system is in
20# steady state is a leak suspect. NB: the catalog reflects activity
21# since the watch began, not preexisting allocations, and caches
22# legitimately hold what they allocate.
23#
24############################################################ PRAGMAS
25
26case "$PROFILE" in
27dtmalloc-top)
28	DTRACE_PRAGMA="
29		option quiet
30		option aggsortrev
31	" # END-QUOTE
32	;;
33esac
34
35############################################################ PROBE
36
37case "$PROFILE" in
38dtmalloc)
39	: ${PROBE:=dtmalloc:::malloc, dtmalloc:::free} ;;
40dtmalloc-top)
41	: ${PROBE:=profile:::tick-3s} ;;
42*)
43	: ${PROBE:=$( echo \
44		dtmalloc::${PROFILE#dtmalloc-}:malloc, \
45		dtmalloc::${PROFILE#dtmalloc-}:free )} ;;
46esac
47
48############################################################ EVENT ACTION
49
50: ${DWATCH_MALLOC_MIN:=65536}
51
52case "$DWATCH_MALLOC_MIN" in
53""|*[!0-9]*) die "DWATCH_MALLOC_MIN must be a number" ;; # NOTREACHED
54esac
55
56[ "$CUSTOM_TEST" ] || case "$PROFILE" in
57dtmalloc-top)	;;
58*)		EVENT_TEST="(uint64_t)arg3 >= $DWATCH_MALLOC_MIN"
59esac
60
61############################################################ ACTIONS
62
63if [ "$PROFILE" = "dtmalloc-top" ]; then
64exec 9<<EOF
65this int64_t	dtmalloc_delta;
66
67BEGIN { printf("Cataloging malloc(9) activity ...") } /* probe ID $ID */
68
69dtmalloc:::malloc /* probe ID $(( $ID + 1 )) */
70{
71	this->dtmalloc_delta = (int64_t)arg3;
72}
73
74dtmalloc:::free /* probe ID $(( $ID + 2 )) */
75{
76	this->dtmalloc_delta = -(int64_t)arg3;
77}
78
79dtmalloc:::malloc, dtmalloc:::free /* probe ID $(( $ID + 3 )) */
80{
81	@dtmalloc_bytes[probefunc] = sum(this->dtmalloc_delta);
82	@dtmalloc_allocs[probefunc] =
83		sum(this->dtmalloc_delta > 0 ? 1 : -1);
84}
85EOF
86ACTIONS=$( cat <&9 )
87ID=$(( $ID + 4 ))
88fi
89
90############################################################ EVENT TAG
91
92# For the running catalog, override the default `UID.GID CMD[PID]: ' tag
93# with ANSI cursor-homing and screen-clearing codes plus column headers.
94
95if [ "$PROFILE" = "dtmalloc-top" ]; then
96size=$( stty size 2> /dev/null )
97rows="${size%% *}"
98cols="${size#* }"
99
100exec 9<<EOF
101	printf("\033[H"); /* Position the cursor at top-left */
102	printf("\033[J"); /* Clear display from cursor to end */
103
104	/* Header line containing probe (left) and date (right) */
105	printf("%-*s%s%Y%s\n",
106		$(( ${cols:-80} - 20 )), "$PROBE",
107		console ? "\033[32m" : "",
108		walltimestamp,
109		console ? "\033[39m" : "");
110
111	/* Column headers */
112	printf("%s%14s %10s %s%s\n",
113		console ? "\033[1m" : "",
114		"NET(B)",
115		"BALANCE",
116		"TYPE",
117		console ? "\033[22m" : "");
118EOF
119EVENT_TAG=$( cat <&9 )
120fi
121
122############################################################ EVENT DETAILS
123
124if [ "$PROFILE" = "dtmalloc-top" ]; then
125exec 9<<EOF
126	/* NB: Cumulative; not truncated between updates */
127	printa("%@14d %@10d %s\n", @dtmalloc_bytes, @dtmalloc_allocs);
128EOF
129EVENT_DETAILS=$( cat <&9 )
130elif [ ! "$CUSTOM_DETAILS" ]; then
131exec 9<<EOF
132	/*
133	 * Print malloc(9) activity details
134	 */
135	printf("%s(9) type %s %d byte%s",
136		probename,
137		probefunc,
138		(long)arg3,
139		(long)arg3 == 1 ? "" : "s");
140EOF
141EVENT_DETAILS=$( cat <&9 )
142fi
143
144################################################################################
145# END
146################################################################################
147