mwrap 2.2.0.1.g867b → 3.0.0.pre1

Sign up to get free protection for your applications and to get access to all the features.
data/ext/mwrap/mwrap.c CHANGED
@@ -2,851 +2,14 @@
2
2
  * Copyright (C) mwrap hackers <mwrap-public@80x24.org>
3
3
  * License: GPL-2.0+ <https://www.gnu.org/licenses/gpl-2.0.txt>
4
4
  */
5
- #define _LGPL_SOURCE /* allows URCU to inline some stuff */
6
- #include <ruby.h> /* defines HAVE_RUBY_RACTOR_H on 3.0+ */
7
- #include <ruby/thread.h>
8
- #include <ruby/io.h>
9
- #include <execinfo.h>
10
- #include <stdio.h>
11
- #include <stdlib.h>
12
- #include <string.h>
13
- #include <dlfcn.h>
14
- #include <assert.h>
15
- #include <errno.h>
16
- #include <sys/types.h>
17
- #include <sys/stat.h>
18
- #include <fcntl.h>
19
- #include <pthread.h>
20
- #include <urcu-bp.h>
21
- #include <urcu/rculfhash.h>
22
- #include <urcu/rculist.h>
23
- #include "jhash.h"
24
-
25
- #if __STDC_VERSION__ >= 201112
26
- # define MWRAP_TSD _Thread_local
27
- #elif defined(__GNUC__)
28
- # define MWRAP_TSD __thread
29
- #else
30
- # error _Thread_local nor __thread supported
31
- #endif
5
+ #define MWRAP_RUBY 1
6
+ #include "mwrap_core.h"
32
7
 
33
8
  static ID id_uminus;
34
- const char *rb_source_location_cstr(int *line); /* requires 2.6.0dev */
35
-
36
- #ifdef HAVE_RUBY_RACTOR_H /* Ruby 3.0+ */
37
- extern MWRAP_TSD void * __attribute__((weak)) ruby_current_ec;
38
- #else /* Ruby 2.6-2.7 */
39
- extern void * __attribute__((weak)) ruby_current_execution_context_ptr;
40
- # define ruby_current_ec ruby_current_execution_context_ptr
41
- #endif
42
- extern void * __attribute__((weak)) ruby_current_vm_ptr; /* for rb_gc_count */
43
- extern size_t __attribute__((weak)) rb_gc_count(void);
9
+ extern VALUE __attribute__((weak)) rb_stderr;
44
10
  extern VALUE __attribute__((weak)) rb_cObject;
45
11
  extern VALUE __attribute__((weak)) rb_eTypeError;
46
12
  extern VALUE __attribute__((weak)) rb_yield(VALUE);
47
- int __attribute__((weak)) ruby_thread_has_gvl_p(void);
48
-
49
- static size_t total_bytes_inc, total_bytes_dec;
50
-
51
- /* true for glibc/dlmalloc/ptmalloc, not sure about jemalloc */
52
- #define ASSUMED_MALLOC_ALIGNMENT (sizeof(void *) * 2)
53
-
54
- /* match values in Ruby gc.c */
55
- #define HEAP_PAGE_ALIGN_LOG 14
56
- enum {
57
- HEAP_PAGE_ALIGN = (1UL << HEAP_PAGE_ALIGN_LOG)
58
- #ifndef HEAP_PAGE_SIZE /* Ruby 2.6-2.7 only */
59
- ,
60
- REQUIRED_SIZE_BY_MALLOC = (sizeof(size_t) * 5),
61
- HEAP_PAGE_SIZE = (HEAP_PAGE_ALIGN - REQUIRED_SIZE_BY_MALLOC)
62
- #endif
63
- };
64
-
65
- #define IS_HEAP_PAGE_BODY ((struct src_loc *)-1)
66
-
67
- #ifdef __FreeBSD__
68
- void *__malloc(size_t);
69
- void __free(void *);
70
- # define real_malloc __malloc
71
- # define real_free __free
72
- #else
73
- static void *(*real_malloc)(size_t);
74
- static void (*real_free)(void *);
75
- static int resolving_malloc;
76
- #endif /* !FreeBSD */
77
-
78
- /*
79
- * we need to fake an OOM condition while dlsym is running,
80
- * as that calls calloc under glibc, but we don't have the
81
- * symbol for the jemalloc calloc, yet
82
- */
83
- # define RETURN_IF_NOT_READY() do { \
84
- if (!real_malloc) { \
85
- errno = ENOMEM; \
86
- return NULL; \
87
- } \
88
- } while (0)
89
-
90
- static MWRAP_TSD size_t locating;
91
- static size_t generation;
92
- static size_t page_size;
93
- static struct cds_lfht *totals;
94
- union padded_mutex {
95
- pthread_mutex_t mtx;
96
- char pad[64];
97
- };
98
-
99
- /* a round-robin pool of mutexes */
100
- #define MUTEX_NR (1 << 6)
101
- #define MUTEX_MASK (MUTEX_NR - 1)
102
- static size_t mutex_i;
103
- static union padded_mutex mutexes[MUTEX_NR] = {
104
- [0 ... (MUTEX_NR-1)].mtx = PTHREAD_MUTEX_INITIALIZER
105
- };
106
-
107
- static pthread_mutex_t *mutex_assign(void)
108
- {
109
- return &mutexes[uatomic_add_return(&mutex_i, 1) & MUTEX_MASK].mtx;
110
- }
111
-
112
- static struct cds_lfht *
113
- lfht_new(void)
114
- {
115
- return cds_lfht_new(16384, 1, 0, CDS_LFHT_AUTO_RESIZE, 0);
116
- }
117
-
118
- __attribute__((constructor)) static void resolve_malloc(void)
119
- {
120
- int err;
121
- ++locating;
122
-
123
- #ifdef __FreeBSD__
124
- /*
125
- * PTHREAD_MUTEX_INITIALIZER on FreeBSD means lazy initialization,
126
- * which happens at pthread_mutex_lock, and that calls calloc
127
- */
128
- {
129
- size_t i;
130
-
131
- for (i = 0; i < MUTEX_NR; i++) {
132
- err = pthread_mutex_init(&mutexes[i].mtx, 0);
133
- if (err) {
134
- fprintf(stderr, "error: %s\n", strerror(err));
135
- _exit(1);
136
- }
137
- }
138
- /* initialize mutexes used by urcu-bp */
139
- rcu_read_lock();
140
- rcu_read_unlock();
141
- }
142
- #else /* !FreeBSD (tested on GNU/Linux) */
143
- if (!real_malloc) {
144
- resolving_malloc = 1;
145
- real_malloc = dlsym(RTLD_NEXT, "malloc");
146
- }
147
- real_free = dlsym(RTLD_NEXT, "free");
148
- if (!real_malloc || !real_free) {
149
- fprintf(stderr, "missing malloc/aligned_alloc/free\n"
150
- "\t%p %p\n", real_malloc, real_free);
151
- _exit(1);
152
- }
153
- #endif /* !FreeBSD */
154
- CMM_STORE_SHARED(totals, lfht_new());
155
- if (!CMM_LOAD_SHARED(totals))
156
- fprintf(stderr, "failed to allocate totals table\n");
157
-
158
- err = pthread_atfork(call_rcu_before_fork,
159
- call_rcu_after_fork_parent,
160
- call_rcu_after_fork_child);
161
- if (err)
162
- fprintf(stderr, "pthread_atfork failed: %s\n", strerror(err));
163
- page_size = sysconf(_SC_PAGESIZE);
164
- --locating;
165
- }
166
-
167
- #ifdef NDEBUG
168
- #define QUIET_CC_WARNING(var) (void)var;
169
- #else
170
- #define QUIET_CC_WARNING(var)
171
- #endif
172
-
173
- static void
174
- mutex_lock(pthread_mutex_t *m)
175
- {
176
- int err = pthread_mutex_lock(m);
177
- assert(err == 0);
178
- QUIET_CC_WARNING(err)
179
- }
180
-
181
- static void
182
- mutex_unlock(pthread_mutex_t *m)
183
- {
184
- int err = pthread_mutex_unlock(m);
185
- assert(err == 0);
186
- QUIET_CC_WARNING(err)
187
- }
188
-
189
- #ifndef HAVE_MEMPCPY
190
- static void *
191
- my_mempcpy(void *dest, const void *src, size_t n)
192
- {
193
- return (char *)memcpy(dest, src, n) + n;
194
- }
195
- #define mempcpy(dst,src,n) my_mempcpy(dst,src,n)
196
- #endif
197
-
198
- /* stolen from glibc: */
199
- #define RETURN_ADDRESS(nr) \
200
- (uintptr_t)(__builtin_extract_return_addr(__builtin_return_address(nr)))
201
-
202
- #define INT2STR_MAX (sizeof(int) == 4 ? 10 : 19)
203
- static char *int2str(int num, char *dst, size_t * size)
204
- {
205
- if (num <= 9) {
206
- *size -= 1;
207
- *dst++ = (char)(num + '0');
208
- return dst;
209
- } else {
210
- char buf[INT2STR_MAX];
211
- char *end = buf + sizeof(buf);
212
- char *p = end;
213
- size_t adj;
214
-
215
- do {
216
- *size -= 1;
217
- *--p = (char)((num % 10) + '0');
218
- num /= 10;
219
- } while (num && *size);
220
-
221
- if (!num) {
222
- adj = end - p;
223
- return mempcpy(dst, p, adj);
224
- }
225
- }
226
- return NULL;
227
- }
228
-
229
- /*
230
- * rb_source_location_cstr relies on GET_EC(), and it's possible
231
- * to have a native thread but no EC during the early and late
232
- * (teardown) phases of the Ruby process
233
- */
234
- static int has_ec_p(void)
235
- {
236
- return ruby_thread_has_gvl_p && ruby_thread_has_gvl_p() &&
237
- ruby_current_vm_ptr && ruby_current_ec;
238
- }
239
-
240
- struct acc {
241
- uint64_t nr;
242
- int64_t min;
243
- int64_t max;
244
- double m2;
245
- double mean;
246
- };
247
-
248
- #define ACC_INIT(name) { .nr=0, .min=INT64_MAX, .max=-1, .m2=0, .mean=0 }
249
-
250
- /* for tracking 16K-aligned heap page bodies (protected by GVL) */
251
- struct {
252
- pthread_mutex_t lock;
253
- struct cds_list_head bodies;
254
- struct cds_list_head freed;
255
-
256
- struct acc alive;
257
- struct acc reborn;
258
- } hpb_stats = {
259
- .lock = PTHREAD_MUTEX_INITIALIZER,
260
- .bodies = CDS_LIST_HEAD_INIT(hpb_stats.bodies),
261
- .freed = CDS_LIST_HEAD_INIT(hpb_stats.freed),
262
- .alive = ACC_INIT(hpb_stats.alive),
263
- .reborn = ACC_INIT(hpb_stats.reborn)
264
- };
265
-
266
- /* allocated via real_malloc/real_free */
267
- struct src_loc {
268
- pthread_mutex_t *mtx;
269
- size_t total;
270
- size_t allocations;
271
- size_t frees;
272
- size_t age_total; /* (age_total / frees) => mean age at free */
273
- size_t max_lifespan;
274
- struct cds_lfht_node hnode;
275
- struct cds_list_head allocs; /* <=> alloc_hdr.node */
276
- uint32_t hval;
277
- uint32_t capa;
278
- char k[];
279
- };
280
-
281
- /* every allocation has this in the header, maintain alignment with malloc */
282
- struct alloc_hdr {
283
- struct cds_list_head anode; /* <=> src_loc.allocs */
284
- union {
285
- struct {
286
- size_t gen; /* rb_gc_count() */
287
- struct src_loc *loc;
288
- } live;
289
- struct rcu_head dead;
290
- struct {
291
- size_t at; /* rb_gc_count() */
292
- } hpb_freed;
293
- } as;
294
- void *real; /* what to call real_free on */
295
- size_t size;
296
- };
297
-
298
- static char kbuf[PATH_MAX + INT2STR_MAX + sizeof(struct alloc_hdr) + 2];
299
-
300
- static struct alloc_hdr *ptr2hdr(void *p)
301
- {
302
- return (struct alloc_hdr *)((uintptr_t)p - sizeof(struct alloc_hdr));
303
- }
304
-
305
- static void *hdr2ptr(struct alloc_hdr *h)
306
- {
307
- return (void *)((uintptr_t)h + sizeof(struct alloc_hdr));
308
- }
309
-
310
- static int loc_is_addr(const struct src_loc *l)
311
- {
312
- return l->capa == 0;
313
- }
314
-
315
- static size_t loc_size(const struct src_loc *l)
316
- {
317
- return loc_is_addr(l) ? sizeof(uintptr_t) : l->capa;
318
- }
319
-
320
- static int loc_eq(struct cds_lfht_node *node, const void *key)
321
- {
322
- const struct src_loc *existing;
323
- const struct src_loc *k = key;
324
-
325
- existing = caa_container_of(node, struct src_loc, hnode);
326
-
327
- return (k->hval == existing->hval &&
328
- k->capa == existing->capa &&
329
- memcmp(k->k, existing->k, loc_size(k)) == 0);
330
- }
331
-
332
- /* note: not atomic */
333
- static void
334
- acc_add(struct acc *acc, size_t val)
335
- {
336
- double delta = val - acc->mean;
337
- uint64_t nr = ++acc->nr;
338
-
339
- /* just don't divide-by-zero if we ever hit this (unlikely :P) */
340
- if (nr)
341
- acc->mean += delta / nr;
342
-
343
- acc->m2 += delta * (val - acc->mean);
344
- if ((int64_t)val < acc->min)
345
- acc->min = (int64_t)val;
346
- if ((int64_t)val > acc->max)
347
- acc->max = (int64_t)val;
348
- }
349
-
350
- #if SIZEOF_LONG == 8
351
- # define INT64toNUM(x) LONG2NUM((long)x)
352
- #elif defined(HAVE_LONG_LONG) && SIZEOF_LONG_LONG == 8
353
- # define INT64toNUM(x) LL2NUM((LONG_LONG)x)
354
- #endif
355
-
356
- static VALUE
357
- acc_max(const struct acc *acc)
358
- {
359
- return INT64toNUM(acc->max);
360
- }
361
-
362
- static VALUE
363
- acc_min(const struct acc *acc)
364
- {
365
- return acc->min == INT64_MAX ? INT2FIX(-1) : INT64toNUM(acc->min);
366
- }
367
-
368
- static VALUE
369
- acc_mean(const struct acc *acc)
370
- {
371
- return DBL2NUM(acc->nr ? acc->mean : HUGE_VAL);
372
- }
373
-
374
- static double
375
- acc_stddev_dbl(const struct acc *acc)
376
- {
377
- if (acc->nr > 1) {
378
- double variance = acc->m2 / (acc->nr - 1);
379
- return sqrt(variance);
380
- }
381
- return 0.0;
382
- }
383
-
384
- static VALUE
385
- acc_stddev(const struct acc *acc)
386
- {
387
- return DBL2NUM(acc_stddev_dbl(acc));
388
- }
389
-
390
- static struct src_loc *totals_add_rcu(const struct src_loc *k)
391
- {
392
- struct cds_lfht_iter iter;
393
- struct cds_lfht_node *cur;
394
- struct src_loc *l = 0;
395
- struct cds_lfht *t;
396
-
397
- again:
398
- t = CMM_LOAD_SHARED(totals);
399
- if (!t) goto out_unlock;
400
- cds_lfht_lookup(t, k->hval, loc_eq, k, &iter);
401
- cur = cds_lfht_iter_get_node(&iter);
402
- if (cur) {
403
- l = caa_container_of(cur, struct src_loc, hnode);
404
- uatomic_add(&l->total, k->total);
405
- uatomic_add(&l->allocations, 1);
406
- } else {
407
- size_t n = loc_size(k);
408
- l = real_malloc(sizeof(*l) + n);
409
- if (!l) goto out_unlock;
410
- memcpy(l, k, sizeof(*l) + n);
411
- l->mtx = mutex_assign();
412
- l->age_total = 0;
413
- l->max_lifespan = 0;
414
- l->frees = 0;
415
- l->allocations = 1;
416
- CDS_INIT_LIST_HEAD(&l->allocs);
417
- cur = cds_lfht_add_unique(t, k->hval, loc_eq, l, &l->hnode);
418
- if (cur != &l->hnode) { /* lost race */
419
- rcu_read_unlock();
420
- real_free(l);
421
- rcu_read_lock();
422
- goto again;
423
- }
424
- }
425
- out_unlock:
426
- return l;
427
- }
428
-
429
- static void update_stats_rcu_unlock(const struct src_loc *l)
430
- {
431
- if (caa_likely(l)) rcu_read_unlock();
432
- }
433
-
434
- static struct src_loc *update_stats_rcu_lock(size_t size, uintptr_t caller)
435
- {
436
- struct src_loc *k, *ret = 0;
437
- static const size_t xlen = sizeof(caller);
438
- char *dst;
439
-
440
- if (caa_unlikely(!CMM_LOAD_SHARED(totals))) return 0;
441
- if (locating++) goto out; /* do not recurse into another *alloc */
442
-
443
- uatomic_add(&total_bytes_inc, size);
444
-
445
- rcu_read_lock();
446
- if (has_ec_p()) {
447
- int line;
448
- const char *ptr = rb_source_location_cstr(&line);
449
- size_t len;
450
- size_t int_size = INT2STR_MAX;
451
-
452
- generation = rb_gc_count();
453
-
454
- if (!ptr) goto unknown;
455
-
456
- /* avoid vsnprintf or anything which could call malloc here: */
457
- len = strlen(ptr);
458
- k = (void *)kbuf;
459
- k->total = size;
460
- dst = mempcpy(k->k, ptr, len);
461
- *dst++ = ':';
462
- dst = int2str(line, dst, &int_size);
463
- if (dst) {
464
- *dst = 0; /* terminate string */
465
- k->capa = (uint32_t)(dst - k->k + 1);
466
- k->hval = jhash(k->k, k->capa, 0xdeadbeef);
467
- ret = totals_add_rcu(k);
468
- } else {
469
- rb_bug("bad math making key from location %s:%d\n",
470
- ptr, line);
471
- }
472
- } else {
473
- unknown:
474
- k = alloca(sizeof(*k) + xlen);
475
- k->total = size;
476
- memcpy(k->k, &caller, xlen);
477
- k->capa = 0;
478
- k->hval = jhash(k->k, xlen, 0xdeadbeef);
479
- ret = totals_add_rcu(k);
480
- }
481
- out:
482
- --locating;
483
- return ret;
484
- }
485
-
486
- size_t malloc_usable_size(void *p)
487
- {
488
- return ptr2hdr(p)->size;
489
- }
490
-
491
- static void
492
- free_hdr_rcu(struct rcu_head *dead)
493
- {
494
- struct alloc_hdr *h = caa_container_of(dead, struct alloc_hdr, as.dead);
495
- real_free(h->real);
496
- }
497
-
498
- void free(void *p)
499
- {
500
- if (p) {
501
- struct alloc_hdr *h = ptr2hdr(p);
502
- struct src_loc *l = h->as.live.loc;
503
-
504
- if (!real_free) return; /* oh well, leak a little */
505
- if (l && l != IS_HEAP_PAGE_BODY) {
506
- size_t age = generation - h->as.live.gen;
507
-
508
- uatomic_add(&total_bytes_dec, h->size);
509
- uatomic_set(&h->size, 0);
510
- uatomic_add(&l->frees, 1);
511
- uatomic_add(&l->age_total, age);
512
-
513
- mutex_lock(l->mtx);
514
- cds_list_del_rcu(&h->anode);
515
- if (age > l->max_lifespan)
516
- l->max_lifespan = age;
517
- mutex_unlock(l->mtx);
518
-
519
- call_rcu(&h->as.dead, free_hdr_rcu);
520
- } else if (l == IS_HEAP_PAGE_BODY) {
521
- size_t gen = generation;
522
- size_t age = gen - h->as.live.gen;
523
-
524
- h->as.hpb_freed.at = gen;
525
-
526
- mutex_lock(&hpb_stats.lock);
527
- acc_add(&hpb_stats.alive, age);
528
-
529
- /* hpb_stats.bodies => hpb_stats.freed */
530
- cds_list_move(&h->anode, &hpb_stats.freed);
531
-
532
- mutex_unlock(&hpb_stats.lock);
533
- } else {
534
- real_free(h->real);
535
- }
536
- }
537
- }
538
-
539
- static void
540
- alloc_insert_rcu(struct src_loc *l, struct alloc_hdr *h, size_t size, void *real)
541
- {
542
- /* we need src_loc to remain alive for the duration of this call */
543
- if (!h) return;
544
- h->size = size;
545
- h->real = real;
546
- h->as.live.loc = l;
547
- h->as.live.gen = generation;
548
- if (l) {
549
- mutex_lock(l->mtx);
550
- cds_list_add_rcu(&h->anode, &l->allocs);
551
- mutex_unlock(l->mtx);
552
- }
553
- }
554
-
555
- static size_t size_align(size_t size, size_t alignment)
556
- {
557
- return ((size + (alignment - 1)) & ~(alignment - 1));
558
- }
559
-
560
- static bool ptr_is_aligned(const void *ptr, size_t alignment)
561
- {
562
- return ((uintptr_t)ptr & (alignment - 1)) == 0;
563
- }
564
-
565
- static void *ptr_align(void *ptr, size_t alignment)
566
- {
567
- return (void *)(((uintptr_t)ptr + (alignment - 1)) & ~(alignment - 1));
568
- }
569
-
570
- static bool is_power_of_two(size_t n) { return (n & (n - 1)) == 0; }
571
-
572
- static int
573
- internal_memalign(void **pp, size_t alignment, size_t size, uintptr_t caller)
574
- {
575
- struct src_loc *l;
576
- struct alloc_hdr *h;
577
- void *real;
578
- size_t asize;
579
- size_t d = alignment / sizeof(void*);
580
- size_t r = alignment % sizeof(void*);
581
-
582
- if (!real_malloc) return ENOMEM;
583
-
584
- if (r != 0 || d == 0 || !is_power_of_two(d))
585
- return EINVAL;
586
-
587
- if (alignment <= ASSUMED_MALLOC_ALIGNMENT) {
588
- void *p = malloc(size);
589
- if (!p) return ENOMEM;
590
- *pp = p;
591
- return 0;
592
- }
593
- for (; alignment < sizeof(struct alloc_hdr); alignment *= 2)
594
- ; /* double alignment until >= sizeof(struct alloc_hdr) */
595
- if (__builtin_add_overflow(size, alignment, &asize) ||
596
- __builtin_add_overflow(asize, sizeof(struct alloc_hdr), &asize))
597
- return ENOMEM;
598
-
599
-
600
- if (alignment == HEAP_PAGE_ALIGN && size == HEAP_PAGE_SIZE) {
601
- if (has_ec_p()) generation = rb_gc_count();
602
- l = IS_HEAP_PAGE_BODY;
603
- } else {
604
- l = update_stats_rcu_lock(size, caller);
605
- }
606
-
607
- if (l == IS_HEAP_PAGE_BODY) {
608
- void *p;
609
- size_t gen = generation;
610
-
611
- mutex_lock(&hpb_stats.lock);
612
-
613
- /* reuse existing entry */
614
- if (!cds_list_empty(&hpb_stats.freed)) {
615
- size_t deathspan;
616
-
617
- h = cds_list_first_entry(&hpb_stats.freed,
618
- struct alloc_hdr, anode);
619
- /* hpb_stats.freed => hpb_stats.bodies */
620
- cds_list_move(&h->anode, &hpb_stats.bodies);
621
- assert(h->size == size);
622
- assert(h->real);
623
- real = h->real;
624
- p = hdr2ptr(h);
625
- assert(ptr_is_aligned(p, alignment));
626
-
627
- deathspan = gen - h->as.hpb_freed.at;
628
- acc_add(&hpb_stats.reborn, deathspan);
629
- }
630
- else {
631
- real = real_malloc(asize);
632
- if (!real) return ENOMEM;
633
-
634
- p = hdr2ptr(real);
635
- if (!ptr_is_aligned(p, alignment))
636
- p = ptr_align(p, alignment);
637
- h = ptr2hdr(p);
638
- h->size = size;
639
- h->real = real;
640
- cds_list_add(&h->anode, &hpb_stats.bodies);
641
- }
642
- mutex_unlock(&hpb_stats.lock);
643
- h->as.live.loc = l;
644
- h->as.live.gen = gen;
645
- *pp = p;
646
- }
647
- else {
648
- real = real_malloc(asize);
649
- if (real) {
650
- void *p = hdr2ptr(real);
651
- if (!ptr_is_aligned(p, alignment))
652
- p = ptr_align(p, alignment);
653
- h = ptr2hdr(p);
654
- alloc_insert_rcu(l, h, size, real);
655
- *pp = p;
656
- }
657
- update_stats_rcu_unlock(l);
658
- }
659
-
660
- return real ? 0 : ENOMEM;
661
- }
662
-
663
- static void *
664
- memalign_result(int err, void *p)
665
- {
666
- if (caa_unlikely(err))
667
- errno = err;
668
- return p;
669
- }
670
-
671
- void *memalign(size_t alignment, size_t size)
672
- {
673
- void *p = NULL;
674
- int err = internal_memalign(&p, alignment, size, RETURN_ADDRESS(0));
675
- return memalign_result(err, p);
676
- }
677
-
678
- int posix_memalign(void **p, size_t alignment, size_t size)
679
- {
680
- return internal_memalign(p, alignment, size, RETURN_ADDRESS(0));
681
- }
682
-
683
- void *aligned_alloc(size_t, size_t) __attribute__((alias("memalign")));
684
- void cfree(void *) __attribute__((alias("free")));
685
-
686
- void *valloc(size_t size)
687
- {
688
- void *p = NULL;
689
- int err = internal_memalign(&p, page_size, size, RETURN_ADDRESS(0));
690
- return memalign_result(err, p);
691
- }
692
-
693
- #if __GNUC__ < 7
694
- # define add_overflow_p(a,b) __extension__({ \
695
- __typeof__(a) _c; \
696
- __builtin_add_overflow(a,b,&_c); \
697
- })
698
- #else
699
- # define add_overflow_p(a,b) \
700
- __builtin_add_overflow_p((a),(b),(__typeof__(a+b))0)
701
- #endif
702
-
703
- void *pvalloc(size_t size)
704
- {
705
- size_t alignment = page_size;
706
- void *p = NULL;
707
- int err;
708
-
709
- if (add_overflow_p(size, alignment)) {
710
- errno = ENOMEM;
711
- return 0;
712
- }
713
- size = size_align(size, alignment);
714
- err = internal_memalign(&p, alignment, size, RETURN_ADDRESS(0));
715
- return memalign_result(err, p);
716
- }
717
-
718
- void *malloc(size_t size)
719
- {
720
- struct src_loc *l;
721
- struct alloc_hdr *h;
722
- size_t asize;
723
- void *p;
724
-
725
- if (__builtin_add_overflow(size, sizeof(struct alloc_hdr), &asize))
726
- goto enomem;
727
-
728
- /*
729
- * Needed for C++ global declarations using "new",
730
- * which happens before our constructor
731
- */
732
- #ifndef __FreeBSD__
733
- if (!real_malloc) {
734
- if (resolving_malloc) goto enomem;
735
- resolving_malloc = 1;
736
- real_malloc = dlsym(RTLD_NEXT, "malloc");
737
- }
738
- #endif
739
- l = update_stats_rcu_lock(size, RETURN_ADDRESS(0));
740
- p = h = real_malloc(asize);
741
- if (h) {
742
- alloc_insert_rcu(l, h, size, h);
743
- p = hdr2ptr(h);
744
- }
745
- update_stats_rcu_unlock(l);
746
- if (caa_unlikely(!p)) errno = ENOMEM;
747
- return p;
748
- enomem:
749
- errno = ENOMEM;
750
- return 0;
751
- }
752
-
753
- void *calloc(size_t nmemb, size_t size)
754
- {
755
- void *p;
756
- struct src_loc *l;
757
- struct alloc_hdr *h;
758
- size_t asize;
759
-
760
- if (__builtin_mul_overflow(size, nmemb, &size)) {
761
- errno = ENOMEM;
762
- return 0;
763
- }
764
- if (__builtin_add_overflow(size, sizeof(struct alloc_hdr), &asize)) {
765
- errno = ENOMEM;
766
- return 0;
767
- }
768
- RETURN_IF_NOT_READY();
769
- l = update_stats_rcu_lock(size, RETURN_ADDRESS(0));
770
- p = h = real_malloc(asize);
771
- if (p) {
772
- alloc_insert_rcu(l, h, size, h);
773
- p = hdr2ptr(h);
774
- memset(p, 0, size);
775
- }
776
- update_stats_rcu_unlock(l);
777
- if (caa_unlikely(!p)) errno = ENOMEM;
778
- return p;
779
- }
780
-
781
- void *realloc(void *ptr, size_t size)
782
- {
783
- void *p;
784
- struct src_loc *l;
785
- struct alloc_hdr *h;
786
- size_t asize;
787
-
788
- if (!size) {
789
- free(ptr);
790
- return 0;
791
- }
792
- if (__builtin_add_overflow(size, sizeof(struct alloc_hdr), &asize)) {
793
- errno = ENOMEM;
794
- return 0;
795
- }
796
- RETURN_IF_NOT_READY();
797
-
798
- l = update_stats_rcu_lock(size, RETURN_ADDRESS(0));
799
- p = h = real_malloc(asize);
800
- if (p) {
801
- alloc_insert_rcu(l, h, size, h);
802
- p = hdr2ptr(h);
803
- }
804
- update_stats_rcu_unlock(l);
805
-
806
- if (ptr && p) {
807
- struct alloc_hdr *old = ptr2hdr(ptr);
808
- memcpy(p, ptr, old->size < size ? old->size : size);
809
- free(ptr);
810
- }
811
- if (caa_unlikely(!p)) errno = ENOMEM;
812
- return p;
813
- }
814
-
815
- struct dump_arg {
816
- FILE *fp;
817
- size_t min;
818
- };
819
-
820
- static void *dump_to_file(void *x)
821
- {
822
- struct dump_arg *a = x;
823
- struct cds_lfht_iter iter;
824
- struct src_loc *l;
825
- struct cds_lfht *t;
826
-
827
- ++locating;
828
- rcu_read_lock();
829
- t = CMM_LOAD_SHARED(totals);
830
- if (!t)
831
- goto out_unlock;
832
- cds_lfht_for_each_entry(t, &iter, l, hnode) {
833
- const void *p = l->k;
834
- char **s = 0;
835
- if (l->total <= a->min) continue;
836
-
837
- if (loc_is_addr(l)) {
838
- s = backtrace_symbols(p, 1);
839
- p = s[0];
840
- }
841
- fprintf(a->fp, "%16zu %12zu %s\n",
842
- l->total, l->allocations, (const char *)p);
843
- if (s) free(s);
844
- }
845
- out_unlock:
846
- rcu_read_unlock();
847
- --locating;
848
- return 0;
849
- }
850
13
 
851
14
  /*
852
15
  * call-seq:
@@ -861,7 +24,7 @@ out_unlock:
861
24
  *
862
25
  * total_size call_count location
863
26
  */
864
- static VALUE mwrap_dump(int argc, VALUE * argv, VALUE mod)
27
+ static VALUE mwrap_dump(int argc, VALUE *argv, VALUE mod)
865
28
  {
866
29
  VALUE io, min;
867
30
  struct dump_arg a;
@@ -871,7 +34,7 @@ static VALUE mwrap_dump(int argc, VALUE * argv, VALUE mod)
871
34
 
872
35
  if (NIL_P(io))
873
36
  /* library may be linked w/o Ruby */
874
- io = *((VALUE *)dlsym(RTLD_DEFAULT, "rb_stderr"));
37
+ io = rb_stderr;
875
38
 
876
39
  a.min = NIL_P(min) ? 0 : NUM2SIZET(min);
877
40
  io = rb_io_get_io(io);
@@ -879,7 +42,7 @@ static VALUE mwrap_dump(int argc, VALUE * argv, VALUE mod)
879
42
  GetOpenFile(io, fptr);
880
43
  a.fp = rb_io_stdio_file(fptr);
881
44
 
882
- rb_thread_call_without_gvl(dump_to_file, &a, 0, 0);
45
+ rb_thread_call_without_gvl((void *(*)(void *))dump_to_file, &a, 0, 0);
883
46
  RB_GC_GUARD(io);
884
47
  return Qnil;
885
48
  }
@@ -887,24 +50,8 @@ static VALUE mwrap_dump(int argc, VALUE * argv, VALUE mod)
887
50
  /* The whole operation is not remotely atomic... */
888
51
  static void *totals_reset(void *ign)
889
52
  {
890
- struct cds_lfht *t;
891
- struct cds_lfht_iter iter;
892
- struct src_loc *l;
893
-
894
- uatomic_set(&total_bytes_inc, 0);
895
- uatomic_set(&total_bytes_dec, 0);
896
-
897
- rcu_read_lock();
898
- t = CMM_LOAD_SHARED(totals);
899
- cds_lfht_for_each_entry(t, &iter, l, hnode) {
900
- uatomic_set(&l->total, 0);
901
- uatomic_set(&l->allocations, 0);
902
- uatomic_set(&l->frees, 0);
903
- uatomic_set(&l->age_total, 0);
904
- uatomic_set(&l->max_lifespan, 0);
905
- }
906
- rcu_read_unlock();
907
- return 0;
53
+ mwrap_reset();
54
+ return NULL;
908
55
  }
909
56
 
910
57
  /*
@@ -916,18 +63,12 @@ static void *totals_reset(void *ign)
916
63
  * This resets all statistics. This is not an atomic operation
917
64
  * as other threads (outside of GVL) may increment counters.
918
65
  */
919
- static VALUE mwrap_reset(VALUE mod)
66
+ static VALUE reset_m(VALUE mod)
920
67
  {
921
68
  rb_thread_call_without_gvl(totals_reset, 0, 0, 0);
922
69
  return Qnil;
923
70
  }
924
71
 
925
- /* :nodoc: */
926
- static VALUE mwrap_clear(VALUE mod)
927
- {
928
- return mwrap_reset(mod);
929
- }
930
-
931
72
  static VALUE rcu_unlock_ensure(VALUE ignored)
932
73
  {
933
74
  rcu_read_unlock();
@@ -935,21 +76,31 @@ static VALUE rcu_unlock_ensure(VALUE ignored)
935
76
  return Qfalse;
936
77
  }
937
78
 
938
- static VALUE location_string(struct src_loc *l)
79
+ static VALUE location_string(const struct src_loc *l)
939
80
  {
940
- VALUE ret, tmp;
81
+ VALUE tmp = rb_str_new(NULL, 0);
941
82
 
942
- if (loc_is_addr(l)) {
943
- char **s = backtrace_symbols((void *)l->k, 1);
944
- tmp = rb_str_new_cstr(s[0]);
945
- free(s);
83
+ if (l->f) {
84
+ rb_str_cat(tmp, l->f->fn, l->f->fn_len);
85
+ if (l->lineno == U24_MAX)
86
+ rb_str_cat_cstr(tmp, ":-");
87
+ else
88
+ rb_str_catf(tmp, ":%u", l->lineno);
946
89
  }
947
- else {
948
- tmp = rb_str_new(l->k, l->capa - 1);
90
+ if (l->bt_len) {
91
+ AUTO_FREE char **s = bt_syms(l->bt, l->bt_len);
92
+
93
+ if (s) {
94
+ if (l->f)
95
+ rb_str_cat_cstr(tmp, "\n");
96
+ rb_str_cat_cstr(tmp, s[0]);
97
+ for (uint32_t i = 1; i < l->bt_len; ++i)
98
+ rb_str_catf(tmp, "\n%s", s[i]);
99
+ }
949
100
  }
950
101
 
951
102
  /* deduplicate and try to free up some memory */
952
- ret = rb_funcall(tmp, id_uminus, 0);
103
+ VALUE ret = rb_funcall(tmp, id_uminus, 0);
953
104
  if (!OBJ_FROZEN_RAW(tmp))
954
105
  rb_str_resize(tmp, 0);
955
106
 
@@ -1021,17 +172,6 @@ static const rb_data_type_t src_loc_type = {
1021
172
 
1022
173
  static VALUE cSrcLoc;
1023
174
 
1024
- static int
1025
- extract_addr(const char *str, size_t len, void **p)
1026
- {
1027
- const char *c;
1028
- #if defined(__GLIBC__)
1029
- return ((c = memrchr(str, '[', len)) && sscanf(c, "[%p]", p));
1030
- #else /* tested FreeBSD */
1031
- return ((c = strstr(str, "0x")) && sscanf(c, "%p", p));
1032
- #endif
1033
- }
1034
-
1035
175
  /*
1036
176
  * call-seq:
1037
177
  * Mwrap[location] -> Mwrap::SourceLocation
@@ -1044,41 +184,11 @@ extract_addr(const char *str, size_t len, void **p)
1044
184
  static VALUE mwrap_aref(VALUE mod, VALUE loc)
1045
185
  {
1046
186
  const char *str = StringValueCStr(loc);
1047
- int len = RSTRING_LENINT(loc);
1048
- struct src_loc *k = 0;
1049
- uintptr_t p;
1050
- struct cds_lfht_iter iter;
1051
- struct cds_lfht_node *cur;
1052
- struct cds_lfht *t;
1053
- struct src_loc *l;
1054
- VALUE val = Qnil;
1055
-
1056
- if (extract_addr(str, len, (void **)&p)) {
1057
- k = (void *)kbuf;
1058
- memcpy(k->k, &p, sizeof(p));
1059
- k->capa = 0;
1060
- k->hval = jhash(k->k, sizeof(p), 0xdeadbeef);
1061
- } else {
1062
- k = (void *)kbuf;
1063
- memcpy(k->k, str, len + 1);
1064
- k->capa = len + 1;
1065
- k->hval = jhash(k->k, k->capa, 0xdeadbeef);
1066
- }
187
+ long len = RSTRING_LEN(loc);
188
+ assert(len >= 0);
189
+ struct src_loc *l = mwrap_get(str, (size_t)len);
1067
190
 
1068
- if (!k) return val;
1069
-
1070
- t = CMM_LOAD_SHARED(totals);
1071
- if (!t) return val;
1072
- rcu_read_lock();
1073
-
1074
- cds_lfht_lookup(t, k->hval, loc_eq, k, &iter);
1075
- cur = cds_lfht_iter_get_node(&iter);
1076
- if (cur) {
1077
- l = caa_container_of(cur, struct src_loc, hnode);
1078
- val = TypedData_Wrap_Struct(cSrcLoc, &src_loc_type, l);
1079
- }
1080
- rcu_read_unlock();
1081
- return val;
191
+ return l ? TypedData_Wrap_Struct(cSrcLoc, &src_loc_type, l) : Qnil;
1082
192
  }
1083
193
 
1084
194
  static VALUE src_loc_each_i(VALUE p)
@@ -1102,7 +212,7 @@ static VALUE src_loc_each_i(VALUE p)
1102
212
  return Qfalse;
1103
213
  }
1104
214
 
1105
- static struct src_loc *src_loc_get(VALUE self)
215
+ static struct src_loc *src_loc_of(VALUE self)
1106
216
  {
1107
217
  struct src_loc *l;
1108
218
  TypedData_Get_Struct(self, struct src_loc, &src_loc_type, l);
@@ -1124,7 +234,7 @@ static struct src_loc *src_loc_get(VALUE self)
1124
234
  */
1125
235
  static VALUE src_loc_each(VALUE self)
1126
236
  {
1127
- struct src_loc *l = src_loc_get(self);
237
+ struct src_loc *l = src_loc_of(self);
1128
238
 
1129
239
  assert(locating == 0 && "forgot to clear locating");
1130
240
  ++locating;
@@ -1139,7 +249,7 @@ static VALUE src_loc_each(VALUE self)
1139
249
  */
1140
250
  static VALUE src_loc_mean_lifespan(VALUE self)
1141
251
  {
1142
- struct src_loc *l = src_loc_get(self);
252
+ struct src_loc *l = src_loc_of(self);
1143
253
  size_t tot, frees;
1144
254
 
1145
255
  frees = uatomic_read(&l->frees);
@@ -1150,19 +260,19 @@ static VALUE src_loc_mean_lifespan(VALUE self)
1150
260
  /* The number of frees made from this location */
1151
261
  static VALUE src_loc_frees(VALUE self)
1152
262
  {
1153
- return SIZET2NUM(uatomic_read(&src_loc_get(self)->frees));
263
+ return SIZET2NUM(uatomic_read(&src_loc_of(self)->frees));
1154
264
  }
1155
265
 
1156
266
  /* The number of allocations made from this location */
1157
267
  static VALUE src_loc_allocations(VALUE self)
1158
268
  {
1159
- return SIZET2NUM(uatomic_read(&src_loc_get(self)->allocations));
269
+ return SIZET2NUM(uatomic_read(&src_loc_of(self)->allocations));
1160
270
  }
1161
271
 
1162
272
  /* The total number of bytes allocated from this location */
1163
273
  static VALUE src_loc_total(VALUE self)
1164
274
  {
1165
- return SIZET2NUM(uatomic_read(&src_loc_get(self)->total));
275
+ return SIZET2NUM(uatomic_read(&src_loc_of(self)->total));
1166
276
  }
1167
277
 
1168
278
  /*
@@ -1171,7 +281,7 @@ static VALUE src_loc_total(VALUE self)
1171
281
  */
1172
282
  static VALUE src_loc_max_lifespan(VALUE self)
1173
283
  {
1174
- return SIZET2NUM(uatomic_read(&src_loc_get(self)->max_lifespan));
284
+ return SIZET2NUM(uatomic_read(&src_loc_of(self)->max_lifespan));
1175
285
  }
1176
286
 
1177
287
  /*
@@ -1179,7 +289,7 @@ static VALUE src_loc_max_lifespan(VALUE self)
1179
289
  */
1180
290
  static VALUE src_loc_name(VALUE self)
1181
291
  {
1182
- struct src_loc *l = src_loc_get(self);
292
+ struct src_loc *l = src_loc_of(self);
1183
293
  VALUE ret;
1184
294
 
1185
295
  ++locating;
@@ -1226,73 +336,6 @@ static VALUE total_dec(VALUE mod)
1226
336
  return SIZET2NUM(total_bytes_dec);
1227
337
  }
1228
338
 
1229
- static VALUE hpb_each_yield(VALUE ignore)
1230
- {
1231
- struct alloc_hdr *h, *next;
1232
-
1233
- cds_list_for_each_entry_safe(h, next, &hpb_stats.bodies, anode) {
1234
- VALUE v[2]; /* [ generation, address ] */
1235
- void *addr = hdr2ptr(h);
1236
- assert(ptr_is_aligned(addr, HEAP_PAGE_ALIGN));
1237
- v[0] = LONG2NUM((long)addr);
1238
- v[1] = SIZET2NUM(h->as.live.gen);
1239
- rb_yield_values2(2, v);
1240
- }
1241
- return Qnil;
1242
- }
1243
-
1244
- /*
1245
- * call-seq:
1246
- *
1247
- * Mwrap::HeapPageBody.each { |gen, addr| } -> Integer
1248
- *
1249
- * Yields the generation (GC.count) the heap page body was created
1250
- * and address of the heap page body as an Integer. Returns the
1251
- * number of allocated pages as an Integer. This return value should
1252
- * match the result of GC.stat(:heap_allocated_pages)
1253
- */
1254
- static VALUE hpb_each(VALUE mod)
1255
- {
1256
- ++locating;
1257
- return rb_ensure(hpb_each_yield, Qfalse, reset_locating, 0);
1258
- }
1259
-
1260
- /*
1261
- * call-seq:
1262
- *
1263
- * Mwrap::HeapPageBody.stat -> Hash
1264
- * Mwrap::HeapPageBody.stat(hash) -> hash
1265
- *
1266
- * The maximum lifespan of a heap page body in the Ruby VM.
1267
- * This may be Infinity if no heap page bodies were ever freed.
1268
- */
1269
- static VALUE hpb_stat(int argc, VALUE *argv, VALUE hpb)
1270
- {
1271
- VALUE h;
1272
-
1273
- rb_scan_args(argc, argv, "01", &h);
1274
- if (NIL_P(h))
1275
- h = rb_hash_new();
1276
- else if (!RB_TYPE_P(h, T_HASH))
1277
- rb_raise(rb_eTypeError, "not a hash %+"PRIsVALUE, h);
1278
-
1279
- ++locating;
1280
- #define S(x) ID2SYM(rb_intern(#x))
1281
- rb_hash_aset(h, S(lifespan_max), acc_max(&hpb_stats.alive));
1282
- rb_hash_aset(h, S(lifespan_min), acc_min(&hpb_stats.alive));
1283
- rb_hash_aset(h, S(lifespan_mean), acc_mean(&hpb_stats.alive));
1284
- rb_hash_aset(h, S(lifespan_stddev), acc_stddev(&hpb_stats.alive));
1285
- rb_hash_aset(h, S(deathspan_max), acc_max(&hpb_stats.reborn));
1286
- rb_hash_aset(h, S(deathspan_min), acc_min(&hpb_stats.reborn));
1287
- rb_hash_aset(h, S(deathspan_mean), acc_mean(&hpb_stats.reborn));
1288
- rb_hash_aset(h, S(deathspan_stddev), acc_stddev(&hpb_stats.reborn));
1289
- rb_hash_aset(h, S(resurrects), SIZET2NUM(hpb_stats.reborn.nr));
1290
- #undef S
1291
- --locating;
1292
-
1293
- return h;
1294
- }
1295
-
1296
339
  /*
1297
340
  * Document-module: Mwrap
1298
341
  *
@@ -1311,19 +354,13 @@ static VALUE hpb_stat(int argc, VALUE *argv, VALUE hpb)
1311
354
  * * dump_fd: a writable FD to dump to
1312
355
  * * dump_path: a path to dump to, the file is opened in O_APPEND mode
1313
356
  * * dump_min: the minimum allocation size (total) to dump
1314
- * * dump_heap: mask of heap_page_body statistics to dump
1315
357
  *
1316
358
  * If both `dump_fd' and `dump_path' are specified, dump_path takes
1317
359
  * precedence.
1318
- *
1319
- * dump_heap bitmask
1320
- * * 0x01 - summary stats (same info as HeapPageBody.stat)
1321
- * * 0x02 - all live heaps (similar to HeapPageBody.each)
1322
- * * 0x04 - skip non-heap_page_body-related output
1323
360
  */
1324
361
  void Init_mwrap(void)
1325
362
  {
1326
- VALUE mod, hpb;
363
+ VALUE mod;
1327
364
 
1328
365
  ++locating;
1329
366
  mod = rb_define_module("Mwrap");
@@ -1337,9 +374,10 @@ void Init_mwrap(void)
1337
374
  * This class is only available since mwrap 2.0.0+.
1338
375
  */
1339
376
  cSrcLoc = rb_define_class_under(mod, "SourceLocation", rb_cObject);
377
+ rb_undef_alloc_func(cSrcLoc);
1340
378
  rb_define_singleton_method(mod, "dump", mwrap_dump, -1);
1341
- rb_define_singleton_method(mod, "reset", mwrap_reset, 0);
1342
- rb_define_singleton_method(mod, "clear", mwrap_clear, 0);
379
+ rb_define_singleton_method(mod, "reset", reset_m, 0);
380
+ rb_define_singleton_method(mod, "clear", reset_m, 0);
1343
381
  rb_define_singleton_method(mod, "each", mwrap_each, -1);
1344
382
  rb_define_singleton_method(mod, "[]", mwrap_aref, 1);
1345
383
  rb_define_singleton_method(mod, "quiet", mwrap_quiet, 0);
@@ -1355,137 +393,5 @@ void Init_mwrap(void)
1355
393
  rb_define_method(cSrcLoc, "max_lifespan", src_loc_max_lifespan, 0);
1356
394
  rb_define_method(cSrcLoc, "name", src_loc_name, 0);
1357
395
 
1358
- /*
1359
- * Information about "struct heap_page_body" allocations from
1360
- * Ruby gc.c. This can be useful for tracking fragmentation
1361
- * from posix_memalign(3) use in mainline Ruby:
1362
- *
1363
- * https://sourceware.org/bugzilla/show_bug.cgi?id=14581
1364
- *
1365
- * These statistics are never reset by Mwrap.reset or
1366
- * any other method. They only make sense in the context
1367
- * of an entire program lifetime.
1368
- */
1369
- hpb = rb_define_class_under(mod, "HeapPageBody", rb_cObject);
1370
- rb_define_singleton_method(hpb, "stat", hpb_stat, -1);
1371
- rb_define_singleton_method(hpb, "each", hpb_each, 0);
1372
-
1373
- --locating;
1374
- }
1375
-
1376
- enum {
1377
- DUMP_HPB_STATS = 0x1,
1378
- DUMP_HPB_EACH = 0x2,
1379
- DUMP_HPB_EXCL = 0x4,
1380
- };
1381
-
1382
- static void dump_hpb(FILE *fp, unsigned flags)
1383
- {
1384
- if (flags & DUMP_HPB_STATS) {
1385
- fprintf(fp,
1386
- "lifespan_max: %"PRId64"\n"
1387
- "lifespan_min:%s%"PRId64"\n"
1388
- "lifespan_mean: %0.3f\n"
1389
- "lifespan_stddev: %0.3f\n"
1390
- "deathspan_max: %"PRId64"\n"
1391
- "deathspan_min:%s%"PRId64"\n"
1392
- "deathspan_mean: %0.3f\n"
1393
- "deathspan_stddev: %0.3f\n"
1394
- "gc_count: %zu\n",
1395
- hpb_stats.alive.max,
1396
- hpb_stats.alive.min == INT64_MAX ? " -" : " ",
1397
- hpb_stats.alive.min,
1398
- hpb_stats.alive.mean,
1399
- acc_stddev_dbl(&hpb_stats.alive),
1400
- hpb_stats.reborn.max,
1401
- hpb_stats.reborn.min == INT64_MAX ? " -" : " ",
1402
- hpb_stats.reborn.min,
1403
- hpb_stats.reborn.mean,
1404
- acc_stddev_dbl(&hpb_stats.reborn),
1405
- /* n.b.: unsafe to call rb_gc_count() in destructor */
1406
- generation);
1407
- }
1408
- if (flags & DUMP_HPB_EACH) {
1409
- struct alloc_hdr *h;
1410
-
1411
- cds_list_for_each_entry(h, &hpb_stats.bodies, anode) {
1412
- void *addr = hdr2ptr(h);
1413
-
1414
- fprintf(fp, "%p\t%zu\n", addr, h->as.live.gen);
1415
- }
1416
- }
1417
- }
1418
-
1419
- /* rb_cloexec_open isn't usable by non-Ruby processes */
1420
- #ifndef O_CLOEXEC
1421
- # define O_CLOEXEC 0
1422
- #endif
1423
-
1424
- __attribute__ ((destructor))
1425
- static void mwrap_dump_destructor(void)
1426
- {
1427
- const char *opt = getenv("MWRAP");
1428
- const char *modes[] = { "a", "a+", "w", "w+", "r+" };
1429
- struct dump_arg a = { .min = 0 };
1430
- size_t i;
1431
- int dump_fd;
1432
- unsigned dump_heap = 0;
1433
- char *dump_path;
1434
- char *s;
1435
-
1436
- if (!opt)
1437
- return;
1438
-
1439
- ++locating;
1440
- if ((dump_path = strstr(opt, "dump_path:")) &&
1441
- (dump_path += sizeof("dump_path")) &&
1442
- *dump_path) {
1443
- char *end = strchr(dump_path, ',');
1444
- if (end) {
1445
- char *tmp = alloca(end - dump_path + 1);
1446
- end = mempcpy(tmp, dump_path, end - dump_path);
1447
- *end = 0;
1448
- dump_path = tmp;
1449
- }
1450
- dump_fd = open(dump_path, O_CLOEXEC|O_WRONLY|O_APPEND|O_CREAT,
1451
- 0666);
1452
- if (dump_fd < 0) {
1453
- fprintf(stderr, "open %s failed: %s\n", dump_path,
1454
- strerror(errno));
1455
- goto out;
1456
- }
1457
- }
1458
- else if (!sscanf(opt, "dump_fd:%d", &dump_fd))
1459
- goto out;
1460
-
1461
- if ((s = strstr(opt, "dump_min:")))
1462
- sscanf(s, "dump_min:%zu", &a.min);
1463
-
1464
- if ((s = strstr(opt, "dump_heap:")))
1465
- sscanf(s, "dump_heap:%u", &dump_heap);
1466
-
1467
- switch (dump_fd) {
1468
- case 0: goto out;
1469
- case 1: a.fp = stdout; break;
1470
- case 2: a.fp = stderr; break;
1471
- default:
1472
- if (dump_fd < 0)
1473
- goto out;
1474
- a.fp = 0;
1475
-
1476
- for (i = 0; !a.fp && i < 5; i++)
1477
- a.fp = fdopen(dump_fd, modes[i]);
1478
-
1479
- if (!a.fp) {
1480
- fprintf(stderr, "failed to open fd=%d: %s\n",
1481
- dump_fd, strerror(errno));
1482
- goto out;
1483
- }
1484
- /* we'll leak some memory here, but this is a destructor */
1485
- }
1486
- if ((dump_heap & DUMP_HPB_EXCL) == 0)
1487
- dump_to_file(&a);
1488
- dump_hpb(a.fp, dump_heap);
1489
- out:
1490
396
  --locating;
1491
397
  }