heap-profiler 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,6 +2,17 @@
2
2
 
3
3
  module HeapProfiler
4
4
  class Diff
5
+ class DumpSubset
6
+ def initialize(path, generation)
7
+ @path = path
8
+ @generation = generation
9
+ end
10
+
11
+ def each_object(&block)
12
+ Parser.load_many(@path, since: @generation, &block)
13
+ end
14
+ end
15
+
5
16
  attr_reader :allocated
6
17
 
7
18
  def initialize(report_directory)
@@ -11,23 +22,15 @@ module HeapProfiler
11
22
  end
12
23
 
13
24
  def allocated_diff
14
- @allocated_diff ||= build_diff('allocated-diff', @allocated)
25
+ @allocated_diff ||= DumpSubset.new(@allocated.path, @generation)
15
26
  end
16
27
 
17
28
  def retained_diff
18
- @retained_diff ||= build_diff('retained-diff', open_dump('retained'))
29
+ @retained_diff ||= DumpSubset.new(open_dump('retained').path, @generation)
19
30
  end
20
31
 
21
32
  private
22
33
 
23
- def build_diff(name, base)
24
- diff = open_dump(name)
25
- unless diff.exist?
26
- base.filter(File.join(@report_directory, "#{name}.heap"), since: @generation)
27
- end
28
- diff
29
- end
30
-
31
34
  def open_dump(name)
32
35
  Dump.open(@report_directory, name)
33
36
  end
@@ -61,12 +61,8 @@ module HeapProfiler
61
61
  end
62
62
  end
63
63
 
64
- def filter(as, since:)
65
- Native.filter_heap(path, as, since: since)
66
- end
67
-
68
- def each_object(&block)
69
- Native.load_many(path, batch_size: 10_000_000, &block)
64
+ def each_object(since: 0, &block)
65
+ Parser.load_many(path, since: since, batch_size: 10_000_000, &block)
70
66
  end
71
67
 
72
68
  def stats
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "heap_profiler/runtime"
4
- require "heap_profiler/native"
4
+ require "heap_profiler/parser"
5
5
  require "heap_profiler/dump"
6
6
  require "heap_profiler/index"
7
7
  require "heap_profiler/diff"
@@ -11,26 +11,26 @@ module HeapProfiler
11
11
  end
12
12
 
13
13
  def build!
14
- @classes, @strings = Native.build_index(@heap.path)
14
+ @classes, @strings = Parser.build_index(@heap.path)
15
15
  self
16
16
  end
17
17
 
18
18
  BUILTIN_CLASSES = {
19
- "FILE" => "File",
20
- "ICLASS" => "ICLASS",
21
- "COMPLEX" => "Complex",
22
- "RATIONAL" => "Rational",
23
- "BIGNUM" => "Bignum",
24
- "FLOAT" => "Float",
25
- "ARRAY" => "Array",
26
- "STRING" => "String",
27
- "HASH" => "Hash",
28
- "SYMBOL" => "Symbol",
29
- "MODULE" => "Module",
30
- "CLASS" => "Class",
31
- "REGEXP" => "Regexp",
32
- "MATCH" => "MatchData",
33
- "ROOT" => "<VM Root>",
19
+ FILE: "File",
20
+ ICLASS: "ICLASS",
21
+ COMPLEX: "Complex",
22
+ RATIONAL: "Rational",
23
+ BIGNUM: "Bignum",
24
+ FLOAT: "Float",
25
+ ARRAY: "Array",
26
+ STRING: "String",
27
+ HASH: "Hash",
28
+ SYMBOL: "Symbol",
29
+ MODULE: "Module",
30
+ CLASS: "Class",
31
+ REGEXP: "Regexp",
32
+ MATCH: "MatchData",
33
+ ROOT: "<VM Root>",
34
34
  }.freeze
35
35
 
36
36
  IMEMO_TYPES = Hash.new { |h, k| h[k] = "<#{k || 'unknown'}> (IMEMO)" }
@@ -42,20 +42,17 @@ module HeapProfiler
42
42
  return class_name
43
43
  end
44
44
 
45
- return IMEMO_TYPES[object[:imemo_type]] if type == 'IMEMO'
46
- return DATA_TYPES[object[:struct]] if type == 'DATA'
45
+ return IMEMO_TYPES[object[:imemo_type]] if type == :IMEMO
47
46
 
48
- if type == "OBJECT" || type == "STRUCT"
49
- class_address = object[:class]
50
- return unless class_address
47
+ class_address = object[:class]
48
+ return unless class_address
51
49
 
52
- return @classes.fetch(class_address) do
53
- $stderr.puts("WARNING: Couldn't infer class name of: #{object.inspect}")
54
- nil
55
- end
56
- end
50
+ @classes.fetch(class_address) do
51
+ return DATA_TYPES[object[:struct]] if type == :DATA
57
52
 
58
- raise "[BUG] Couldn't infer type of #{object.inspect}"
53
+ $stderr.puts("WARNING: Couldn't infer class name of: #{object.inspect}")
54
+ nil
55
+ end
59
56
  end
60
57
 
61
58
  def string_value(object)
@@ -63,15 +60,12 @@ module HeapProfiler
63
60
  return value if value
64
61
 
65
62
  if object[:shared]
66
- @strings[cast_address(object[:references].first)]
63
+ @strings[Native.parse_address(object[:references].first)]
67
64
  end
68
65
  end
69
66
 
70
- def cast_address(address)
71
- address.to_s.to_i(16)
72
- end
73
-
74
- def guess_gem(path)
67
+ def guess_gem(object)
68
+ path = object[:file]
75
69
  @gems[path] ||=
76
70
  if %r{(/gems/.*)*/gems/(?<gemname>[^/]+)} =~ path
77
71
  gemname
@@ -1,22 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module HeapProfiler
4
- module Native
5
- DEFAULT_BATCH_SIZE = 10_000_000 # 10MB
6
- class << self
7
- def build_index(path, batch_size: DEFAULT_BATCH_SIZE)
8
- _build_index(path, batch_size)
9
- end
10
-
11
- def load_many(path, batch_size: DEFAULT_BATCH_SIZE, &block)
12
- _load_many(path, batch_size, &block)
13
- end
14
-
15
- def filter_heap(source_path, destination_path, since:)
16
- _filter_heap(source_path, destination_path, since)
17
- end
18
-
19
- def ruby_build_index(path)
4
+ module Parser
5
+ class Ruby
6
+ def build_index(path)
20
7
  require 'json'
21
8
  classes_index = {}
22
9
  strings_index = {}
@@ -26,12 +13,12 @@ module HeapProfiler
26
13
  case object[:type]
27
14
  when 'MODULE', 'CLASS'
28
15
  if (name = object[:name])
29
- classes_index[ruby_parse_address(object[:address])] = name
16
+ classes_index[parse_address(object[:address])] = name
30
17
  end
31
18
  when 'STRING'
32
19
  next if object[:shared]
33
20
  if (value = object[:value])
34
- strings_index[ruby_parse_address(object[:address])] = value
21
+ strings_index[parse_address(object[:address])] = value
35
22
  end
36
23
  end
37
24
  end
@@ -39,10 +26,38 @@ module HeapProfiler
39
26
  [classes_index, strings_index]
40
27
  end
41
28
 
42
- def ruby_parse_address(address)
29
+ def parse_address(address)
43
30
  address.to_i(16)
44
31
  end
45
32
  end
33
+
34
+ class Native
35
+ DEFAULT_BATCH_SIZE = 10_000_000 # 10MB
36
+
37
+ def build_index(path, batch_size: DEFAULT_BATCH_SIZE)
38
+ _build_index(path, batch_size)
39
+ end
40
+
41
+ def load_many(path, since: nil, batch_size: DEFAULT_BATCH_SIZE, &block)
42
+ _load_many(path, since, batch_size, &block)
43
+ end
44
+ end
45
+
46
+ class << self
47
+ def build_index(path)
48
+ current.build_index(path)
49
+ end
50
+
51
+ def load_many(path, **kwargs, &block)
52
+ current.load_many(path, **kwargs, &block)
53
+ end
54
+
55
+ private
56
+
57
+ def current
58
+ Thread.current[:HeapProfilerParser] ||= Native.new
59
+ end
60
+ end
46
61
  end
47
62
  require "heap_profiler/heap_profiler"
48
63
  end
@@ -78,8 +78,8 @@ module HeapProfiler
78
78
  analyzer = Analyzer.new(heap, index)
79
79
  dimensions = analyzer.run(@metrics, @groupings)
80
80
 
81
- io.puts "Total: #{scale_bytes(dimensions['total_memory'].stats)} " \
82
- "(#{dimensions['total_objects'].stats} objects)"
81
+ io.puts "Total: #{scale_bytes(dimensions['total'].memory)} " \
82
+ "(#{dimensions['total'].objects} objects)"
83
83
 
84
84
  @metrics.each do |metric|
85
85
  next if metric == "strings"
@@ -95,7 +95,7 @@ module HeapProfiler
95
95
 
96
96
  def dump_data(io, dimensions, metric, grouping, options)
97
97
  print_title io, "#{metric} by #{grouping}"
98
- data = dimensions["#{metric}_by_#{grouping}"].top_n(options.fetch(:top, 50))
98
+ data = dimensions[grouping].top_n(metric, options.fetch(:top, 50))
99
99
 
100
100
  scale_data = metric == "memory" && options[:scale_bytes]
101
101
  normalize_paths = options[:normalize_paths]
@@ -141,7 +141,7 @@ module HeapProfiler
141
141
 
142
142
  def pretty_print(io = $stdout, **options)
143
143
  diff = Diff.new(@directory)
144
- heaps = @types.to_h { |t| [t, diff.public_send("#{t}_diff")] }
144
+ heaps = @types.each_with_object({}) { |t, h| h[t] = diff.public_send("#{t}_diff") }
145
145
  index = Index.new(diff.allocated)
146
146
 
147
147
  color_output = options.fetch(:color_output) { io.respond_to?(:isatty) && io.isatty }
@@ -154,8 +154,8 @@ module HeapProfiler
154
154
  end
155
155
 
156
156
  dimensions.each do |type, metrics|
157
- io.puts "Total #{type}: #{scale_bytes(metrics['total_memory'].stats)} " \
158
- "(#{metrics['total_objects'].stats} objects)"
157
+ io.puts "Total #{type}: #{scale_bytes(metrics['total'].memory)} " \
158
+ "(#{metrics['total'].objects} objects)"
159
159
  end
160
160
 
161
161
  @types.each do |type|
@@ -176,7 +176,7 @@ module HeapProfiler
176
176
 
177
177
  def dump_data(io, dimensions, type, metric, grouping, options)
178
178
  print_title io, "#{type} #{metric} by #{grouping}"
179
- data = dimensions[type]["#{metric}_by_#{grouping}"].top_n(options.fetch(:top, 50))
179
+ data = dimensions[type][grouping].top_n(metric, options.fetch(:top, 50))
180
180
 
181
181
  scale_data = metric == "memory" && options[:scale_bytes]
182
182
  normalize_paths = options[:normalize_paths]
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Heap
3
3
  module Profiler
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.0"
5
5
  end
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: heap-profiler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jean Boussier
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-08-03 00:00:00.000000000 Z
11
+ date: 2020-08-19 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Make several heap dumps and summarize allocated, retained memory
14
14
  email:
@@ -51,7 +51,7 @@ files:
51
51
  - lib/heap_profiler/full.rb
52
52
  - lib/heap_profiler/index.rb
53
53
  - lib/heap_profiler/monochrome.rb
54
- - lib/heap_profiler/native.rb
54
+ - lib/heap_profiler/parser.rb
55
55
  - lib/heap_profiler/polychrome.rb
56
56
  - lib/heap_profiler/reporter.rb
57
57
  - lib/heap_profiler/results.rb
@@ -72,14 +72,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
74
74
  - !ruby/object:Gem::Version
75
- version: 2.6.0
75
+ version: 2.5.0
76
76
  required_rubygems_version: !ruby/object:Gem::Requirement
77
77
  requirements:
78
78
  - - ">="
79
79
  - !ruby/object:Gem::Version
80
80
  version: '0'
81
81
  requirements: []
82
- rubygems_version: 3.0.3
82
+ rubygems_version: 3.1.2
83
83
  signing_key:
84
84
  specification_version: 4
85
85
  summary: Ruby heap profiling tool