allocations-1.0.3/0000755000175600017570000000000012646076047013100 5ustar pravipraviallocations-1.0.3/LICENSE0000644000175600017570000000203712646076047014107 0ustar pravipraviCopyright (c) 2015 GitLab B.V. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. allocations-1.0.3/README.md0000644000175600017570000000451712646076047014366 0ustar pravipravi# README A Gem for counting the amount of objects that have been allocated but not released yet. Tracking this makes it easier to see if objects are being leaked over time and if so what kind of objects. This Gem does _not_ provide any insight into where objects are being leaked or why. This Gem can _only_ be used on CRuby, it does not work on Rubinius due to TracePoint not being supported on Rubinius. ## Why The usual approach of getting object counts is by using `ObjectSpace.each_object`. For example: counts = Hash.new(0) ObjectSpace.each_object(Object) do |obj| counts[obj.class] += 1 end Sadly this approach is rather slow (e.g. for GitLab this would take roughly 800 ms) and (seems to) force a garbage collection run after every call. In other words, this isn't something you'd want to run in a production environment. The allocations Gem on the other hand doesn't suffer from this problem as it counts objects whenever they're allocated and released. This does mean that allocating objects is slightly slower than usual, but the overhead should be small enough to use this Gem in a production environment. Another big difference between ObjectSpace and this Gem is that the former gives an overview of _all_ currently retained objects whereas the allocations Gem only tracks objects that have been allocated since it was enabled. ## Usage Load the Gem: require 'allocations' Enable it: Allocations.start Getting a snapshot of the current statistics: Allocations.to_hash This will return a Hash with the keys set to various classes and the values to the amount of instances (of each class) that have not yet been released by the garbage collector. Disable it again and clear any existing statistics: Allocations.stop ## Thread Safety The C extension uses a mutex to ensure the various methods provided by this Gem can be used in different threads simultaneously. Each call to `Allocations.to_hash` returns a new Hash containing a copy of the current statistics (instead of just referring to a single global Hash). Do note that calling `Allocation.start` and `Allocations.stop` affects _all_ running threads instead of only the current thread. ## License All source code in this repository is subject to the terms of the MIT license, unless stated otherwise. A copy of this license can be found the file "LICENSE". allocations-1.0.3/allocations.gemspec0000644000175600017570000000164212646076047016760 0ustar pravipravirequire File.expand_path('../lib/allocations/version', __FILE__) Gem::Specification.new do |gem| gem.name = 'allocations' gem.version = Allocations::VERSION gem.authors = ['Yorick Peterse'] gem.email = 'yorickpeterse@gmail.com' gem.summary = 'Tracking of retained objects in CRuby' gem.homepage = 'https://gitlab.com/gitlab-org/allocations' gem.description = gem.summary gem.license = 'MIT' gem.extensions = ['ext/liballocations/extconf.rb'] gem.files = Dir.glob([ 'lib/**/*.rb', 'ext/**/*', 'README.md', 'LICENSE', '*.gemspec' ]).select { |file| File.file?(file) } gem.has_rdoc = 'yard' gem.required_ruby_version = '>= 2.1.0' gem.add_development_dependency 'rake' gem.add_development_dependency 'rake-compiler' gem.add_development_dependency 'benchmark-ips', ['~> 2.0'] gem.add_development_dependency 'rspec', ['~> 3.0'] end allocations-1.0.3/metadata.yml0000644000175600017570000000513212646076047015404 0ustar pravipravi--- !ruby/object:Gem::Specification name: allocations version: !ruby/object:Gem::Version version: 1.0.3 platform: ruby authors: - Yorick Peterse autorequire: bindir: bin cert_chain: [] date: 2015-12-31 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: rake requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: rake-compiler requirement: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: benchmark-ips requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '2.0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '2.0' - !ruby/object:Gem::Dependency name: rspec requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '3.0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '3.0' description: Tracking of retained objects in CRuby email: yorickpeterse@gmail.com executables: [] extensions: - ext/liballocations/extconf.rb extra_rdoc_files: [] files: - LICENSE - README.md - allocations.gemspec - ext/liballocations/extconf.rb - ext/liballocations/liballocations.c - ext/liballocations/liballocations.h - lib/allocations.rb - lib/allocations/version.rb homepage: https://gitlab.com/gitlab-org/allocations licenses: - MIT metadata: {} post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: 2.1.0 required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.2.5 signing_key: specification_version: 4 summary: Tracking of retained objects in CRuby test_files: [] has_rdoc: yard allocations-1.0.3/lib/0000755000175600017570000000000012646076047013646 5ustar pravipraviallocations-1.0.3/lib/allocations/0000755000175600017570000000000012646076047016156 5ustar pravipraviallocations-1.0.3/lib/allocations/version.rb0000644000175600017570000000005312646076047020166 0ustar pravipravimodule Allocations VERSION = '1.0.3' end allocations-1.0.3/lib/allocations.rb0000644000175600017570000000006712646076047016506 0ustar pravipravirequire 'liballocations' require 'allocations/version' allocations-1.0.3/ext/0000755000175600017570000000000012646076047013700 5ustar pravipraviallocations-1.0.3/ext/liballocations/0000755000175600017570000000000012646076047016677 5ustar pravipraviallocations-1.0.3/ext/liballocations/liballocations.c0000644000175600017570000001042112646076047022040 0ustar pravipravi#include "liballocations.h" st_table *object_counts; VALUE allocation_tracer; VALUE free_tracer; ID id_enabled; /** * Called whenever a new Ruby object is allocated. */ void newobj_callback(VALUE tracepoint, void* data) { rb_trace_arg_t *trace_arg = rb_tracearg_from_tracepoint(tracepoint); st_data_t count = 0; VALUE obj = rb_tracearg_object(trace_arg); VALUE klass = RBASIC_CLASS(obj); /* These aren't actually allocated so there's no point in tracking them. */ if ( klass == Qtrue || klass == Qfalse || klass == Qnil ) { return; } st_lookup(object_counts, (st_data_t) klass, &count); st_insert(object_counts, (st_data_t) klass, count + 1); } /** * Called whenever a Ruby object is about to be released. * * Important: any Ruby allocations in this function will cause CRuby to * segfault. */ void freeobj_callback(VALUE tracepoint, void* data) { rb_trace_arg_t *trace_arg = rb_tracearg_from_tracepoint(tracepoint); st_data_t count; VALUE obj = rb_tracearg_object(trace_arg); VALUE klass = RBASIC_CLASS(obj); if ( st_lookup(object_counts, (st_data_t) klass, &count) ) { if ( count > 0 && (count - 1) > 0) { st_insert(object_counts, (st_data_t) klass, count - 1); } /* Remove the entry if the count is now 0 */ else { st_delete(object_counts, (st_data_t*) &klass, NULL); } } } /** * Copies every value in an st_table to a given Ruby Hash. */ static int each_count(st_data_t key, st_data_t value, st_data_t hash_ptr) { rb_hash_aset((VALUE) hash_ptr, (VALUE) key, INT2NUM(value)); return ST_CONTINUE; } /** * Returns a Hash containing the current allocation statistics. * * The returned Hash contains its own copy of the statistics, any further object * allocations/frees will not modify said Hash. * * call-seq: * Allocations.to_hash -> Hash */ VALUE allocations_to_hash(VALUE self) { st_table *local_counts; VALUE hash; if ( !object_counts ) { return rb_hash_new(); } local_counts = st_copy(object_counts); hash = rb_hash_new(); st_foreach(local_counts, each_count, (st_data_t) hash); st_free_table(local_counts); return hash; } /** * Starts the counting of object allocations. * * call-seq: * Allocations.start -> nil */ VALUE allocations_start(VALUE self) { if ( rb_ivar_get(self, id_enabled) == Qtrue ) { return Qnil; } object_counts = st_init_numtable(); rb_ivar_set(self, id_enabled, Qtrue); rb_tracepoint_enable(allocation_tracer); rb_tracepoint_enable(free_tracer); return Qnil; } /** * Stops the counting of object allocations and clears the current statistics. * * call-seq: * Allocations.stop -> nil */ VALUE allocations_stop(VALUE self) { if ( rb_ivar_get(self, id_enabled) != Qtrue ) { return Qnil; } rb_tracepoint_disable(allocation_tracer); rb_tracepoint_disable(free_tracer); if ( object_counts ) { st_free_table(object_counts); } object_counts = NULL; rb_ivar_set(self, id_enabled, Qfalse); return Qnil; } /** * Returns true if tracking allocations has been enabled, false otherwise. * * call-seq: * Allocations.enabled? -> true/false */ VALUE allocations_enabled_p(VALUE self) { VALUE enabled = Qfalse; if ( rb_ivar_get(self, id_enabled) == Qtrue ) { enabled = Qtrue; } return enabled; } void Init_liballocations() { VALUE mAllocations = rb_define_module_under(rb_cObject, "Allocations"); allocation_tracer = rb_tracepoint_new(Qnil, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_callback, NULL); free_tracer = rb_tracepoint_new(Qnil, RUBY_INTERNAL_EVENT_FREEOBJ, freeobj_callback, NULL); id_enabled = rb_intern("enabled"); rb_define_singleton_method(mAllocations, "to_hash", allocations_to_hash, 0); rb_define_singleton_method(mAllocations, "start", allocations_start, 0); rb_define_singleton_method(mAllocations, "stop", allocations_stop, 0); rb_define_singleton_method(mAllocations, "enabled?", allocations_enabled_p, 0); rb_define_const(mAllocations, "ALLOCATION_TRACER", allocation_tracer); rb_define_const(mAllocations, "FREE_TRACER", free_tracer); } allocations-1.0.3/ext/liballocations/liballocations.h0000644000175600017570000000022712646076047022050 0ustar pravipravi#ifndef LIBALLOCATIONS_H #define LIBALLOCATIONS_H #include #include #include void Init_liballocations(); #endif allocations-1.0.3/ext/liballocations/extconf.rb0000644000175600017570000000024612646076047020674 0ustar pravipravirequire 'mkmf' if RbConfig::CONFIG['CC'] =~ /clang|gcc/ $CFLAGS << ' -pedantic' end if ENV['DEBUG'] $CFLAGS << ' -O0 -g' end create_makefile('liballocations')