allocation_tracer 0.0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3ec726b2a738166d01a261edd355fe34d8a727af
4
+ data.tar.gz: 4bcfe98dad9c2e41621992b780a60c419f710b70
5
+ SHA512:
6
+ metadata.gz: 0c2195f1424ca3220e939519b79130a4baad68993643fd895d1c26433d313876738372232cdd12df1a673b6d248af736647d11c0d35ed2018996ee49dad6927d
7
+ data.tar.gz: 6a28c18f2c5a943c28cbde895f020dfe7cc8e4542c83bdf3d8b05058d7210d7ee6f3ff8fffcd055ed65e374bd9e5f88b82fb980747a02b2b9c873cef91997dec
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in allocation_tracer.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Koichi Sasada
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,149 @@
1
+ # ObjectSpace::AllocationTracer
2
+
3
+ This module allows to trace object allocation.
4
+
5
+ This feature is similar to https://github.com/SamSaffron/memory_profiler
6
+ and https://github.com/srawlins/allocation_stats. But this feature
7
+ focused on `age' of objects.
8
+
9
+ This gem was separated from gc_tracer.gem.
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ gem 'allocation_tracer'
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install allocation_tracer
24
+
25
+ ## Usage
26
+
27
+ ### Allocation tracing
28
+
29
+ You can trace allocation information and you can get aggregated information.
30
+
31
+ ```ruby
32
+ require 'allocation_tracer'
33
+ require 'pp'
34
+
35
+ pp ObjectSpace::AllocationTracer.trace{
36
+ 50_000.times{|i|
37
+ i.to_s
38
+ i.to_s
39
+ i.to_s
40
+ }
41
+ }
42
+ ```
43
+
44
+ will show
45
+
46
+ ```
47
+ {["test.rb", 6]=>[50000, 44290, 0, 6],
48
+ ["test.rb", 7]=>[50000, 44289, 0, 5],
49
+ ["test.rb", 8]=>[50000, 44295, 0, 6]}
50
+ ```
51
+
52
+ In this case, 50,000 objects are created at `test.rb:6'. 44,290 is total
53
+ age of objects created at this line. Average age of object created at
54
+ this line is 50000/44290 = 0.8858. 0 is minimum age and 6 is maximum age.
55
+
56
+ You can also specify `type' in GC::Tracer.setup_allocation_tracing() to
57
+ specify what should be keys to aggregate like that.
58
+
59
+ ```ruby
60
+ require 'allocation_tracer'
61
+ require 'pp'
62
+
63
+ ObjectSpace::AllocationTracer.setup(%i{path line type})
64
+
65
+ result = ObjectSpace::AllocationTracer.trace do
66
+ 50_000.times{|i|
67
+ a = [i.to_s]
68
+ b = {i.to_s => nil}
69
+ c = (i.to_s .. i.to_s)
70
+ }
71
+ end
72
+
73
+ pp result
74
+ ```
75
+
76
+ and you will get:
77
+
78
+ ```
79
+ {["test.rb", 8, :T_STRING]=>[50000, 49067, 0, 17],
80
+ ["test.rb", 8, :T_ARRAY]=>[50000, 49053, 0, 17],
81
+ ["test.rb", 9, :T_STRING]=>[100000, 98146, 0, 17],
82
+ ["test.rb", 9, :T_HASH]=>[50000, 49111, 0, 17],
83
+ ["test.rb", 10, :T_STRING]=>[100000, 98267, 0, 17],
84
+ ["test.rb", 10, :T_STRUCT]=>[50000, 49126, 0, 17]}
85
+ ```
86
+
87
+ Interestingly, you can not see array creations in a middle of block:
88
+
89
+ ```ruby
90
+ require 'allocation_tracer'
91
+ require 'pp'
92
+
93
+ ObjectSpace::AllocationTracer.setup(%i{path line type})
94
+
95
+ result = ObjectSpace::AllocationTracer.trace do
96
+ 50_000.times{|i|
97
+ [i.to_s]
98
+ nil
99
+ }
100
+ end
101
+
102
+ pp result
103
+ ```
104
+
105
+ and it prints:
106
+
107
+ ```
108
+ {["test.rb", 8, :T_STRING]=>[50000, 38322, 0, 2]}
109
+ ```
110
+
111
+ There are only string creation. This is because unused array creation is
112
+ ommitted by optimizer.
113
+
114
+ Simply you can require `allocation_tracer/trace' to start allocation
115
+ tracer and output the aggregated information into stdot at the end of
116
+ program.
117
+
118
+ ```ruby
119
+ require 'allocation_tracer/trace'
120
+
121
+ # Run your program here
122
+ 50_000.times{|i|
123
+ i.to_s
124
+ i.to_s
125
+ i.to_s
126
+ }
127
+ ```
128
+
129
+ and you will see:
130
+
131
+ ```
132
+ path line count total_age max_age min_age
133
+ .../lib/ruby/2.2.0/rubygems/core_ext/kernel_require.rb 55 18 23 1 6
134
+ .../lib/allocation_tracer/trace.rb 6 2 12 6 6
135
+ test.rb 0 1 0 0 0
136
+ test.rb 5 50000 41645 0 4
137
+ test.rb 6 50000 41648 0 5
138
+ test.rb 7 50000 41650 0 5
139
+ ```
140
+
141
+ (tab separated colums)
142
+
143
+ ## Contributing
144
+
145
+ 1. Fork it ( http://github.com/<my-github-username>/allocation_tracer/fork )
146
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
147
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
148
+ 4. Push to the branch (`git push origin my-new-feature`)
149
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/extensiontask"
3
+ require 'rspec/core/rake_task'
4
+
5
+ spec = Gem::Specification.load('allocation_tracer.gemspec')
6
+
7
+ Rake::ExtensionTask.new("allocation_tracer", spec){|ext|
8
+ ext.lib_dir = "lib/allocation_tracer"
9
+ }
10
+
11
+ RSpec::Core::RakeTask.new('spec' => 'compile')
12
+
13
+ task default: :spec
14
+
15
+ task :run => 'compile' do
16
+ ruby %q{-I ./lib test.rb}
17
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'allocation_tracer/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "allocation_tracer"
8
+ spec.version = ObjectSpace::AllocationTracer::VERSION
9
+ spec.authors = ["Koichi Sasada"]
10
+ spec.email = ["ko1@atdot.net"]
11
+ spec.summary = %q{allocation_tracer gem adds ObjectSpace::AllocationTracer module.}
12
+ spec.description = %q{allocation_tracer gem adds ObjectSpace::AllocationTracer module.}
13
+ spec.homepage = "https://github.com/ko1/allocation_tracer"
14
+ spec.license = "MIT"
15
+
16
+ spec.extensions = %w[ext/allocation_tracer/extconf.rb]
17
+ spec.required_ruby_version = '>= 2.1.0'
18
+
19
+ spec.files = `git ls-files -z`.split("\x0")
20
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
21
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
22
+ spec.require_paths = ["lib"]
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.5"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "rake-compiler"
27
+ spec.add_development_dependency "rspec"
28
+ end
@@ -0,0 +1,568 @@
1
+ /*
2
+ * allocation tracer: adds GC::Tracer::start_allocation_tracing
3
+ *
4
+ * By Koichi Sasada
5
+ * created at Thu Apr 17 03:50:38 2014.
6
+ */
7
+
8
+ #include "ruby/ruby.h"
9
+ #include "ruby/debug.h"
10
+
11
+ static VALUE rb_mAllocationTracer;
12
+
13
+ struct allocation_info {
14
+ /* all of information don't need marking. */
15
+ int living;
16
+ VALUE flags;
17
+ VALUE klass;
18
+ const char *klass_path;
19
+ size_t generation;
20
+
21
+ /* allocation info */
22
+ const char *path;
23
+ unsigned long line;
24
+
25
+ struct allocation_info *next;
26
+ };
27
+
28
+ #define KEY_PATH (1<<1)
29
+ #define KEY_LINE (1<<2)
30
+ #define KEY_TYPE (1<<3)
31
+ #define KEY_CLASS (1<<4)
32
+
33
+ #define VAL_COUNT (1<<1)
34
+ #define VAL_TOTAL_AGE (1<<2)
35
+ #define VAL_MAX_AGE (1<<3)
36
+ #define VAL_MIN_AGE (1<<4)
37
+
38
+ struct traceobj_arg {
39
+ int running;
40
+ int keys, vals;
41
+ st_table *aggregate_table; /* user defined key -> [count, total_age, max_age, min_age] */
42
+ st_table *object_table; /* obj (VALUE) -> allocation_info */
43
+ st_table *str_table; /* cstr -> refcount */
44
+ struct allocation_info *freed_allocation_info;
45
+ };
46
+
47
+ static char *
48
+ keep_unique_str(st_table *tbl, const char *str)
49
+ {
50
+ st_data_t n;
51
+
52
+ if (str && st_lookup(tbl, (st_data_t)str, &n)) {
53
+ char *result;
54
+
55
+ st_insert(tbl, (st_data_t)str, n+1);
56
+ st_get_key(tbl, (st_data_t)str, (st_data_t *)&result);
57
+
58
+ return result;
59
+ }
60
+ else {
61
+ return NULL;
62
+ }
63
+ }
64
+
65
+ static const char *
66
+ make_unique_str(st_table *tbl, const char *str, long len)
67
+ {
68
+ if (!str) {
69
+ return NULL;
70
+ }
71
+ else {
72
+ char *result;
73
+
74
+ if ((result = keep_unique_str(tbl, str)) == NULL) {
75
+ result = (char *)ruby_xmalloc(len+1);
76
+ strncpy(result, str, len);
77
+ result[len] = 0;
78
+ st_add_direct(tbl, (st_data_t)result, 1);
79
+ }
80
+ return result;
81
+ }
82
+ }
83
+
84
+ static void
85
+ delete_unique_str(st_table *tbl, const char *str)
86
+ {
87
+ if (str) {
88
+ st_data_t n;
89
+
90
+ st_lookup(tbl, (st_data_t)str, &n);
91
+ if (n == 1) {
92
+ st_delete(tbl, (st_data_t *)&str, 0);
93
+ ruby_xfree((char *)str);
94
+ }
95
+ else {
96
+ st_insert(tbl, (st_data_t)str, n-1);
97
+ }
98
+ }
99
+ }
100
+
101
+ /* file, line, type */
102
+ #define MAX_KEY_DATA 4
103
+
104
+ struct memcmp_key_data {
105
+ int n;
106
+ st_data_t data[4];
107
+ };
108
+
109
+ static int
110
+ memcmp_hash_compare(st_data_t a, st_data_t b)
111
+ {
112
+ struct memcmp_key_data *k1 = (struct memcmp_key_data *)a;
113
+ struct memcmp_key_data *k2 = (struct memcmp_key_data *)b;
114
+ return memcmp(&k1->data[0], &k2->data[0], k1->n * sizeof(st_data_t));
115
+ }
116
+
117
+ static st_index_t
118
+ memcmp_hash_hash(st_data_t a)
119
+ {
120
+ struct memcmp_key_data *k = (struct memcmp_key_data *)a;
121
+ return rb_memhash(k->data, sizeof(st_data_t) * k->n);
122
+ }
123
+
124
+ static const struct st_hash_type memcmp_hash_type = {
125
+ memcmp_hash_compare, memcmp_hash_hash
126
+ };
127
+
128
+ static struct traceobj_arg *tmp_trace_arg; /* TODO: Do not use global variables */
129
+
130
+ static struct traceobj_arg *
131
+ get_traceobj_arg(void)
132
+ {
133
+ if (tmp_trace_arg == 0) {
134
+ tmp_trace_arg = ALLOC_N(struct traceobj_arg, 1);
135
+ tmp_trace_arg->running = 0;
136
+ tmp_trace_arg->keys = 0;
137
+ tmp_trace_arg->vals = VAL_COUNT | VAL_TOTAL_AGE | VAL_MAX_AGE | VAL_MIN_AGE;
138
+ tmp_trace_arg->aggregate_table = st_init_table(&memcmp_hash_type);
139
+ tmp_trace_arg->object_table = st_init_numtable();
140
+ tmp_trace_arg->str_table = st_init_strtable();
141
+ tmp_trace_arg->freed_allocation_info = NULL;
142
+ }
143
+ return tmp_trace_arg;
144
+ }
145
+
146
+ static int
147
+ free_keys_i(st_data_t key, st_data_t value, void *data)
148
+ {
149
+ ruby_xfree((void *)key);
150
+ return ST_CONTINUE;
151
+ }
152
+
153
+ static int
154
+ free_values_i(st_data_t key, st_data_t value, void *data)
155
+ {
156
+ ruby_xfree((void *)value);
157
+ return ST_CONTINUE;
158
+ }
159
+
160
+ static int
161
+ free_key_values_i(st_data_t key, st_data_t value, void *data)
162
+ {
163
+ ruby_xfree((void *)key);
164
+ ruby_xfree((void *)value);
165
+ return ST_CONTINUE;
166
+ }
167
+
168
+ static void
169
+ clear_traceobj_arg(void)
170
+ {
171
+ struct traceobj_arg * arg = get_traceobj_arg();
172
+
173
+ st_foreach(arg->aggregate_table, free_key_values_i, 0);
174
+ st_clear(arg->aggregate_table);
175
+ st_foreach(arg->object_table, free_values_i, 0);
176
+ st_clear(arg->object_table);
177
+ st_foreach(arg->str_table, free_keys_i, 0);
178
+ st_clear(arg->str_table);
179
+ }
180
+
181
+ static struct allocation_info *
182
+ create_allocation_info(void)
183
+ {
184
+ return (struct allocation_info *)ruby_xmalloc(sizeof(struct allocation_info));
185
+ }
186
+
187
+ static void
188
+ free_allocation_info(struct traceobj_arg *arg, struct allocation_info *info)
189
+ {
190
+ delete_unique_str(arg->str_table, info->path);
191
+ delete_unique_str(arg->str_table, info->klass_path);
192
+ ruby_xfree(info);
193
+ }
194
+
195
+ static void
196
+ newobj_i(VALUE tpval, void *data)
197
+ {
198
+ struct traceobj_arg *arg = (struct traceobj_arg *)data;
199
+ struct allocation_info *info;
200
+ rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
201
+ VALUE obj = rb_tracearg_object(tparg);
202
+ VALUE klass = RBASIC_CLASS(obj);
203
+ VALUE path = rb_tracearg_path(tparg);
204
+ VALUE line = rb_tracearg_lineno(tparg);
205
+ VALUE klass_path = (RTEST(klass) && !OBJ_FROZEN(klass)) ? rb_class_path_cached(klass) : Qnil;
206
+ const char *path_cstr = RTEST(path) ? make_unique_str(arg->str_table, RSTRING_PTR(path), RSTRING_LEN(path)) : NULL;
207
+ const char *klass_path_cstr = RTEST(klass_path) ? make_unique_str(arg->str_table, RSTRING_PTR(klass_path), RSTRING_LEN(klass_path)) : NULL;
208
+
209
+ if (st_lookup(arg->object_table, (st_data_t)obj, (st_data_t *)&info)) {
210
+ if (info->living) {
211
+ /* do nothing. there is possibility to keep living if FREEOBJ events while suppressing tracing */
212
+ }
213
+ /* reuse info */
214
+ delete_unique_str(arg->str_table, info->path);
215
+ delete_unique_str(arg->str_table, info->klass_path);
216
+ }
217
+ else {
218
+ info = create_allocation_info();
219
+ }
220
+
221
+ info->next = NULL;
222
+ info->living = 1;
223
+ info->flags = RBASIC(obj)->flags;
224
+ info->klass = klass;
225
+ info->klass_path = klass_path_cstr;
226
+ info->generation = rb_gc_count();
227
+
228
+ info->path = path_cstr;
229
+ info->line = NUM2INT(line);
230
+
231
+ st_insert(arg->object_table, (st_data_t)obj, (st_data_t)info);
232
+ }
233
+
234
+ /* file, line, type, klass */
235
+ #define MAX_KEY_SIZE 4
236
+
237
+ void
238
+ aggregator_i(void *data)
239
+ {
240
+ size_t gc_count = rb_gc_count();
241
+ struct traceobj_arg *arg = (struct traceobj_arg *)data;
242
+ struct allocation_info *info = arg->freed_allocation_info;
243
+
244
+ arg->freed_allocation_info = NULL;
245
+
246
+ while (info) {
247
+ struct allocation_info *next_info = info->next;
248
+ st_data_t key, val;
249
+ struct memcmp_key_data key_data;
250
+ int *val_buff;
251
+ int age = (int)(gc_count - info->generation);
252
+ int i;
253
+
254
+ i = 0;
255
+ if (arg->keys & KEY_PATH) {
256
+ key_data.data[i++] = (st_data_t)info->path;
257
+ }
258
+ if (arg->keys & KEY_LINE) {
259
+ key_data.data[i++] = (st_data_t)info->line;
260
+ }
261
+ if (arg->keys & KEY_TYPE) {
262
+ key_data.data[i++] = (st_data_t)(info->flags & T_MASK);
263
+ }
264
+ if (arg->keys & KEY_CLASS) {
265
+ key_data.data[i++] = (st_data_t)info->klass_path;
266
+ }
267
+ key_data.n = i;
268
+ key = (st_data_t)&key_data;
269
+
270
+ if (st_lookup(arg->aggregate_table, key, &val) == 0) {
271
+ struct memcmp_key_data *key_buff = ruby_xmalloc(sizeof(int) + sizeof(st_data_t) * key_data.n);
272
+ key_buff->n = key_data.n;
273
+
274
+ for (i=0; i<key_data.n; i++) {
275
+ key_buff->data[i] = key_data.data[i];
276
+ }
277
+ key = (st_data_t)key_buff;
278
+
279
+ /* count, total age, max age, min age */
280
+ val_buff = ALLOC_N(int, 4);
281
+ val_buff[0] = val_buff[1] = 0;
282
+ val_buff[2] = val_buff[3] = age;
283
+
284
+ if (arg->keys & KEY_PATH) keep_unique_str(arg->str_table, info->path);
285
+ if (arg->keys & KEY_CLASS) keep_unique_str(arg->str_table, info->klass_path);
286
+
287
+ st_insert(arg->aggregate_table, (st_data_t)key_buff, (st_data_t)val_buff);
288
+ }
289
+ else {
290
+ val_buff = (int *)val;
291
+ }
292
+
293
+ val_buff[0] += 1;
294
+ val_buff[1] += age;
295
+ if (val_buff[2] > age) val_buff[2] = age;
296
+ if (val_buff[3] < age) val_buff[3] = age;
297
+
298
+ free_allocation_info(arg, info);
299
+ info = next_info;
300
+ }
301
+ }
302
+
303
+ static void
304
+ move_to_freed_list(struct traceobj_arg *arg, VALUE obj, struct allocation_info *info)
305
+ {
306
+ info->next = arg->freed_allocation_info;
307
+ arg->freed_allocation_info = info;
308
+ st_delete(arg->object_table, (st_data_t *)&obj, (st_data_t *)&info);
309
+ }
310
+
311
+ static void
312
+ freeobj_i(VALUE tpval, void *data)
313
+ {
314
+ struct traceobj_arg *arg = (struct traceobj_arg *)data;
315
+ rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
316
+ VALUE obj = rb_tracearg_object(tparg);
317
+ struct allocation_info *info;
318
+
319
+ if (arg->freed_allocation_info == NULL) {
320
+ rb_postponed_job_register_one(0, aggregator_i, arg);
321
+ }
322
+
323
+ if (st_lookup(arg->object_table, (st_data_t)obj, (st_data_t *)&info)) {
324
+ move_to_freed_list(arg, obj, info);
325
+ }
326
+ }
327
+
328
+ static void
329
+ start_alloc_hooks(VALUE mod)
330
+ {
331
+ VALUE newobj_hook, freeobj_hook;
332
+ struct traceobj_arg *arg = get_traceobj_arg();
333
+
334
+ if ((newobj_hook = rb_ivar_get(rb_mAllocationTracer, rb_intern("newobj_hook"))) == Qnil) {
335
+ rb_ivar_set(rb_mAllocationTracer, rb_intern("newobj_hook"), newobj_hook = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i, arg));
336
+ rb_ivar_set(rb_mAllocationTracer, rb_intern("freeobj_hook"), freeobj_hook = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_FREEOBJ, freeobj_i, arg));
337
+ }
338
+ else {
339
+ freeobj_hook = rb_ivar_get(rb_mAllocationTracer, rb_intern("freeobj_hook"));
340
+ }
341
+
342
+ rb_tracepoint_enable(newobj_hook);
343
+ rb_tracepoint_enable(freeobj_hook);
344
+ }
345
+
346
+ static const char *
347
+ type_name(int type)
348
+ {
349
+ switch (type) {
350
+ #define TYPE_NAME(t) case (t): return #t;
351
+ TYPE_NAME(T_NONE);
352
+ TYPE_NAME(T_OBJECT);
353
+ TYPE_NAME(T_CLASS);
354
+ TYPE_NAME(T_MODULE);
355
+ TYPE_NAME(T_FLOAT);
356
+ TYPE_NAME(T_STRING);
357
+ TYPE_NAME(T_REGEXP);
358
+ TYPE_NAME(T_ARRAY);
359
+ TYPE_NAME(T_HASH);
360
+ TYPE_NAME(T_STRUCT);
361
+ TYPE_NAME(T_BIGNUM);
362
+ TYPE_NAME(T_FILE);
363
+ TYPE_NAME(T_MATCH);
364
+ TYPE_NAME(T_COMPLEX);
365
+ TYPE_NAME(T_RATIONAL);
366
+ TYPE_NAME(T_NIL);
367
+ TYPE_NAME(T_TRUE);
368
+ TYPE_NAME(T_FALSE);
369
+ TYPE_NAME(T_SYMBOL);
370
+ TYPE_NAME(T_FIXNUM);
371
+ TYPE_NAME(T_UNDEF);
372
+ TYPE_NAME(T_NODE);
373
+ TYPE_NAME(T_ICLASS);
374
+ TYPE_NAME(T_ZOMBIE);
375
+ TYPE_NAME(T_DATA);
376
+ #undef TYPE_NAME
377
+ }
378
+ return "unknown";
379
+ }
380
+
381
+ struct arg_and_result {
382
+ struct traceobj_arg *arg;
383
+ VALUE result;
384
+ };
385
+
386
+ static int
387
+ aggregate_result_i(st_data_t key, st_data_t val, void *data)
388
+ {
389
+ struct arg_and_result *aar = (struct arg_and_result *)data;
390
+ struct traceobj_arg *arg = aar->arg;
391
+ VALUE result = aar->result;
392
+
393
+ int *val_buff = (int *)val;
394
+ struct memcmp_key_data *key_buff = (struct memcmp_key_data *)key;
395
+ VALUE v = rb_ary_new3(4, INT2FIX(val_buff[0]), INT2FIX(val_buff[1]), INT2FIX(val_buff[2]), INT2FIX(val_buff[3]));
396
+ VALUE k = rb_ary_new();
397
+ int i = 0;
398
+ static VALUE type_symbols[T_MASK] = {0};
399
+
400
+ if (type_symbols[0] == 0) {
401
+ int i;
402
+ for (i=0; i<T_MASK; i++) {
403
+ type_symbols[i] = ID2SYM(rb_intern(type_name(i)));
404
+ }
405
+ }
406
+
407
+ i = 0;
408
+ if (arg->keys & KEY_PATH) {
409
+ const char *path = (const char *)key_buff->data[i++];
410
+ if (path) {
411
+ rb_ary_push(k, rb_str_new2(path));
412
+ delete_unique_str(arg->str_table, path);
413
+ }
414
+ else {
415
+ rb_ary_push(k, Qnil);
416
+ }
417
+ }
418
+ if (arg->keys & KEY_LINE) {
419
+ rb_ary_push(k, INT2FIX((int)key_buff->data[i++]));
420
+ }
421
+ if (arg->keys & KEY_TYPE) {
422
+ rb_ary_push(k, type_symbols[key_buff->data[i++]]);
423
+ }
424
+ if (arg->keys & KEY_CLASS) {
425
+ const char *klass_path = (const char *)key_buff->data[i++];
426
+ if (klass_path) {
427
+
428
+ delete_unique_str(arg->str_table, klass_path);
429
+ }
430
+ else {
431
+ rb_ary_push(k, Qnil);
432
+ }
433
+ }
434
+
435
+ rb_hash_aset(result, k, v);
436
+
437
+ return ST_CONTINUE;
438
+ }
439
+
440
+ static int
441
+ aggregate_rest_object_i(st_data_t key, st_data_t val, void *data)
442
+ {
443
+ struct traceobj_arg *arg = (struct traceobj_arg *)data;
444
+ struct allocation_info *info = (struct allocation_info *)val;
445
+ move_to_freed_list(arg, (VALUE)key, info);
446
+ return ST_CONTINUE;
447
+ }
448
+
449
+ static VALUE
450
+ aggregate_result(struct traceobj_arg *arg)
451
+ {
452
+ struct arg_and_result aar;
453
+ aar.result = rb_hash_new();
454
+ aar.arg = arg;
455
+
456
+ st_foreach(arg->object_table, aggregate_rest_object_i, (st_data_t)arg);
457
+ aggregator_i(arg);
458
+ st_foreach(arg->aggregate_table, aggregate_result_i, (st_data_t)&aar);
459
+ clear_traceobj_arg();
460
+ return aar.result;
461
+ }
462
+
463
+ static VALUE
464
+ stop_allocation_tracing(VALUE self)
465
+ {
466
+ struct traceobj_arg * arg = get_traceobj_arg();
467
+
468
+ if (arg->running) {
469
+ VALUE newobj_hook = rb_ivar_get(rb_mAllocationTracer, rb_intern("newobj_hook"));
470
+ VALUE freeobj_hook = rb_ivar_get(rb_mAllocationTracer, rb_intern("freeobj_hook"));
471
+ rb_tracepoint_disable(newobj_hook);
472
+ rb_tracepoint_disable(freeobj_hook);
473
+
474
+ arg->running = 0;
475
+ }
476
+ else {
477
+ rb_raise(rb_eRuntimeError, "not started yet.");
478
+ }
479
+
480
+ return Qnil;
481
+ }
482
+
483
+ static VALUE
484
+ allocation_tracer_stop(VALUE self)
485
+ {
486
+ stop_allocation_tracing(self);
487
+ return aggregate_result(get_traceobj_arg());
488
+ }
489
+
490
+ static VALUE
491
+ allocation_tracer_trace(VALUE self)
492
+ {
493
+ struct traceobj_arg * arg = get_traceobj_arg();
494
+
495
+ if (arg->running) {
496
+ rb_raise(rb_eRuntimeError, "can't run recursivly");
497
+ }
498
+ else {
499
+ arg->running = 1;
500
+ if (arg->keys == 0) arg->keys = KEY_PATH | KEY_LINE;
501
+ start_alloc_hooks(rb_mAllocationTracer);
502
+
503
+ if (rb_block_given_p()) {
504
+ rb_ensure(rb_yield, Qnil, stop_allocation_tracing, Qnil);
505
+ return aggregate_result(get_traceobj_arg());
506
+ }
507
+ }
508
+
509
+ return Qnil;
510
+ }
511
+
512
+ static VALUE
513
+ allocation_tracer_setup(int argc, VALUE *argv, VALUE self)
514
+ {
515
+ struct traceobj_arg * arg = get_traceobj_arg();
516
+
517
+ if (arg->running) {
518
+ rb_raise(rb_eRuntimeError, "can't change configuration during running");
519
+ }
520
+ else {
521
+ int i;
522
+ VALUE ary = rb_check_array_type(argv[0]);
523
+
524
+ for (i=0; i<(int)RARRAY_LEN(ary); i++) {
525
+ if (RARRAY_AREF(ary, i) == ID2SYM(rb_intern("path"))) arg->keys |= KEY_PATH;
526
+ else if (RARRAY_AREF(ary, i) == ID2SYM(rb_intern("line"))) arg->keys |= KEY_LINE;
527
+ else if (RARRAY_AREF(ary, i) == ID2SYM(rb_intern("type"))) arg->keys |= KEY_TYPE;
528
+ else if (RARRAY_AREF(ary, i) == ID2SYM(rb_intern("class"))) arg->keys |= KEY_CLASS;
529
+ else {
530
+ rb_raise(rb_eArgError, "not supported key type");
531
+ }
532
+ }
533
+ }
534
+
535
+ return Qnil;
536
+ }
537
+
538
+ VALUE
539
+ allocation_tracer_header(VALUE self)
540
+ {
541
+ VALUE ary = rb_ary_new();
542
+ struct traceobj_arg * arg = get_traceobj_arg();
543
+
544
+ if (arg->keys & KEY_PATH) rb_ary_push(ary, ID2SYM(rb_intern("path")));
545
+ if (arg->keys & KEY_LINE) rb_ary_push(ary, ID2SYM(rb_intern("line")));
546
+ if (arg->keys & KEY_TYPE) rb_ary_push(ary, ID2SYM(rb_intern("type")));
547
+ if (arg->keys & KEY_CLASS) rb_ary_push(ary, ID2SYM(rb_intern("class")));
548
+
549
+ if (arg->vals & VAL_COUNT) rb_ary_push(ary, ID2SYM(rb_intern("count")));
550
+ if (arg->vals & VAL_TOTAL_AGE) rb_ary_push(ary, ID2SYM(rb_intern("total_age")));
551
+ if (arg->vals & VAL_MAX_AGE) rb_ary_push(ary, ID2SYM(rb_intern("max_age")));
552
+ if (arg->vals & VAL_MIN_AGE) rb_ary_push(ary, ID2SYM(rb_intern("min_age")));
553
+
554
+ return ary;
555
+ }
556
+
557
+ void
558
+ Init_allocation_tracer(void)
559
+ {
560
+ VALUE rb_mObjSpace = rb_const_get(rb_cObject, rb_intern("ObjectSpace"));
561
+ VALUE mod = rb_mAllocationTracer = rb_define_module_under(rb_mObjSpace, "AllocationTracer");
562
+
563
+ /* allocation tracer methods */
564
+ rb_define_module_function(mod, "trace", allocation_tracer_trace, 0);
565
+ rb_define_module_function(mod, "stop", allocation_tracer_stop, 0);
566
+ rb_define_module_function(mod, "setup", allocation_tracer_setup, -1);
567
+ rb_define_module_function(mod, "header", allocation_tracer_header, 0);
568
+ }
@@ -0,0 +1,2 @@
1
+ require 'mkmf'
2
+ create_makefile('allocation_tracer/allocation_tracer')
@@ -0,0 +1,6 @@
1
+ require "allocation_tracer/version"
2
+ require "allocation_tracer/allocation_tracer"
3
+
4
+ module ObjectSpace::AllocationTracer
5
+ # Your code goes here...
6
+ end
@@ -0,0 +1,15 @@
1
+ require 'allocation_tracer'
2
+
3
+ # ObjectSpace::AllocationTracer.setup(%i{path line})
4
+ ObjectSpace::AllocationTracer.trace
5
+
6
+ at_exit{
7
+ results = ObjectSpace::AllocationTracer.stop
8
+
9
+ puts ObjectSpace::AllocationTracer.header.join("\t")
10
+ results.sort_by{|k, v| k}.each{|k, v|
11
+ puts (k+v).join("\t")
12
+ }
13
+ }
14
+
15
+
@@ -0,0 +1,3 @@
1
+ module ObjectSpace::AllocationTracer
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,27 @@
1
+ require 'spec_helper'
2
+ require 'tmpdir'
3
+ require 'fileutils'
4
+
5
+ describe ObjectSpace::AllocationTracer do
6
+ describe 'ObjectSpace::AllocationTracer.trace' do
7
+ it 'should includes allocation information' do
8
+ line = __LINE__ + 2
9
+ result = ObjectSpace::AllocationTracer.trace do
10
+ Object.new
11
+ end
12
+
13
+ expect(result.length).to be >= 1
14
+ expect(result[[__FILE__, line]]).to eq [1, 0, 0, 0]
15
+ end
16
+
17
+ it 'should run twice' do
18
+ line = __LINE__ + 2
19
+ result = ObjectSpace::AllocationTracer.trace do
20
+ Object.new
21
+ end
22
+
23
+ expect(result.length).to be >= 1
24
+ expect(result[[__FILE__, line]]).to eq [1, 0, 0, 0]
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,7 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'allocation_tracer'
4
+
5
+ RSpec.configure do |config|
6
+ config.mock_framework = :rspec
7
+ end
metadata ADDED
@@ -0,0 +1,118 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: allocation_tracer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Koichi Sasada
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-04-17 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.5'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.5'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake-compiler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: allocation_tracer gem adds ObjectSpace::AllocationTracer module.
70
+ email:
71
+ - ko1@atdot.net
72
+ executables: []
73
+ extensions:
74
+ - ext/allocation_tracer/extconf.rb
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - allocation_tracer.gemspec
83
+ - ext/allocation_tracer/allocation_tracer.c
84
+ - ext/allocation_tracer/extconf.rb
85
+ - lib/allocation_tracer.rb
86
+ - lib/allocation_tracer/allocation_tracer.so
87
+ - lib/allocation_tracer/trace.rb
88
+ - lib/allocation_tracer/version.rb
89
+ - spec/allocation_tracer_spec.rb
90
+ - spec/spec_helper.rb
91
+ homepage: https://github.com/ko1/allocation_tracer
92
+ licenses:
93
+ - MIT
94
+ metadata: {}
95
+ post_install_message:
96
+ rdoc_options: []
97
+ require_paths:
98
+ - lib
99
+ required_ruby_version: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: 2.1.0
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubyforge_project:
111
+ rubygems_version: 2.2.2
112
+ signing_key:
113
+ specification_version: 4
114
+ summary: allocation_tracer gem adds ObjectSpace::AllocationTracer module.
115
+ test_files:
116
+ - spec/allocation_tracer_spec.rb
117
+ - spec/spec_helper.rb
118
+ has_rdoc: