xref: /freebsd/contrib/jemalloc/src/prof_log.c (revision c43cad87172039ccf38172129c79755ea79e6102)
1*c43cad87SWarner Losh #include "jemalloc/internal/jemalloc_preamble.h"
2*c43cad87SWarner Losh #include "jemalloc/internal/jemalloc_internal_includes.h"
3*c43cad87SWarner Losh 
4*c43cad87SWarner Losh #include "jemalloc/internal/assert.h"
5*c43cad87SWarner Losh #include "jemalloc/internal/buf_writer.h"
6*c43cad87SWarner Losh #include "jemalloc/internal/ckh.h"
7*c43cad87SWarner Losh #include "jemalloc/internal/emitter.h"
8*c43cad87SWarner Losh #include "jemalloc/internal/hash.h"
9*c43cad87SWarner Losh #include "jemalloc/internal/malloc_io.h"
10*c43cad87SWarner Losh #include "jemalloc/internal/mutex.h"
11*c43cad87SWarner Losh #include "jemalloc/internal/prof_data.h"
12*c43cad87SWarner Losh #include "jemalloc/internal/prof_log.h"
13*c43cad87SWarner Losh #include "jemalloc/internal/prof_sys.h"
14*c43cad87SWarner Losh 
15*c43cad87SWarner Losh bool opt_prof_log = false;
16*c43cad87SWarner Losh typedef enum prof_logging_state_e prof_logging_state_t;
17*c43cad87SWarner Losh enum prof_logging_state_e {
18*c43cad87SWarner Losh 	prof_logging_state_stopped,
19*c43cad87SWarner Losh 	prof_logging_state_started,
20*c43cad87SWarner Losh 	prof_logging_state_dumping
21*c43cad87SWarner Losh };
22*c43cad87SWarner Losh 
23*c43cad87SWarner Losh /*
24*c43cad87SWarner Losh  * - stopped: log_start never called, or previous log_stop has completed.
25*c43cad87SWarner Losh  * - started: log_start called, log_stop not called yet. Allocations are logged.
26*c43cad87SWarner Losh  * - dumping: log_stop called but not finished; samples are not logged anymore.
27*c43cad87SWarner Losh  */
28*c43cad87SWarner Losh prof_logging_state_t prof_logging_state = prof_logging_state_stopped;
29*c43cad87SWarner Losh 
30*c43cad87SWarner Losh /* Used in unit tests. */
31*c43cad87SWarner Losh static bool prof_log_dummy = false;
32*c43cad87SWarner Losh 
33*c43cad87SWarner Losh /* Incremented for every log file that is output. */
34*c43cad87SWarner Losh static uint64_t log_seq = 0;
35*c43cad87SWarner Losh static char log_filename[
36*c43cad87SWarner Losh     /* Minimize memory bloat for non-prof builds. */
37*c43cad87SWarner Losh #ifdef JEMALLOC_PROF
38*c43cad87SWarner Losh     PATH_MAX +
39*c43cad87SWarner Losh #endif
40*c43cad87SWarner Losh     1];
41*c43cad87SWarner Losh 
42*c43cad87SWarner Losh /* Timestamp for most recent call to log_start(). */
43*c43cad87SWarner Losh static nstime_t log_start_timestamp;
44*c43cad87SWarner Losh 
45*c43cad87SWarner Losh /* Increment these when adding to the log_bt and log_thr linked lists. */
46*c43cad87SWarner Losh static size_t log_bt_index = 0;
47*c43cad87SWarner Losh static size_t log_thr_index = 0;
48*c43cad87SWarner Losh 
49*c43cad87SWarner Losh /* Linked list node definitions. These are only used in this file. */
50*c43cad87SWarner Losh typedef struct prof_bt_node_s prof_bt_node_t;
51*c43cad87SWarner Losh 
52*c43cad87SWarner Losh struct prof_bt_node_s {
53*c43cad87SWarner Losh 	prof_bt_node_t *next;
54*c43cad87SWarner Losh 	size_t index;
55*c43cad87SWarner Losh 	prof_bt_t bt;
56*c43cad87SWarner Losh 	/* Variable size backtrace vector pointed to by bt. */
57*c43cad87SWarner Losh 	void *vec[1];
58*c43cad87SWarner Losh };
59*c43cad87SWarner Losh 
60*c43cad87SWarner Losh typedef struct prof_thr_node_s prof_thr_node_t;
61*c43cad87SWarner Losh 
62*c43cad87SWarner Losh struct prof_thr_node_s {
63*c43cad87SWarner Losh 	prof_thr_node_t *next;
64*c43cad87SWarner Losh 	size_t index;
65*c43cad87SWarner Losh 	uint64_t thr_uid;
66*c43cad87SWarner Losh 	/* Variable size based on thr_name_sz. */
67*c43cad87SWarner Losh 	char name[1];
68*c43cad87SWarner Losh };
69*c43cad87SWarner Losh 
70*c43cad87SWarner Losh typedef struct prof_alloc_node_s prof_alloc_node_t;
71*c43cad87SWarner Losh 
72*c43cad87SWarner Losh /* This is output when logging sampled allocations. */
73*c43cad87SWarner Losh struct prof_alloc_node_s {
74*c43cad87SWarner Losh 	prof_alloc_node_t *next;
75*c43cad87SWarner Losh 	/* Indices into an array of thread data. */
76*c43cad87SWarner Losh 	size_t alloc_thr_ind;
77*c43cad87SWarner Losh 	size_t free_thr_ind;
78*c43cad87SWarner Losh 
79*c43cad87SWarner Losh 	/* Indices into an array of backtraces. */
80*c43cad87SWarner Losh 	size_t alloc_bt_ind;
81*c43cad87SWarner Losh 	size_t free_bt_ind;
82*c43cad87SWarner Losh 
83*c43cad87SWarner Losh 	uint64_t alloc_time_ns;
84*c43cad87SWarner Losh 	uint64_t free_time_ns;
85*c43cad87SWarner Losh 
86*c43cad87SWarner Losh 	size_t usize;
87*c43cad87SWarner Losh };
88*c43cad87SWarner Losh 
89*c43cad87SWarner Losh /*
90*c43cad87SWarner Losh  * Created on the first call to prof_try_log and deleted on prof_log_stop.
91*c43cad87SWarner Losh  * These are the backtraces and threads that have already been logged by an
92*c43cad87SWarner Losh  * allocation.
93*c43cad87SWarner Losh  */
94*c43cad87SWarner Losh static bool log_tables_initialized = false;
95*c43cad87SWarner Losh static ckh_t log_bt_node_set;
96*c43cad87SWarner Losh static ckh_t log_thr_node_set;
97*c43cad87SWarner Losh 
98*c43cad87SWarner Losh /* Store linked lists for logged data. */
99*c43cad87SWarner Losh static prof_bt_node_t *log_bt_first = NULL;
100*c43cad87SWarner Losh static prof_bt_node_t *log_bt_last = NULL;
101*c43cad87SWarner Losh static prof_thr_node_t *log_thr_first = NULL;
102*c43cad87SWarner Losh static prof_thr_node_t *log_thr_last = NULL;
103*c43cad87SWarner Losh static prof_alloc_node_t *log_alloc_first = NULL;
104*c43cad87SWarner Losh static prof_alloc_node_t *log_alloc_last = NULL;
105*c43cad87SWarner Losh 
106*c43cad87SWarner Losh /* Protects the prof_logging_state and any log_{...} variable. */
107*c43cad87SWarner Losh malloc_mutex_t log_mtx;
108*c43cad87SWarner Losh 
109*c43cad87SWarner Losh /******************************************************************************/
110*c43cad87SWarner Losh /*
111*c43cad87SWarner Losh  * Function prototypes for static functions that are referenced prior to
112*c43cad87SWarner Losh  * definition.
113*c43cad87SWarner Losh  */
114*c43cad87SWarner Losh 
115*c43cad87SWarner Losh /* Hashtable functions for log_bt_node_set and log_thr_node_set. */
116*c43cad87SWarner Losh static void prof_thr_node_hash(const void *key, size_t r_hash[2]);
117*c43cad87SWarner Losh static bool prof_thr_node_keycomp(const void *k1, const void *k2);
118*c43cad87SWarner Losh static void prof_bt_node_hash(const void *key, size_t r_hash[2]);
119*c43cad87SWarner Losh static bool prof_bt_node_keycomp(const void *k1, const void *k2);
120*c43cad87SWarner Losh 
121*c43cad87SWarner Losh /******************************************************************************/
122*c43cad87SWarner Losh 
123*c43cad87SWarner Losh static size_t
124*c43cad87SWarner Losh prof_log_bt_index(tsd_t *tsd, prof_bt_t *bt) {
125*c43cad87SWarner Losh 	assert(prof_logging_state == prof_logging_state_started);
126*c43cad87SWarner Losh 	malloc_mutex_assert_owner(tsd_tsdn(tsd), &log_mtx);
127*c43cad87SWarner Losh 
128*c43cad87SWarner Losh 	prof_bt_node_t dummy_node;
129*c43cad87SWarner Losh 	dummy_node.bt = *bt;
130*c43cad87SWarner Losh 	prof_bt_node_t *node;
131*c43cad87SWarner Losh 
132*c43cad87SWarner Losh 	/* See if this backtrace is already cached in the table. */
133*c43cad87SWarner Losh 	if (ckh_search(&log_bt_node_set, (void *)(&dummy_node),
134*c43cad87SWarner Losh 	    (void **)(&node), NULL)) {
135*c43cad87SWarner Losh 		size_t sz = offsetof(prof_bt_node_t, vec) +
136*c43cad87SWarner Losh 			        (bt->len * sizeof(void *));
137*c43cad87SWarner Losh 		prof_bt_node_t *new_node = (prof_bt_node_t *)
138*c43cad87SWarner Losh 		    iallocztm(tsd_tsdn(tsd), sz, sz_size2index(sz), false, NULL,
139*c43cad87SWarner Losh 		    true, arena_get(TSDN_NULL, 0, true), true);
140*c43cad87SWarner Losh 		if (log_bt_first == NULL) {
141*c43cad87SWarner Losh 			log_bt_first = new_node;
142*c43cad87SWarner Losh 			log_bt_last = new_node;
143*c43cad87SWarner Losh 		} else {
144*c43cad87SWarner Losh 			log_bt_last->next = new_node;
145*c43cad87SWarner Losh 			log_bt_last = new_node;
146*c43cad87SWarner Losh 		}
147*c43cad87SWarner Losh 
148*c43cad87SWarner Losh 		new_node->next = NULL;
149*c43cad87SWarner Losh 		new_node->index = log_bt_index;
150*c43cad87SWarner Losh 		/*
151*c43cad87SWarner Losh 		 * Copy the backtrace: bt is inside a tdata or gctx, which
152*c43cad87SWarner Losh 		 * might die before prof_log_stop is called.
153*c43cad87SWarner Losh 		 */
154*c43cad87SWarner Losh 		new_node->bt.len = bt->len;
155*c43cad87SWarner Losh 		memcpy(new_node->vec, bt->vec, bt->len * sizeof(void *));
156*c43cad87SWarner Losh 		new_node->bt.vec = new_node->vec;
157*c43cad87SWarner Losh 
158*c43cad87SWarner Losh 		log_bt_index++;
159*c43cad87SWarner Losh 		ckh_insert(tsd, &log_bt_node_set, (void *)new_node, NULL);
160*c43cad87SWarner Losh 		return new_node->index;
161*c43cad87SWarner Losh 	} else {
162*c43cad87SWarner Losh 		return node->index;
163*c43cad87SWarner Losh 	}
164*c43cad87SWarner Losh }
165*c43cad87SWarner Losh 
166*c43cad87SWarner Losh static size_t
167*c43cad87SWarner Losh prof_log_thr_index(tsd_t *tsd, uint64_t thr_uid, const char *name) {
168*c43cad87SWarner Losh 	assert(prof_logging_state == prof_logging_state_started);
169*c43cad87SWarner Losh 	malloc_mutex_assert_owner(tsd_tsdn(tsd), &log_mtx);
170*c43cad87SWarner Losh 
171*c43cad87SWarner Losh 	prof_thr_node_t dummy_node;
172*c43cad87SWarner Losh 	dummy_node.thr_uid = thr_uid;
173*c43cad87SWarner Losh 	prof_thr_node_t *node;
174*c43cad87SWarner Losh 
175*c43cad87SWarner Losh 	/* See if this thread is already cached in the table. */
176*c43cad87SWarner Losh 	if (ckh_search(&log_thr_node_set, (void *)(&dummy_node),
177*c43cad87SWarner Losh 	    (void **)(&node), NULL)) {
178*c43cad87SWarner Losh 		size_t sz = offsetof(prof_thr_node_t, name) + strlen(name) + 1;
179*c43cad87SWarner Losh 		prof_thr_node_t *new_node = (prof_thr_node_t *)
180*c43cad87SWarner Losh 		    iallocztm(tsd_tsdn(tsd), sz, sz_size2index(sz), false, NULL,
181*c43cad87SWarner Losh 		    true, arena_get(TSDN_NULL, 0, true), true);
182*c43cad87SWarner Losh 		if (log_thr_first == NULL) {
183*c43cad87SWarner Losh 			log_thr_first = new_node;
184*c43cad87SWarner Losh 			log_thr_last = new_node;
185*c43cad87SWarner Losh 		} else {
186*c43cad87SWarner Losh 			log_thr_last->next = new_node;
187*c43cad87SWarner Losh 			log_thr_last = new_node;
188*c43cad87SWarner Losh 		}
189*c43cad87SWarner Losh 
190*c43cad87SWarner Losh 		new_node->next = NULL;
191*c43cad87SWarner Losh 		new_node->index = log_thr_index;
192*c43cad87SWarner Losh 		new_node->thr_uid = thr_uid;
193*c43cad87SWarner Losh 		strcpy(new_node->name, name);
194*c43cad87SWarner Losh 
195*c43cad87SWarner Losh 		log_thr_index++;
196*c43cad87SWarner Losh 		ckh_insert(tsd, &log_thr_node_set, (void *)new_node, NULL);
197*c43cad87SWarner Losh 		return new_node->index;
198*c43cad87SWarner Losh 	} else {
199*c43cad87SWarner Losh 		return node->index;
200*c43cad87SWarner Losh 	}
201*c43cad87SWarner Losh }
202*c43cad87SWarner Losh 
203*c43cad87SWarner Losh JEMALLOC_COLD
204*c43cad87SWarner Losh void
205*c43cad87SWarner Losh prof_try_log(tsd_t *tsd, size_t usize, prof_info_t *prof_info) {
206*c43cad87SWarner Losh 	cassert(config_prof);
207*c43cad87SWarner Losh 	prof_tctx_t *tctx = prof_info->alloc_tctx;
208*c43cad87SWarner Losh 	malloc_mutex_assert_owner(tsd_tsdn(tsd), tctx->tdata->lock);
209*c43cad87SWarner Losh 
210*c43cad87SWarner Losh 	prof_tdata_t *cons_tdata = prof_tdata_get(tsd, false);
211*c43cad87SWarner Losh 	if (cons_tdata == NULL) {
212*c43cad87SWarner Losh 		/*
213*c43cad87SWarner Losh 		 * We decide not to log these allocations. cons_tdata will be
214*c43cad87SWarner Losh 		 * NULL only when the current thread is in a weird state (e.g.
215*c43cad87SWarner Losh 		 * it's being destroyed).
216*c43cad87SWarner Losh 		 */
217*c43cad87SWarner Losh 		return;
218*c43cad87SWarner Losh 	}
219*c43cad87SWarner Losh 
220*c43cad87SWarner Losh 	malloc_mutex_lock(tsd_tsdn(tsd), &log_mtx);
221*c43cad87SWarner Losh 
222*c43cad87SWarner Losh 	if (prof_logging_state != prof_logging_state_started) {
223*c43cad87SWarner Losh 		goto label_done;
224*c43cad87SWarner Losh 	}
225*c43cad87SWarner Losh 
226*c43cad87SWarner Losh 	if (!log_tables_initialized) {
227*c43cad87SWarner Losh 		bool err1 = ckh_new(tsd, &log_bt_node_set, PROF_CKH_MINITEMS,
228*c43cad87SWarner Losh 				prof_bt_node_hash, prof_bt_node_keycomp);
229*c43cad87SWarner Losh 		bool err2 = ckh_new(tsd, &log_thr_node_set, PROF_CKH_MINITEMS,
230*c43cad87SWarner Losh 				prof_thr_node_hash, prof_thr_node_keycomp);
231*c43cad87SWarner Losh 		if (err1 || err2) {
232*c43cad87SWarner Losh 			goto label_done;
233*c43cad87SWarner Losh 		}
234*c43cad87SWarner Losh 		log_tables_initialized = true;
235*c43cad87SWarner Losh 	}
236*c43cad87SWarner Losh 
237*c43cad87SWarner Losh 	nstime_t alloc_time = prof_info->alloc_time;
238*c43cad87SWarner Losh 	nstime_t free_time;
239*c43cad87SWarner Losh 	nstime_prof_init_update(&free_time);
240*c43cad87SWarner Losh 
241*c43cad87SWarner Losh 	size_t sz = sizeof(prof_alloc_node_t);
242*c43cad87SWarner Losh 	prof_alloc_node_t *new_node = (prof_alloc_node_t *)
243*c43cad87SWarner Losh 	    iallocztm(tsd_tsdn(tsd), sz, sz_size2index(sz), false, NULL, true,
244*c43cad87SWarner Losh 	    arena_get(TSDN_NULL, 0, true), true);
245*c43cad87SWarner Losh 
246*c43cad87SWarner Losh 	const char *prod_thr_name = (tctx->tdata->thread_name == NULL)?
247*c43cad87SWarner Losh 				        "" : tctx->tdata->thread_name;
248*c43cad87SWarner Losh 	const char *cons_thr_name = prof_thread_name_get(tsd);
249*c43cad87SWarner Losh 
250*c43cad87SWarner Losh 	prof_bt_t bt;
251*c43cad87SWarner Losh 	/* Initialize the backtrace, using the buffer in tdata to store it. */
252*c43cad87SWarner Losh 	bt_init(&bt, cons_tdata->vec);
253*c43cad87SWarner Losh 	prof_backtrace(tsd, &bt);
254*c43cad87SWarner Losh 	prof_bt_t *cons_bt = &bt;
255*c43cad87SWarner Losh 
256*c43cad87SWarner Losh 	/* We haven't destroyed tctx yet, so gctx should be good to read. */
257*c43cad87SWarner Losh 	prof_bt_t *prod_bt = &tctx->gctx->bt;
258*c43cad87SWarner Losh 
259*c43cad87SWarner Losh 	new_node->next = NULL;
260*c43cad87SWarner Losh 	new_node->alloc_thr_ind = prof_log_thr_index(tsd, tctx->tdata->thr_uid,
261*c43cad87SWarner Losh 				      prod_thr_name);
262*c43cad87SWarner Losh 	new_node->free_thr_ind = prof_log_thr_index(tsd, cons_tdata->thr_uid,
263*c43cad87SWarner Losh 				     cons_thr_name);
264*c43cad87SWarner Losh 	new_node->alloc_bt_ind = prof_log_bt_index(tsd, prod_bt);
265*c43cad87SWarner Losh 	new_node->free_bt_ind = prof_log_bt_index(tsd, cons_bt);
266*c43cad87SWarner Losh 	new_node->alloc_time_ns = nstime_ns(&alloc_time);
267*c43cad87SWarner Losh 	new_node->free_time_ns = nstime_ns(&free_time);
268*c43cad87SWarner Losh 	new_node->usize = usize;
269*c43cad87SWarner Losh 
270*c43cad87SWarner Losh 	if (log_alloc_first == NULL) {
271*c43cad87SWarner Losh 		log_alloc_first = new_node;
272*c43cad87SWarner Losh 		log_alloc_last = new_node;
273*c43cad87SWarner Losh 	} else {
274*c43cad87SWarner Losh 		log_alloc_last->next = new_node;
275*c43cad87SWarner Losh 		log_alloc_last = new_node;
276*c43cad87SWarner Losh 	}
277*c43cad87SWarner Losh 
278*c43cad87SWarner Losh label_done:
279*c43cad87SWarner Losh 	malloc_mutex_unlock(tsd_tsdn(tsd), &log_mtx);
280*c43cad87SWarner Losh }
281*c43cad87SWarner Losh 
282*c43cad87SWarner Losh static void
283*c43cad87SWarner Losh prof_bt_node_hash(const void *key, size_t r_hash[2]) {
284*c43cad87SWarner Losh 	const prof_bt_node_t *bt_node = (prof_bt_node_t *)key;
285*c43cad87SWarner Losh 	prof_bt_hash((void *)(&bt_node->bt), r_hash);
286*c43cad87SWarner Losh }
287*c43cad87SWarner Losh 
288*c43cad87SWarner Losh static bool
289*c43cad87SWarner Losh prof_bt_node_keycomp(const void *k1, const void *k2) {
290*c43cad87SWarner Losh 	const prof_bt_node_t *bt_node1 = (prof_bt_node_t *)k1;
291*c43cad87SWarner Losh 	const prof_bt_node_t *bt_node2 = (prof_bt_node_t *)k2;
292*c43cad87SWarner Losh 	return prof_bt_keycomp((void *)(&bt_node1->bt),
293*c43cad87SWarner Losh 	    (void *)(&bt_node2->bt));
294*c43cad87SWarner Losh }
295*c43cad87SWarner Losh 
296*c43cad87SWarner Losh static void
297*c43cad87SWarner Losh prof_thr_node_hash(const void *key, size_t r_hash[2]) {
298*c43cad87SWarner Losh 	const prof_thr_node_t *thr_node = (prof_thr_node_t *)key;
299*c43cad87SWarner Losh 	hash(&thr_node->thr_uid, sizeof(uint64_t), 0x94122f35U, r_hash);
300*c43cad87SWarner Losh }
301*c43cad87SWarner Losh 
302*c43cad87SWarner Losh static bool
303*c43cad87SWarner Losh prof_thr_node_keycomp(const void *k1, const void *k2) {
304*c43cad87SWarner Losh 	const prof_thr_node_t *thr_node1 = (prof_thr_node_t *)k1;
305*c43cad87SWarner Losh 	const prof_thr_node_t *thr_node2 = (prof_thr_node_t *)k2;
306*c43cad87SWarner Losh 	return thr_node1->thr_uid == thr_node2->thr_uid;
307*c43cad87SWarner Losh }
308*c43cad87SWarner Losh 
309*c43cad87SWarner Losh /* Used in unit tests. */
310*c43cad87SWarner Losh size_t
311*c43cad87SWarner Losh prof_log_bt_count(void) {
312*c43cad87SWarner Losh 	cassert(config_prof);
313*c43cad87SWarner Losh 	size_t cnt = 0;
314*c43cad87SWarner Losh 	prof_bt_node_t *node = log_bt_first;
315*c43cad87SWarner Losh 	while (node != NULL) {
316*c43cad87SWarner Losh 		cnt++;
317*c43cad87SWarner Losh 		node = node->next;
318*c43cad87SWarner Losh 	}
319*c43cad87SWarner Losh 	return cnt;
320*c43cad87SWarner Losh }
321*c43cad87SWarner Losh 
322*c43cad87SWarner Losh /* Used in unit tests. */
323*c43cad87SWarner Losh size_t
324*c43cad87SWarner Losh prof_log_alloc_count(void) {
325*c43cad87SWarner Losh 	cassert(config_prof);
326*c43cad87SWarner Losh 	size_t cnt = 0;
327*c43cad87SWarner Losh 	prof_alloc_node_t *node = log_alloc_first;
328*c43cad87SWarner Losh 	while (node != NULL) {
329*c43cad87SWarner Losh 		cnt++;
330*c43cad87SWarner Losh 		node = node->next;
331*c43cad87SWarner Losh 	}
332*c43cad87SWarner Losh 	return cnt;
333*c43cad87SWarner Losh }
334*c43cad87SWarner Losh 
335*c43cad87SWarner Losh /* Used in unit tests. */
336*c43cad87SWarner Losh size_t
337*c43cad87SWarner Losh prof_log_thr_count(void) {
338*c43cad87SWarner Losh 	cassert(config_prof);
339*c43cad87SWarner Losh 	size_t cnt = 0;
340*c43cad87SWarner Losh 	prof_thr_node_t *node = log_thr_first;
341*c43cad87SWarner Losh 	while (node != NULL) {
342*c43cad87SWarner Losh 		cnt++;
343*c43cad87SWarner Losh 		node = node->next;
344*c43cad87SWarner Losh 	}
345*c43cad87SWarner Losh 	return cnt;
346*c43cad87SWarner Losh }
347*c43cad87SWarner Losh 
348*c43cad87SWarner Losh /* Used in unit tests. */
349*c43cad87SWarner Losh bool
350*c43cad87SWarner Losh prof_log_is_logging(void) {
351*c43cad87SWarner Losh 	cassert(config_prof);
352*c43cad87SWarner Losh 	return prof_logging_state == prof_logging_state_started;
353*c43cad87SWarner Losh }
354*c43cad87SWarner Losh 
355*c43cad87SWarner Losh /* Used in unit tests. */
356*c43cad87SWarner Losh bool
357*c43cad87SWarner Losh prof_log_rep_check(void) {
358*c43cad87SWarner Losh 	cassert(config_prof);
359*c43cad87SWarner Losh 	if (prof_logging_state == prof_logging_state_stopped
360*c43cad87SWarner Losh 	    && log_tables_initialized) {
361*c43cad87SWarner Losh 		return true;
362*c43cad87SWarner Losh 	}
363*c43cad87SWarner Losh 
364*c43cad87SWarner Losh 	if (log_bt_last != NULL && log_bt_last->next != NULL) {
365*c43cad87SWarner Losh 		return true;
366*c43cad87SWarner Losh 	}
367*c43cad87SWarner Losh 	if (log_thr_last != NULL && log_thr_last->next != NULL) {
368*c43cad87SWarner Losh 		return true;
369*c43cad87SWarner Losh 	}
370*c43cad87SWarner Losh 	if (log_alloc_last != NULL && log_alloc_last->next != NULL) {
371*c43cad87SWarner Losh 		return true;
372*c43cad87SWarner Losh 	}
373*c43cad87SWarner Losh 
374*c43cad87SWarner Losh 	size_t bt_count = prof_log_bt_count();
375*c43cad87SWarner Losh 	size_t thr_count = prof_log_thr_count();
376*c43cad87SWarner Losh 	size_t alloc_count = prof_log_alloc_count();
377*c43cad87SWarner Losh 
378*c43cad87SWarner Losh 
379*c43cad87SWarner Losh 	if (prof_logging_state == prof_logging_state_stopped) {
380*c43cad87SWarner Losh 		if (bt_count != 0 || thr_count != 0 || alloc_count || 0) {
381*c43cad87SWarner Losh 			return true;
382*c43cad87SWarner Losh 		}
383*c43cad87SWarner Losh 	}
384*c43cad87SWarner Losh 
385*c43cad87SWarner Losh 	prof_alloc_node_t *node = log_alloc_first;
386*c43cad87SWarner Losh 	while (node != NULL) {
387*c43cad87SWarner Losh 		if (node->alloc_bt_ind >= bt_count) {
388*c43cad87SWarner Losh 			return true;
389*c43cad87SWarner Losh 		}
390*c43cad87SWarner Losh 		if (node->free_bt_ind >= bt_count) {
391*c43cad87SWarner Losh 			return true;
392*c43cad87SWarner Losh 		}
393*c43cad87SWarner Losh 		if (node->alloc_thr_ind >= thr_count) {
394*c43cad87SWarner Losh 			return true;
395*c43cad87SWarner Losh 		}
396*c43cad87SWarner Losh 		if (node->free_thr_ind >= thr_count) {
397*c43cad87SWarner Losh 			return true;
398*c43cad87SWarner Losh 		}
399*c43cad87SWarner Losh 		if (node->alloc_time_ns > node->free_time_ns) {
400*c43cad87SWarner Losh 			return true;
401*c43cad87SWarner Losh 		}
402*c43cad87SWarner Losh 		node = node->next;
403*c43cad87SWarner Losh 	}
404*c43cad87SWarner Losh 
405*c43cad87SWarner Losh 	return false;
406*c43cad87SWarner Losh }
407*c43cad87SWarner Losh 
408*c43cad87SWarner Losh /* Used in unit tests. */
409*c43cad87SWarner Losh void
410*c43cad87SWarner Losh prof_log_dummy_set(bool new_value) {
411*c43cad87SWarner Losh 	cassert(config_prof);
412*c43cad87SWarner Losh 	prof_log_dummy = new_value;
413*c43cad87SWarner Losh }
414*c43cad87SWarner Losh 
415*c43cad87SWarner Losh /* Used as an atexit function to stop logging on exit. */
416*c43cad87SWarner Losh static void
417*c43cad87SWarner Losh prof_log_stop_final(void) {
418*c43cad87SWarner Losh 	tsd_t *tsd = tsd_fetch();
419*c43cad87SWarner Losh 	prof_log_stop(tsd_tsdn(tsd));
420*c43cad87SWarner Losh }
421*c43cad87SWarner Losh 
422*c43cad87SWarner Losh JEMALLOC_COLD
423*c43cad87SWarner Losh bool
424*c43cad87SWarner Losh prof_log_start(tsdn_t *tsdn, const char *filename) {
425*c43cad87SWarner Losh 	cassert(config_prof);
426*c43cad87SWarner Losh 
427*c43cad87SWarner Losh 	if (!opt_prof) {
428*c43cad87SWarner Losh 		return true;
429*c43cad87SWarner Losh 	}
430*c43cad87SWarner Losh 
431*c43cad87SWarner Losh 	bool ret = false;
432*c43cad87SWarner Losh 
433*c43cad87SWarner Losh 	malloc_mutex_lock(tsdn, &log_mtx);
434*c43cad87SWarner Losh 
435*c43cad87SWarner Losh 	static bool prof_log_atexit_called = false;
436*c43cad87SWarner Losh 	if (!prof_log_atexit_called) {
437*c43cad87SWarner Losh 		prof_log_atexit_called = true;
438*c43cad87SWarner Losh 		if (atexit(prof_log_stop_final) != 0) {
439*c43cad87SWarner Losh 			malloc_write("<jemalloc>: Error in atexit() "
440*c43cad87SWarner Losh 			    "for logging\n");
441*c43cad87SWarner Losh 			if (opt_abort) {
442*c43cad87SWarner Losh 				abort();
443*c43cad87SWarner Losh 			}
444*c43cad87SWarner Losh 			ret = true;
445*c43cad87SWarner Losh 			goto label_done;
446*c43cad87SWarner Losh 		}
447*c43cad87SWarner Losh 	}
448*c43cad87SWarner Losh 
449*c43cad87SWarner Losh 	if (prof_logging_state != prof_logging_state_stopped) {
450*c43cad87SWarner Losh 		ret = true;
451*c43cad87SWarner Losh 	} else if (filename == NULL) {
452*c43cad87SWarner Losh 		/* Make default name. */
453*c43cad87SWarner Losh 		prof_get_default_filename(tsdn, log_filename, log_seq);
454*c43cad87SWarner Losh 		log_seq++;
455*c43cad87SWarner Losh 		prof_logging_state = prof_logging_state_started;
456*c43cad87SWarner Losh 	} else if (strlen(filename) >= PROF_DUMP_FILENAME_LEN) {
457*c43cad87SWarner Losh 		ret = true;
458*c43cad87SWarner Losh 	} else {
459*c43cad87SWarner Losh 		strcpy(log_filename, filename);
460*c43cad87SWarner Losh 		prof_logging_state = prof_logging_state_started;
461*c43cad87SWarner Losh 	}
462*c43cad87SWarner Losh 
463*c43cad87SWarner Losh 	if (!ret) {
464*c43cad87SWarner Losh 		nstime_prof_init_update(&log_start_timestamp);
465*c43cad87SWarner Losh 	}
466*c43cad87SWarner Losh label_done:
467*c43cad87SWarner Losh 	malloc_mutex_unlock(tsdn, &log_mtx);
468*c43cad87SWarner Losh 
469*c43cad87SWarner Losh 	return ret;
470*c43cad87SWarner Losh }
471*c43cad87SWarner Losh 
472*c43cad87SWarner Losh struct prof_emitter_cb_arg_s {
473*c43cad87SWarner Losh 	int fd;
474*c43cad87SWarner Losh 	ssize_t ret;
475*c43cad87SWarner Losh };
476*c43cad87SWarner Losh 
477*c43cad87SWarner Losh static void
478*c43cad87SWarner Losh prof_emitter_write_cb(void *opaque, const char *to_write) {
479*c43cad87SWarner Losh 	struct prof_emitter_cb_arg_s *arg =
480*c43cad87SWarner Losh 	    (struct prof_emitter_cb_arg_s *)opaque;
481*c43cad87SWarner Losh 	size_t bytes = strlen(to_write);
482*c43cad87SWarner Losh 	if (prof_log_dummy) {
483*c43cad87SWarner Losh 		return;
484*c43cad87SWarner Losh 	}
485*c43cad87SWarner Losh 	arg->ret = malloc_write_fd(arg->fd, to_write, bytes);
486*c43cad87SWarner Losh }
487*c43cad87SWarner Losh 
488*c43cad87SWarner Losh /*
489*c43cad87SWarner Losh  * prof_log_emit_{...} goes through the appropriate linked list, emitting each
490*c43cad87SWarner Losh  * node to the json and deallocating it.
491*c43cad87SWarner Losh  */
492*c43cad87SWarner Losh static void
493*c43cad87SWarner Losh prof_log_emit_threads(tsd_t *tsd, emitter_t *emitter) {
494*c43cad87SWarner Losh 	emitter_json_array_kv_begin(emitter, "threads");
495*c43cad87SWarner Losh 	prof_thr_node_t *thr_node = log_thr_first;
496*c43cad87SWarner Losh 	prof_thr_node_t *thr_old_node;
497*c43cad87SWarner Losh 	while (thr_node != NULL) {
498*c43cad87SWarner Losh 		emitter_json_object_begin(emitter);
499*c43cad87SWarner Losh 
500*c43cad87SWarner Losh 		emitter_json_kv(emitter, "thr_uid", emitter_type_uint64,
501*c43cad87SWarner Losh 		    &thr_node->thr_uid);
502*c43cad87SWarner Losh 
503*c43cad87SWarner Losh 		char *thr_name = thr_node->name;
504*c43cad87SWarner Losh 
505*c43cad87SWarner Losh 		emitter_json_kv(emitter, "thr_name", emitter_type_string,
506*c43cad87SWarner Losh 		    &thr_name);
507*c43cad87SWarner Losh 
508*c43cad87SWarner Losh 		emitter_json_object_end(emitter);
509*c43cad87SWarner Losh 		thr_old_node = thr_node;
510*c43cad87SWarner Losh 		thr_node = thr_node->next;
511*c43cad87SWarner Losh 		idalloctm(tsd_tsdn(tsd), thr_old_node, NULL, NULL, true, true);
512*c43cad87SWarner Losh 	}
513*c43cad87SWarner Losh 	emitter_json_array_end(emitter);
514*c43cad87SWarner Losh }
515*c43cad87SWarner Losh 
516*c43cad87SWarner Losh static void
517*c43cad87SWarner Losh prof_log_emit_traces(tsd_t *tsd, emitter_t *emitter) {
518*c43cad87SWarner Losh 	emitter_json_array_kv_begin(emitter, "stack_traces");
519*c43cad87SWarner Losh 	prof_bt_node_t *bt_node = log_bt_first;
520*c43cad87SWarner Losh 	prof_bt_node_t *bt_old_node;
521*c43cad87SWarner Losh 	/*
522*c43cad87SWarner Losh 	 * Calculate how many hex digits we need: twice number of bytes, two for
523*c43cad87SWarner Losh 	 * "0x", and then one more for terminating '\0'.
524*c43cad87SWarner Losh 	 */
525*c43cad87SWarner Losh 	char buf[2 * sizeof(intptr_t) + 3];
526*c43cad87SWarner Losh 	size_t buf_sz = sizeof(buf);
527*c43cad87SWarner Losh 	while (bt_node != NULL) {
528*c43cad87SWarner Losh 		emitter_json_array_begin(emitter);
529*c43cad87SWarner Losh 		size_t i;
530*c43cad87SWarner Losh 		for (i = 0; i < bt_node->bt.len; i++) {
531*c43cad87SWarner Losh 			malloc_snprintf(buf, buf_sz, "%p", bt_node->bt.vec[i]);
532*c43cad87SWarner Losh 			char *trace_str = buf;
533*c43cad87SWarner Losh 			emitter_json_value(emitter, emitter_type_string,
534*c43cad87SWarner Losh 			    &trace_str);
535*c43cad87SWarner Losh 		}
536*c43cad87SWarner Losh 		emitter_json_array_end(emitter);
537*c43cad87SWarner Losh 
538*c43cad87SWarner Losh 		bt_old_node = bt_node;
539*c43cad87SWarner Losh 		bt_node = bt_node->next;
540*c43cad87SWarner Losh 		idalloctm(tsd_tsdn(tsd), bt_old_node, NULL, NULL, true, true);
541*c43cad87SWarner Losh 	}
542*c43cad87SWarner Losh 	emitter_json_array_end(emitter);
543*c43cad87SWarner Losh }
544*c43cad87SWarner Losh 
545*c43cad87SWarner Losh static void
546*c43cad87SWarner Losh prof_log_emit_allocs(tsd_t *tsd, emitter_t *emitter) {
547*c43cad87SWarner Losh 	emitter_json_array_kv_begin(emitter, "allocations");
548*c43cad87SWarner Losh 	prof_alloc_node_t *alloc_node = log_alloc_first;
549*c43cad87SWarner Losh 	prof_alloc_node_t *alloc_old_node;
550*c43cad87SWarner Losh 	while (alloc_node != NULL) {
551*c43cad87SWarner Losh 		emitter_json_object_begin(emitter);
552*c43cad87SWarner Losh 
553*c43cad87SWarner Losh 		emitter_json_kv(emitter, "alloc_thread", emitter_type_size,
554*c43cad87SWarner Losh 		    &alloc_node->alloc_thr_ind);
555*c43cad87SWarner Losh 
556*c43cad87SWarner Losh 		emitter_json_kv(emitter, "free_thread", emitter_type_size,
557*c43cad87SWarner Losh 		    &alloc_node->free_thr_ind);
558*c43cad87SWarner Losh 
559*c43cad87SWarner Losh 		emitter_json_kv(emitter, "alloc_trace", emitter_type_size,
560*c43cad87SWarner Losh 		    &alloc_node->alloc_bt_ind);
561*c43cad87SWarner Losh 
562*c43cad87SWarner Losh 		emitter_json_kv(emitter, "free_trace", emitter_type_size,
563*c43cad87SWarner Losh 		    &alloc_node->free_bt_ind);
564*c43cad87SWarner Losh 
565*c43cad87SWarner Losh 		emitter_json_kv(emitter, "alloc_timestamp",
566*c43cad87SWarner Losh 		    emitter_type_uint64, &alloc_node->alloc_time_ns);
567*c43cad87SWarner Losh 
568*c43cad87SWarner Losh 		emitter_json_kv(emitter, "free_timestamp", emitter_type_uint64,
569*c43cad87SWarner Losh 		    &alloc_node->free_time_ns);
570*c43cad87SWarner Losh 
571*c43cad87SWarner Losh 		emitter_json_kv(emitter, "usize", emitter_type_uint64,
572*c43cad87SWarner Losh 		    &alloc_node->usize);
573*c43cad87SWarner Losh 
574*c43cad87SWarner Losh 		emitter_json_object_end(emitter);
575*c43cad87SWarner Losh 
576*c43cad87SWarner Losh 		alloc_old_node = alloc_node;
577*c43cad87SWarner Losh 		alloc_node = alloc_node->next;
578*c43cad87SWarner Losh 		idalloctm(tsd_tsdn(tsd), alloc_old_node, NULL, NULL, true,
579*c43cad87SWarner Losh 		    true);
580*c43cad87SWarner Losh 	}
581*c43cad87SWarner Losh 	emitter_json_array_end(emitter);
582*c43cad87SWarner Losh }
583*c43cad87SWarner Losh 
584*c43cad87SWarner Losh static void
585*c43cad87SWarner Losh prof_log_emit_metadata(emitter_t *emitter) {
586*c43cad87SWarner Losh 	emitter_json_object_kv_begin(emitter, "info");
587*c43cad87SWarner Losh 
588*c43cad87SWarner Losh 	nstime_t now;
589*c43cad87SWarner Losh 
590*c43cad87SWarner Losh 	nstime_prof_init_update(&now);
591*c43cad87SWarner Losh 	uint64_t ns = nstime_ns(&now) - nstime_ns(&log_start_timestamp);
592*c43cad87SWarner Losh 	emitter_json_kv(emitter, "duration", emitter_type_uint64, &ns);
593*c43cad87SWarner Losh 
594*c43cad87SWarner Losh 	char *vers = JEMALLOC_VERSION;
595*c43cad87SWarner Losh 	emitter_json_kv(emitter, "version",
596*c43cad87SWarner Losh 	    emitter_type_string, &vers);
597*c43cad87SWarner Losh 
598*c43cad87SWarner Losh 	emitter_json_kv(emitter, "lg_sample_rate",
599*c43cad87SWarner Losh 	    emitter_type_int, &lg_prof_sample);
600*c43cad87SWarner Losh 
601*c43cad87SWarner Losh 	const char *res_type = prof_time_res_mode_names[opt_prof_time_res];
602*c43cad87SWarner Losh 	emitter_json_kv(emitter, "prof_time_resolution", emitter_type_string,
603*c43cad87SWarner Losh 	    &res_type);
604*c43cad87SWarner Losh 
605*c43cad87SWarner Losh 	int pid = prof_getpid();
606*c43cad87SWarner Losh 	emitter_json_kv(emitter, "pid", emitter_type_int, &pid);
607*c43cad87SWarner Losh 
608*c43cad87SWarner Losh 	emitter_json_object_end(emitter);
609*c43cad87SWarner Losh }
610*c43cad87SWarner Losh 
611*c43cad87SWarner Losh #define PROF_LOG_STOP_BUFSIZE PROF_DUMP_BUFSIZE
612*c43cad87SWarner Losh JEMALLOC_COLD
613*c43cad87SWarner Losh bool
614*c43cad87SWarner Losh prof_log_stop(tsdn_t *tsdn) {
615*c43cad87SWarner Losh 	cassert(config_prof);
616*c43cad87SWarner Losh 	if (!opt_prof || !prof_booted) {
617*c43cad87SWarner Losh 		return true;
618*c43cad87SWarner Losh 	}
619*c43cad87SWarner Losh 
620*c43cad87SWarner Losh 	tsd_t *tsd = tsdn_tsd(tsdn);
621*c43cad87SWarner Losh 	malloc_mutex_lock(tsdn, &log_mtx);
622*c43cad87SWarner Losh 
623*c43cad87SWarner Losh 	if (prof_logging_state != prof_logging_state_started) {
624*c43cad87SWarner Losh 		malloc_mutex_unlock(tsdn, &log_mtx);
625*c43cad87SWarner Losh 		return true;
626*c43cad87SWarner Losh 	}
627*c43cad87SWarner Losh 
628*c43cad87SWarner Losh 	/*
629*c43cad87SWarner Losh 	 * Set the state to dumping. We'll set it to stopped when we're done.
630*c43cad87SWarner Losh 	 * Since other threads won't be able to start/stop/log when the state is
631*c43cad87SWarner Losh 	 * dumping, we don't have to hold the lock during the whole method.
632*c43cad87SWarner Losh 	 */
633*c43cad87SWarner Losh 	prof_logging_state = prof_logging_state_dumping;
634*c43cad87SWarner Losh 	malloc_mutex_unlock(tsdn, &log_mtx);
635*c43cad87SWarner Losh 
636*c43cad87SWarner Losh 
637*c43cad87SWarner Losh 	emitter_t emitter;
638*c43cad87SWarner Losh 
639*c43cad87SWarner Losh 	/* Create a file. */
640*c43cad87SWarner Losh 
641*c43cad87SWarner Losh 	int fd;
642*c43cad87SWarner Losh 	if (prof_log_dummy) {
643*c43cad87SWarner Losh 		fd = 0;
644*c43cad87SWarner Losh 	} else {
645*c43cad87SWarner Losh 		fd = creat(log_filename, 0644);
646*c43cad87SWarner Losh 	}
647*c43cad87SWarner Losh 
648*c43cad87SWarner Losh 	if (fd == -1) {
649*c43cad87SWarner Losh 		malloc_printf("<jemalloc>: creat() for log file \"%s\" "
650*c43cad87SWarner Losh 			      " failed with %d\n", log_filename, errno);
651*c43cad87SWarner Losh 		if (opt_abort) {
652*c43cad87SWarner Losh 			abort();
653*c43cad87SWarner Losh 		}
654*c43cad87SWarner Losh 		return true;
655*c43cad87SWarner Losh 	}
656*c43cad87SWarner Losh 
657*c43cad87SWarner Losh 	struct prof_emitter_cb_arg_s arg;
658*c43cad87SWarner Losh 	arg.fd = fd;
659*c43cad87SWarner Losh 
660*c43cad87SWarner Losh 	buf_writer_t buf_writer;
661*c43cad87SWarner Losh 	buf_writer_init(tsdn, &buf_writer, prof_emitter_write_cb, &arg, NULL,
662*c43cad87SWarner Losh 	    PROF_LOG_STOP_BUFSIZE);
663*c43cad87SWarner Losh 	emitter_init(&emitter, emitter_output_json_compact, buf_writer_cb,
664*c43cad87SWarner Losh 	    &buf_writer);
665*c43cad87SWarner Losh 
666*c43cad87SWarner Losh 	emitter_begin(&emitter);
667*c43cad87SWarner Losh 	prof_log_emit_metadata(&emitter);
668*c43cad87SWarner Losh 	prof_log_emit_threads(tsd, &emitter);
669*c43cad87SWarner Losh 	prof_log_emit_traces(tsd, &emitter);
670*c43cad87SWarner Losh 	prof_log_emit_allocs(tsd, &emitter);
671*c43cad87SWarner Losh 	emitter_end(&emitter);
672*c43cad87SWarner Losh 
673*c43cad87SWarner Losh 	buf_writer_terminate(tsdn, &buf_writer);
674*c43cad87SWarner Losh 
675*c43cad87SWarner Losh 	/* Reset global state. */
676*c43cad87SWarner Losh 	if (log_tables_initialized) {
677*c43cad87SWarner Losh 		ckh_delete(tsd, &log_bt_node_set);
678*c43cad87SWarner Losh 		ckh_delete(tsd, &log_thr_node_set);
679*c43cad87SWarner Losh 	}
680*c43cad87SWarner Losh 	log_tables_initialized = false;
681*c43cad87SWarner Losh 	log_bt_index = 0;
682*c43cad87SWarner Losh 	log_thr_index = 0;
683*c43cad87SWarner Losh 	log_bt_first = NULL;
684*c43cad87SWarner Losh 	log_bt_last = NULL;
685*c43cad87SWarner Losh 	log_thr_first = NULL;
686*c43cad87SWarner Losh 	log_thr_last = NULL;
687*c43cad87SWarner Losh 	log_alloc_first = NULL;
688*c43cad87SWarner Losh 	log_alloc_last = NULL;
689*c43cad87SWarner Losh 
690*c43cad87SWarner Losh 	malloc_mutex_lock(tsdn, &log_mtx);
691*c43cad87SWarner Losh 	prof_logging_state = prof_logging_state_stopped;
692*c43cad87SWarner Losh 	malloc_mutex_unlock(tsdn, &log_mtx);
693*c43cad87SWarner Losh 
694*c43cad87SWarner Losh 	if (prof_log_dummy) {
695*c43cad87SWarner Losh 		return false;
696*c43cad87SWarner Losh 	}
697*c43cad87SWarner Losh 	return close(fd) || arg.ret == -1;
698*c43cad87SWarner Losh }
699*c43cad87SWarner Losh #undef PROF_LOG_STOP_BUFSIZE
700*c43cad87SWarner Losh 
701*c43cad87SWarner Losh JEMALLOC_COLD
702*c43cad87SWarner Losh bool
703*c43cad87SWarner Losh prof_log_init(tsd_t *tsd) {
704*c43cad87SWarner Losh 	cassert(config_prof);
705*c43cad87SWarner Losh 	if (malloc_mutex_init(&log_mtx, "prof_log",
706*c43cad87SWarner Losh 	    WITNESS_RANK_PROF_LOG, malloc_mutex_rank_exclusive)) {
707*c43cad87SWarner Losh 		return true;
708*c43cad87SWarner Losh 	}
709*c43cad87SWarner Losh 
710*c43cad87SWarner Losh 	if (opt_prof_log) {
711*c43cad87SWarner Losh 		prof_log_start(tsd_tsdn(tsd), NULL);
712*c43cad87SWarner Losh 	}
713*c43cad87SWarner Losh 
714*c43cad87SWarner Losh 	return false;
715*c43cad87SWarner Losh }
716*c43cad87SWarner Losh 
717*c43cad87SWarner Losh /******************************************************************************/
718