alloc_track 0.0.2
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 +4 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +18 -0
- data/LICENSE.md +21 -0
- data/README.md +52 -0
- data/Rakefile +36 -0
- data/alloc_track.gemspec +15 -0
- data/ext/alloc_track/alloc_track.c +270 -0
- data/ext/alloc_track/extconf.rb +15 -0
- data/test/benchmark_alloc_track.rb +28 -0
- data/test/test_alloc_track.rb +76 -0
- metadata +69 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c2bcef216fe18bd1578ecc1b47f26e10cf6669ad
|
4
|
+
data.tar.gz: 2f003bcc8dc4f0f30a7123518192fb7c9a3c22da
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 50d0ecf47e7a19ac2f9b59e484ab84e8379243d25c7a31487be87924d6b4f486bdecb50613874fcdc07b98a90b1c60946d264257df4597cab590233ebbd6b142
|
7
|
+
data.tar.gz: 86ee7fc454d489abb83df54d01d6fb226da1f0d66f56e5dacc764bf02fbc173494b3ef0bfe1abf2af7f159a978899ca05c4e3c8030c15d62d3bbb743c009a7e7
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2014 Scott Francis <scott.francis@shopify.com>
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
## alloc_track
|
2
|
+
|
3
|
+
Tracks the number of outstanding allocations on a Ruby thread using the internal tracepoint APIs.
|
4
|
+
|
5
|
+
### Features
|
6
|
+
|
7
|
+
- C extension for webscale
|
8
|
+
- Allocations are counted per thread
|
9
|
+
- Can raise an exception when allocations exceed a certain threshold
|
10
|
+
|
11
|
+
### Usage
|
12
|
+
|
13
|
+
It can be used to track the number of allocated objects over a period of time:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
require 'alloc_track/alloc_track'
|
17
|
+
|
18
|
+
AllocTrack.start
|
19
|
+
100.times { Object.new }
|
20
|
+
puts AllocTrack.delta # >= 100
|
21
|
+
GC.start
|
22
|
+
puts AllocTrack.alloc # >= 100
|
23
|
+
puts AllocTrack.delta # <= 100
|
24
|
+
puts AllocTrack.free # >= 100
|
25
|
+
AllocTrack.stop
|
26
|
+
```
|
27
|
+
|
28
|
+
Perhaps more useful is the ability to raise when the number of allocations crosses a certain threshold:
|
29
|
+
```ruby
|
30
|
+
require 'alloc_track/alloc_track'
|
31
|
+
|
32
|
+
AllocTrack.limit 100 do
|
33
|
+
200.times { Object.new } # raises AllocTrack::LimitExceeded
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
### Performance
|
38
|
+
|
39
|
+
In a contrived benchmark that simply allocates 10,000,000 new objects, alloc_track adds ~30% overhead:
|
40
|
+
|
41
|
+
```
|
42
|
+
[vagrant] ~/src/alloc_track (master *%) $ ruby -Ilib ./test/benchmark_alloc_track.rb
|
43
|
+
user system total real
|
44
|
+
none: 1.540000 0.000000 1.540000 ( 1.545192)
|
45
|
+
tracking: 2.020000 0.000000 2.020000 ( 2.034162)
|
46
|
+
```
|
47
|
+
|
48
|
+
Expect real-world (operations other than just memory allocation) performance overhead to be much less severe.
|
49
|
+
|
50
|
+
### Limitations
|
51
|
+
|
52
|
+
- Allocation tracker can only be run on one thread per process
|
data/Rakefile
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
task :default => :test
|
2
|
+
|
3
|
+
# ==========================================================
|
4
|
+
# Packaging
|
5
|
+
# ==========================================================
|
6
|
+
|
7
|
+
GEMSPEC = eval(File.read('alloc_track.gemspec'))
|
8
|
+
|
9
|
+
require 'rubygems/package_task'
|
10
|
+
Gem::PackageTask.new(GEMSPEC) do |pkg|
|
11
|
+
end
|
12
|
+
|
13
|
+
# ==========================================================
|
14
|
+
# Ruby Extension
|
15
|
+
# ==========================================================
|
16
|
+
|
17
|
+
require 'rake/extensiontask'
|
18
|
+
Rake::ExtensionTask.new('alloc_track', GEMSPEC) do |ext|
|
19
|
+
ext.ext_dir = 'ext/alloc_track'
|
20
|
+
ext.lib_dir = 'lib/alloc_track'
|
21
|
+
end
|
22
|
+
task :build => :compile
|
23
|
+
|
24
|
+
# ==========================================================
|
25
|
+
# Testing
|
26
|
+
# ==========================================================
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
Rake::TestTask.new 'test' do |t|
|
30
|
+
t.test_files = FileList['test/test_*.rb']
|
31
|
+
end
|
32
|
+
task :test => :build
|
33
|
+
|
34
|
+
task :benchmark => :build do
|
35
|
+
ruby "-Ilib ./test/benchmark_alloc_track.rb"
|
36
|
+
end
|
data/alloc_track.gemspec
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = 'alloc_track'
|
3
|
+
s.version = '0.0.2'
|
4
|
+
s.summary = 'allocation tracker for ruby 2.1+'
|
5
|
+
s.description = 'tracks memory allocations with rgengc in ruby 2.1'
|
6
|
+
|
7
|
+
s.homepage = 'https://github.com/csfrancis/alloc_track'
|
8
|
+
s.authors = 'Scott Francis'
|
9
|
+
s.email = 'scott.francis@shopify.com'
|
10
|
+
s.license = 'MIT'
|
11
|
+
|
12
|
+
s.files = `git ls-files`.split("\n")
|
13
|
+
s.extensions = ['ext/alloc_track/extconf.rb']
|
14
|
+
s.add_development_dependency 'rake-compiler', '~> 0.9'
|
15
|
+
end
|
@@ -0,0 +1,270 @@
|
|
1
|
+
#include "ruby/ruby.h"
|
2
|
+
#include "ruby/intern.h"
|
3
|
+
#include "ruby/debug.h"
|
4
|
+
|
5
|
+
#define ALLOCTRACK_OBJ_BIT FL_USER18
|
6
|
+
|
7
|
+
typedef struct stat_collector {
|
8
|
+
struct stat_collector *next; /* not currently used */
|
9
|
+
VALUE thread;
|
10
|
+
int current_alloc;
|
11
|
+
int current_free;
|
12
|
+
int current_limit;
|
13
|
+
int limit_signal;
|
14
|
+
} stat_collector_t;
|
15
|
+
|
16
|
+
static VALUE mAllocTrack;
|
17
|
+
static VALUE tpval, tpval_exception;
|
18
|
+
static VALUE eAllocTrackError, eAllocTrackLimitExceeded;
|
19
|
+
static stat_collector_t *root_collector, *current_collector;
|
20
|
+
|
21
|
+
#define LOG(s) fprintf(stderr, s); fflush(stderr);
|
22
|
+
|
23
|
+
static stat_collector_t *
|
24
|
+
add_collector(VALUE thread)
|
25
|
+
{
|
26
|
+
stat_collector_t *c = (stat_collector_t *) calloc(1, sizeof(*c));
|
27
|
+
c->thread = thread;
|
28
|
+
if (root_collector) {
|
29
|
+
c->next = root_collector;
|
30
|
+
root_collector = c;
|
31
|
+
} else {
|
32
|
+
root_collector = c;
|
33
|
+
rb_tracepoint_enable(tpval);
|
34
|
+
}
|
35
|
+
return c;
|
36
|
+
}
|
37
|
+
|
38
|
+
static void
|
39
|
+
remove_collector(VALUE thread)
|
40
|
+
{
|
41
|
+
stat_collector_t *c, *prev = NULL;
|
42
|
+
|
43
|
+
for (c = root_collector; c != NULL; prev = c, c = c->next) {
|
44
|
+
if (c->thread == thread) {
|
45
|
+
if (!prev) {
|
46
|
+
root_collector = c->next;
|
47
|
+
} else {
|
48
|
+
prev->next = c->next;
|
49
|
+
}
|
50
|
+
current_collector = NULL;
|
51
|
+
free(c);
|
52
|
+
break;
|
53
|
+
}
|
54
|
+
}
|
55
|
+
|
56
|
+
if (!root_collector) {
|
57
|
+
rb_tracepoint_disable(tpval);
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
static stat_collector_t *
|
62
|
+
get_collector(VALUE thread)
|
63
|
+
{
|
64
|
+
stat_collector_t *c;
|
65
|
+
|
66
|
+
if (current_collector && current_collector->thread == thread) {
|
67
|
+
return current_collector;
|
68
|
+
}
|
69
|
+
|
70
|
+
for (c = root_collector; c != NULL; c = c->next) {
|
71
|
+
if (c->thread == thread) {
|
72
|
+
/* cache the collector so we don't have to scan the list every time */
|
73
|
+
current_collector = c;
|
74
|
+
return c;
|
75
|
+
}
|
76
|
+
}
|
77
|
+
|
78
|
+
return NULL;
|
79
|
+
}
|
80
|
+
|
81
|
+
static VALUE
|
82
|
+
started()
|
83
|
+
{
|
84
|
+
return get_collector(rb_thread_current()) ? Qtrue : Qfalse;
|
85
|
+
}
|
86
|
+
|
87
|
+
static void
|
88
|
+
validate_started()
|
89
|
+
{
|
90
|
+
if (!started()) {
|
91
|
+
rb_raise(eAllocTrackError, "allocation tracker has not been started");
|
92
|
+
}
|
93
|
+
}
|
94
|
+
|
95
|
+
static void
|
96
|
+
validate_stopped()
|
97
|
+
{
|
98
|
+
if (started()) {
|
99
|
+
rb_raise(eAllocTrackError, "allocation tracker already started");
|
100
|
+
}
|
101
|
+
}
|
102
|
+
|
103
|
+
static VALUE
|
104
|
+
start()
|
105
|
+
{
|
106
|
+
validate_stopped();
|
107
|
+
/* TODO: support multiple running trackers */
|
108
|
+
if (root_collector) {
|
109
|
+
rb_raise(eAllocTrackError, "allocation tracker already running on another thread");
|
110
|
+
}
|
111
|
+
add_collector(rb_thread_current());
|
112
|
+
return Qnil;
|
113
|
+
}
|
114
|
+
|
115
|
+
static VALUE
|
116
|
+
stop()
|
117
|
+
{
|
118
|
+
validate_started();
|
119
|
+
remove_collector(rb_thread_current());
|
120
|
+
return Qnil;
|
121
|
+
}
|
122
|
+
|
123
|
+
static VALUE
|
124
|
+
alloc()
|
125
|
+
{
|
126
|
+
validate_started();
|
127
|
+
return INT2FIX(get_collector(rb_thread_current())->current_alloc);
|
128
|
+
}
|
129
|
+
|
130
|
+
static VALUE
|
131
|
+
_free()
|
132
|
+
{
|
133
|
+
validate_started();
|
134
|
+
return INT2FIX(get_collector(rb_thread_current())->current_free);
|
135
|
+
}
|
136
|
+
|
137
|
+
static VALUE
|
138
|
+
delta()
|
139
|
+
{
|
140
|
+
stat_collector_t *c;
|
141
|
+
validate_started();
|
142
|
+
c = get_collector(rb_thread_current());
|
143
|
+
return INT2FIX(c->current_alloc - c->current_free);
|
144
|
+
}
|
145
|
+
|
146
|
+
static VALUE
|
147
|
+
do_limit(VALUE arg)
|
148
|
+
{
|
149
|
+
start();
|
150
|
+
get_collector(rb_thread_current())->current_limit = FIX2INT(arg);
|
151
|
+
return rb_yield(Qnil);
|
152
|
+
}
|
153
|
+
|
154
|
+
static VALUE
|
155
|
+
ensure_stopped(VALUE arg)
|
156
|
+
{
|
157
|
+
if (started()) {
|
158
|
+
stop();
|
159
|
+
}
|
160
|
+
return Qnil;
|
161
|
+
}
|
162
|
+
|
163
|
+
static VALUE
|
164
|
+
limit(VALUE self, VALUE num_allocs)
|
165
|
+
{
|
166
|
+
if (!rb_block_given_p()) {
|
167
|
+
rb_raise(rb_eArgError, "block required");
|
168
|
+
}
|
169
|
+
if (!RB_TYPE_P(num_allocs, T_FIXNUM)) {
|
170
|
+
rb_raise(rb_eArgError, "limit() must be passed a number");
|
171
|
+
}
|
172
|
+
validate_stopped();
|
173
|
+
return rb_ensure(do_limit, num_allocs, ensure_stopped, Qnil);
|
174
|
+
}
|
175
|
+
|
176
|
+
static int
|
177
|
+
is_collector_enabled(stat_collector_t *c)
|
178
|
+
{
|
179
|
+
return c->limit_signal == 0 ? 1 : 0;
|
180
|
+
}
|
181
|
+
|
182
|
+
static int
|
183
|
+
is_collector_limit_exceeded(stat_collector_t *c)
|
184
|
+
{
|
185
|
+
return c->limit_signal;
|
186
|
+
}
|
187
|
+
|
188
|
+
static void
|
189
|
+
tracepoint_hook(VALUE tpval, void *data)
|
190
|
+
{
|
191
|
+
stat_collector_t *c;
|
192
|
+
rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval);
|
193
|
+
rb_event_flag_t flag = rb_tracearg_event_flag(tparg);
|
194
|
+
VALUE obj = rb_tracearg_object(tparg);
|
195
|
+
switch(flag) {
|
196
|
+
case RUBY_INTERNAL_EVENT_NEWOBJ:
|
197
|
+
if ((c = get_collector(rb_thread_current())) != NULL && is_collector_enabled(c)) {
|
198
|
+
RBASIC(obj)->flags |= ALLOCTRACK_OBJ_BIT;
|
199
|
+
c->current_alloc++;
|
200
|
+
|
201
|
+
if (c->current_limit && (c->current_alloc - c->current_free) > c->current_limit) {
|
202
|
+
c->limit_signal = 1;
|
203
|
+
if (!rb_tracepoint_enabled_p(tpval_exception)) {
|
204
|
+
/*
|
205
|
+
it's not safe to raise an exception from an internal event handler.
|
206
|
+
in order to get around this, we enable a normal tracepoint on all
|
207
|
+
events and raise from there.
|
208
|
+
*/
|
209
|
+
rb_tracepoint_enable(tpval_exception);
|
210
|
+
}
|
211
|
+
}
|
212
|
+
}
|
213
|
+
break;
|
214
|
+
case RUBY_INTERNAL_EVENT_FREEOBJ:
|
215
|
+
if ((c = get_collector(rb_thread_current())) != NULL && is_collector_enabled(c) &&
|
216
|
+
(RBASIC(obj)->flags & ALLOCTRACK_OBJ_BIT)) {
|
217
|
+
c->current_free++;
|
218
|
+
}
|
219
|
+
break;
|
220
|
+
}
|
221
|
+
}
|
222
|
+
|
223
|
+
static int
|
224
|
+
any_collectors_with_exceeded_limits()
|
225
|
+
{
|
226
|
+
stat_collector_t *c;
|
227
|
+
for (c = root_collector; c != NULL; c = c->next) {
|
228
|
+
if (c->limit_signal) {
|
229
|
+
return 1;
|
230
|
+
}
|
231
|
+
}
|
232
|
+
return 0;
|
233
|
+
}
|
234
|
+
|
235
|
+
static void
|
236
|
+
exception_tracepoint_hook(VALUE tpval, void *data)
|
237
|
+
{
|
238
|
+
VALUE th = rb_thread_current();
|
239
|
+
stat_collector_t *c;
|
240
|
+
if ((c = get_collector(th)) != NULL && is_collector_limit_exceeded(c)) {
|
241
|
+
remove_collector(th);
|
242
|
+
if (!any_collectors_with_exceeded_limits()) {
|
243
|
+
rb_tracepoint_disable(tpval_exception);
|
244
|
+
}
|
245
|
+
rb_raise(eAllocTrackLimitExceeded, "allocation limit exceeded");
|
246
|
+
}
|
247
|
+
}
|
248
|
+
|
249
|
+
void
|
250
|
+
Init_alloc_track()
|
251
|
+
{
|
252
|
+
mAllocTrack = rb_define_module("AllocTrack");
|
253
|
+
|
254
|
+
rb_define_singleton_method(mAllocTrack, "start", start, 0);
|
255
|
+
rb_define_singleton_method(mAllocTrack, "started?", started, 0);
|
256
|
+
rb_define_singleton_method(mAllocTrack, "stop", stop, 0);
|
257
|
+
rb_define_singleton_method(mAllocTrack, "alloc", alloc, 0);
|
258
|
+
rb_define_singleton_method(mAllocTrack, "free", _free, 0);
|
259
|
+
rb_define_singleton_method(mAllocTrack, "delta", delta, 0);
|
260
|
+
rb_define_singleton_method(mAllocTrack, "limit", limit, 1);
|
261
|
+
|
262
|
+
eAllocTrackError = rb_define_class_under(mAllocTrack, "Error", rb_eStandardError);
|
263
|
+
eAllocTrackLimitExceeded = rb_define_class_under(mAllocTrack, "LimitExceeded", rb_eStandardError);
|
264
|
+
|
265
|
+
tpval = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ|RUBY_INTERNAL_EVENT_FREEOBJ, tracepoint_hook, NULL);
|
266
|
+
tpval_exception = rb_tracepoint_new(0, RUBY_EVENT_TRACEPOINT_ALL, exception_tracepoint_hook, NULL);
|
267
|
+
|
268
|
+
rb_gc_register_mark_object(tpval);
|
269
|
+
rb_gc_register_mark_object(tpval_exception);
|
270
|
+
}
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'mkmf'
|
2
|
+
|
3
|
+
$CFLAGS = "-O3"
|
4
|
+
|
5
|
+
have_func('rb_tracepoint_enable')
|
6
|
+
|
7
|
+
gc_event = have_const('RUBY_INTERNAL_EVENT_NEWOBJ')
|
8
|
+
|
9
|
+
if gc_event
|
10
|
+
create_makefile('alloc_track/alloc_track')
|
11
|
+
else
|
12
|
+
File.open('Makefile', 'w') do |f|
|
13
|
+
f.puts "install:\n\t\n"
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'benchmark'
|
2
|
+
require 'alloc_track/alloc_track'
|
3
|
+
|
4
|
+
def alloc_obj
|
5
|
+
Object.new
|
6
|
+
end
|
7
|
+
|
8
|
+
n = 100
|
9
|
+
i = 100000
|
10
|
+
|
11
|
+
Benchmark.bm(8) do |x|
|
12
|
+
x.report("none:") do
|
13
|
+
GC.start
|
14
|
+
n.times do
|
15
|
+
i.times { alloc_obj }
|
16
|
+
GC.start
|
17
|
+
end
|
18
|
+
end
|
19
|
+
x.report("tracking:") do
|
20
|
+
GC.start
|
21
|
+
AllocTrack.start
|
22
|
+
n.times do
|
23
|
+
i.times { alloc_obj }
|
24
|
+
GC.start
|
25
|
+
end
|
26
|
+
AllocTrack.stop
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'alloc_track/alloc_track'
|
3
|
+
|
4
|
+
class TestAllocTrack < Test::Unit::TestCase
|
5
|
+
def test_allocate
|
6
|
+
AllocTrack.start
|
7
|
+
100.times { Object.new }
|
8
|
+
assert_operator AllocTrack.delta, :>=, 100
|
9
|
+
AllocTrack.stop
|
10
|
+
end
|
11
|
+
|
12
|
+
def test_allocate_with_gc
|
13
|
+
AllocTrack.start
|
14
|
+
100.times { Object.new }
|
15
|
+
GC.start
|
16
|
+
assert_operator AllocTrack.alloc, :>=, 100
|
17
|
+
assert_operator AllocTrack.delta, :<, 100
|
18
|
+
assert_operator AllocTrack.free, :>=, 100
|
19
|
+
AllocTrack.stop
|
20
|
+
end
|
21
|
+
|
22
|
+
def test_thread_not_included
|
23
|
+
AllocTrack.start
|
24
|
+
t = Thread.new do
|
25
|
+
100.times { Object.new }
|
26
|
+
end
|
27
|
+
t.join
|
28
|
+
assert_operator AllocTrack.delta, :<, 100
|
29
|
+
AllocTrack.stop
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_delta_raises_when_not_started
|
33
|
+
assert_raise AllocTrack::Error do
|
34
|
+
AllocTrack.delta
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_limit_with_no_block
|
39
|
+
assert_raise ArgumentError do
|
40
|
+
AllocTrack.limit 100
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_limit_with_non_number
|
45
|
+
assert_raise ArgumentError do
|
46
|
+
AllocTrack.limit "foo" do
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_limit_raises
|
52
|
+
assert_raise AllocTrack::LimitExceeded do
|
53
|
+
AllocTrack.limit 10 do
|
54
|
+
200.times { Object.new }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def test_within_limit
|
60
|
+
assert_nothing_raised do
|
61
|
+
AllocTrack.limit 100 do
|
62
|
+
50.times { Object.new }
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_limit_exception_stops
|
68
|
+
assert_raise RuntimeError do
|
69
|
+
AllocTrack.limit 100 do
|
70
|
+
raise RuntimeError
|
71
|
+
end
|
72
|
+
end
|
73
|
+
refute AllocTrack.started?
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
metadata
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: alloc_track
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Scott Francis
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-08-05 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake-compiler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.9'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.9'
|
27
|
+
description: tracks memory allocations with rgengc in ruby 2.1
|
28
|
+
email: scott.francis@shopify.com
|
29
|
+
executables: []
|
30
|
+
extensions:
|
31
|
+
- ext/alloc_track/extconf.rb
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- ".gitignore"
|
35
|
+
- Gemfile
|
36
|
+
- Gemfile.lock
|
37
|
+
- LICENSE.md
|
38
|
+
- README.md
|
39
|
+
- Rakefile
|
40
|
+
- alloc_track.gemspec
|
41
|
+
- ext/alloc_track/alloc_track.c
|
42
|
+
- ext/alloc_track/extconf.rb
|
43
|
+
- test/benchmark_alloc_track.rb
|
44
|
+
- test/test_alloc_track.rb
|
45
|
+
homepage: https://github.com/csfrancis/alloc_track
|
46
|
+
licenses:
|
47
|
+
- MIT
|
48
|
+
metadata: {}
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options: []
|
51
|
+
require_paths:
|
52
|
+
- lib
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - ">="
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
requirements: []
|
64
|
+
rubyforge_project:
|
65
|
+
rubygems_version: 2.2.2
|
66
|
+
signing_key:
|
67
|
+
specification_version: 4
|
68
|
+
summary: allocation tracker for ruby 2.1+
|
69
|
+
test_files: []
|