allocations 1.0.0
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/LICENSE +19 -0
- data/README.md +70 -0
- data/allocations.gemspec +28 -0
- data/ext/liballocations/extconf.rb +11 -0
- data/ext/liballocations/liballocations.c +207 -0
- data/ext/liballocations/liballocations.h +10 -0
- data/lib/allocations.rb +2 -0
- data/lib/allocations/version.rb +3 -0
- metadata +108 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 3018875c080ce9c5819d0333a7e0a0fc14a33902
|
4
|
+
data.tar.gz: 2db420d0079e764b2ff8d3e81e3d6bc9fb5bd2f6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8dceef4fcddc3047711a7158dafea4e29662bb9b0a24f0c0625b2d016c579ea86073005462aecc0db9aaea95b4485d4f87ee3e03d51a428d7f64487cb0830390
|
7
|
+
data.tar.gz: 4ab041cf427df77b169bbf068fbd041ad463f1f46c6a386fc97e765925df45fb1a414ec532057dee29bc8d313c4c66681ca00cc0c61fa425c3ec3fe5317baca2
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2015 GitLab B.V.
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# README
|
2
|
+
|
3
|
+
A Gem for counting the amount of objects that have been allocated but not
|
4
|
+
released yet. Tracking this makes it easier to see if objects are being leaked
|
5
|
+
over time and if so what kind of objects. This Gem does _not_ provide any
|
6
|
+
insight into where objects are being leaked or why.
|
7
|
+
|
8
|
+
This Gem can _only_ be used on CRuby, it does not work on Rubinius due to
|
9
|
+
TracePoint not being supported on Rubinius.
|
10
|
+
|
11
|
+
## Why
|
12
|
+
|
13
|
+
The usual approach of getting object counts is by using
|
14
|
+
`ObjectSpace.each_object`. For example:
|
15
|
+
|
16
|
+
counts = Hash.new(0)
|
17
|
+
|
18
|
+
ObjectSpace.each_object(Object) do |obj|
|
19
|
+
counts[obj.class] += 1
|
20
|
+
end
|
21
|
+
|
22
|
+
Sadly this approach is rather slow (e.g. for GitLab this would take roughly
|
23
|
+
800 ms) and (seems to) force a garbage collection run after every call. In other
|
24
|
+
words, this isn't something you'd want to run in a production environment.
|
25
|
+
|
26
|
+
The allocations Gem on the other hand doesn't suffer from this problem as it
|
27
|
+
counts objects whenever they're allocated and released. This does mean that
|
28
|
+
allocating objects is slightly slower than usual, but the overhead should be
|
29
|
+
small enough to use this Gem in a production environment.
|
30
|
+
|
31
|
+
Another big difference between ObjectSpace and this Gem is that the former gives
|
32
|
+
an overview of _all_ currently retained objects whereas the allocations Gem only
|
33
|
+
tracks objects that have been allocated since it was enabled.
|
34
|
+
|
35
|
+
## Usage
|
36
|
+
|
37
|
+
Load the Gem:
|
38
|
+
|
39
|
+
require 'allocations'
|
40
|
+
|
41
|
+
Enable it:
|
42
|
+
|
43
|
+
Allocations.start
|
44
|
+
|
45
|
+
Getting a snapshot of the current statistics:
|
46
|
+
|
47
|
+
Allocations.to_hash
|
48
|
+
|
49
|
+
This will return a Hash with the keys set to various classes and the values to
|
50
|
+
the amount of instances (of each class) that have not yet been released by the
|
51
|
+
garbage collector.
|
52
|
+
|
53
|
+
Disable it again and clear any existing statistics:
|
54
|
+
|
55
|
+
Allocations.stop
|
56
|
+
|
57
|
+
## Thread Safety
|
58
|
+
|
59
|
+
The C extension uses a mutex to ensure the various methods provided by this Gem
|
60
|
+
can be used in different threads simultaneously. Each call to
|
61
|
+
`Allocations.to_hash` returns a new Hash containing a copy of the current
|
62
|
+
statistics (instead of just referring to a single global Hash).
|
63
|
+
|
64
|
+
Do note that calling `Allocation.start` and `Allocations.stop` affects _all_
|
65
|
+
running threads instead of only the current thread.
|
66
|
+
|
67
|
+
## License
|
68
|
+
|
69
|
+
All source code in this repository is subject to the terms of the MIT license,
|
70
|
+
unless stated otherwise. A copy of this license can be found the file "LICENSE".
|
data/allocations.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require File.expand_path('../lib/allocations/version', __FILE__)
|
2
|
+
|
3
|
+
Gem::Specification.new do |gem|
|
4
|
+
gem.name = 'allocations'
|
5
|
+
gem.version = Allocations::VERSION
|
6
|
+
gem.authors = ['Yorick Peterse']
|
7
|
+
gem.email = 'yorickpeterse@gmail.com'
|
8
|
+
gem.summary = 'Tracking of retained objects in CRuby'
|
9
|
+
gem.homepage = 'https://gitlab.com/gitlab/allocations'
|
10
|
+
gem.description = gem.summary
|
11
|
+
gem.license = 'MIT'
|
12
|
+
|
13
|
+
gem.files = Dir.glob([
|
14
|
+
'lib/**/*.rb',
|
15
|
+
'ext/**/*',
|
16
|
+
'README.md',
|
17
|
+
'LICENSE',
|
18
|
+
'*.gemspec'
|
19
|
+
]).select { |file| File.file?(file) }
|
20
|
+
|
21
|
+
gem.has_rdoc = 'yard'
|
22
|
+
gem.required_ruby_version = '>= 2.1.0'
|
23
|
+
|
24
|
+
gem.add_development_dependency 'rake'
|
25
|
+
gem.add_development_dependency 'rake-compiler'
|
26
|
+
gem.add_development_dependency 'benchmark-ips', ['~> 2.0']
|
27
|
+
gem.add_development_dependency 'rspec', ['~> 3.0']
|
28
|
+
end
|
@@ -0,0 +1,207 @@
|
|
1
|
+
#include "liballocations.h"
|
2
|
+
|
3
|
+
st_table *object_counts;
|
4
|
+
|
5
|
+
VALUE mutex;
|
6
|
+
|
7
|
+
VALUE allocation_tracer;
|
8
|
+
VALUE free_tracer;
|
9
|
+
|
10
|
+
ID id_enabled;
|
11
|
+
|
12
|
+
/**
|
13
|
+
* Called whenever a new Ruby object is allocated.
|
14
|
+
*/
|
15
|
+
void newobj_callback(VALUE tracepoint, void* data) {
|
16
|
+
rb_trace_arg_t *trace_arg = rb_tracearg_from_tracepoint(tracepoint);
|
17
|
+
|
18
|
+
st_data_t count = 0;
|
19
|
+
|
20
|
+
VALUE obj = rb_tracearg_object(trace_arg);
|
21
|
+
VALUE klass = RBASIC_CLASS(obj);
|
22
|
+
|
23
|
+
/* These aren't actually allocated so there's no point in tracking them. */
|
24
|
+
if ( klass == Qtrue || klass == Qfalse || klass == Qnil ) {
|
25
|
+
return;
|
26
|
+
}
|
27
|
+
|
28
|
+
rb_mutex_lock(mutex);
|
29
|
+
|
30
|
+
st_lookup(object_counts, (st_data_t) klass, &count);
|
31
|
+
st_insert(object_counts, (st_data_t) klass, count + 1);
|
32
|
+
|
33
|
+
rb_mutex_unlock(mutex);
|
34
|
+
}
|
35
|
+
|
36
|
+
/**
|
37
|
+
* Called whenever a Ruby object is about to be released.
|
38
|
+
*
|
39
|
+
* Important: any Ruby allocations in this function will cause CRuby to
|
40
|
+
* segfault.
|
41
|
+
*/
|
42
|
+
void freeobj_callback(VALUE tracepoint, void* data) {
|
43
|
+
rb_trace_arg_t *trace_arg = rb_tracearg_from_tracepoint(tracepoint);
|
44
|
+
|
45
|
+
st_data_t count;
|
46
|
+
|
47
|
+
VALUE obj = rb_tracearg_object(trace_arg);
|
48
|
+
VALUE klass = RBASIC_CLASS(obj);
|
49
|
+
|
50
|
+
rb_mutex_lock(mutex);
|
51
|
+
|
52
|
+
if ( st_lookup(object_counts, (st_data_t) klass, &count) ) {
|
53
|
+
if ( count > 0 && (count - 1) > 0) {
|
54
|
+
st_insert(object_counts, (st_data_t) klass, count - 1);
|
55
|
+
}
|
56
|
+
/* Remove the entry if the count is now 0 */
|
57
|
+
else {
|
58
|
+
st_delete(object_counts, (st_data_t*) &klass, NULL);
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
rb_mutex_unlock(mutex);
|
63
|
+
}
|
64
|
+
|
65
|
+
/**
|
66
|
+
* Copies every value in an st_table to a given Ruby Hash.
|
67
|
+
*/
|
68
|
+
static int each_count(st_data_t key, st_data_t value, st_data_t hash_ptr) {
|
69
|
+
rb_hash_aset((VALUE) hash_ptr, (VALUE) key, INT2NUM(value));
|
70
|
+
|
71
|
+
return ST_CONTINUE;
|
72
|
+
}
|
73
|
+
|
74
|
+
/**
|
75
|
+
* Returns a Hash containing the current allocation statistics.
|
76
|
+
*
|
77
|
+
* The returned Hash contains its own copy of the statistics, any further object
|
78
|
+
* allocations/frees will not modify said Hash.
|
79
|
+
*
|
80
|
+
* call-seq:
|
81
|
+
* Allocations.to_hash -> Hash
|
82
|
+
*/
|
83
|
+
VALUE allocations_to_hash(VALUE self) {
|
84
|
+
st_table *local_counts;
|
85
|
+
VALUE hash;
|
86
|
+
|
87
|
+
rb_mutex_lock(mutex);
|
88
|
+
|
89
|
+
if ( !object_counts ) {
|
90
|
+
rb_mutex_unlock(mutex);
|
91
|
+
|
92
|
+
return rb_hash_new();
|
93
|
+
}
|
94
|
+
|
95
|
+
local_counts = st_copy(object_counts);
|
96
|
+
|
97
|
+
rb_mutex_unlock(mutex);
|
98
|
+
|
99
|
+
hash = rb_hash_new();
|
100
|
+
|
101
|
+
st_foreach(local_counts, each_count, (st_data_t) hash);
|
102
|
+
|
103
|
+
st_free_table(local_counts);
|
104
|
+
|
105
|
+
return hash;
|
106
|
+
}
|
107
|
+
|
108
|
+
/**
|
109
|
+
* Starts the counting of object allocations.
|
110
|
+
*
|
111
|
+
* call-seq:
|
112
|
+
* Allocations.start -> nil
|
113
|
+
*/
|
114
|
+
VALUE allocations_start(VALUE self) {
|
115
|
+
rb_mutex_lock(mutex);
|
116
|
+
|
117
|
+
if ( rb_ivar_get(self, id_enabled) == Qtrue ) {
|
118
|
+
rb_mutex_unlock(mutex);
|
119
|
+
|
120
|
+
return Qnil;
|
121
|
+
}
|
122
|
+
|
123
|
+
object_counts = st_init_numtable();
|
124
|
+
|
125
|
+
rb_ivar_set(self, id_enabled, Qtrue);
|
126
|
+
|
127
|
+
rb_mutex_unlock(mutex);
|
128
|
+
|
129
|
+
rb_tracepoint_enable(allocation_tracer);
|
130
|
+
rb_tracepoint_enable(free_tracer);
|
131
|
+
|
132
|
+
return Qnil;
|
133
|
+
}
|
134
|
+
|
135
|
+
/**
|
136
|
+
* Stops the counting of object allocations and clears the current statistics.
|
137
|
+
*
|
138
|
+
* call-seq:
|
139
|
+
* Allocations.stop -> nil
|
140
|
+
*/
|
141
|
+
VALUE allocations_stop(VALUE self) {
|
142
|
+
rb_mutex_lock(mutex);
|
143
|
+
|
144
|
+
if ( rb_ivar_get(self, id_enabled) != Qtrue ) {
|
145
|
+
rb_mutex_unlock(mutex);
|
146
|
+
|
147
|
+
return Qnil;
|
148
|
+
}
|
149
|
+
|
150
|
+
rb_tracepoint_disable(allocation_tracer);
|
151
|
+
rb_tracepoint_disable(free_tracer);
|
152
|
+
|
153
|
+
if ( object_counts ) {
|
154
|
+
st_free_table(object_counts);
|
155
|
+
}
|
156
|
+
|
157
|
+
object_counts = NULL;
|
158
|
+
|
159
|
+
rb_ivar_set(self, id_enabled, Qfalse);
|
160
|
+
|
161
|
+
rb_mutex_unlock(mutex);
|
162
|
+
|
163
|
+
return Qnil;
|
164
|
+
}
|
165
|
+
|
166
|
+
/**
|
167
|
+
* Returns true if tracking allocations has been enabled, false otherwise.
|
168
|
+
*
|
169
|
+
* call-seq:
|
170
|
+
* Allocations.enabled? -> true/false
|
171
|
+
*/
|
172
|
+
VALUE allocations_enabled_p(VALUE self) {
|
173
|
+
VALUE enabled = Qfalse;
|
174
|
+
|
175
|
+
rb_mutex_lock(mutex);
|
176
|
+
|
177
|
+
if ( rb_ivar_get(self, id_enabled) == Qtrue ) {
|
178
|
+
enabled = Qtrue;
|
179
|
+
}
|
180
|
+
|
181
|
+
rb_mutex_unlock(mutex);
|
182
|
+
|
183
|
+
return enabled;
|
184
|
+
}
|
185
|
+
|
186
|
+
void Init_liballocations() {
|
187
|
+
VALUE mAllocations = rb_define_module_under(rb_cObject, "Allocations");
|
188
|
+
|
189
|
+
allocation_tracer = rb_tracepoint_new(Qnil, RUBY_INTERNAL_EVENT_NEWOBJ,
|
190
|
+
newobj_callback, NULL);
|
191
|
+
|
192
|
+
free_tracer = rb_tracepoint_new(Qnil, RUBY_INTERNAL_EVENT_FREEOBJ,
|
193
|
+
freeobj_callback, NULL);
|
194
|
+
|
195
|
+
mutex = rb_mutex_new();
|
196
|
+
|
197
|
+
id_enabled = rb_intern("enabled");
|
198
|
+
|
199
|
+
rb_define_singleton_method(mAllocations, "to_hash", allocations_to_hash, 0);
|
200
|
+
rb_define_singleton_method(mAllocations, "start", allocations_start, 0);
|
201
|
+
rb_define_singleton_method(mAllocations, "stop", allocations_stop, 0);
|
202
|
+
rb_define_singleton_method(mAllocations, "enabled?", allocations_enabled_p, 0);
|
203
|
+
|
204
|
+
rb_define_const(mAllocations, "MUTEX", mutex);
|
205
|
+
rb_define_const(mAllocations, "ALLOCATION_TRACER", allocation_tracer);
|
206
|
+
rb_define_const(mAllocations, "FREE_TRACER", free_tracer);
|
207
|
+
}
|
data/lib/allocations.rb
ADDED
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: allocations
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Yorick Peterse
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-12-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rake
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake-compiler
|
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: benchmark-ips
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.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: '3.0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
description: Tracking of retained objects in CRuby
|
70
|
+
email: yorickpeterse@gmail.com
|
71
|
+
executables: []
|
72
|
+
extensions: []
|
73
|
+
extra_rdoc_files: []
|
74
|
+
files:
|
75
|
+
- LICENSE
|
76
|
+
- README.md
|
77
|
+
- allocations.gemspec
|
78
|
+
- ext/liballocations/extconf.rb
|
79
|
+
- ext/liballocations/liballocations.c
|
80
|
+
- ext/liballocations/liballocations.h
|
81
|
+
- lib/allocations.rb
|
82
|
+
- lib/allocations/version.rb
|
83
|
+
homepage: https://gitlab.com/gitlab/allocations
|
84
|
+
licenses:
|
85
|
+
- MIT
|
86
|
+
metadata: {}
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - ">="
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: 2.1.0
|
96
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - ">="
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '0'
|
101
|
+
requirements: []
|
102
|
+
rubyforge_project:
|
103
|
+
rubygems_version: 2.4.8
|
104
|
+
signing_key:
|
105
|
+
specification_version: 4
|
106
|
+
summary: Tracking of retained objects in CRuby
|
107
|
+
test_files: []
|
108
|
+
has_rdoc: yard
|