class AllocationStats::AllocationsProxy

replace the previous grouping transform.
grouping transform is allowed; subsequent calls to {#group_by} will only
Only one method will allow a grouping transform: {#group_by}. Only one
——————
Grouping Transform
——————
Mapping Transforms
filtering transforms together with a logical disjunction.
(_“and”_) of of filtering transforms. Presently there is no way to _“or”_
is applied serially, so that ‘@wheres` represents a logical conjunction
an Array, `@wheres`. When the result set is finally retrieved, each where
Methods that filter the collection of Allocations will add a transform to
——————–
Filtering Transforms
Allocations until a call to {#to_a} ({#all}) resolves them.
list of transforms. The transforms will not be applied to the collection of
order. Apply methods such as {#from} and {#group_by} to build the internal
Use of the Command pattern and Procs allows for transform-chaining in any
========
Chaining
transformed collection of Allocations.
the list of transforms it will ultimately perform, before retrieving the
This class uses the Command pattern heavily, in order to build and maintain
for transforming (filtering, sorting, and grouping) allocation information.
idea behind this class is merely to provide some domain-specific methods
AllocationsProxy acts as a proxy for an array of Allocation objects. The

def alias_paths(value = nil)

will return the @alias_paths.
AllocationStats object will be returned. If no value is passed in, this
If a value is passed in, @alias_paths will be set to this value, and the
def alias_paths(value = nil)
  # reader
  return @alias_paths if value.nil?
  # writer
  @alias_paths = value
  return self
end

def at_least(count)

Parameters:
  • count (Fixnum) -- the minimum number of Allocations for each group to
def at_least(count)
  @mappers << Proc.new do |allocations|
    allocations.delete_if { |key,value| value.size < count }
  end
  self
end

def attribute_getters(faux_attributes)

def attribute_getters(faux_attributes)
  faux_attributes.map do |faux|
    if faux == :sourcefile
      lambda { |allocation| allocation.sourcefile(@alias_paths) }
    elsif Allocation::HELPERS.include?(faux) ||
          Allocation::ATTRIBUTES.include?(faux)
      lambda { |allocation| allocation.__send__(faux) }
    else
      lambda { |allocation| allocation.object.__send__(faux) }
    end
  end
end

def bytes

Allocations) to the sum of the Allocation `#memsizes` within.
this transform maps each value in the Hash (which is an Array of
* If the current result set is a Hash (meaning it has been grouped), then
each Allocation to its `#memsize`.
* If the current result set is an Array, then this transform just maps

two ways:
Map to bytes via {Allocation#memsize #memsize}. This is done in one of
def bytes
  @mappers << Proc.new do |allocations|
    if allocations.is_a? Array
      allocations.map(&:memsize)
    elsif allocations.is_a? Hash
      bytes_h = {}
      allocations.each do |key, allocations|
        bytes_h[key] = allocations.inject(0) { |sum, allocation| sum + allocation.memsize }
      end
      bytes_h
    end
  end
  self
end

def from(pattern)

Parameters:
  • pattern (String) -- the partial file path to match against, in the
def from(pattern)
  @wheres << Proc.new do |allocations|
    allocations.select { |allocation| allocation.sourcefile[pattern] }
  end
  self
end

def from_pwd

documentation for {AllocationsProxy} for more information about chaining.
`#from_pwd` can be called multiple times, adding to `@wheres`. See

includes the present working directory.
Select allocations for which the {Allocation#sourcefile sourcefile}
def from_pwd
  @wheres << Proc.new do |allocations|
    allocations.select { |allocation| allocation.sourcefile[@pwd] }
  end
  self
end

def group_by(*args)

to `:classpath`, the classpath where the allocation occured).
In this case, `:class` is the class of the allocated object (as opposed

* :classpath, :method_id, :class
* :sourcefile, :method_id, :class
* :sourcefile, :sourceline, :class

Commonly, you might want to group allocations by:
Group allocations by one or more attributes, that is, a list of symbols.
def group_by(*args)
  @group_keys = args
  @group_by = Proc.new do |allocations|
    getters = attribute_getters(@group_keys)
    allocations.group_by do |allocation|
      getters.map { |getter| getter.call(allocation) }
    end
  end
  self
end

def initialize(allocations, alias_paths: false)

Parameters:
  • allocations (Array) -- array of Allocation objects
def initialize(allocations, alias_paths: false)
  @allocations = allocations
  @pwd = Dir.pwd
  @wheres = []
  @group_by = nil
  @mappers  = []
  @alias_paths = alias_paths
end

def not_from(pattern)

Parameters:
  • pattern (String) -- the partial file path to match against, in the
def not_from(pattern)
  @wheres << Proc.new do |allocations|
    allocations.reject { |allocation| allocation.sourcefile[pattern] }
  end
  self
end

def sort_by_size

Sort allocation groups by the number of allocations in each group.
def sort_by_size
  @mappers << Proc.new do |allocations|
    allocations.sort_by { |key, value| -value.size }
               .inject({}) { |hash, pair| hash[pair[0]] = pair[1]; hash }
  end
  self
end

def to_a

aliased as `:all`.
Apply all transformations to the contained list of Allocations. This is
def to_a
  results = @allocations
  @wheres.each do |where|
    results = where.call(results)
  end
  # First apply group_by
  results = @group_by.call(results) if @group_by
  # Apply each mapper
  @mappers.each do |mapper|
    results = mapper.call(results)
  end
  results
end

def to_json

Resolve all transformations, and convert the resultant Array to JSON.
def to_json
  to_a.to_json
end

def to_text(columns: DEFAULT_COLUMNS)

Returns:
  • (String) - information about the Allocations, in a tabular format

Parameters:
  • columns (Array) -- a list of columns to print out
def to_text(columns: DEFAULT_COLUMNS)
  resolved = to_a
  # if resolved is an Array of Allocations
  if resolved.is_a?(Array) && resolved.first.is_a?(Allocation)
    to_text_from_plain(resolved, columns: columns)
  # if resolved is a Hash (was grouped)
  elsif resolved.is_a?(Hash)
    to_text_from_groups(resolved)
  end
end

def to_text_from_groups(resolved)

Other tags:
    Private: -
def to_text_from_groups(resolved)
  columns = @group_keys + ["count"]
  keys = resolved.is_a?(Hash) ? resolved.keys : resolved.map(&:first)
  widths = columns.each_with_index.map do |column, idx|
    (keys.map { |group| group[idx].to_s.size } << columns[idx].to_s.size).max
  end
  text = []
  text << columns.each_with_index.map { |attr, idx|
    attr.to_s.center(widths[idx])
  }.join("  ").rstrip
  text << widths.map { |width| "-" * width }.join("  ")
  text += resolved.map { |group, allocations|
    line = group.each_with_index.map { |attr, idx|
      NUMERIC_COLUMNS.include?(columns[idx]) ?
        attr.to_s.rjust(widths[idx]) :
        attr.to_s.ljust(widths[idx])
    }.join("  ")
    line << "  " + allocations.size.to_s.rjust(5)
  }
  text.join("\n")
end

def to_text_from_plain(resolved, columns: DEFAULT_COLUMNS)

Other tags:
    Private: -
def to_text_from_plain(resolved, columns: DEFAULT_COLUMNS)
  getters = attribute_getters(columns)
  widths = getters.each_with_index.map do |attr, idx|
    (resolved.map { |a| attr.call(a).to_s.size } << columns[idx].to_s.size).max
  end
  text = []
  text << columns.each_with_index.map { |attr, idx|
    attr.to_s.center(widths[idx])
  }.join("  ").rstrip
  text << widths.map { |width| "-" * width }.join("  ")
  text += resolved.map { |allocation|
    getters.each_with_index.map { |getter, idx|
      value = getter.call(allocation).to_s
      NUMERIC_COLUMNS.include?(columns[idx]) ? value.rjust(widths[idx]) : value.ljust(widths[idx])
    }.join("  ").rstrip
  }
  text.join("\n")
end

def where(conditions)

Other tags:
    Example: select allocations of String objects: -

Parameters:
  • conditions (Hash) -- pairs of attribute names and values to be matched amongst allocations.
def where(conditions)
  @wheres << Proc.new do |allocations|
    conditions = conditions.inject({}) do |memo, pair|
      faux, value = *pair
      getter = attribute_getters([faux]).first
      memo.merge(getter => value)
    end
    allocations.select do |allocation|
      conditions.all? { |getter, value| getter.call(allocation) == value }
    end
  end
  self
end