Module: DeadBro::MemoryDetails

Defined in:
lib/dead_bro/memory_details.rb

Constant Summary collapse

OBJECT_TYPE_NAMES =

Maps Ruby internal ObjectSpace type codes to human-readable names. Types omitted here are filtered out (internal noise).

{
  T_STRING: "String",
  T_ARRAY: "Array",
  T_HASH: "Hash",
  T_OBJECT: "Object",
  T_DATA: "C Extension",
  T_CLASS: "Class",
  T_MODULE: "Module",
  T_STRUCT: "Struct",
  T_MATCH: "MatchData",
  T_REGEXP: "Regexp",
  T_SYMBOL: "Symbol",
  T_FLOAT: "Float",
  T_FILE: "File",
  T_BIGNUM: "Integer (big)"
}.freeze
SKIP_TYPES =

Noise types never shown to users.

%i[FREE T_IMEMO TOTAL T_NODE T_ICLASS T_ZOMBIE T_MOVED].freeze

Class Method Summary collapse

Class Method Details

.build(gc_before:, gc_after:, memory_before_mb:, memory_after_mb:, object_counts_before:, object_counts_after:, large_objects:) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/dead_bro/memory_details.rb', line 38

def self.build(gc_before:, gc_after:, memory_before_mb:, memory_after_mb:,
               object_counts_before:, object_counts_after:, large_objects:)
  memory_delta_mb = (memory_after_mb - memory_before_mb).round(2)
  gc_collections = (gc_after[:count] || 0) - (gc_before[:count] || 0)
  heap_pages_added = (gc_after[:heap_allocated_pages] || 0) - (gc_before[:heap_allocated_pages] || 0)
  new_objects = (gc_after[:total_allocated_objects] || 0) - (gc_before[:total_allocated_objects] || 0)

  raw_deltas = {}
  if object_counts_before.any? && object_counts_after.any?
    keys = (object_counts_before.keys + object_counts_after.keys).uniq
    keys.each do |k|
      diff = (object_counts_after[k] || 0) - (object_counts_before[k] || 0)
      raw_deltas[k] = diff unless diff.zero?
    end
    raw_deltas = raw_deltas.sort_by { |_, v| -v.abs }.first(20).to_h
  end

  warnings = []
  warnings << "Memory grew #{memory_delta_mb}MB — possible leak or large allocation" if memory_delta_mb > 20
  warnings << "GC ran #{gc_collections} times — many short-lived objects being created" if gc_collections > 5
  warnings << "Heap grew by #{heap_pages_added} pages — Ruby needed more memory from the OS" if heap_pages_added > 10
  warnings << "#{large_objects.length} object(s) over 1MB found in memory" if large_objects.any?

  {
    gc_collections: gc_collections,
    heap_pages_added: heap_pages_added,
    new_objects: new_objects,
    object_breakdown: format_object_breakdown(raw_deltas),
    large_objects: large_objects,
    warnings: warnings
  }
end

.format_object_breakdown(deltas) ⇒ Object



27
28
29
30
31
32
33
34
35
36
# File 'lib/dead_bro/memory_details.rb', line 27

def self.format_object_breakdown(deltas)
  result = {}
  deltas.each do |type, count|
    next if SKIP_TYPES.include?(type)
    next unless count.positive?
    name = OBJECT_TYPE_NAMES[type] || type.to_s.sub(/\AT_/, "")
    result[name] = count
  end
  result.sort_by { |_, v| -v }.to_h
end