heap-profiler 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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