class Memtf::Analyzer

Encapsulates logic that measures the memory footprint of all objects at a given point in time and compares the memory footprints of two points in time.

Constants

DEFAULT_THRESHOLD

The threshold of total memory consumption required to be included in the output

MB

Represents 1 million bytes

Attributes

filter[R]
memory_tracker[R]
threshold[R]

Public Class Methods

analyze(options={}) click to toggle source

Determine the memory footprint of each class and filter out classes that do not meet the configured threshold.

@param [Hash] options

# File lib/memtf/analyzer.rb, line 18
def self.analyze(options={})
  new(options).analyze
end
analyze_group(group) click to toggle source

Compare the memory footprints for the start and end memory snapshots within the same snapshot group.

@param [String] group @return [Hash]

# File lib/memtf/analyzer.rb, line 27
def self.analyze_group(group)
  start_analysis = Memtf::Persistance.load(Memtf::START, group)
  end_analysis   = Memtf::Persistance.load(Memtf::STOP,  group)

  comparison    = {}
  total_memsize = 0

  end_analysis.each do |clazz,end_stats|
    start_stats       = start_analysis[clazz]
    comparison[clazz] = {}

    end_stats.each do |stat_key, stat_values|
      start_val = start_stats.nil? ? 0 : start_stats[stat_key]
      end_val   = end_stats[stat_key]
      delta     = end_val - start_val

      comparison[clazz][stat_key]            = end_val
      comparison[clazz]["#{stat_key}_delta"] = delta

      total_memsize += end_val if stat_key == 'size'
    end
  end

  # Determine the relative memory impact of each class
  # TODO look into object count impact via ObjectSpace.count_objects
  comparison.keys.each do |klazz|
    stats           = comparison[klazz]
    stats['impact'] = (stats['size']*1.0) / total_memsize
  end

  comparison
end
new(options={}) click to toggle source
# File lib/memtf/analyzer.rb, line 60
def initialize(options={})
  @filter         = options[:filter]
  @threshold      = options.fetch(:threshold, DEFAULT_THRESHOLD)
  @memory_tracker = options.fetch(:memory_tracker, Memtf::Analyzer::Memory)
end

Public Instance Methods

analyze() click to toggle source

Determine the memory footprint of each class and filter out classes that do not meet the configured threshold.

@return [Hash]

# File lib/memtf/analyzer.rb, line 70
def analyze
  # Signal a new GC to attempt to clear out non-leaked memory
  # TODO investigate ObjectSpace.garbage_collect
  GC.start

  classes_stats = {}
  # TODO investigate ObjectSpace.count_objects_size[:TOTAL]
  total_memsize = 0

  # Track the memory footprint of each class
  # and calculate the cumulative footprint.
  #
  # Output:
  #
  #   {
  #     'Hash'   => [10, 15],
  #     'Fixnum' => [1],
  #     'Array'  => [20, 30, 40],
  #     'String' => [2,1]
  #   }
  #
  memory_tracker.iterate do |obj|
    if (clazz = obj.class).respond_to?(:name)
      class_name    = clazz.name
      class_stats   = (classes_stats[class_name] ||= [])

      obj_memsize   = memory_tracker.size_of(obj)
      class_stats   << obj_memsize

      # Note: could also use ObjectSpace.memsize_of_all(clazz)
      total_memsize += obj_memsize
    end
  end

  sorted_mem_hogs = identify_hogs(classes_stats, total_memsize)
  translate_hogs(sorted_mem_hogs)
end

Private Instance Methods

identify_hogs(memory_by_class, total_memory_size) click to toggle source

Identify the most meaningful memory hogs, rollup the non-meaningful classes into a single slot called ‘Other*’ and sort the results.

Output:

{
  'Array'   => [20, 30, 40],
  'Hash'    => [10, 15],
  'Others*' => [1,2,1]
}

TODO Ensure threshold is easily configurable

@param [Hash] memory_by_class @param [Fixnum] total_memory_size @return [Hash]

# File lib/memtf/analyzer.rb, line 126
def identify_hogs(memory_by_class, total_memory_size)
  mem_threshold = threshold * total_memory_size
  mem_hogs, others = {}, []
  memory_by_class.each_pair do |k,v|
    if v.sum >= mem_threshold
      mem_hogs[k] = v
    else
      others += v
    end
  end

  mem_hogs.merge!({'Others*' => others})
  Hash[mem_hogs.sort_by {|k,v| -v.sum }]
end
translate_hogs(memory_hogs) click to toggle source

Translate hogs into a suitable format

Output:

{
  'Array'   => {:count => 3, :size => 90},
  'Hash'    => {:count => 2, :size => 25},
  'Others*' => {:count => 3, :size => 4}
}

@param [Hash] memory_hogs @return [Hash]

# File lib/memtf/analyzer.rb, line 153
def translate_hogs(memory_hogs)
  smh_hash = {}
  memory_hogs.each do |k,v|
    count = v.size
    size  = v.sum / MB

    smh_hash[k] = {
      count: count,
      size: size
    }
  end
  smh_hash
end