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 +7 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +149 -0
- data/Rakefile +17 -0
- data/allocation_tracer.gemspec +28 -0
- data/ext/allocation_tracer/allocation_tracer.c +568 -0
- data/ext/allocation_tracer/extconf.rb +2 -0
- data/lib/allocation_tracer.rb +6 -0
- data/lib/allocation_tracer/allocation_tracer.so +0 -0
- data/lib/allocation_tracer/trace.rb +15 -0
- data/lib/allocation_tracer/version.rb +3 -0
- data/spec/allocation_tracer_spec.rb +27 -0
- data/spec/spec_helper.rb +7 -0
- metadata +118 -0
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
data/Gemfile
ADDED
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
|
+
}
|
Binary file
|
@@ -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,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
|
data/spec/spec_helper.rb
ADDED
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:
|