heap-profiler 0.8.0.rc1-aarch64-linux

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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/cibuildgem.yaml +87 -0
  3. data/.github/workflows/tests.yml +33 -0
  4. data/.gitignore +11 -0
  5. data/.rubocop.yml +29 -0
  6. data/.ruby-version +1 -0
  7. data/Gemfile +13 -0
  8. data/Gemfile.lock +69 -0
  9. data/LICENSE.txt +21 -0
  10. data/README.md +291 -0
  11. data/Rakefile +17 -0
  12. data/TODO.md +3 -0
  13. data/benchmark/address-parsing.rb +15 -0
  14. data/benchmark/indexing.rb +17 -0
  15. data/bin/console +15 -0
  16. data/bin/generate-report +49 -0
  17. data/bin/rubocop +29 -0
  18. data/bin/setup +8 -0
  19. data/bin/testunit +9 -0
  20. data/dev.yml +20 -0
  21. data/exe/heap-profiler +5 -0
  22. data/ext/heap_profiler/extconf.rb +9 -0
  23. data/ext/heap_profiler/heap_profiler.cpp +335 -0
  24. data/ext/heap_profiler/simdjson.cpp +15047 -0
  25. data/ext/heap_profiler/simdjson.h +32071 -0
  26. data/heap-profiler.gemspec +31 -0
  27. data/lib/heap-profiler.rb +6 -0
  28. data/lib/heap_profiler/3.1/heap_profiler.so +0 -0
  29. data/lib/heap_profiler/3.2/heap_profiler.so +0 -0
  30. data/lib/heap_profiler/3.3/heap_profiler.so +0 -0
  31. data/lib/heap_profiler/3.4/heap_profiler.so +0 -0
  32. data/lib/heap_profiler/4.0/heap_profiler.so +0 -0
  33. data/lib/heap_profiler/analyzer.rb +232 -0
  34. data/lib/heap_profiler/cli.rb +140 -0
  35. data/lib/heap_profiler/diff.rb +39 -0
  36. data/lib/heap_profiler/dump.rb +97 -0
  37. data/lib/heap_profiler/full.rb +12 -0
  38. data/lib/heap_profiler/index.rb +89 -0
  39. data/lib/heap_profiler/monochrome.rb +19 -0
  40. data/lib/heap_profiler/parser.rb +83 -0
  41. data/lib/heap_profiler/polychrome.rb +93 -0
  42. data/lib/heap_profiler/reporter.rb +118 -0
  43. data/lib/heap_profiler/results.rb +256 -0
  44. data/lib/heap_profiler/runtime.rb +30 -0
  45. data/lib/heap_profiler/version.rb +4 -0
  46. metadata +94 -0
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HeapProfiler
4
+ module Parser
5
+ CLASS_DEFAULT_PROC = ->(_hash, key) { "<Class#0x#{key.to_s(16)}>" }
6
+
7
+ class << self
8
+ attr_accessor :batch_size
9
+ end
10
+ self.batch_size = 10_000_000 # 10MB
11
+
12
+ class Ruby
13
+ def build_index(path)
14
+ require 'json'
15
+ classes_index = {}
16
+ classes_index.default_proc = CLASS_DEFAULT_PROC
17
+ strings_index = {}
18
+
19
+ File.open(path).each_line do |line|
20
+ object = JSON.parse(line, symbolize_names: true)
21
+ case object[:type]
22
+ when 'MODULE', 'CLASS'
23
+ address = parse_address(object[:address])
24
+
25
+ name = object[:name]
26
+ name ||= if object[:file] && object[:line]
27
+ "<Class #{object[:file]}:#{object[:line]}>"
28
+ end
29
+
30
+ if name
31
+ classes_index[address] = name
32
+ end
33
+ when 'STRING'
34
+ next if object[:shared]
35
+ if (value = object[:value])
36
+ strings_index[parse_address(object[:address])] = value
37
+ end
38
+ end
39
+ end
40
+
41
+ [classes_index, strings_index]
42
+ end
43
+
44
+ def parse_address(address)
45
+ address.to_i(16)
46
+ end
47
+ end
48
+
49
+ class Native
50
+ def build_index(path, batch_size: Parser.batch_size)
51
+ indexes = _build_index(path, batch_size)
52
+ indexes.first.default_proc = CLASS_DEFAULT_PROC
53
+ indexes
54
+ end
55
+
56
+ def load_many(path, since: nil, batch_size: Parser.batch_size, &block)
57
+ _load_many(path, since, batch_size, &block)
58
+ end
59
+ end
60
+
61
+ class << self
62
+ def build_index(path)
63
+ current.build_index(path)
64
+ end
65
+
66
+ def load_many(path, **kwargs, &block)
67
+ current.load_many(path, **kwargs, &block)
68
+ end
69
+
70
+ private
71
+
72
+ def current
73
+ Thread.current[:HeapProfilerParser] ||= Native.new
74
+ end
75
+ end
76
+ end
77
+
78
+ begin
79
+ require "heap_profiler/#{RUBY_VERSION[/^\d+\.\d+/]}/heap_profiler"
80
+ rescue LoadError
81
+ require "heap_profiler/heap_profiler"
82
+ end
83
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HeapProfiler
4
+ module Polychrome
5
+ class << self
6
+ def path(text)
7
+ blue(text)
8
+ end
9
+
10
+ def string(text)
11
+ green(text)
12
+ end
13
+
14
+ def line(text)
15
+ cyan(text)
16
+ end
17
+
18
+ private
19
+
20
+ def black(str)
21
+ "\033[30m#{str}\033[0m"
22
+ end
23
+
24
+ def red(str)
25
+ "\033[31m#{str}\033[0m"
26
+ end
27
+
28
+ def green(str)
29
+ "\033[32m#{str}\033[0m"
30
+ end
31
+
32
+ def brown(str)
33
+ "\033[33m#{str}\033[0m"
34
+ end
35
+
36
+ def blue(str)
37
+ "\033[34m#{str}\033[0m"
38
+ end
39
+
40
+ def magenta(str)
41
+ "\033[35m#{str}\033[0m"
42
+ end
43
+
44
+ def cyan(str)
45
+ "\033[36m#{str}\033[0m"
46
+ end
47
+
48
+ def gray(str)
49
+ "\033[37m#{str}\033[0m"
50
+ end
51
+
52
+ def bg_black(str)
53
+ "\033[40m#{str}\033[0m"
54
+ end
55
+
56
+ def bg_red(str)
57
+ "\033[41m#{str}\033[0m"
58
+ end
59
+
60
+ def bg_green(str)
61
+ "\033[42m#{str}\033[0m"
62
+ end
63
+
64
+ def bg_brown(str)
65
+ "\033[43m#{str}\033[0m"
66
+ end
67
+
68
+ def bg_blue(str)
69
+ "\033[44m#{str}\033[0m"
70
+ end
71
+
72
+ def bg_magenta(str)
73
+ "\033[45m#{str}\033[0m"
74
+ end
75
+
76
+ def bg_cyan(str)
77
+ "\033[46m#{str}\033[0m"
78
+ end
79
+
80
+ def bg_gray(str)
81
+ "\033[47m#{str}\033[0m"
82
+ end
83
+
84
+ def bold(str)
85
+ "\033[1m#{str}\033[22m"
86
+ end
87
+
88
+ def reverse_color(str)
89
+ "\033[7m#{str}\033[27m"
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HeapProfiler
4
+ class << self
5
+ # This works around a Ruby bug present until at least 2.7.1
6
+ # ObjectSpace.dump include module and class names in the dump
7
+ # and for anonymous modules and classes this mean naming them.
8
+ #
9
+ # So we name them at the start of the profile to avoid that.
10
+ #
11
+ # See: https://github.com/ruby/ruby/pull/3349
12
+ if RUBY_VERSION < '2.8'
13
+ def name_anonymous_modules!
14
+ ObjectSpace.each_object(Module) do |mod|
15
+ next if mod.singleton_class?
16
+ next if real_mod_name(mod)
17
+ # We have to assign it at the top level to avoid allocating a string for the name
18
+ ::Object.const_set(:AnonymousClassOrModule, mod)
19
+ ::Object.send(:remove_const, :AnonymousClassOrModule)
20
+ end
21
+ end
22
+
23
+ ::Module.alias_method(:__real_mod_name, :name)
24
+ def real_mod_name(mod)
25
+ mod.__real_mod_name
26
+ end
27
+ else
28
+ def name_anonymous_modules!
29
+ end
30
+ end
31
+ end
32
+
33
+ class Reporter
34
+ def initialize(dir_path)
35
+ @dir_path = dir_path
36
+ @enable_tracing = !allocation_tracing_enabled?
37
+ @generation = nil
38
+ @partial = true
39
+ end
40
+
41
+ def start(partial: true)
42
+ @partial = partial
43
+ FileUtils.mkdir_p(@dir_path)
44
+ ObjectSpace.trace_object_allocations_start if @enable_tracing
45
+
46
+ @allocated_heap = open_heap("allocated")
47
+ @retained_heap = open_heap("retained")
48
+
49
+ HeapProfiler.name_anonymous_modules!
50
+
51
+ GC.start
52
+ GC.disable
53
+ @generation = GC.count
54
+ end
55
+
56
+ def stop
57
+ HeapProfiler.name_anonymous_modules!
58
+ ObjectSpace.trace_object_allocations_stop if @enable_tracing
59
+
60
+ # we can't use partial dump for allocated.heap, because we need old generations
61
+ # as well to build the classes and strings indexes.
62
+ dump_heap(@allocated_heap)
63
+
64
+ GC.enable
65
+ GC.start
66
+ dump_heap(@retained_heap, partial: @partial)
67
+ @allocated_heap.close
68
+ @retained_heap.close
69
+ write_info("generation", @partial ? @generation.to_s : "0")
70
+ end
71
+
72
+ def run
73
+ start
74
+ begin
75
+ yield
76
+ rescue Exception
77
+ ObjectSpace.trace_object_allocations_stop if @enable_tracing
78
+ GC.enable
79
+ raise
80
+ else
81
+ stop
82
+ end
83
+ end
84
+
85
+ private
86
+
87
+ def write_info(key, value)
88
+ File.write(File.join(@dir_path, "#{key}.info"), value)
89
+ end
90
+
91
+ if RUBY_VERSION >= '3.0'
92
+ def dump_heap(file, partial: false)
93
+ ObjectSpace.dump_all(output: file, since: partial ? @generation : nil)
94
+ file.close
95
+ end
96
+ else
97
+ # ObjectSpace.dump_all does allocate a few objects in itself (https://bugs.ruby-lang.org/issues/17045)
98
+ # because of this even en empty block of code will report a handful of allocations.
99
+ # To filter them more easily we attribute call `dump_all` from a method with a very specific `file`
100
+ # property.
101
+ class_eval <<~RUBY, '__hprof', __LINE__
102
+ # frozen_string_literal: true
103
+ def dump_heap(file, partial: false)
104
+ ObjectSpace.dump_all(output: file)
105
+ file.close
106
+ end
107
+ RUBY
108
+ end
109
+
110
+ def open_heap(name)
111
+ File.open(File.join(@dir_path, "#{name}.heap"), 'w+')
112
+ end
113
+
114
+ def allocation_tracing_enabled?
115
+ ObjectSpace.allocation_sourceline(Object.new)
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,256 @@
1
+ # frozen_string_literal: true
2
+
3
+ module HeapProfiler
4
+ class AbstractResults
5
+ UNIT_PREFIXES = {
6
+ 0 => 'B',
7
+ 3 => 'kB',
8
+ 6 => 'MB',
9
+ 9 => 'GB',
10
+ 12 => 'TB',
11
+ 15 => 'PB',
12
+ 18 => 'EB',
13
+ 21 => 'ZB',
14
+ 24 => 'YB',
15
+ }.freeze
16
+
17
+ METRICS = ["memory", "objects", "strings", "shape_edges"].freeze
18
+ GROUPED_METRICS = ["memory", "objects"]
19
+ GROUPINGS = ["gem", "file", "location", "class"].freeze
20
+
21
+ attr_reader :types, :dimensions
22
+
23
+ @top_entries_count = 50
24
+ class << self
25
+ attr_accessor :top_entries_count
26
+ end
27
+
28
+ def initialize(*, **)
29
+ raise NotImplementedError
30
+ end
31
+
32
+ def print_title(io, title)
33
+ io.puts
34
+ io.puts title
35
+ io.puts @colorize.line("-----------------------------------")
36
+ end
37
+
38
+ def print_output(io, topic, detail)
39
+ io.puts "#{@colorize.path(topic.to_s.rjust(10))} #{detail}"
40
+ end
41
+
42
+ def print_output2(io, topic1, topic2, detail)
43
+ io.puts "#{@colorize.path(topic1.to_s.rjust(10))} #{@colorize.path(topic2.to_s.rjust(6))} #{detail}"
44
+ end
45
+
46
+ def normalize_path(path)
47
+ @normalize_path ||= {}
48
+ @normalize_path[path] ||= begin
49
+ if %r!(/gems/.*)*/gems/(?<gemname>[^/]+)(?<rest>.*)! =~ path
50
+ "#{gemname}#{rest}"
51
+ elsif %r!ruby/2\.[^/]+/(?<stdlib>[^/.]+)(?<rest>.*)! =~ path
52
+ "ruby/lib/#{stdlib}#{rest}"
53
+ elsif %r!(?<app>[^/]+/(bin|app|lib))(?<rest>.*)! =~ path
54
+ "#{app}#{rest}"
55
+ else
56
+ path
57
+ end
58
+ end
59
+ end
60
+
61
+ def scale_bytes(bytes)
62
+ return "0 B" if bytes.zero?
63
+
64
+ scale = Math.log10(bytes).div(3) * 3
65
+ scale = 24 if scale > 24
66
+ format("%.2f #{UNIT_PREFIXES[scale]}", bytes / 10.0**scale)
67
+ end
68
+ end
69
+
70
+ class HeapResults < AbstractResults
71
+ def initialize(heap_path, metrics = METRICS, groupings = GROUPINGS)
72
+ @path = heap_path
73
+ @metrics = metrics
74
+ @groupings = groupings
75
+ end
76
+
77
+ def pretty_print(io = $stdout, **options)
78
+ heap = Dump.new(@path)
79
+ index = Index.new(heap)
80
+
81
+ color_output = options.fetch(:color_output) { io.respond_to?(:isatty) && io.isatty }
82
+ @colorize = color_output ? Polychrome : Monochrome
83
+
84
+ analyzer = Analyzer.new(heap, index)
85
+ dimensions = analyzer.run(@metrics, @groupings)
86
+
87
+ if dimensions['total']
88
+ io.puts "Total: #{scale_bytes(dimensions['total'].memory)} " \
89
+ "(#{dimensions['total'].objects} objects)"
90
+ end
91
+
92
+ @metrics.each do |metric|
93
+ next unless GROUPED_METRICS.include?(metric)
94
+ @groupings.each do |grouping|
95
+ dump_data(io, dimensions, metric, grouping, options)
96
+ end
97
+ end
98
+
99
+ if @metrics.include?("strings")
100
+ dump_strings(io, dimensions, options)
101
+ end
102
+
103
+ if @metrics.include?("shape_edges")
104
+ dump_shape_edges(io, dimensions, options)
105
+ end
106
+ end
107
+
108
+ def dump_data(io, dimensions, metric, grouping, options)
109
+ print_title io, "#{metric} by #{grouping}"
110
+ data = dimensions[grouping].top_n(metric, AbstractResults.top_entries_count)
111
+
112
+ scale_data = metric == "memory" && options[:scale_bytes]
113
+ normalize_paths = options[:normalize_paths]
114
+
115
+ if data && !data.empty?
116
+ data.each { |pair| pair[0] = normalize_path(pair[0]) } if normalize_paths
117
+ data.each { |pair| pair[1] = scale_bytes(pair[1]) } if scale_data
118
+ data.each { |k, v| print_output(io, v, k) }
119
+ else
120
+ io.puts "NO DATA"
121
+ end
122
+ end
123
+
124
+ def dump_strings(io, dimensions, options)
125
+ normalize_paths = options[:normalize_paths]
126
+ scale_data = options[:scale_bytes]
127
+ top = AbstractResults.top_entries_count
128
+
129
+ print_title(io, "String Report")
130
+
131
+ dimensions["strings"].top_n(top).each do |string|
132
+ memsize = scale_data ? scale_bytes(string.memsize) : string.memsize
133
+ print_output2 io, memsize, string.count, @colorize.string(string.value.inspect)
134
+ string.top_n(top).each do |string_location|
135
+ location = string_location.location
136
+ location = normalize_path(location) if normalize_paths
137
+ print_output2 io, '', string_location.count, location
138
+ end
139
+ io.puts
140
+ end
141
+ end
142
+
143
+ def dump_shape_edges(io, dimensions, _options)
144
+ top = AbstractResults.top_entries_count
145
+
146
+ data = dimensions["shape_edges"].top_n(top)
147
+ unless data.empty?
148
+ print_title(io, "Shape Edges Report")
149
+
150
+ data.each do |edge_name, count|
151
+ print_output io, count, edge_name
152
+ end
153
+ end
154
+ end
155
+ end
156
+
157
+ class DiffResults < AbstractResults
158
+ TYPES = ["allocated", "retained"].freeze
159
+
160
+ def initialize(directory, types = TYPES, metrics = METRICS, groupings = GROUPINGS)
161
+ @directory = directory
162
+ @types = types
163
+ @metrics = metrics
164
+ @groupings = groupings
165
+ end
166
+
167
+ def pretty_print(io = $stdout, **options)
168
+ diff = Diff.new(@directory)
169
+ heaps = @types.each_with_object({}) { |t, h| h[t] = diff.public_send("#{t}_diff") }
170
+ index = Index.new(diff.allocated)
171
+
172
+ color_output = options.fetch(:color_output) { io.respond_to?(:isatty) && io.isatty }
173
+ @colorize = color_output ? Polychrome : Monochrome
174
+
175
+ dimensions = {}
176
+ heaps.each do |type, heap|
177
+ analyzer = Analyzer.new(heap, index)
178
+ dimensions[type] = analyzer.run(@metrics, @groupings)
179
+ end
180
+
181
+ dimensions.each do |type, metrics|
182
+ io.puts "Total #{type}: #{scale_bytes(metrics['total'].memory)} " \
183
+ "(#{metrics['total'].objects} objects)"
184
+ end
185
+
186
+ @types.each do |type|
187
+ @metrics.each do |metric|
188
+ next unless GROUPED_METRICS.include?(metric)
189
+ @groupings.each do |grouping|
190
+ dump_data(io, dimensions, type, metric, grouping, options)
191
+ end
192
+ end
193
+ end
194
+
195
+ if @metrics.include?("strings")
196
+ @types.each do |type|
197
+ dump_strings(io, dimensions[type], type, options)
198
+ end
199
+ end
200
+
201
+ if @metrics.include?("shape_edges")
202
+ @types.each do |type|
203
+ dump_shape_edges(io, dimensions[type], type, options)
204
+ end
205
+ end
206
+ end
207
+
208
+ def dump_data(io, dimensions, type, metric, grouping, options)
209
+ print_title io, "#{type} #{metric} by #{grouping}"
210
+ data = dimensions[type][grouping].top_n(metric, AbstractResults.top_entries_count)
211
+
212
+ scale_data = metric == "memory" && options[:scale_bytes]
213
+ normalize_paths = options[:normalize_paths]
214
+
215
+ if data && !data.empty?
216
+ data.each { |pair| pair[0] = normalize_path(pair[0]) } if normalize_paths
217
+ data.each { |pair| pair[1] = scale_bytes(pair[1]) } if scale_data
218
+ data.each { |k, v| print_output(io, v, k) }
219
+ else
220
+ io.puts "NO DATA"
221
+ end
222
+ end
223
+
224
+ def dump_strings(io, dimensions, type, options)
225
+ normalize_paths = options[:normalize_paths]
226
+ scale_data = options[:scale_bytes]
227
+ top = AbstractResults.top_entries_count
228
+
229
+ print_title(io, "#{type.capitalize} String Report")
230
+
231
+ dimensions["strings"].top_n(top).each do |string|
232
+ memsize = scale_data ? scale_bytes(string.memsize) : string.memsize
233
+ print_output2 io, memsize, string.count, @colorize.string(string.value.inspect)
234
+ string.top_n(top).each do |string_location|
235
+ location = string_location.location
236
+ location = normalize_path(location) if normalize_paths
237
+ print_output2 io, '', string_location.count, location
238
+ end
239
+ io.puts
240
+ end
241
+ end
242
+
243
+ def dump_shape_edges(io, dimensions, _type, _options)
244
+ top = AbstractResults.top_entries_count
245
+
246
+ data = dimensions["shape_edges"].top_n(top)
247
+ unless data.empty?
248
+ print_title(io, "Shape Edges Report")
249
+
250
+ data.each do |edge_name, count|
251
+ print_output io, count, edge_name
252
+ end
253
+ end
254
+ end
255
+ end
256
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "objspace"
4
+ require "fileutils"
5
+
6
+ require "heap_profiler/version"
7
+ require "heap_profiler/reporter"
8
+
9
+ module HeapProfiler
10
+ Error = Class.new(StandardError)
11
+ CapacityError = Class.new(Error)
12
+
13
+ class << self
14
+ attr_accessor :current_reporter
15
+
16
+ def start(dir, **kwargs)
17
+ return if current_reporter
18
+ self.current_reporter = Reporter.new(dir)
19
+ current_reporter.start(**kwargs)
20
+ end
21
+
22
+ def stop
23
+ current_reporter&.stop
24
+ end
25
+
26
+ def report(dir, &block)
27
+ Reporter.new(dir).run(&block)
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+ module HeapProfiler
3
+ VERSION = "0.8.0.rc1"
4
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: heap-profiler
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.0.rc1
5
+ platform: aarch64-linux
6
+ authors:
7
+ - Jean Boussier
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2026-01-14 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Make several heap dumps and summarize allocated, retained memory
14
+ email:
15
+ - jean.boussier@gmail.com
16
+ executables:
17
+ - heap-profiler
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - ".github/workflows/cibuildgem.yaml"
22
+ - ".github/workflows/tests.yml"
23
+ - ".gitignore"
24
+ - ".rubocop.yml"
25
+ - ".ruby-version"
26
+ - Gemfile
27
+ - Gemfile.lock
28
+ - LICENSE.txt
29
+ - README.md
30
+ - Rakefile
31
+ - TODO.md
32
+ - benchmark/address-parsing.rb
33
+ - benchmark/indexing.rb
34
+ - bin/console
35
+ - bin/generate-report
36
+ - bin/rubocop
37
+ - bin/setup
38
+ - bin/testunit
39
+ - dev.yml
40
+ - exe/heap-profiler
41
+ - ext/heap_profiler/extconf.rb
42
+ - ext/heap_profiler/heap_profiler.cpp
43
+ - ext/heap_profiler/simdjson.cpp
44
+ - ext/heap_profiler/simdjson.h
45
+ - heap-profiler.gemspec
46
+ - lib/heap-profiler.rb
47
+ - lib/heap_profiler/3.1/heap_profiler.so
48
+ - lib/heap_profiler/3.2/heap_profiler.so
49
+ - lib/heap_profiler/3.3/heap_profiler.so
50
+ - lib/heap_profiler/3.4/heap_profiler.so
51
+ - lib/heap_profiler/4.0/heap_profiler.so
52
+ - lib/heap_profiler/analyzer.rb
53
+ - lib/heap_profiler/cli.rb
54
+ - lib/heap_profiler/diff.rb
55
+ - lib/heap_profiler/dump.rb
56
+ - lib/heap_profiler/full.rb
57
+ - lib/heap_profiler/index.rb
58
+ - lib/heap_profiler/monochrome.rb
59
+ - lib/heap_profiler/parser.rb
60
+ - lib/heap_profiler/polychrome.rb
61
+ - lib/heap_profiler/reporter.rb
62
+ - lib/heap_profiler/results.rb
63
+ - lib/heap_profiler/runtime.rb
64
+ - lib/heap_profiler/version.rb
65
+ homepage: https://github.com/Shopify/heap-profiler
66
+ licenses:
67
+ - MIT
68
+ metadata:
69
+ allowed_push_host: https://rubygems.org/
70
+ homepage_uri: https://github.com/Shopify/heap-profiler
71
+ source_code_uri: https://github.com/Shopify/heap-profiler
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '3.1'
81
+ - - "<"
82
+ - !ruby/object:Gem::Version
83
+ version: 4.1.dev
84
+ required_rubygems_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">"
87
+ - !ruby/object:Gem::Version
88
+ version: 1.3.1
89
+ requirements: []
90
+ rubygems_version: 3.3.27
91
+ signing_key:
92
+ specification_version: 4
93
+ summary: Ruby heap profiling tool
94
+ test_files: []