Class: AllocationStats
- Inherits:
-
Object
- Object
- AllocationStats
- Defined in:
- lib/allocation_stats.rb,
lib/allocation_stats/allocation.rb,
lib/allocation_stats/trace_rspec.rb,
lib/allocation_stats/allocations_proxy.rb
Overview
Copyright 2014 Google Inc. All Rights Reserved. Licensed under the Apache License, Version 2.0, found in the LICENSE file.
Defined Under Namespace
Classes: Allocation, AllocationsProxy
Constant Summary collapse
- RUBYLIBDIR =
a convenience constant
RbConfig::CONFIG["rubylibdir"]
- GEMDIR =
a convenience constant
Gem.dir
- TRACE_RSPEC_HOOK =
proc do |example| # TODO s/false/some config option/ if true # wrap loosely stats = AllocationStats.new(burn: 1).trace { example.run } else # wrap tightly # Super hacky, but presumably more correct results? stats = AllocationStats.new(burn: 1) example_block = @example.instance_variable_get(:@example_block).clone @example.instance_variable_set( :@example_block, Proc.new do stats.trace { example_block.call } end ) example.run end allocations = stats.allocations(alias_paths: true). not_from("rspec-core").not_from("rspec-expectations").not_from("rspec-mocks"). group_by(:sourcefile, :sourceline, :class). sort_by_count AllocationStats.add_to_top_sites(allocations.all, @example.location) end
Instance Attribute Summary collapse
-
#burn ⇒ Fixnum
burn count for block tracing.
-
#gc_profiler_report ⇒ Object
Returns the value of attribute gc_profiler_report.
-
#new_allocations ⇒ Array
readonly
allocation data for all new objects that were allocated during the #initialize block.
Class Method Summary collapse
-
.add_to_top_sites(allocations, location, limit = 10) ⇒ Object
Add a Hash of allocation groups (derived from an
AllocationStats.allocations...group_by(...)
) to the top allocation sites (file/line/class groups). -
.top_sites ⇒ Object
private
Read the sorted list of the top "sites", that is, top file/line/class groups, encountered while tracing RSpec.
-
.top_sites=(value) ⇒ Object
private
Write to the sorted list of the top "sites", that is, top file/line/class groups, encountered while tracing RSpec.
-
.top_sites_text ⇒ Object
private
Textual String representing the sorted list of the top allocation sites.
- .trace(&block) ⇒ Object
- .trace_rspec ⇒ Object
Instance Method Summary collapse
-
#allocations(alias_paths: false) ⇒ Object
Proxy for the @new_allocations array that allows for individual filtering, sorting, and grouping of the Allocation objects.
- #collect_new_allocations ⇒ Object
-
#initialize(burn: 0) ⇒ AllocationStats
constructor
A new instance of AllocationStats.
-
#inspect ⇒ Object
Inspect @new_allocations, the canonical array of Allocation objects.
-
#start ⇒ Object
Begin tracing object allocations.
-
#stop ⇒ Object
Stop tracing object allocations that was started with AllocationStats#start.
- #trace(&block) ⇒ Object
- #trace_block ⇒ Object
Constructor Details
#initialize(burn: 0) ⇒ AllocationStats
Returns a new instance of AllocationStats.
41 42 43 44 45 46 |
# 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 |
Instance Attribute Details
#burn ⇒ 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.
28 29 30 |
# File 'lib/allocation_stats.rb', line 28 def burn @burn end |
#gc_profiler_report ⇒ Object
Returns the value of attribute gc_profiler_report.
30 31 32 |
# File 'lib/allocation_stats.rb', line 30 def gc_profiler_report @gc_profiler_report end |
#new_allocations ⇒ Array (readonly)
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.
39 40 41 |
# File 'lib/allocation_stats.rb', line 39 def new_allocations @new_allocations end |
Class Method Details
.add_to_top_sites(allocations, location, limit = 10) ⇒ Object
Add a Hash of allocation groups (derived from an
AllocationStats.allocations...group_by(...)
) to the top allocation sites
(file/line/class groups).
72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 |
# 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 |
.top_sites ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Read the sorted list of the top "sites", that is, top file/line/class groups, encountered while tracing RSpec.
52 53 54 |
# File 'lib/allocation_stats/trace_rspec.rb', line 52 def self.top_sites @top_sites end |
.top_sites=(value) ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Write to the sorted list of the top "sites", that is, top file/line/class groups, encountered while tracing RSpec.
60 61 62 |
# File 'lib/allocation_stats/trace_rspec.rb', line 60 def self.top_sites=(value) @top_sites = value end |
.top_sites_text ⇒ Object
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
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.
105 106 107 108 109 110 111 112 113 114 115 116 117 |
# 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 |
.trace(&block) ⇒ Object
48 49 50 51 |
# File 'lib/allocation_stats.rb', line 48 def self.trace(&block) allocation_stats = AllocationStats.new allocation_stats.trace(&block) end |
.trace_rspec ⇒ Object
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# 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 |
Instance Method Details
#allocations(alias_paths: false) ⇒ Object
Proxy for the @new_allocations array that allows for individual filtering, sorting, and grouping of the Allocation objects.
131 132 133 |
# File 'lib/allocation_stats.rb', line 131 def allocations(alias_paths: false) AllocationsProxy.new(@new_allocations, alias_paths: alias_paths) end |
#collect_new_allocations ⇒ Object
104 105 106 107 108 109 110 111 112 113 114 |
# 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 ⇒ Object
Inspect @new_allocations, the canonical array of Allocation objects.
125 126 127 |
# File 'lib/allocation_stats.rb', line 125 def inspect @new_allocations.inspect end |
#start ⇒ Object
Begin tracing object allocations. Tracing must be stopped with AllocationStats#stop. Garbage collection is disabled while tracing is enabled.
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
# 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 ⇒ Object
Stop tracing object allocations that was started with AllocationStats#start.
117 118 119 120 121 122 |
# 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 |
#trace(&block) ⇒ Object
53 54 55 56 57 58 59 |
# File 'lib/allocation_stats.rb', line 53 def trace(&block) if block_given? trace_block(&block) else start end end |
#trace_block ⇒ Object
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# 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 |