allocation_tracer 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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: