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