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.
@@ -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