allocations 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.
@@ -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".
@@ -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,11 @@
1
+ require 'mkmf'
2
+
3
+ if RbConfig::CONFIG['CC'] =~ /clang|gcc/
4
+ $CFLAGS << ' -pedantic'
5
+ end
6
+
7
+ if ENV['DEBUG']
8
+ $CFLAGS << ' -O0 -g'
9
+ end
10
+
11
+ create_makefile('liballocations')
@@ -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
+ }
@@ -0,0 +1,10 @@
1
+ #ifndef LIBALLOCATIONS_H
2
+ #define LIBALLOCATIONS_H
3
+
4
+ #include <ruby.h>
5
+ #include <ruby/debug.h>
6
+ #include <ruby/st.h>
7
+
8
+ void Init_liballocations();
9
+
10
+ #endif
@@ -0,0 +1,2 @@
1
+ require 'liballocations'
2
+ require 'allocations/version'
@@ -0,0 +1,3 @@
1
+ module Allocations
2
+ VERSION = '1.0.0'
3
+ end
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