mwrap 2.0.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.document +1 -0
- data/.gitignore +3 -0
- data/.olddoc.yml +10 -0
- data/MANIFEST +1 -0
- data/README +7 -5
- data/Rakefile +29 -1
- data/bin/mwrap +1 -1
- data/ext/mwrap/extconf.rb +8 -1
- data/ext/mwrap/mwrap.c +387 -53
- data/lib/mwrap_rack.rb +170 -0
- data/mwrap.gemspec +4 -2
- data/test/test_mwrap.rb +65 -11
- metadata +6 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 406933abeb7d63d1304c63f0f5aa7a2f4deb9ecb1820816886df8bfcf2d01601
|
4
|
+
data.tar.gz: 214975358778869db55691d8ddcac9e078cc16be6870e143094bb837d88551eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: a0a353c1ed720192b6dc63699d7d53661fc80c204429df3c93d4795b39cdec1d0e1b83c90e4250ddee11abd1911d578dd4f83516368a787a21fb1327de0ac32b
|
7
|
+
data.tar.gz: 28fd47f870ec74cd4c15570e6f6df0b9ee80ff30eb460185f841d2bc796836dd8aa6d82a7e3304bf811d22acc2dfb3e00fa4f92143a0cbb04dbd78e99ae7231e
|
data/.document
CHANGED
data/.gitignore
CHANGED
data/.olddoc.yml
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
---
|
2
|
+
cgit_url: https://80x24.org/mwrap.git
|
3
|
+
git_url: https://80x24.org/mwrap.git
|
4
|
+
rdoc_url: https://80x24.org/mwrap/
|
5
|
+
ml_url: https://80x24.org/mwrap-public/
|
6
|
+
public_email: mwrap-public@80x24.org
|
7
|
+
nntp_url:
|
8
|
+
- nntps://news.public-inbox.org/inbox.comp.lang.ruby.mwrap
|
9
|
+
imap_url:
|
10
|
+
- imaps://;AUTH=ANONYMOUS@80x24.org/inbox.comp.lang.ruby.mwrap.0
|
data/MANIFEST
CHANGED
data/README
CHANGED
@@ -67,16 +67,18 @@ first two columns to find the hottest malloc locations.
|
|
67
67
|
mwrap 2.0.0+ also supports a Rack application endpoint,
|
68
68
|
it is documented at:
|
69
69
|
|
70
|
-
|
70
|
+
https://80x24.org/mwrap/MwrapRack.html
|
71
71
|
|
72
72
|
== Known problems
|
73
73
|
|
74
74
|
* 32-bit machines are prone to overflow (WONTFIX)
|
75
75
|
|
76
|
-
==
|
76
|
+
== Public mail archives and contact info:
|
77
77
|
|
78
|
-
|
79
|
-
|
78
|
+
* https://80x24.org/mwrap-public/
|
79
|
+
* nntps://80x24.org/inbox.comp.lang.ruby.mwrap
|
80
|
+
* imaps://;AUTH=ANONYMOUS@80x24.org/inbox.comp.lang.ruby.mwrap.0
|
81
|
+
* https://80x24.org/mwrap-public/_/text/help/#pop3
|
80
82
|
|
81
83
|
No subscription will ever be required to post, but HTML mail
|
82
84
|
will be rejected:
|
@@ -88,7 +90,7 @@ will be rejected:
|
|
88
90
|
git clone https://80x24.org/mwrap.git
|
89
91
|
|
90
92
|
Send all patches and pull requests (use "git request-pull" to format) to
|
91
|
-
|
93
|
+
mwrap-public@80x24.org. We do not use centralized or proprietary messaging
|
92
94
|
systems.
|
93
95
|
|
94
96
|
== License
|
data/Rakefile
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# Copyright (C)
|
1
|
+
# Copyright (C) mwrap hackers <mwrap-public@80x24.org>
|
2
2
|
# License: GPL-2.0+ <https://www.gnu.org/licenses/gpl-2.0.txt>
|
3
3
|
require 'rake/testtask'
|
4
4
|
begin
|
@@ -14,3 +14,31 @@ task :default => :compile
|
|
14
14
|
|
15
15
|
c_files = File.readlines('MANIFEST').grep(%r{ext/.*\.[ch]$}).map!(&:chomp!)
|
16
16
|
task 'compile:mwrap' => c_files
|
17
|
+
|
18
|
+
olddoc = ENV['OLDDOC'] || 'olddoc'
|
19
|
+
rdoc = ENV['RDOC'] || 'rdoc'
|
20
|
+
task :rsync_docs do
|
21
|
+
require 'fileutils'
|
22
|
+
top = %w(README COPYING LATEST NEWS NEWS.atom.xml)
|
23
|
+
system("git", "set-file-times")
|
24
|
+
dest = ENV["RSYNC_DEST"] || "80x24.org:/srv/80x24/mwrap/"
|
25
|
+
FileUtils.rm_rf('doc')
|
26
|
+
sh "#{olddoc} prepare"
|
27
|
+
sh "#{rdoc} -f dark216" # dark216 requires olddoc 1.7+
|
28
|
+
File.unlink('doc/created.rid') rescue nil
|
29
|
+
File.unlink('doc/index.html') rescue nil
|
30
|
+
FileUtils.cp(top, 'doc')
|
31
|
+
sh "#{olddoc} merge"
|
32
|
+
|
33
|
+
Dir['doc/**/*'].each do |txt|
|
34
|
+
st = File.stat(txt)
|
35
|
+
if st.file?
|
36
|
+
gz = "#{txt}.gz"
|
37
|
+
tmp = "#{gz}.#$$"
|
38
|
+
sh("gzip --rsyncable -9 <#{txt} >#{tmp}")
|
39
|
+
File.utime(st.atime, st.mtime, tmp) # make nginx gzip_static happy
|
40
|
+
File.rename(tmp, gz)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
sh("rsync --chmod=Fugo=r #{ENV['RSYNC_OPT']} -av doc/ #{dest}/")
|
44
|
+
end
|
data/bin/mwrap
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
2
|
# frozen_string_literal: true
|
3
|
-
# Copyright (C)
|
3
|
+
# Copyright (C) mwrap hackers <mwrap-public@80x24.org>
|
4
4
|
# License: GPL-2.0+ <https://www.gnu.org/licenses/gpl-2.0.txt>
|
5
5
|
require 'mwrap'
|
6
6
|
mwrap_so = $".grep(%r{/mwrap\.so\z})[0] or abort "mwrap.so not loaded"
|
data/ext/mwrap/extconf.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
# Copyright (C)
|
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
|
require 'mkmf'
|
5
5
|
|
@@ -25,4 +25,11 @@ else
|
|
25
25
|
abort 'missing __builtin_add_overflow'
|
26
26
|
end
|
27
27
|
|
28
|
+
begin
|
29
|
+
if n = GC::INTERNAL_CONSTANTS[:HEAP_PAGE_SIZE]
|
30
|
+
$defs << "-DHEAP_PAGE_SIZE=#{n}"
|
31
|
+
end
|
32
|
+
rescue NameError
|
33
|
+
end
|
34
|
+
|
28
35
|
create_makefile 'mwrap'
|
data/ext/mwrap/mwrap.c
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
/*
|
2
|
-
* Copyright (C)
|
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
5
|
#define _LGPL_SOURCE /* allows URCU to inline some stuff */
|
6
|
-
#include <ruby
|
6
|
+
#include <ruby.h> /* defines HAVE_RUBY_RACTOR_H on 3.0+ */
|
7
7
|
#include <ruby/thread.h>
|
8
8
|
#include <ruby/io.h>
|
9
9
|
#include <execinfo.h>
|
@@ -22,19 +22,48 @@
|
|
22
22
|
#include <urcu/rculist.h>
|
23
23
|
#include "jhash.h"
|
24
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
|
32
|
+
|
25
33
|
static ID id_uminus;
|
26
|
-
static unsigned int track_memalign;
|
27
34
|
const char *rb_source_location_cstr(int *line); /* requires 2.6.0dev */
|
28
35
|
extern int __attribute__((weak)) ruby_thread_has_gvl_p(void);
|
36
|
+
|
37
|
+
#ifdef HAVE_RUBY_RACTOR_H /* Ruby 3.0+ */
|
38
|
+
extern MWRAP_TSD void * __attribute__((weak)) ruby_current_ec;
|
39
|
+
#else /* Ruby 2.6-2.7 */
|
29
40
|
extern void * __attribute__((weak)) ruby_current_execution_context_ptr;
|
41
|
+
# define ruby_current_ec ruby_current_execution_context_ptr
|
42
|
+
#endif
|
30
43
|
extern void * __attribute__((weak)) ruby_current_vm_ptr; /* for rb_gc_count */
|
31
44
|
extern size_t __attribute__((weak)) rb_gc_count(void);
|
32
45
|
extern VALUE __attribute__((weak)) rb_cObject;
|
46
|
+
extern VALUE __attribute__((weak)) rb_eTypeError;
|
33
47
|
extern VALUE __attribute__((weak)) rb_yield(VALUE);
|
34
48
|
|
49
|
+
static size_t total_bytes_inc, total_bytes_dec;
|
50
|
+
|
35
51
|
/* true for glibc/dlmalloc/ptmalloc, not sure about jemalloc */
|
36
52
|
#define ASSUMED_MALLOC_ALIGNMENT (sizeof(void *) * 2)
|
37
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
|
+
|
38
67
|
int __attribute__((weak)) ruby_thread_has_gvl_p(void)
|
39
68
|
{
|
40
69
|
return 0;
|
@@ -63,7 +92,7 @@ static int resolving_malloc;
|
|
63
92
|
} \
|
64
93
|
} while (0)
|
65
94
|
|
66
|
-
static
|
95
|
+
static MWRAP_TSD size_t locating;
|
67
96
|
static size_t generation;
|
68
97
|
static size_t page_size;
|
69
98
|
static struct cds_lfht *totals;
|
@@ -94,7 +123,6 @@ lfht_new(void)
|
|
94
123
|
__attribute__((constructor)) static void resolve_malloc(void)
|
95
124
|
{
|
96
125
|
int err;
|
97
|
-
const char *opt;
|
98
126
|
++locating;
|
99
127
|
|
100
128
|
#ifdef __FreeBSD__
|
@@ -128,8 +156,8 @@ __attribute__((constructor)) static void resolve_malloc(void)
|
|
128
156
|
_exit(1);
|
129
157
|
}
|
130
158
|
#endif /* !FreeBSD */
|
131
|
-
totals
|
132
|
-
if (!totals)
|
159
|
+
CMM_STORE_SHARED(totals, lfht_new());
|
160
|
+
if (!CMM_LOAD_SHARED(totals))
|
133
161
|
fprintf(stderr, "failed to allocate totals table\n");
|
134
162
|
|
135
163
|
err = pthread_atfork(call_rcu_before_fork,
|
@@ -138,19 +166,21 @@ __attribute__((constructor)) static void resolve_malloc(void)
|
|
138
166
|
if (err)
|
139
167
|
fprintf(stderr, "pthread_atfork failed: %s\n", strerror(err));
|
140
168
|
page_size = sysconf(_SC_PAGESIZE);
|
141
|
-
opt = getenv("MWRAP");
|
142
|
-
if (opt && (opt = strstr(opt, "memalign:"))) {
|
143
|
-
if (!sscanf(opt, "memalign:%u", &track_memalign))
|
144
|
-
fprintf(stderr, "not an unsigned int: %s\n", opt);
|
145
|
-
}
|
146
169
|
--locating;
|
147
170
|
}
|
148
171
|
|
172
|
+
#ifdef NDEBUG
|
173
|
+
#define QUIET_CC_WARNING(var) (void)var;
|
174
|
+
#else
|
175
|
+
#define QUIET_CC_WARNING(var)
|
176
|
+
#endif
|
177
|
+
|
149
178
|
static void
|
150
179
|
mutex_lock(pthread_mutex_t *m)
|
151
180
|
{
|
152
181
|
int err = pthread_mutex_lock(m);
|
153
182
|
assert(err == 0);
|
183
|
+
QUIET_CC_WARNING(err)
|
154
184
|
}
|
155
185
|
|
156
186
|
static void
|
@@ -158,6 +188,7 @@ mutex_unlock(pthread_mutex_t *m)
|
|
158
188
|
{
|
159
189
|
int err = pthread_mutex_unlock(m);
|
160
190
|
assert(err == 0);
|
191
|
+
QUIET_CC_WARNING(err)
|
161
192
|
}
|
162
193
|
|
163
194
|
#ifndef HAVE_MEMPCPY
|
@@ -208,9 +239,35 @@ static char *int2str(int num, char *dst, size_t * size)
|
|
208
239
|
static int has_ec_p(void)
|
209
240
|
{
|
210
241
|
return (ruby_thread_has_gvl_p() && ruby_current_vm_ptr &&
|
211
|
-
|
242
|
+
ruby_current_ec);
|
212
243
|
}
|
213
244
|
|
245
|
+
struct acc {
|
246
|
+
uint64_t nr;
|
247
|
+
int64_t min;
|
248
|
+
int64_t max;
|
249
|
+
double m2;
|
250
|
+
double mean;
|
251
|
+
};
|
252
|
+
|
253
|
+
#define ACC_INIT(name) { .nr=0, .min=INT64_MAX, .max=-1, .m2=0, .mean=0 }
|
254
|
+
|
255
|
+
/* for tracking 16K-aligned heap page bodies (protected by GVL) */
|
256
|
+
struct {
|
257
|
+
pthread_mutex_t lock;
|
258
|
+
struct cds_list_head bodies;
|
259
|
+
struct cds_list_head freed;
|
260
|
+
|
261
|
+
struct acc alive;
|
262
|
+
struct acc reborn;
|
263
|
+
} hpb_stats = {
|
264
|
+
.lock = PTHREAD_MUTEX_INITIALIZER,
|
265
|
+
.bodies = CDS_LIST_HEAD_INIT(hpb_stats.bodies),
|
266
|
+
.freed = CDS_LIST_HEAD_INIT(hpb_stats.freed),
|
267
|
+
.alive = ACC_INIT(hpb_stats.alive),
|
268
|
+
.reborn = ACC_INIT(hpb_stats.reborn)
|
269
|
+
};
|
270
|
+
|
214
271
|
/* allocated via real_malloc/real_free */
|
215
272
|
struct src_loc {
|
216
273
|
pthread_mutex_t *mtx;
|
@@ -235,6 +292,9 @@ struct alloc_hdr {
|
|
235
292
|
struct src_loc *loc;
|
236
293
|
} live;
|
237
294
|
struct rcu_head dead;
|
295
|
+
struct {
|
296
|
+
size_t at; /* rb_gc_count() */
|
297
|
+
} hpb_freed;
|
238
298
|
} as;
|
239
299
|
void *real; /* what to call real_free on */
|
240
300
|
size_t size;
|
@@ -274,7 +334,65 @@ static int loc_eq(struct cds_lfht_node *node, const void *key)
|
|
274
334
|
memcmp(k->k, existing->k, loc_size(k)) == 0);
|
275
335
|
}
|
276
336
|
|
277
|
-
|
337
|
+
/* note: not atomic */
|
338
|
+
static void
|
339
|
+
acc_add(struct acc *acc, size_t val)
|
340
|
+
{
|
341
|
+
double delta = val - acc->mean;
|
342
|
+
uint64_t nr = ++acc->nr;
|
343
|
+
|
344
|
+
/* just don't divide-by-zero if we ever hit this (unlikely :P) */
|
345
|
+
if (nr)
|
346
|
+
acc->mean += delta / nr;
|
347
|
+
|
348
|
+
acc->m2 += delta * (val - acc->mean);
|
349
|
+
if ((int64_t)val < acc->min)
|
350
|
+
acc->min = (int64_t)val;
|
351
|
+
if ((int64_t)val > acc->max)
|
352
|
+
acc->max = (int64_t)val;
|
353
|
+
}
|
354
|
+
|
355
|
+
#if SIZEOF_LONG == 8
|
356
|
+
# define INT64toNUM(x) LONG2NUM((long)x)
|
357
|
+
#elif defined(HAVE_LONG_LONG) && SIZEOF_LONG_LONG == 8
|
358
|
+
# define INT64toNUM(x) LL2NUM((LONG_LONG)x)
|
359
|
+
#endif
|
360
|
+
|
361
|
+
static VALUE
|
362
|
+
acc_max(const struct acc *acc)
|
363
|
+
{
|
364
|
+
return INT64toNUM(acc->max);
|
365
|
+
}
|
366
|
+
|
367
|
+
static VALUE
|
368
|
+
acc_min(const struct acc *acc)
|
369
|
+
{
|
370
|
+
return acc->min == INT64_MAX ? INT2FIX(-1) : INT64toNUM(acc->min);
|
371
|
+
}
|
372
|
+
|
373
|
+
static VALUE
|
374
|
+
acc_mean(const struct acc *acc)
|
375
|
+
{
|
376
|
+
return DBL2NUM(acc->nr ? acc->mean : HUGE_VAL);
|
377
|
+
}
|
378
|
+
|
379
|
+
static double
|
380
|
+
acc_stddev_dbl(const struct acc *acc)
|
381
|
+
{
|
382
|
+
if (acc->nr > 1) {
|
383
|
+
double variance = acc->m2 / (acc->nr - 1);
|
384
|
+
return sqrt(variance);
|
385
|
+
}
|
386
|
+
return 0.0;
|
387
|
+
}
|
388
|
+
|
389
|
+
static VALUE
|
390
|
+
acc_stddev(const struct acc *acc)
|
391
|
+
{
|
392
|
+
return DBL2NUM(acc_stddev_dbl(acc));
|
393
|
+
}
|
394
|
+
|
395
|
+
static struct src_loc *totals_add_rcu(const struct src_loc *k)
|
278
396
|
{
|
279
397
|
struct cds_lfht_iter iter;
|
280
398
|
struct cds_lfht_node *cur;
|
@@ -282,7 +400,7 @@ static struct src_loc *totals_add_rcu(struct src_loc *k)
|
|
282
400
|
struct cds_lfht *t;
|
283
401
|
|
284
402
|
again:
|
285
|
-
t =
|
403
|
+
t = CMM_LOAD_SHARED(totals);
|
286
404
|
if (!t) goto out_unlock;
|
287
405
|
cds_lfht_lookup(t, k->hval, loc_eq, k, &iter);
|
288
406
|
cur = cds_lfht_iter_get_node(&iter);
|
@@ -324,9 +442,11 @@ static struct src_loc *update_stats_rcu_lock(size_t size, uintptr_t caller)
|
|
324
442
|
static const size_t xlen = sizeof(caller);
|
325
443
|
char *dst;
|
326
444
|
|
327
|
-
if (caa_unlikely(!totals)) return 0;
|
445
|
+
if (caa_unlikely(!CMM_LOAD_SHARED(totals))) return 0;
|
328
446
|
if (locating++) goto out; /* do not recurse into another *alloc */
|
329
447
|
|
448
|
+
uatomic_add(&total_bytes_inc, size);
|
449
|
+
|
330
450
|
rcu_read_lock();
|
331
451
|
if (has_ec_p()) {
|
332
452
|
int line;
|
@@ -387,9 +507,10 @@ void free(void *p)
|
|
387
507
|
struct src_loc *l = h->as.live.loc;
|
388
508
|
|
389
509
|
if (!real_free) return; /* oh well, leak a little */
|
390
|
-
if (l) {
|
510
|
+
if (l && l != IS_HEAP_PAGE_BODY) {
|
391
511
|
size_t age = generation - h->as.live.gen;
|
392
512
|
|
513
|
+
uatomic_add(&total_bytes_dec, h->size);
|
393
514
|
uatomic_set(&h->size, 0);
|
394
515
|
uatomic_add(&l->frees, 1);
|
395
516
|
uatomic_add(&l->age_total, age);
|
@@ -401,8 +522,20 @@ void free(void *p)
|
|
401
522
|
mutex_unlock(l->mtx);
|
402
523
|
|
403
524
|
call_rcu(&h->as.dead, free_hdr_rcu);
|
404
|
-
}
|
405
|
-
|
525
|
+
} else if (l == IS_HEAP_PAGE_BODY) {
|
526
|
+
size_t gen = generation;
|
527
|
+
size_t age = gen - h->as.live.gen;
|
528
|
+
|
529
|
+
h->as.hpb_freed.at = gen;
|
530
|
+
|
531
|
+
mutex_lock(&hpb_stats.lock);
|
532
|
+
acc_add(&hpb_stats.alive, age);
|
533
|
+
|
534
|
+
/* hpb_stats.bodies => hpb_stats.freed */
|
535
|
+
cds_list_move(&h->anode, &hpb_stats.freed);
|
536
|
+
|
537
|
+
mutex_unlock(&hpb_stats.lock);
|
538
|
+
} else {
|
406
539
|
real_free(h->real);
|
407
540
|
}
|
408
541
|
}
|
@@ -429,7 +562,7 @@ static size_t size_align(size_t size, size_t alignment)
|
|
429
562
|
return ((size + (alignment - 1)) & ~(alignment - 1));
|
430
563
|
}
|
431
564
|
|
432
|
-
static bool ptr_is_aligned(void *ptr, size_t alignment)
|
565
|
+
static bool ptr_is_aligned(const void *ptr, size_t alignment)
|
433
566
|
{
|
434
567
|
return ((uintptr_t)ptr & (alignment - 1)) == 0;
|
435
568
|
}
|
@@ -468,18 +601,66 @@ internal_memalign(void **pp, size_t alignment, size_t size, uintptr_t caller)
|
|
468
601
|
__builtin_add_overflow(asize, sizeof(struct alloc_hdr), &asize))
|
469
602
|
return ENOMEM;
|
470
603
|
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
604
|
+
|
605
|
+
if (alignment == HEAP_PAGE_ALIGN && size == HEAP_PAGE_SIZE) {
|
606
|
+
if (has_ec_p()) generation = rb_gc_count();
|
607
|
+
l = IS_HEAP_PAGE_BODY;
|
608
|
+
} else {
|
609
|
+
l = update_stats_rcu_lock(size, caller);
|
610
|
+
}
|
611
|
+
|
612
|
+
if (l == IS_HEAP_PAGE_BODY) {
|
613
|
+
void *p;
|
614
|
+
size_t gen = generation;
|
615
|
+
|
616
|
+
mutex_lock(&hpb_stats.lock);
|
617
|
+
|
618
|
+
/* reuse existing entry */
|
619
|
+
if (!cds_list_empty(&hpb_stats.freed)) {
|
620
|
+
size_t deathspan;
|
621
|
+
|
622
|
+
h = cds_list_first_entry(&hpb_stats.freed,
|
623
|
+
struct alloc_hdr, anode);
|
624
|
+
/* hpb_stats.freed => hpb_stats.bodies */
|
625
|
+
cds_list_move(&h->anode, &hpb_stats.bodies);
|
626
|
+
assert(h->size == size);
|
627
|
+
assert(h->real);
|
628
|
+
real = h->real;
|
629
|
+
p = hdr2ptr(h);
|
630
|
+
assert(ptr_is_aligned(p, alignment));
|
631
|
+
|
632
|
+
deathspan = gen - h->as.hpb_freed.at;
|
633
|
+
acc_add(&hpb_stats.reborn, deathspan);
|
634
|
+
}
|
635
|
+
else {
|
636
|
+
real = real_malloc(asize);
|
637
|
+
if (!real) return ENOMEM;
|
638
|
+
|
639
|
+
p = hdr2ptr(real);
|
640
|
+
if (!ptr_is_aligned(p, alignment))
|
641
|
+
p = ptr_align(p, alignment);
|
642
|
+
h = ptr2hdr(p);
|
643
|
+
h->size = size;
|
644
|
+
h->real = real;
|
645
|
+
cds_list_add(&h->anode, &hpb_stats.bodies);
|
646
|
+
}
|
647
|
+
mutex_unlock(&hpb_stats.lock);
|
648
|
+
h->as.live.loc = l;
|
649
|
+
h->as.live.gen = gen;
|
480
650
|
*pp = p;
|
481
651
|
}
|
482
|
-
|
652
|
+
else {
|
653
|
+
real = real_malloc(asize);
|
654
|
+
if (real) {
|
655
|
+
void *p = hdr2ptr(real);
|
656
|
+
if (!ptr_is_aligned(p, alignment))
|
657
|
+
p = ptr_align(p, alignment);
|
658
|
+
h = ptr2hdr(p);
|
659
|
+
alloc_insert_rcu(l, h, size, real);
|
660
|
+
*pp = p;
|
661
|
+
}
|
662
|
+
update_stats_rcu_unlock(l);
|
663
|
+
}
|
483
664
|
|
484
665
|
return real ? 0 : ENOMEM;
|
485
666
|
}
|
@@ -487,16 +668,14 @@ internal_memalign(void **pp, size_t alignment, size_t size, uintptr_t caller)
|
|
487
668
|
static void *
|
488
669
|
memalign_result(int err, void *p)
|
489
670
|
{
|
490
|
-
if (caa_unlikely(err))
|
671
|
+
if (caa_unlikely(err))
|
491
672
|
errno = err;
|
492
|
-
return 0;
|
493
|
-
}
|
494
673
|
return p;
|
495
674
|
}
|
496
675
|
|
497
676
|
void *memalign(size_t alignment, size_t size)
|
498
677
|
{
|
499
|
-
void *p;
|
678
|
+
void *p = NULL;
|
500
679
|
int err = internal_memalign(&p, alignment, size, RETURN_ADDRESS(0));
|
501
680
|
return memalign_result(err, p);
|
502
681
|
}
|
@@ -511,7 +690,7 @@ void cfree(void *) __attribute__((alias("free")));
|
|
511
690
|
|
512
691
|
void *valloc(size_t size)
|
513
692
|
{
|
514
|
-
void *p;
|
693
|
+
void *p = NULL;
|
515
694
|
int err = internal_memalign(&p, page_size, size, RETURN_ADDRESS(0));
|
516
695
|
return memalign_result(err, p);
|
517
696
|
}
|
@@ -529,7 +708,7 @@ void *valloc(size_t size)
|
|
529
708
|
void *pvalloc(size_t size)
|
530
709
|
{
|
531
710
|
size_t alignment = page_size;
|
532
|
-
void *p;
|
711
|
+
void *p = NULL;
|
533
712
|
int err;
|
534
713
|
|
535
714
|
if (add_overflow_p(size, alignment)) {
|
@@ -652,7 +831,7 @@ static void *dump_to_file(void *x)
|
|
652
831
|
|
653
832
|
++locating;
|
654
833
|
rcu_read_lock();
|
655
|
-
t =
|
834
|
+
t = CMM_LOAD_SHARED(totals);
|
656
835
|
if (!t)
|
657
836
|
goto out_unlock;
|
658
837
|
cds_lfht_for_each_entry(t, &iter, l, hnode) {
|
@@ -710,14 +889,18 @@ static VALUE mwrap_dump(int argc, VALUE * argv, VALUE mod)
|
|
710
889
|
return Qnil;
|
711
890
|
}
|
712
891
|
|
892
|
+
/* The whole operation is not remotely atomic... */
|
713
893
|
static void *totals_reset(void *ign)
|
714
894
|
{
|
715
895
|
struct cds_lfht *t;
|
716
896
|
struct cds_lfht_iter iter;
|
717
897
|
struct src_loc *l;
|
718
898
|
|
899
|
+
uatomic_set(&total_bytes_inc, 0);
|
900
|
+
uatomic_set(&total_bytes_dec, 0);
|
901
|
+
|
719
902
|
rcu_read_lock();
|
720
|
-
t =
|
903
|
+
t = CMM_LOAD_SHARED(totals);
|
721
904
|
cds_lfht_for_each_entry(t, &iter, l, hnode) {
|
722
905
|
uatomic_set(&l->total, 0);
|
723
906
|
uatomic_set(&l->allocations, 0);
|
@@ -785,7 +968,7 @@ static VALUE dump_each_rcu(VALUE x)
|
|
785
968
|
struct cds_lfht_iter iter;
|
786
969
|
struct src_loc *l;
|
787
970
|
|
788
|
-
t =
|
971
|
+
t = CMM_LOAD_SHARED(totals);
|
789
972
|
cds_lfht_for_each_entry(t, &iter, l, hnode) {
|
790
973
|
VALUE v[6];
|
791
974
|
if (l->total <= a->min) continue;
|
@@ -889,9 +1072,9 @@ static VALUE mwrap_aref(VALUE mod, VALUE loc)
|
|
889
1072
|
|
890
1073
|
if (!k) return val;
|
891
1074
|
|
1075
|
+
t = CMM_LOAD_SHARED(totals);
|
1076
|
+
if (!t) return val;
|
892
1077
|
rcu_read_lock();
|
893
|
-
t = rcu_dereference(totals);
|
894
|
-
if (!t) goto out_unlock;
|
895
1078
|
|
896
1079
|
cds_lfht_lookup(t, k->hval, loc_eq, k, &iter);
|
897
1080
|
cur = cds_lfht_iter_get_node(&iter);
|
@@ -899,7 +1082,6 @@ static VALUE mwrap_aref(VALUE mod, VALUE loc)
|
|
899
1082
|
l = caa_container_of(cur, struct src_loc, hnode);
|
900
1083
|
val = TypedData_Wrap_Struct(cSrcLoc, &src_loc_type, l);
|
901
1084
|
}
|
902
|
-
out_unlock:
|
903
1085
|
rcu_read_unlock();
|
904
1086
|
return val;
|
905
1087
|
}
|
@@ -1033,6 +1215,89 @@ static VALUE mwrap_quiet(VALUE mod)
|
|
1033
1215
|
return rb_ensure(rb_yield, SIZET2NUM(cur), reset_locating, 0);
|
1034
1216
|
}
|
1035
1217
|
|
1218
|
+
/*
|
1219
|
+
* total bytes allocated as tracked by mwrap
|
1220
|
+
*/
|
1221
|
+
static VALUE total_inc(VALUE mod)
|
1222
|
+
{
|
1223
|
+
return SIZET2NUM(total_bytes_inc);
|
1224
|
+
}
|
1225
|
+
|
1226
|
+
/*
|
1227
|
+
* total bytes freed as tracked by mwrap
|
1228
|
+
*/
|
1229
|
+
static VALUE total_dec(VALUE mod)
|
1230
|
+
{
|
1231
|
+
return SIZET2NUM(total_bytes_dec);
|
1232
|
+
}
|
1233
|
+
|
1234
|
+
static VALUE hpb_each_yield(VALUE ignore)
|
1235
|
+
{
|
1236
|
+
struct alloc_hdr *h, *next;
|
1237
|
+
|
1238
|
+
cds_list_for_each_entry_safe(h, next, &hpb_stats.bodies, anode) {
|
1239
|
+
VALUE v[2]; /* [ generation, address ] */
|
1240
|
+
void *addr = hdr2ptr(h);
|
1241
|
+
assert(ptr_is_aligned(addr, HEAP_PAGE_ALIGN));
|
1242
|
+
v[0] = LONG2NUM((long)addr);
|
1243
|
+
v[1] = SIZET2NUM(h->as.live.gen);
|
1244
|
+
rb_yield_values2(2, v);
|
1245
|
+
}
|
1246
|
+
return Qnil;
|
1247
|
+
}
|
1248
|
+
|
1249
|
+
/*
|
1250
|
+
* call-seq:
|
1251
|
+
*
|
1252
|
+
* Mwrap::HeapPageBody.each { |gen, addr| } -> Integer
|
1253
|
+
*
|
1254
|
+
* Yields the generation (GC.count) the heap page body was created
|
1255
|
+
* and address of the heap page body as an Integer. Returns the
|
1256
|
+
* number of allocated pages as an Integer. This return value should
|
1257
|
+
* match the result of GC.stat(:heap_allocated_pages)
|
1258
|
+
*/
|
1259
|
+
static VALUE hpb_each(VALUE mod)
|
1260
|
+
{
|
1261
|
+
++locating;
|
1262
|
+
return rb_ensure(hpb_each_yield, Qfalse, reset_locating, 0);
|
1263
|
+
}
|
1264
|
+
|
1265
|
+
/*
|
1266
|
+
* call-seq:
|
1267
|
+
*
|
1268
|
+
* Mwrap::HeapPageBody.stat -> Hash
|
1269
|
+
* Mwrap::HeapPageBody.stat(hash) -> hash
|
1270
|
+
*
|
1271
|
+
* The maximum lifespan of a heap page body in the Ruby VM.
|
1272
|
+
* This may be Infinity if no heap page bodies were ever freed.
|
1273
|
+
*/
|
1274
|
+
static VALUE hpb_stat(int argc, VALUE *argv, VALUE hpb)
|
1275
|
+
{
|
1276
|
+
VALUE h;
|
1277
|
+
|
1278
|
+
rb_scan_args(argc, argv, "01", &h);
|
1279
|
+
if (NIL_P(h))
|
1280
|
+
h = rb_hash_new();
|
1281
|
+
else if (!RB_TYPE_P(h, T_HASH))
|
1282
|
+
rb_raise(rb_eTypeError, "not a hash %+"PRIsVALUE, h);
|
1283
|
+
|
1284
|
+
++locating;
|
1285
|
+
#define S(x) ID2SYM(rb_intern(#x))
|
1286
|
+
rb_hash_aset(h, S(lifespan_max), acc_max(&hpb_stats.alive));
|
1287
|
+
rb_hash_aset(h, S(lifespan_min), acc_min(&hpb_stats.alive));
|
1288
|
+
rb_hash_aset(h, S(lifespan_mean), acc_mean(&hpb_stats.alive));
|
1289
|
+
rb_hash_aset(h, S(lifespan_stddev), acc_stddev(&hpb_stats.alive));
|
1290
|
+
rb_hash_aset(h, S(deathspan_max), acc_max(&hpb_stats.reborn));
|
1291
|
+
rb_hash_aset(h, S(deathspan_min), acc_min(&hpb_stats.reborn));
|
1292
|
+
rb_hash_aset(h, S(deathspan_mean), acc_mean(&hpb_stats.reborn));
|
1293
|
+
rb_hash_aset(h, S(deathspan_stddev), acc_stddev(&hpb_stats.reborn));
|
1294
|
+
rb_hash_aset(h, S(resurrects), SIZET2NUM(hpb_stats.reborn.nr));
|
1295
|
+
#undef S
|
1296
|
+
--locating;
|
1297
|
+
|
1298
|
+
return h;
|
1299
|
+
}
|
1300
|
+
|
1036
1301
|
/*
|
1037
1302
|
* Document-module: Mwrap
|
1038
1303
|
*
|
@@ -1051,20 +1316,19 @@ static VALUE mwrap_quiet(VALUE mod)
|
|
1051
1316
|
* * dump_fd: a writable FD to dump to
|
1052
1317
|
* * dump_path: a path to dump to, the file is opened in O_APPEND mode
|
1053
1318
|
* * dump_min: the minimum allocation size (total) to dump
|
1054
|
-
* *
|
1319
|
+
* * dump_heap: mask of heap_page_body statistics to dump
|
1055
1320
|
*
|
1056
1321
|
* If both `dump_fd' and `dump_path' are specified, dump_path takes
|
1057
1322
|
* precedence.
|
1058
1323
|
*
|
1059
|
-
*
|
1060
|
-
*
|
1061
|
-
*
|
1062
|
-
*
|
1063
|
-
* Thus tracking memalign functions is disabled by default.
|
1324
|
+
* dump_heap bitmask
|
1325
|
+
* * 0x01 - summary stats (same info as HeapPageBody.stat)
|
1326
|
+
* * 0x02 - all live heaps (similar to HeapPageBody.each)
|
1327
|
+
* * 0x04 - skip non-heap_page_body-related output
|
1064
1328
|
*/
|
1065
1329
|
void Init_mwrap(void)
|
1066
1330
|
{
|
1067
|
-
VALUE mod;
|
1331
|
+
VALUE mod, hpb;
|
1068
1332
|
|
1069
1333
|
++locating;
|
1070
1334
|
mod = rb_define_module("Mwrap");
|
@@ -1084,6 +1348,10 @@ void Init_mwrap(void)
|
|
1084
1348
|
rb_define_singleton_method(mod, "each", mwrap_each, -1);
|
1085
1349
|
rb_define_singleton_method(mod, "[]", mwrap_aref, 1);
|
1086
1350
|
rb_define_singleton_method(mod, "quiet", mwrap_quiet, 0);
|
1351
|
+
rb_define_singleton_method(mod, "total_bytes_allocated", total_inc, 0);
|
1352
|
+
rb_define_singleton_method(mod, "total_bytes_freed", total_dec, 0);
|
1353
|
+
|
1354
|
+
|
1087
1355
|
rb_define_method(cSrcLoc, "each", src_loc_each, 0);
|
1088
1356
|
rb_define_method(cSrcLoc, "frees", src_loc_frees, 0);
|
1089
1357
|
rb_define_method(cSrcLoc, "allocations", src_loc_allocations, 0);
|
@@ -1091,9 +1359,68 @@ void Init_mwrap(void)
|
|
1091
1359
|
rb_define_method(cSrcLoc, "mean_lifespan", src_loc_mean_lifespan, 0);
|
1092
1360
|
rb_define_method(cSrcLoc, "max_lifespan", src_loc_max_lifespan, 0);
|
1093
1361
|
rb_define_method(cSrcLoc, "name", src_loc_name, 0);
|
1362
|
+
|
1363
|
+
/*
|
1364
|
+
* Information about "struct heap_page_body" allocations from
|
1365
|
+
* Ruby gc.c. This can be useful for tracking fragmentation
|
1366
|
+
* from posix_memalign(3) use in mainline Ruby:
|
1367
|
+
*
|
1368
|
+
* https://sourceware.org/bugzilla/show_bug.cgi?id=14581
|
1369
|
+
*
|
1370
|
+
* These statistics are never reset by Mwrap.reset or
|
1371
|
+
* any other method. They only make sense in the context
|
1372
|
+
* of an entire program lifetime.
|
1373
|
+
*/
|
1374
|
+
hpb = rb_define_class_under(mod, "HeapPageBody", rb_cObject);
|
1375
|
+
rb_define_singleton_method(hpb, "stat", hpb_stat, -1);
|
1376
|
+
rb_define_singleton_method(hpb, "each", hpb_each, 0);
|
1377
|
+
|
1094
1378
|
--locating;
|
1095
1379
|
}
|
1096
1380
|
|
1381
|
+
enum {
|
1382
|
+
DUMP_HPB_STATS = 0x1,
|
1383
|
+
DUMP_HPB_EACH = 0x2,
|
1384
|
+
DUMP_HPB_EXCL = 0x4,
|
1385
|
+
};
|
1386
|
+
|
1387
|
+
static void dump_hpb(FILE *fp, unsigned flags)
|
1388
|
+
{
|
1389
|
+
if (flags & DUMP_HPB_STATS) {
|
1390
|
+
fprintf(fp,
|
1391
|
+
"lifespan_max: %"PRId64"\n"
|
1392
|
+
"lifespan_min:%s%"PRId64"\n"
|
1393
|
+
"lifespan_mean: %0.3f\n"
|
1394
|
+
"lifespan_stddev: %0.3f\n"
|
1395
|
+
"deathspan_max: %"PRId64"\n"
|
1396
|
+
"deathspan_min:%s%"PRId64"\n"
|
1397
|
+
"deathspan_mean: %0.3f\n"
|
1398
|
+
"deathspan_stddev: %0.3f\n"
|
1399
|
+
"gc_count: %zu\n",
|
1400
|
+
hpb_stats.alive.max,
|
1401
|
+
hpb_stats.alive.min == INT64_MAX ? " -" : " ",
|
1402
|
+
hpb_stats.alive.min,
|
1403
|
+
hpb_stats.alive.mean,
|
1404
|
+
acc_stddev_dbl(&hpb_stats.alive),
|
1405
|
+
hpb_stats.reborn.max,
|
1406
|
+
hpb_stats.reborn.min == INT64_MAX ? " -" : " ",
|
1407
|
+
hpb_stats.reborn.min,
|
1408
|
+
hpb_stats.reborn.mean,
|
1409
|
+
acc_stddev_dbl(&hpb_stats.reborn),
|
1410
|
+
/* n.b.: unsafe to call rb_gc_count() in destructor */
|
1411
|
+
generation);
|
1412
|
+
}
|
1413
|
+
if (flags & DUMP_HPB_EACH) {
|
1414
|
+
struct alloc_hdr *h;
|
1415
|
+
|
1416
|
+
cds_list_for_each_entry(h, &hpb_stats.bodies, anode) {
|
1417
|
+
void *addr = hdr2ptr(h);
|
1418
|
+
|
1419
|
+
fprintf(fp, "%p\t%zu\n", addr, h->as.live.gen);
|
1420
|
+
}
|
1421
|
+
}
|
1422
|
+
}
|
1423
|
+
|
1097
1424
|
/* rb_cloexec_open isn't usable by non-Ruby processes */
|
1098
1425
|
#ifndef O_CLOEXEC
|
1099
1426
|
# define O_CLOEXEC 0
|
@@ -1104,10 +1431,12 @@ static void mwrap_dump_destructor(void)
|
|
1104
1431
|
{
|
1105
1432
|
const char *opt = getenv("MWRAP");
|
1106
1433
|
const char *modes[] = { "a", "a+", "w", "w+", "r+" };
|
1107
|
-
struct dump_arg a;
|
1434
|
+
struct dump_arg a = { .min = 0 };
|
1108
1435
|
size_t i;
|
1109
1436
|
int dump_fd;
|
1437
|
+
unsigned dump_heap = 0;
|
1110
1438
|
char *dump_path;
|
1439
|
+
char *s;
|
1111
1440
|
|
1112
1441
|
if (!opt)
|
1113
1442
|
return;
|
@@ -1134,8 +1463,11 @@ static void mwrap_dump_destructor(void)
|
|
1134
1463
|
else if (!sscanf(opt, "dump_fd:%d", &dump_fd))
|
1135
1464
|
goto out;
|
1136
1465
|
|
1137
|
-
if (
|
1138
|
-
a.min
|
1466
|
+
if ((s = strstr(opt, "dump_min:")))
|
1467
|
+
sscanf(s, "dump_min:%zu", &a.min);
|
1468
|
+
|
1469
|
+
if ((s = strstr(opt, "dump_heap:")))
|
1470
|
+
sscanf(s, "dump_heap:%u", &dump_heap);
|
1139
1471
|
|
1140
1472
|
switch (dump_fd) {
|
1141
1473
|
case 0: goto out;
|
@@ -1156,7 +1488,9 @@ static void mwrap_dump_destructor(void)
|
|
1156
1488
|
}
|
1157
1489
|
/* we'll leak some memory here, but this is a destructor */
|
1158
1490
|
}
|
1159
|
-
|
1491
|
+
if ((dump_heap & DUMP_HPB_EXCL) == 0)
|
1492
|
+
dump_to_file(&a);
|
1493
|
+
dump_hpb(a.fp, dump_heap);
|
1160
1494
|
out:
|
1161
1495
|
--locating;
|
1162
1496
|
}
|
data/lib/mwrap_rack.rb
ADDED
@@ -0,0 +1,170 @@
|
|
1
|
+
# Copyright (C) all contributors <mwrap-public@80x24.org>
|
2
|
+
# License: GPL-2.0+ <https://www.gnu.org/licenses/gpl-2.0.txt>
|
3
|
+
# frozen_string_literal: true
|
4
|
+
require 'mwrap'
|
5
|
+
require 'rack'
|
6
|
+
require 'cgi'
|
7
|
+
|
8
|
+
# MwrapRack is a standalone Rack application which can be
|
9
|
+
# mounted to run within your application process.
|
10
|
+
#
|
11
|
+
# Using the Rack::Builder API in config.ru, you can map it to
|
12
|
+
# the "/MWRAP/" endpoint. As with the rest of the Mwrap API,
|
13
|
+
# your Rack server needs to be spawned with the mwrap(1)
|
14
|
+
# wrapper to enable the LD_PRELOAD.
|
15
|
+
#
|
16
|
+
# require 'mwrap_rack'
|
17
|
+
# map('/MWRAP') { run(MwrapRack.new) }
|
18
|
+
# map('/') { run(your_normal_app) }
|
19
|
+
#
|
20
|
+
# This module is only available in mwrap 2.0.0+
|
21
|
+
class MwrapRack
|
22
|
+
module HtmlResponse # :nodoc:
|
23
|
+
def response
|
24
|
+
[ 200, {
|
25
|
+
'Expires' => 'Fri, 01 Jan 1980 00:00:00 GMT',
|
26
|
+
'Pragma' => 'no-cache',
|
27
|
+
'Cache-Control' => 'no-cache, max-age=0, must-revalidate',
|
28
|
+
'Content-Type' => 'text/html; charset=UTF-8',
|
29
|
+
}, self ]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Each < Struct.new(:script_name, :min, :sort) # :nodoc:
|
34
|
+
include HtmlResponse
|
35
|
+
HEADER = '<tr><th>' + %w(total allocations frees mean_life max_life
|
36
|
+
location).join('</th><th>') + '</th></tr>'
|
37
|
+
FIELDS = %w(total allocations frees mean_life max_life location)
|
38
|
+
def each
|
39
|
+
Mwrap.quiet do
|
40
|
+
t = -"Mwrap.each(#{min})"
|
41
|
+
sn = script_name
|
42
|
+
all = []
|
43
|
+
f = FIELDS.dup
|
44
|
+
sc = FIELDS.index(sort || 'total') || 0
|
45
|
+
f[sc] = -"<b>#{f[sc]}</b>"
|
46
|
+
f.map! do |hdr|
|
47
|
+
if hdr.start_with?('<b>')
|
48
|
+
hdr
|
49
|
+
else
|
50
|
+
-%Q(<a\nhref="#{sn}/each/#{min}?sort=#{hdr}">#{hdr}</a>)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
Mwrap.each(min) do |loc, total, allocations, frees, age_sum, max_life|
|
54
|
+
mean_life = frees == 0 ? Float::INFINITY : age_sum/frees.to_f
|
55
|
+
all << [total,allocations,frees,mean_life,max_life,loc]
|
56
|
+
end
|
57
|
+
all.sort_by! { |cols| -cols[sc] }
|
58
|
+
|
59
|
+
yield(-"<html><head><title>#{t}</title></head>" \
|
60
|
+
"<body><h1>#{t}</h1>\n" \
|
61
|
+
"<h2>Current generation: #{GC.count}</h2>\n<table>\n" \
|
62
|
+
"<tr><th>#{f.join('</th><th>')}</th></tr>\n")
|
63
|
+
all.each do |cols|
|
64
|
+
loc = cols.pop
|
65
|
+
cols[3] = sprintf('%0.3f', cols[3]) # mean_life
|
66
|
+
href = -(+"#{sn}/at/#{CGI.escape(loc)}").encode!(xml: :attr)
|
67
|
+
yield(%Q(<tr><td>#{cols.join('</td><td>')}<td><a\nhref=#{
|
68
|
+
href}>#{-loc.encode(xml: :text)}</a></td></tr>\n))
|
69
|
+
cols.clear
|
70
|
+
end.clear
|
71
|
+
yield "</table></body></html>\n"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class EachAt < Struct.new(:loc) # :nodoc:
|
77
|
+
include HtmlResponse
|
78
|
+
HEADER = '<tr><th>size</th><th>generation</th></tr>'
|
79
|
+
|
80
|
+
def each
|
81
|
+
t = loc.name.encode(xml: :text)
|
82
|
+
yield(-"<html><head><title>#{t}</title></head>" \
|
83
|
+
"<body><h1>live allocations at #{t}</h1>" \
|
84
|
+
"<h2>Current generation: #{GC.count}</h2>\n<table>#{HEADER}")
|
85
|
+
loc.each do |size, generation|
|
86
|
+
yield("<tr><td>#{size}</td><td>#{generation}</td></tr>\n")
|
87
|
+
end
|
88
|
+
yield "</table></body></html>\n"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
class HeapPages # :nodoc:
|
93
|
+
include HtmlResponse
|
94
|
+
HEADER = '<tr><th>address</th><th>generation</th></tr>'
|
95
|
+
|
96
|
+
def hpb_rows
|
97
|
+
Mwrap::HeapPageBody.stat(stat = Thread.current[:mwrap_hpb_stat] ||= {})
|
98
|
+
%i(lifespan_max lifespan_min lifespan_mean lifespan_stddev
|
99
|
+
deathspan_max deathspan_min deathspan_mean deathspan_stddev
|
100
|
+
resurrects
|
101
|
+
).map! do |k|
|
102
|
+
"<tr><td>#{k}</td><td>#{stat[k]}</td></tr>\n"
|
103
|
+
end.join
|
104
|
+
end
|
105
|
+
|
106
|
+
def gc_stat_rows
|
107
|
+
GC.stat(stat = Thread.current[:mwrap_gc_stat] ||= {})
|
108
|
+
%i(count heap_allocated_pages heap_eden_pages heap_tomb_pages
|
109
|
+
total_allocated_pages total_freed_pages).map do |k|
|
110
|
+
"<tr><td>GC.stat(:#{k})</td><td>#{stat[k]}</td></tr>\n"
|
111
|
+
end.join
|
112
|
+
end
|
113
|
+
|
114
|
+
GC_STAT_URL = 'https://docs.ruby-lang.org/en/trunk/GC.html#method-c-stat'
|
115
|
+
GC_STAT_HELP = <<~EOM
|
116
|
+
<p>Non-Infinity lifespans can indicate fragmentation.
|
117
|
+
<p>See <a
|
118
|
+
href="#{GC_STAT_URL}">#{GC_STAT_URL}</a> for info on GC.stat values.
|
119
|
+
EOM
|
120
|
+
|
121
|
+
def each
|
122
|
+
Mwrap.quiet do
|
123
|
+
yield("<html><head><title>heap pages</title></head>" \
|
124
|
+
"<body><h1>heap pages</h1>" \
|
125
|
+
"<table><tr><th>stat</th><th>value</th></tr>\n" \
|
126
|
+
"#{hpb_rows}" \
|
127
|
+
"#{gc_stat_rows}" \
|
128
|
+
"</table>\n" \
|
129
|
+
"#{GC_STAT_HELP}" \
|
130
|
+
"<table>#{HEADER}")
|
131
|
+
Mwrap::HeapPageBody.each do |addr, generation|
|
132
|
+
addr = -sprintf('0x%x', addr)
|
133
|
+
yield(-"<tr><td>#{addr}</td><td>#{generation}</td></tr>\n")
|
134
|
+
end
|
135
|
+
yield "</table></body></html>\n"
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def r404 # :nodoc:
|
141
|
+
[404,{'Content-Type'=>'text/plain'},["Not found\n"]]
|
142
|
+
end
|
143
|
+
|
144
|
+
# The standard Rack application endpoint for MwrapRack
|
145
|
+
def call(env)
|
146
|
+
case env['PATH_INFO']
|
147
|
+
when %r{\A/each/(\d+)\z}
|
148
|
+
min = $1.to_i
|
149
|
+
m = env['QUERY_STRING'].match(/\bsort=(\w+)/)
|
150
|
+
Each.new(env['SCRIPT_NAME'], min, m ? m[1] : nil).response
|
151
|
+
when %r{\A/at/(.*)\z}
|
152
|
+
loc = -CGI.unescape($1)
|
153
|
+
loc = Mwrap[loc] or return r404
|
154
|
+
EachAt.new(loc).response
|
155
|
+
when '/heap_pages'
|
156
|
+
HeapPages.new.response
|
157
|
+
when '/'
|
158
|
+
n = 2000
|
159
|
+
u = 'https://80x24.org/mwrap/README.html'
|
160
|
+
b = -('<html><head><title>Mwrap demo</title></head>' \
|
161
|
+
"<body><p><a href=\"each/#{n}\">allocations >#{n} bytes</a>" \
|
162
|
+
"<p><a href=\"#{u}\">#{u}</a>" \
|
163
|
+
"<p><a href=\"heap_pages\">heap pages</a>" \
|
164
|
+
"</body></html>\n")
|
165
|
+
[ 200, {'Content-Type'=>'text/html','Content-Length'=>-b.size.to_s},[b]]
|
166
|
+
else
|
167
|
+
r404
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
data/mwrap.gemspec
CHANGED
@@ -8,11 +8,13 @@ if git_manifest[0] && manifest != git_manifest
|
|
8
8
|
system('git add MANIFEST')
|
9
9
|
end
|
10
10
|
|
11
|
+
desc = `git describe --abbrev=4 HEAD`.strip.tr('-', '.').delete_prefix('v')
|
12
|
+
|
11
13
|
Gem::Specification.new do |s|
|
12
14
|
s.name = 'mwrap'
|
13
|
-
s.version = '2.
|
15
|
+
s.version = desc.empty? ? '2.2.0' : desc
|
14
16
|
s.homepage = 'https://80x24.org/mwrap/'
|
15
|
-
s.authors = ["
|
17
|
+
s.authors = ["mwrap hackers"]
|
16
18
|
s.summary = 'LD_PRELOAD malloc wrapper for Ruby'
|
17
19
|
s.executables = %w(mwrap)
|
18
20
|
s.files = manifest
|
data/test/test_mwrap.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
# Copyright (C)
|
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
|
require 'test/unit'
|
5
5
|
require 'mwrap'
|
@@ -29,7 +29,8 @@ class TestMwrap < Test::Unit::TestCase
|
|
29
29
|
tmp.rewind
|
30
30
|
lines = tmp.readlines
|
31
31
|
line_1 = lines.grep(/\s-e:1\b/)[0].strip
|
32
|
-
|
32
|
+
bytes = line_1.split(/\s+/)[0].to_i
|
33
|
+
assert_operator bytes, :>=, 10001
|
33
34
|
end
|
34
35
|
end
|
35
36
|
|
@@ -42,7 +43,7 @@ class TestMwrap < Test::Unit::TestCase
|
|
42
43
|
res = system(env, *cmd, { 5 => tmp })
|
43
44
|
assert res, $?.inspect
|
44
45
|
tmp.rewind
|
45
|
-
assert_match(/\
|
46
|
+
assert_match(/\b1\d{4}\s+[1-9]\d*\s+-e:1$/, tmp.read)
|
46
47
|
|
47
48
|
env['MWRAP'] = 'dump_fd:1,dump_min:10000'
|
48
49
|
tmp.rewind
|
@@ -50,14 +51,21 @@ class TestMwrap < Test::Unit::TestCase
|
|
50
51
|
res = system(env, *cmd, { 1 => tmp })
|
51
52
|
assert res, $?.inspect
|
52
53
|
tmp.rewind
|
53
|
-
assert_match(/\
|
54
|
+
assert_match(/\b1\d{4}\s+[1-9]\d*\s+-e:1$/, tmp.read)
|
54
55
|
|
55
56
|
tmp.rewind
|
56
57
|
tmp.truncate(0)
|
57
58
|
env['MWRAP'] = "dump_path:#{tmp.path},dump_min:10000"
|
58
59
|
res = system(env, *cmd)
|
59
60
|
assert res, $?.inspect
|
60
|
-
assert_match(/\
|
61
|
+
assert_match(/\b1\d{4}\s+[1-9]\d*\s+-e:1$/, tmp.read)
|
62
|
+
|
63
|
+
tmp.rewind
|
64
|
+
tmp.truncate(0)
|
65
|
+
env['MWRAP'] = "dump_path:#{tmp.path},dump_heap:5"
|
66
|
+
res = system(env, *cmd)
|
67
|
+
assert res, $?.inspect
|
68
|
+
assert_match %r{lifespan_stddev}, tmp.read
|
61
69
|
end
|
62
70
|
end
|
63
71
|
|
@@ -91,7 +99,7 @@ class TestMwrap < Test::Unit::TestCase
|
|
91
99
|
tmp.rewind
|
92
100
|
buf = tmp.read
|
93
101
|
assert_not_match(/\s+-e:1$/, buf)
|
94
|
-
assert_match(/\
|
102
|
+
assert_match(/\b2\d{4}\s+[0-9]\d*\s+-e:3$/, buf)
|
95
103
|
end
|
96
104
|
end
|
97
105
|
|
@@ -169,8 +177,8 @@ class TestMwrap < Test::Unit::TestCase
|
|
169
177
|
-e GC.disable
|
170
178
|
-e keep=("0"*10000)
|
171
179
|
-e loc=Mwrap["-e:3"]
|
172
|
-
-e
|
173
|
-
)
|
180
|
+
-e
|
181
|
+
) + [ 'loc.each{|size,gen|p([size,gen,count]) if size > 10000}' ]
|
174
182
|
buf = IO.popen(@@env, cmd, &:read)
|
175
183
|
assert_predicate $?, :success?
|
176
184
|
assert_match(/\A\[\s*\d+,\s*\d+,\s*\d+\]\s*\z/s, buf)
|
@@ -223,7 +231,8 @@ class TestMwrap < Test::Unit::TestCase
|
|
223
231
|
loc.name == k or abort 'SourceLocation#name broken'
|
224
232
|
loc.total >= 10000 or abort 'SourceLocation#total broken'
|
225
233
|
loc.frees == 0 or abort 'SourceLocation#frees broken'
|
226
|
-
loc.allocations
|
234
|
+
loc.allocations >= 1 or
|
235
|
+
abort "SourceLocation#allocations broken: #{loc.allocations}"
|
227
236
|
seen = false
|
228
237
|
loc.each do |*x| seen = x end
|
229
238
|
seen[1] == loc.total or 'SourceLocation#each broken'
|
@@ -233,7 +242,9 @@ class TestMwrap < Test::Unit::TestCase
|
|
233
242
|
freed = false
|
234
243
|
until freed
|
235
244
|
freed = true
|
236
|
-
loc.each do
|
245
|
+
loc.each do |size, gen|
|
246
|
+
freed = false if size >= 10000
|
247
|
+
end
|
237
248
|
end
|
238
249
|
loc.frees == 1 or abort 'SourceLocation#frees broken (after free)'
|
239
250
|
Float === loc.mean_lifespan or abort 'mean_lifespan broken'
|
@@ -257,8 +268,9 @@ class TestMwrap < Test::Unit::TestCase
|
|
257
268
|
assert_separately(+"#{<<~"begin;"}\n#{<<~'end;'}")
|
258
269
|
begin;
|
259
270
|
require 'mwrap'
|
260
|
-
before =
|
271
|
+
before = nil
|
261
272
|
res = Mwrap.quiet do |depth|
|
273
|
+
before = __LINE__
|
262
274
|
depth == 1 or abort 'depth is not 1'
|
263
275
|
('a' * 10000).clear
|
264
276
|
Mwrap.quiet { |d| d == 2 or abort 'depth is not 2' }
|
@@ -272,4 +284,46 @@ class TestMwrap < Test::Unit::TestCase
|
|
272
284
|
res == :foo or abort 'Mwrap.quiet did not return block result'
|
273
285
|
end;
|
274
286
|
end
|
287
|
+
|
288
|
+
def test_total_bytes
|
289
|
+
assert_separately(+"#{<<~"begin;"}\n#{<<~'end;'}")
|
290
|
+
begin;
|
291
|
+
require 'mwrap'
|
292
|
+
Mwrap.total_bytes_allocated > 0 or abort 'nothing allocated'
|
293
|
+
Mwrap.total_bytes_freed > 0 or abort 'nothing freed'
|
294
|
+
Mwrap.total_bytes_allocated > Mwrap.total_bytes_freed or
|
295
|
+
abort 'freed more than allocated'
|
296
|
+
end;
|
297
|
+
end
|
298
|
+
|
299
|
+
def test_heap_page_body
|
300
|
+
assert_separately(+"#{<<~"begin;"}\n#{<<~'end;'}")
|
301
|
+
begin;
|
302
|
+
require 'mwrap'
|
303
|
+
require 'rubygems' # use up some memory
|
304
|
+
ap = GC.stat(:heap_allocated_pages)
|
305
|
+
h = {}
|
306
|
+
nr = 0
|
307
|
+
Mwrap::HeapPageBody.each do |addr, gen|
|
308
|
+
nr += 1
|
309
|
+
gen <= GC.count && gen >= 0 or abort "bad generation: #{gen}"
|
310
|
+
(0 == (addr & 16383)) or abort "addr not aligned: #{'%x' % addr}"
|
311
|
+
end
|
312
|
+
if RUBY_VERSION.to_f < 3.1 # 3.1+ uses mmap on platforms we care about
|
313
|
+
nr == ap or abort "HeapPageBody.each missed page #{nr} != #{ap}"
|
314
|
+
end
|
315
|
+
10.times { (1..20000).to_a.map(&:to_s) }
|
316
|
+
3.times { GC.start }
|
317
|
+
Mwrap::HeapPageBody.stat(h)
|
318
|
+
Integer === h[:lifespan_max] or abort 'lifespan_max not recorded'
|
319
|
+
Integer === h[:lifespan_min] or abort 'lifespan_min not recorded'
|
320
|
+
Float === h[:lifespan_mean] or abort 'lifespan_mean not recorded'
|
321
|
+
3.times { GC.start }
|
322
|
+
10.times { (1..20000).to_a.map(&:to_s) }
|
323
|
+
Mwrap::HeapPageBody.stat(h)
|
324
|
+
h[:deathspan_min] <= h[:deathspan_max] or
|
325
|
+
abort 'wrong min/max deathtime'
|
326
|
+
Float === h[:deathspan_mean] or abort 'deathspan_mean not recorded'
|
327
|
+
end;
|
328
|
+
end
|
275
329
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: mwrap
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
7
|
+
- mwrap hackers
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-08-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: test-unit
|
@@ -50,6 +50,7 @@ extra_rdoc_files: []
|
|
50
50
|
files:
|
51
51
|
- ".document"
|
52
52
|
- ".gitignore"
|
53
|
+
- ".olddoc.yml"
|
53
54
|
- COPYING
|
54
55
|
- MANIFEST
|
55
56
|
- README
|
@@ -58,6 +59,7 @@ files:
|
|
58
59
|
- ext/mwrap/extconf.rb
|
59
60
|
- ext/mwrap/jhash.h
|
60
61
|
- ext/mwrap/mwrap.c
|
62
|
+
- lib/mwrap_rack.rb
|
61
63
|
- mwrap.gemspec
|
62
64
|
- test/test_mwrap.rb
|
63
65
|
homepage: https://80x24.org/mwrap/
|
@@ -79,8 +81,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
79
81
|
- !ruby/object:Gem::Version
|
80
82
|
version: '0'
|
81
83
|
requirements: []
|
82
|
-
|
83
|
-
rubygems_version: 2.7.7
|
84
|
+
rubygems_version: 3.0.2
|
84
85
|
signing_key:
|
85
86
|
specification_version: 4
|
86
87
|
summary: LD_PRELOAD malloc wrapper for Ruby
|