class AllocationStats
Container for an aggregation of object allocation data. Pass a block to {#trace AllocationStats.new
.trace}. Then use the AllocationStats
object's public interface to dig into the data and discover useful information.
Copyright 2014 Google Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0, found in the LICENSE file.
Copyright 2014 Google Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0, found in the LICENSE file.
Constants
- GEMDIR
a convenience constant
- RUBYLIBDIR
a convenience constant
- TRACE_RSPEC_HOOK
Attributes
@!attribute [rw] burn @return [Fixnum] burn count for block tracing. Defaults to 0. When called with a block, trace
will yield the block @burn-times before actually tracing the object allocations. This offers the benefit of pre-memoizing objects, and loading any required Ruby files before tracing.
@!attribute [r] new_allocations
@return [Array] allocation data for all new objects that were allocated during the {#initialize} block. It is better to use {#allocations}, which returns an {AllocationsProxy}, which has a much more convenient, domain-specific API for filtering, sorting, and grouping {Allocation} objects, than this plain Array object.
Public Class Methods
Add a Hash of allocation groups (derived from an `AllocationStats.allocations…group_by(…)`) to the top allocation sites (file/line/class groups).
@param [Hash] allocations @param [String] location the RSpec spec location that was being executed
when the allocations occurred
@param [Fixnum] limit size of the top sites Array
# File lib/allocation_stats/trace_rspec.rb, line 72 def self.add_to_top_sites(allocations, location, limit = 10) if allocations.size > limit allocations = allocations.to_a[0...limit].to_h # top 10 or so end # TODO: not a great algorithm so far... can instead: # * oly insert when an allocation won't be immediately dropped # * insert into correct position and pop rather than sort and slice allocations.each do |k,v| next if k[0] =~ /spec_helper\.rb$/ if site = @top_sites.detect { |s| s[:key] == k } if lower_idx = site[:counts].index { |loc, count| count < v.size } site[:counts].insert(lower_idx, [location, v.size]) else site[:counts] << [location, v.size] end site[:counts].pop if site[:counts].size > 3 else @top_sites << { key: k, counts: [[location, v.size]] } end end @top_sites = @top_sites.sort_by! { |site| -site[:counts].map(&:last).max }[0...limit] end
# File lib/allocation_stats.rb, line 41 def initialize(burn: 0) @burn = burn # Copying ridiculous workaround from: # https://github.com/ruby/ruby/commit/7170baa878ac0223f26fcf8c8bf25492415e6eaa Class.name end
Read the sorted list of the top “sites”, that is, top file/line/class groups, encountered while tracing RSpec.
@api private
# File lib/allocation_stats/trace_rspec.rb, line 52 def self.top_sites @top_sites end
Write to the sorted list of the top “sites”, that is, top file/line/class groups, encountered while tracing RSpec.
@api private
# File lib/allocation_stats/trace_rspec.rb, line 60 def self.top_sites=(value) @top_sites = value end
Textual String representing the sorted list of the top allocation sites. For each site, this String includes the number of allocations, the class, the sourcefile, the sourceline, and the location of the RSpec spec.
@api private
# File lib/allocation_stats/trace_rspec.rb, line 105 def self.top_sites_text return "" if @top_sites.empty? result = "Top #{@top_sites.size} allocation sites:\n" @top_sites.each do |site| result << " %s allocations at %s:%d\n" % [site[:key][2], site[:key][0], site[:key][1]] site[:counts].each do |location, count| result << " %3d allocations during %s\n" % [count, location] end end result end
# File lib/allocation_stats.rb, line 48 def self.trace(&block) allocation_stats = AllocationStats.new allocation_stats.trace(&block) end
# File lib/allocation_stats/trace_rspec.rb, line 5 def self.trace_rspec @top_sites = [] if (!const_defined?(:RSpec)) raise StandardError, "Cannot trace RSpec until RSpec is loaded" end ::RSpec.configure do |config| config.around(&TRACE_RSPEC_HOOK) end at_exit do puts AllocationStats.top_sites_text end end
Public Instance Methods
Proxy for the @new_allocations array that allows for individual filtering, sorting, and grouping of the Allocation
objects.
# File lib/allocation_stats.rb, line 131 def allocations(alias_paths: false) AllocationsProxy.new(@new_allocations, alias_paths: alias_paths) end
# File lib/allocation_stats.rb, line 104 def collect_new_allocations @new_allocations = [] ObjectSpace.each_object.to_a.each do |object| next if ObjectSpace.allocation_sourcefile(object).nil? next if ObjectSpace.allocation_sourcefile(object) == __FILE__ next if @existing_object_ids[object.__id__ / 1000] && @existing_object_ids[object.__id__ / 1000].include?(object.__id__) @new_allocations << Allocation.new(object) end end
Inspect @new_allocations, the canonical array of {Allocation} objects.
# File lib/allocation_stats.rb, line 125 def inspect @new_allocations.inspect end
Begin tracing object allocations. Tracing must be stopped with AllocationStats#stop
. Garbage collection is disabled while tracing is enabled.
# File lib/allocation_stats.rb, line 88 def start GC.start GC.disable @existing_object_ids = {} ObjectSpace.each_object.to_a.each do |object| @existing_object_ids[object.__id__ / 1000] ||= [] @existing_object_ids[object.__id__ / 1000] << object.__id__ end ObjectSpace.trace_object_allocations_start return self end
Stop tracing object allocations that was started with AllocationStats#start
.
# File lib/allocation_stats.rb, line 117 def stop collect_new_allocations ObjectSpace.trace_object_allocations_stop ObjectSpace.trace_object_allocations_clear profile_and_start_gc end
# File lib/allocation_stats.rb, line 53 def trace(&block) if block_given? trace_block(&block) else start end end
# File lib/allocation_stats.rb, line 61 def trace_block @burn.times { yield } GC.start GC.disable @existing_object_ids = {} ObjectSpace.each_object.to_a.each do |object| @existing_object_ids[object.__id__ / 1000] ||= [] @existing_object_ids[object.__id__ / 1000] << object.__id__ end ObjectSpace.trace_object_allocations { yield } collect_new_allocations ObjectSpace.trace_object_allocations_clear profile_and_start_gc return self end
Private Instance Methods
# File lib/allocation_stats.rb, line 135 def profile_and_start_gc GC::Profiler.enable GC.enable GC.start @gc_profiler_report = GC::Profiler.result GC::Profiler.disable end