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