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)
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)
-
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
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)
-
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
`#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)
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)
-
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)
-
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
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
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
def to_json to_a.to_json end
def to_text(columns: DEFAULT_COLUMNS)
-
(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)
- 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)
- 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)
- 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