gctools 0.2.1
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 +15 -0
- data/.gitignore +4 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +18 -0
- data/README.md +24 -0
- data/Rakefile +36 -0
- data/ext/gcprof/extconf.rb +12 -0
- data/ext/gcprof/gcprof.c +146 -0
- data/ext/oobgc/extconf.rb +12 -0
- data/ext/oobgc/oobgc.c +217 -0
- data/gctools.gemspec +15 -0
- data/lib/gctools/logger.rb +71 -0
- data/test/test_gcprof.rb +16 -0
- data/test/test_oobgc.rb +53 -0
- metadata +73 -0
checksums.yaml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
---
|
2
|
+
!binary "U0hBMQ==":
|
3
|
+
metadata.gz: !binary |-
|
4
|
+
ZDEyYzcxODZkNjQ4NDA4ZDg0NDA4ZDZhZGM3Y2RkOTcwOWYzYTM0MA==
|
5
|
+
data.tar.gz: !binary |-
|
6
|
+
NTBhOWZkOTRkOTg2NzJjODhlNWNhZjk1ZWQ0MWFkYWYzOWIxODAzNQ==
|
7
|
+
SHA512:
|
8
|
+
metadata.gz: !binary |-
|
9
|
+
YzNjMTMyMzZmMjVjMDA3OTUwNGU5YTY3NmVhZjg0OTU2NTMyZWQ4Mjc1ODNj
|
10
|
+
OWYwMDZiNGMxNjNmMTRkNzMwNmM2YmJkYTAyODM4NTQyNmJmMTZmOTU2NGFi
|
11
|
+
NWYyNzQ1NTVmYWZlNzJmODEzYjc2MGRmNzljZWNkMDNlNmIxMDA=
|
12
|
+
data.tar.gz: !binary |-
|
13
|
+
N2IxZTJiYWY3YTUxYmNjZjcyZTMwZGQzNDQ4ZmM1MTllMTY2NmZhMmZlODlj
|
14
|
+
ODVlYjgyODZiZTFiOGNkMDQ0OTY1ZDVjZTBjYjI1ZjE2Y2IyYTZkMWJmNTBi
|
15
|
+
ZTZiNWE5MTQ5YmE2OWNjYzQwMmM0NGJjNjJkYTRmNTQ2ZGU4Zjc=
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/README.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
## gctools
|
2
|
+
|
3
|
+
gc profiler/logger and oobgc for rgengc in ruby 2.1
|
4
|
+
|
5
|
+
### design
|
6
|
+
|
7
|
+
built on new apis and events offered in ruby 2.1:
|
8
|
+
|
9
|
+
* rb_gc_stat()
|
10
|
+
* rb_gc_latest_gc_info()
|
11
|
+
* RUBY_INTERNAL_EVENT_GC_START
|
12
|
+
* RUBY_INTERNAL_EVENT_GC_END_MARK
|
13
|
+
* RUBY_INTERNAL_EVENT_GC_END_SWEEP
|
14
|
+
|
15
|
+
### usage
|
16
|
+
|
17
|
+
#### logger
|
18
|
+
|
19
|
+
require 'gctools/logger'
|
20
|
+
|
21
|
+
#### oobgc
|
22
|
+
|
23
|
+
require 'gctools/oobgc'
|
24
|
+
GC::OOB.run
|
data/Rakefile
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
task :default => :test
|
2
|
+
|
3
|
+
# ==========================================================
|
4
|
+
# Packaging
|
5
|
+
# ==========================================================
|
6
|
+
|
7
|
+
GEMSPEC = eval(File.read('gctools.gemspec'))
|
8
|
+
|
9
|
+
require 'rubygems/package_task'
|
10
|
+
Gem::PackageTask.new(GEMSPEC) do |pkg|
|
11
|
+
end
|
12
|
+
|
13
|
+
# ==========================================================
|
14
|
+
# Ruby Extension
|
15
|
+
# ==========================================================
|
16
|
+
|
17
|
+
require 'rake/extensiontask'
|
18
|
+
Rake::ExtensionTask.new('gcprof', GEMSPEC) do |ext|
|
19
|
+
ext.ext_dir = 'ext/gcprof'
|
20
|
+
ext.lib_dir = 'lib/gctools'
|
21
|
+
end
|
22
|
+
Rake::ExtensionTask.new('oobgc', GEMSPEC) do |ext|
|
23
|
+
ext.ext_dir = 'ext/oobgc'
|
24
|
+
ext.lib_dir = 'lib/gctools'
|
25
|
+
end
|
26
|
+
task :build => :compile
|
27
|
+
|
28
|
+
# ==========================================================
|
29
|
+
# Testing
|
30
|
+
# ==========================================================
|
31
|
+
|
32
|
+
require 'rake/testtask'
|
33
|
+
Rake::TestTask.new 'test' do |t|
|
34
|
+
t.test_files = FileList['test/test_*.rb']
|
35
|
+
end
|
36
|
+
task :test => :build
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
have_func('rb_gc_stat')
|
3
|
+
have_func('rb_gc_latest_gc_info')
|
4
|
+
gc_event = have_const('RUBY_INTERNAL_EVENT_GC_END_MARK')
|
5
|
+
|
6
|
+
if gc_event
|
7
|
+
create_makefile('gctools/gcprof')
|
8
|
+
else
|
9
|
+
File.open('Makefile', 'w') do |f|
|
10
|
+
f.puts "install:\n\t\n"
|
11
|
+
end
|
12
|
+
end
|
data/ext/gcprof/gcprof.c
ADDED
@@ -0,0 +1,146 @@
|
|
1
|
+
#include <time.h>
|
2
|
+
#include <sys/time.h>
|
3
|
+
#include "ruby/ruby.h"
|
4
|
+
#include "ruby/debug.h"
|
5
|
+
|
6
|
+
static VALUE mGCProf;
|
7
|
+
static VALUE sym_time, sym_count, sym_immediate_sweep;
|
8
|
+
static struct {
|
9
|
+
size_t serial;
|
10
|
+
VALUE tpval;
|
11
|
+
VALUE proc;
|
12
|
+
|
13
|
+
VALUE info;
|
14
|
+
VALUE start;
|
15
|
+
VALUE end_mark;
|
16
|
+
VALUE end_sweep;
|
17
|
+
} _gcprof;
|
18
|
+
|
19
|
+
static inline double
|
20
|
+
walltime()
|
21
|
+
{
|
22
|
+
struct timespec ts;
|
23
|
+
#ifdef HAVE_CLOCK_GETTIME
|
24
|
+
if (clock_gettime(CLOCK_REALTIME, &ts) == -1) {
|
25
|
+
rb_sys_fail("clock_gettime");
|
26
|
+
}
|
27
|
+
#else
|
28
|
+
{
|
29
|
+
struct timeval tv;
|
30
|
+
if (gettimeofday(&tv, 0) < 0) {
|
31
|
+
rb_sys_fail("gettimeofday");
|
32
|
+
}
|
33
|
+
ts.tv_sec = tv.tv_sec;
|
34
|
+
ts.tv_nsec = tv.tv_usec * 1000;
|
35
|
+
}
|
36
|
+
#endif
|
37
|
+
return ts.tv_sec + ts.tv_nsec * 1e-9;
|
38
|
+
}
|
39
|
+
|
40
|
+
static const char *
|
41
|
+
event_flag_name(rb_event_flag_t flag)
|
42
|
+
{
|
43
|
+
switch (flag) {
|
44
|
+
case RUBY_INTERNAL_EVENT_GC_START:
|
45
|
+
return "gc_start";
|
46
|
+
case RUBY_INTERNAL_EVENT_GC_END_MARK:
|
47
|
+
return "gc_end_mark";
|
48
|
+
case RUBY_INTERNAL_EVENT_GC_END_SWEEP:
|
49
|
+
return "gc_end_sweep";
|
50
|
+
default:
|
51
|
+
return "unknown";
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
static void
|
56
|
+
invoke_proc(void *data)
|
57
|
+
{
|
58
|
+
VALUE args[4] = {_gcprof.info, _gcprof.start, _gcprof.end_mark, _gcprof.end_sweep};
|
59
|
+
if (_gcprof.serial != (size_t)data) return;
|
60
|
+
if (_gcprof.serial != NUM2SIZET(rb_hash_aref(_gcprof.end_sweep, sym_count))) args[3] = Qnil;
|
61
|
+
rb_proc_call_with_block(_gcprof.proc, 4, args, Qnil);
|
62
|
+
}
|
63
|
+
|
64
|
+
static void
|
65
|
+
gc_hook_i(VALUE tpval, void *data)
|
66
|
+
{
|
67
|
+
rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
|
68
|
+
rb_event_flag_t flag = rb_tracearg_event_flag(tparg);
|
69
|
+
VALUE obj =
|
70
|
+
flag == RUBY_INTERNAL_EVENT_GC_START ? _gcprof.start :
|
71
|
+
flag == RUBY_INTERNAL_EVENT_GC_END_MARK ? _gcprof.end_mark :
|
72
|
+
_gcprof.end_sweep;
|
73
|
+
|
74
|
+
if (0) {
|
75
|
+
fprintf(stderr, "TRACEPOINT: time=%f, event=%s(%zu)\n", walltime(), event_flag_name(rb_tracearg_event_flag(tparg)), rb_gc_count());
|
76
|
+
}
|
77
|
+
|
78
|
+
rb_hash_aset(obj, sym_time, DBL2NUM(walltime()));
|
79
|
+
rb_gc_stat(obj);
|
80
|
+
|
81
|
+
switch (flag) {
|
82
|
+
case RUBY_INTERNAL_EVENT_GC_START:
|
83
|
+
rb_gc_latest_gc_info(_gcprof.info);
|
84
|
+
_gcprof.serial = rb_gc_count();
|
85
|
+
return;
|
86
|
+
case RUBY_INTERNAL_EVENT_GC_END_MARK:
|
87
|
+
if (RTEST(rb_hash_aref(_gcprof.info, sym_immediate_sweep))) return;
|
88
|
+
/* fall through */
|
89
|
+
case RUBY_INTERNAL_EVENT_GC_END_SWEEP:
|
90
|
+
rb_postponed_job_register(0, invoke_proc, (void *)_gcprof.serial);
|
91
|
+
break;
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
static VALUE
|
96
|
+
set_after_gc_hook(VALUE module, VALUE proc)
|
97
|
+
{
|
98
|
+
rb_event_flag_t events;
|
99
|
+
|
100
|
+
/* disable previous keys */
|
101
|
+
if (RTEST(_gcprof.tpval)) {
|
102
|
+
rb_tracepoint_disable(_gcprof.tpval);
|
103
|
+
_gcprof.tpval = Qnil;
|
104
|
+
_gcprof.proc = Qnil;
|
105
|
+
}
|
106
|
+
|
107
|
+
if (RTEST(proc)) {
|
108
|
+
if (!rb_obj_is_proc(proc)) {
|
109
|
+
rb_raise(rb_eTypeError, "trace_func needs to be Proc");
|
110
|
+
}
|
111
|
+
|
112
|
+
events = RUBY_INTERNAL_EVENT_GC_START|RUBY_INTERNAL_EVENT_GC_END_MARK|RUBY_INTERNAL_EVENT_GC_END_SWEEP;
|
113
|
+
_gcprof.tpval = rb_tracepoint_new(0, events, gc_hook_i, (void *)0);
|
114
|
+
_gcprof.proc = proc;
|
115
|
+
rb_tracepoint_enable(_gcprof.tpval);
|
116
|
+
}
|
117
|
+
|
118
|
+
return proc;
|
119
|
+
}
|
120
|
+
|
121
|
+
|
122
|
+
void
|
123
|
+
Init_gcprof(void)
|
124
|
+
{
|
125
|
+
mGCProf = rb_define_module("GCProf");
|
126
|
+
rb_define_module_function(mGCProf, "after_gc_hook=", set_after_gc_hook, 1);
|
127
|
+
|
128
|
+
sym_immediate_sweep = ID2SYM(rb_intern("immediate_sweep"));
|
129
|
+
sym_time = ID2SYM(rb_intern("time"));
|
130
|
+
sym_count = ID2SYM(rb_intern("count"));
|
131
|
+
|
132
|
+
rb_gc_latest_gc_info(_gcprof.info = rb_hash_new());
|
133
|
+
_gcprof.start = rb_hash_new();
|
134
|
+
rb_hash_aset(_gcprof.start, sym_time, INT2FIX(0));
|
135
|
+
rb_gc_stat(_gcprof.start);
|
136
|
+
_gcprof.end_mark = rb_obj_dup(_gcprof.start);
|
137
|
+
_gcprof.end_sweep = rb_obj_dup(_gcprof.start);
|
138
|
+
|
139
|
+
rb_gc_register_mark_object(_gcprof.info);
|
140
|
+
rb_gc_register_mark_object(_gcprof.start);
|
141
|
+
rb_gc_register_mark_object(_gcprof.end_mark);
|
142
|
+
rb_gc_register_mark_object(_gcprof.end_sweep);
|
143
|
+
|
144
|
+
rb_gc_register_address(&_gcprof.proc);
|
145
|
+
rb_gc_register_address(&_gcprof.tpval);
|
146
|
+
}
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
have_func('rb_gc_stat')
|
3
|
+
have_func('rb_gc_latest_gc_info')
|
4
|
+
gc_event = have_const('RUBY_INTERNAL_EVENT_GC_END_MARK')
|
5
|
+
|
6
|
+
if gc_event
|
7
|
+
create_makefile('gctools/oobgc')
|
8
|
+
else
|
9
|
+
File.open('Makefile', 'w') do |f|
|
10
|
+
f.puts "install:\n\t\n"
|
11
|
+
end
|
12
|
+
end
|
data/ext/oobgc/oobgc.c
ADDED
@@ -0,0 +1,217 @@
|
|
1
|
+
#include <time.h>
|
2
|
+
#include <sys/time.h>
|
3
|
+
#include "ruby/ruby.h"
|
4
|
+
#include "ruby/intern.h"
|
5
|
+
#include "ruby/debug.h"
|
6
|
+
|
7
|
+
struct {
|
8
|
+
int installed;
|
9
|
+
VALUE tpval;
|
10
|
+
|
11
|
+
int sweep_needed;
|
12
|
+
size_t prev_allocated_object;
|
13
|
+
size_t allocation_limit;
|
14
|
+
size_t heap_obj_limit;
|
15
|
+
|
16
|
+
struct {
|
17
|
+
size_t total_allocated_object;
|
18
|
+
size_t heap_tomb_page_length;
|
19
|
+
} start;
|
20
|
+
|
21
|
+
struct {
|
22
|
+
size_t mean;
|
23
|
+
size_t max;
|
24
|
+
} threshold;
|
25
|
+
|
26
|
+
struct {
|
27
|
+
size_t minor;
|
28
|
+
size_t major;
|
29
|
+
size_t sweep;
|
30
|
+
} stat;
|
31
|
+
} _oobgc;
|
32
|
+
|
33
|
+
static VALUE mOOB;
|
34
|
+
static VALUE sym_total_allocated_object, sym_heap_swept_slot, sym_heap_tomb_page_length, sym_heap_final_slot;
|
35
|
+
static VALUE sym_old_object, sym_old_object_limit, sym_remembered_shady_object, sym_remembered_shady_object_limit;
|
36
|
+
static VALUE sym_major_by, sym_count, sym_major_count, sym_minor_count, sym_sweep_count, minor_gc_args;
|
37
|
+
static ID id_start;
|
38
|
+
|
39
|
+
static void
|
40
|
+
gc_event_i(VALUE tpval, void *data)
|
41
|
+
{
|
42
|
+
rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
|
43
|
+
rb_event_flag_t flag = rb_tracearg_event_flag(tparg);
|
44
|
+
|
45
|
+
switch (flag) {
|
46
|
+
case RUBY_INTERNAL_EVENT_GC_START:
|
47
|
+
_oobgc.allocation_limit = 0;
|
48
|
+
_oobgc.start.total_allocated_object = rb_gc_stat(sym_total_allocated_object);
|
49
|
+
_oobgc.start.heap_tomb_page_length = rb_gc_stat(sym_heap_tomb_page_length);
|
50
|
+
break;
|
51
|
+
|
52
|
+
case RUBY_INTERNAL_EVENT_GC_END_MARK:
|
53
|
+
_oobgc.sweep_needed = 1;
|
54
|
+
break;
|
55
|
+
|
56
|
+
case RUBY_INTERNAL_EVENT_GC_END_SWEEP:
|
57
|
+
_oobgc.sweep_needed = 0;
|
58
|
+
_oobgc.allocation_limit =
|
59
|
+
_oobgc.start.total_allocated_object +
|
60
|
+
rb_gc_stat(sym_heap_swept_slot) +
|
61
|
+
(rb_gc_stat(sym_heap_tomb_page_length) * _oobgc.heap_obj_limit) -
|
62
|
+
rb_gc_stat(sym_heap_final_slot);
|
63
|
+
break;
|
64
|
+
}
|
65
|
+
}
|
66
|
+
|
67
|
+
static VALUE
|
68
|
+
install()
|
69
|
+
{
|
70
|
+
rb_event_flag_t events =
|
71
|
+
RUBY_INTERNAL_EVENT_GC_START |
|
72
|
+
RUBY_INTERNAL_EVENT_GC_END_MARK |
|
73
|
+
RUBY_INTERNAL_EVENT_GC_END_SWEEP;
|
74
|
+
|
75
|
+
if (_oobgc.installed)
|
76
|
+
return Qfalse;
|
77
|
+
|
78
|
+
if (!_oobgc.tpval) {
|
79
|
+
_oobgc.tpval = rb_tracepoint_new(0, events, gc_event_i, (void *)0);
|
80
|
+
rb_ivar_set(mOOB, rb_intern("tpval"), _oobgc.tpval);
|
81
|
+
}
|
82
|
+
|
83
|
+
rb_tracepoint_enable(_oobgc.tpval);
|
84
|
+
_oobgc.installed = 1;
|
85
|
+
return Qtrue;
|
86
|
+
}
|
87
|
+
|
88
|
+
static void
|
89
|
+
gc_start_major()
|
90
|
+
{
|
91
|
+
rb_gc_start();
|
92
|
+
_oobgc.stat.major++;
|
93
|
+
}
|
94
|
+
|
95
|
+
static void
|
96
|
+
gc_start_minor()
|
97
|
+
{
|
98
|
+
rb_funcall(rb_mGC, id_start, 1, minor_gc_args);
|
99
|
+
|
100
|
+
if (RTEST(rb_gc_latest_gc_info(sym_major_by)))
|
101
|
+
_oobgc.stat.major++;
|
102
|
+
else
|
103
|
+
_oobgc.stat.minor++;
|
104
|
+
}
|
105
|
+
|
106
|
+
static VALUE
|
107
|
+
oobgc(VALUE self)
|
108
|
+
{
|
109
|
+
size_t curr = rb_gc_stat(sym_total_allocated_object);
|
110
|
+
if (!_oobgc.installed) install();
|
111
|
+
|
112
|
+
if (!_oobgc.prev_allocated_object) {
|
113
|
+
_oobgc.prev_allocated_object = curr;
|
114
|
+
} else {
|
115
|
+
size_t diff = curr - _oobgc.prev_allocated_object;
|
116
|
+
_oobgc.prev_allocated_object = curr;
|
117
|
+
|
118
|
+
if (_oobgc.threshold.mean)
|
119
|
+
_oobgc.threshold.mean = (diff / 4) + (_oobgc.threshold.mean * 3 / 4);
|
120
|
+
else
|
121
|
+
_oobgc.threshold.mean = diff;
|
122
|
+
|
123
|
+
if (diff > _oobgc.threshold.max)
|
124
|
+
_oobgc.threshold.max = diff;
|
125
|
+
if (_oobgc.threshold.max > 200000)
|
126
|
+
_oobgc.threshold.max = 200000;
|
127
|
+
}
|
128
|
+
|
129
|
+
if (_oobgc.sweep_needed) {
|
130
|
+
/* lazy sweep started sometime recently.
|
131
|
+
* disable/enable the GC to force gc_rest_sweep() OOB
|
132
|
+
*/
|
133
|
+
if (rb_gc_disable() == Qfalse) rb_gc_enable();
|
134
|
+
_oobgc.stat.sweep++;
|
135
|
+
return Qtrue;
|
136
|
+
|
137
|
+
} else if (_oobgc.allocation_limit) {
|
138
|
+
/* GC will be required when total_allocated_object gets
|
139
|
+
* close to allocation_limit
|
140
|
+
*/
|
141
|
+
if ((rb_gc_stat(sym_old_object) >= rb_gc_stat(sym_old_object_limit)*0.97 ||
|
142
|
+
rb_gc_stat(sym_remembered_shady_object) >= rb_gc_stat(sym_remembered_shady_object_limit)*0.97) &&
|
143
|
+
curr >= _oobgc.allocation_limit - _oobgc.threshold.max*0.98) {
|
144
|
+
/*fprintf(stderr, "oobgc MAJOR: %zu >= %zu - %zu\n", curr, _oobgc.allocation_limit, _oobgc.threshold.max);*/
|
145
|
+
gc_start_major();
|
146
|
+
return Qtrue;
|
147
|
+
|
148
|
+
} else if (curr >= _oobgc.allocation_limit - _oobgc.threshold.mean) {
|
149
|
+
/*fprintf(stderr, "oobgc minor: %zu >= %zu - %zu\n", curr, _oobgc.allocation_limit, _oobgc.threshold.mean);*/
|
150
|
+
gc_start_minor();
|
151
|
+
return Qtrue;
|
152
|
+
}
|
153
|
+
}
|
154
|
+
|
155
|
+
return Qfalse;
|
156
|
+
}
|
157
|
+
|
158
|
+
static VALUE
|
159
|
+
oobgc_stat(VALUE self, VALUE key)
|
160
|
+
{
|
161
|
+
if (!_oobgc.installed)
|
162
|
+
return Qnil;
|
163
|
+
|
164
|
+
if (key == sym_count)
|
165
|
+
return SIZET2NUM(_oobgc.stat.major + _oobgc.stat.minor + _oobgc.stat.sweep);
|
166
|
+
else if (key == sym_major_count)
|
167
|
+
return SIZET2NUM(_oobgc.stat.major);
|
168
|
+
else if (key == sym_minor_count)
|
169
|
+
return SIZET2NUM(_oobgc.stat.minor);
|
170
|
+
else if (key == sym_sweep_count)
|
171
|
+
return SIZET2NUM(_oobgc.stat.sweep);
|
172
|
+
else
|
173
|
+
return Qnil;
|
174
|
+
}
|
175
|
+
|
176
|
+
static VALUE
|
177
|
+
oobgc_clear(VALUE self)
|
178
|
+
{
|
179
|
+
MEMZERO(&_oobgc.stat, _oobgc.stat, 1);
|
180
|
+
return Qnil;
|
181
|
+
}
|
182
|
+
|
183
|
+
void
|
184
|
+
Init_oobgc()
|
185
|
+
{
|
186
|
+
mOOB = rb_define_module_under(rb_mGC, "OOB");
|
187
|
+
rb_define_singleton_method(mOOB, "setup", install, 0);
|
188
|
+
rb_define_singleton_method(mOOB, "run", oobgc, 0);
|
189
|
+
rb_define_singleton_method(mOOB, "stat", oobgc_stat, 1);
|
190
|
+
rb_define_singleton_method(mOOB, "clear", oobgc_clear, 0);
|
191
|
+
|
192
|
+
#define S(name) sym_##name = ID2SYM(rb_intern(#name));
|
193
|
+
S(total_allocated_object);
|
194
|
+
S(heap_swept_slot);
|
195
|
+
S(heap_tomb_page_length);
|
196
|
+
S(heap_final_slot);
|
197
|
+
|
198
|
+
S(old_object);
|
199
|
+
S(old_object_limit);
|
200
|
+
S(remembered_shady_object);
|
201
|
+
S(remembered_shady_object_limit);
|
202
|
+
|
203
|
+
S(major_by);
|
204
|
+
S(count);
|
205
|
+
S(major_count);
|
206
|
+
S(minor_count);
|
207
|
+
S(sweep_count);
|
208
|
+
#undef S
|
209
|
+
|
210
|
+
id_start = rb_intern("start");
|
211
|
+
_oobgc.heap_obj_limit =
|
212
|
+
NUM2SIZET(rb_hash_aref(rb_const_get(rb_mGC, rb_intern("INTERNAL_CONSTANTS")), ID2SYM(rb_intern("HEAP_OBJ_LIMIT"))));
|
213
|
+
|
214
|
+
minor_gc_args = rb_hash_new();
|
215
|
+
rb_hash_aset(minor_gc_args, ID2SYM(rb_intern("full_mark")), Qfalse);
|
216
|
+
rb_ivar_set(mOOB, rb_intern("minor_gc_args"), minor_gc_args);
|
217
|
+
}
|
data/gctools.gemspec
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'gctools'
|
3
|
+
s.version = '0.2.1'
|
4
|
+
s.summary = 'rgengc tools for ruby 2.1+'
|
5
|
+
s.description = 'gc debugger, logger and profiler for rgengc in ruby 2.1'
|
6
|
+
|
7
|
+
s.homepage = 'https://github.com/tmm1/gctools'
|
8
|
+
s.authors = 'Aman Gupta'
|
9
|
+
s.email = 'aman@tmm1.net'
|
10
|
+
s.license = 'MIT'
|
11
|
+
|
12
|
+
s.files = `git ls-files`.split("\n")
|
13
|
+
s.extensions = ['ext/gcprof/extconf.rb', 'ext/oobgc/extconf.rb']
|
14
|
+
s.add_development_dependency 'rake-compiler', '~> 0.9'
|
15
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'gctools/gcprof'
|
2
|
+
|
3
|
+
=begin
|
4
|
+
GC::Profiler.enable
|
5
|
+
at_exit{ GC::Profiler.raw_data.map{ |d| p(d) } }
|
6
|
+
=end
|
7
|
+
|
8
|
+
=begin
|
9
|
+
prev_alloc = GC.stat(:total_allocated_object)
|
10
|
+
GCProf.after_gc_hook = proc { |info, start, end_mark, end_sweep|
|
11
|
+
before = start
|
12
|
+
after = end_sweep || end_mark
|
13
|
+
|
14
|
+
if !info[:immediate_sweep] && end_sweep
|
15
|
+
STDERR.printf "[%f:% 4d] swept after %1.3fs swept:% 8d marked:% 8d\n",
|
16
|
+
after[:time],
|
17
|
+
after[:count],
|
18
|
+
after[:time] - end_mark[:time],
|
19
|
+
after[:heap_swept_slot],
|
20
|
+
marked_num
|
21
|
+
else
|
22
|
+
type = info[:major_by] ? "MAJOR GC" : "minor gc"
|
23
|
+
diff = after[:time] - before[:time]
|
24
|
+
diff_alloc, prev_alloc = before[:total_allocated_object] - prev_alloc, after[:total_allocated_object]
|
25
|
+
|
26
|
+
STDERR.printf "[%f:% 4d] %s (% 13s) took %1.3fs alloc:% 8d live: % 8d -> % 8d free: % 8d -> % 8d %s\n",
|
27
|
+
before[:time],
|
28
|
+
before[:count],
|
29
|
+
type,
|
30
|
+
info.values_at(:major_by, :gc_by).compact.join(","),
|
31
|
+
after[:time] - before[:time],
|
32
|
+
diff_alloc,
|
33
|
+
before[:heap_live_slot],
|
34
|
+
after[:heap_live_slot],
|
35
|
+
before[:heap_free_slot],
|
36
|
+
after[:heap_free_slot],
|
37
|
+
info[:immediate_sweep] ? " +immediate_sweep" : ""
|
38
|
+
end
|
39
|
+
}
|
40
|
+
=end
|
41
|
+
|
42
|
+
GCProf.after_gc_hook =
|
43
|
+
proc{ |info, start, end_mark, end_sweep|
|
44
|
+
if end_sweep
|
45
|
+
# eden_slots = oldgen_slots + remembered_shady_slots + longlived_infant_slots + new_infant_slots + empty_eden_slots
|
46
|
+
# heap_swept_slot = new_infant_slots + empty_eden_slots + empty_moved_tomb_slots
|
47
|
+
eden_slots = GC::INTERNAL_CONSTANTS[:HEAP_OBJ_LIMIT] * end_sweep[:heap_eden_page_length]
|
48
|
+
oldgen_slots = end_sweep[:old_object]
|
49
|
+
remembered_shady_slots = end_sweep[:remembered_shady_object]
|
50
|
+
new_infant_slots = end_sweep[:total_allocated_object] - end_mark[:total_allocated_object]
|
51
|
+
empty_moved_tomb_slots = GC::INTERNAL_CONSTANTS[:HEAP_OBJ_LIMIT] * (end_sweep[:heap_tomb_page_length] - end_mark[:heap_tomb_page_length])
|
52
|
+
empty_eden_slots = end_sweep[:heap_swept_slot] - new_infant_slots - empty_moved_tomb_slots
|
53
|
+
longlived_infant_slots = eden_slots - oldgen_slots - remembered_shady_slots - new_infant_slots - empty_eden_slots
|
54
|
+
|
55
|
+
# total_slots = eden_slots + tomb_slots
|
56
|
+
tomb_slots = GC::INTERNAL_CONSTANTS[:HEAP_OBJ_LIMIT] * end_sweep[:heap_tomb_page_length]
|
57
|
+
total_slots = eden_slots + tomb_slots
|
58
|
+
|
59
|
+
STDERR.printf "[%s in %1.3fs (% 13s)] % 8d slots = old (%4.1f%%) + remembered_shady (%4.1f%%) + shady (%4.1f%%) + infant (%4.1f%%) + empty (%4.1f%%) + tomb (%4.1f%%)\n",
|
60
|
+
info[:major_by] ? 'MAJOR GC' : 'minor gc',
|
61
|
+
end_mark[:time] - start[:time],
|
62
|
+
info.values_at(:major_by, :gc_by).compact.join(","),
|
63
|
+
total_slots,
|
64
|
+
oldgen_slots * 100.0 / total_slots,
|
65
|
+
remembered_shady_slots * 100.0 / total_slots,
|
66
|
+
longlived_infant_slots * 100.0 / total_slots,
|
67
|
+
new_infant_slots * 100.0 / total_slots,
|
68
|
+
empty_eden_slots * 100.0 / total_slots,
|
69
|
+
tomb_slots * 100.0 / total_slots
|
70
|
+
end
|
71
|
+
}
|
data/test/test_gcprof.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'gctools/gcprof'
|
3
|
+
|
4
|
+
class TestGCProf < Test::Unit::TestCase
|
5
|
+
def teardown
|
6
|
+
GCProf.after_gc_hook = nil
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_after_gc_hook
|
10
|
+
n = 0
|
11
|
+
GCProf.after_gc_hook = proc{ n += 1 }
|
12
|
+
GC.start
|
13
|
+
Object.new
|
14
|
+
assert_equal 1, n
|
15
|
+
end
|
16
|
+
end
|
data/test/test_oobgc.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'gctools/oobgc'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
class TestOOBGC < Test::Unit::TestCase
|
6
|
+
def setup
|
7
|
+
GC::OOB.setup
|
8
|
+
GC::OOB.clear
|
9
|
+
end
|
10
|
+
|
11
|
+
def test_oob_sweep
|
12
|
+
GC.start(immediate_sweep: false)
|
13
|
+
assert_equal false, GC.latest_gc_info(:immediate_sweep)
|
14
|
+
|
15
|
+
before = GC.stat(:heap_swept_slot)
|
16
|
+
assert_equal true, GC::OOB.run
|
17
|
+
assert_operator GC.stat(:heap_swept_slot), :>, before
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_oob_mark
|
21
|
+
oob = 0
|
22
|
+
before = GC.count
|
23
|
+
minor, major = GC.stat(:minor_gc_count), GC.stat(:major_gc_count)
|
24
|
+
|
25
|
+
20.times do
|
26
|
+
(10_000 + rand(25_000)).times{ Object.new }
|
27
|
+
oob += 1 if GC::OOB.run
|
28
|
+
end
|
29
|
+
|
30
|
+
assert_equal 0, GC.count - before - oob
|
31
|
+
assert_equal 0, GC.stat(:minor_gc_count) - minor - oob
|
32
|
+
assert_equal major, GC.stat(:major_gc_count)
|
33
|
+
end
|
34
|
+
|
35
|
+
def test_oob_major_mark
|
36
|
+
oob = 0
|
37
|
+
before = GC.count
|
38
|
+
minor, major = GC.stat(:minor_gc_count), GC.stat(:major_gc_count)
|
39
|
+
list = []
|
40
|
+
|
41
|
+
20.times do
|
42
|
+
2_000.times do
|
43
|
+
list << JSON.parse('{"hello":"world", "foo":["bar","bar"], "zap":{"zap":"zap"}}')
|
44
|
+
list.shift if list.size > 2_000
|
45
|
+
end
|
46
|
+
oob += 1 if GC::OOB.run
|
47
|
+
end
|
48
|
+
|
49
|
+
assert_equal oob, GC::OOB.stat(:count)
|
50
|
+
assert_equal 1, GC.count - before -
|
51
|
+
GC::OOB.stat(:major_count) - GC::OOB.stat(:minor_count) - GC::OOB.stat(:sweep_count)
|
52
|
+
end
|
53
|
+
end
|
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gctools
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.2.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Aman Gupta
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-01-09 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake-compiler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.9'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.9'
|
27
|
+
description: gc debugger, logger and profiler for rgengc in ruby 2.1
|
28
|
+
email: aman@tmm1.net
|
29
|
+
executables: []
|
30
|
+
extensions:
|
31
|
+
- ext/gcprof/extconf.rb
|
32
|
+
- ext/oobgc/extconf.rb
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- .gitignore
|
36
|
+
- Gemfile
|
37
|
+
- Gemfile.lock
|
38
|
+
- README.md
|
39
|
+
- Rakefile
|
40
|
+
- ext/gcprof/extconf.rb
|
41
|
+
- ext/gcprof/gcprof.c
|
42
|
+
- ext/oobgc/extconf.rb
|
43
|
+
- ext/oobgc/oobgc.c
|
44
|
+
- gctools.gemspec
|
45
|
+
- lib/gctools/logger.rb
|
46
|
+
- test/test_gcprof.rb
|
47
|
+
- test/test_oobgc.rb
|
48
|
+
homepage: https://github.com/tmm1/gctools
|
49
|
+
licenses:
|
50
|
+
- MIT
|
51
|
+
metadata: {}
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
require_paths:
|
55
|
+
- lib
|
56
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - ! '>='
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '0'
|
61
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
requirements: []
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 2.1.11
|
69
|
+
signing_key:
|
70
|
+
specification_version: 4
|
71
|
+
summary: rgengc tools for ruby 2.1+
|
72
|
+
test_files: []
|
73
|
+
has_rdoc:
|