heapy 0.1.4 → 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.
- checksums.yaml +4 -4
- data/.github/workflows/check_changelog.yml +13 -0
- data/.gitignore +1 -0
- data/CHANGELOG.md +8 -0
- data/README.md +38 -4
- data/bin/heapy +1 -2
- data/heapy.gemspec +4 -4
- data/lib/heapy.rb +90 -55
- data/lib/heapy/analyzer.rb +15 -6
- data/lib/heapy/diff.rb +105 -0
- data/lib/heapy/version.rb +1 -1
- metadata +30 -35
- data/lib/heapy/alive.rb +0 -269
- data/scratch.rb +0 -64
- data/weird_memory/run.rb +0 -31
- data/weird_memory/singleton_class/singleton_class.rb +0 -26
- data/weird_memory/singleton_class/singleton_class_in_class.rb +0 -29
- data/weird_memory/singleton_class/singleton_class_in_proc.rb +0 -28
- data/weird_memory/singleton_class/singleton_class_method_in_proc.rb +0 -26
- data/weird_memory/singleton_class_instance_eval/singleton_class_instance_eval.rb +0 -27
- data/weird_memory/singleton_class_instance_eval/singleton_class_instance_eval_in_class.rb +0 -29
- data/weird_memory/singleton_class_instance_eval/singleton_class_instance_eval_in_proc.rb +0 -29
- data/weird_memory/singleton_class_instance_eval/singleton_class_instance_eval_method_in_proc.rb +0 -27
- data/weird_memory/string/string.rb +0 -25
- data/weird_memory/string/string_in_class.rb +0 -27
- data/weird_memory/string/string_in_proc.rb +0 -26
- data/weird_memory/string/string_method_in_proc.rb +0 -25
- data/weird_memory/times_map/times_map.rb +0 -28
- data/weird_memory/times_map/times_map_in_class.rb +0 -29
- data/weird_memory/times_map/times_map_in_proc.rb +0 -30
- data/weird_memory/times_map/times_map_method_in_proc.rb +0 -29
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a84bd36786d08056d5e172044cbbe30547ca5fc911aeee1a6a46905daef45c7d
|
4
|
+
data.tar.gz: b87cd42efd90097653dbe311955e66c45b961828e8c4538a2604d23f66bc8ba1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bc703b95887ec3343cf21fc75e68ade763a763c357b7ef4e3e6dcc94d18751559241a587ebb9160321dfb2a975e51707ac5a030ba450dd76b8cf3ba31f385660
|
7
|
+
data.tar.gz: b13485e91fdbe6922e7777c7a8acfd48d3be5433891e2e994d38fa6c6c59881ae6085e532e9b9a04e82d6a80a005ef73afbe684ee2efe27fcbc85a402aeb5a17
|
@@ -0,0 +1,13 @@
|
|
1
|
+
name: Check Changelog
|
2
|
+
|
3
|
+
on:
|
4
|
+
pull_request:
|
5
|
+
types: [opened, reopened, edited, synchronize]
|
6
|
+
jobs:
|
7
|
+
build:
|
8
|
+
runs-on: ubuntu-latest
|
9
|
+
steps:
|
10
|
+
- uses: actions/checkout@v1
|
11
|
+
- name: Check that CHANGELOG is touched
|
12
|
+
run: |
|
13
|
+
cat $GITHUB_EVENT_PATH | jq .pull_request.title | grep -i '\[\(\(changelog skip\)\|\(ci skip\)\)\]' || git diff remotes/origin/${{ github.base_ref }} --name-only | grep CHANGELOG.md
|
data/.gitignore
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
## HEAD
|
2
|
+
|
3
|
+
## 0.2.0
|
4
|
+
|
5
|
+
- Heapy::Alive is removed (https://github.com/schneems/heapy/pull/27)
|
6
|
+
- New command `heapy diff` (https://github.com/schneems/heapy/pull/26)
|
7
|
+
- The read command now takes a --lines flag that can be used to limit output when showing generational data
|
8
|
+
|
1
9
|
## 0.1.4 - 2018-07-25
|
2
10
|
|
3
11
|
- Bundler is no longer required so heapy can now be used via a simple
|
data/README.md
CHANGED
@@ -21,6 +21,34 @@ Or install it yourself as:
|
|
21
21
|
|
22
22
|
## Usage
|
23
23
|
|
24
|
+
### Diff 2 heap dumps
|
25
|
+
|
26
|
+
Run with two inputs to output the values of today.dump that are not present in yesterday.dump
|
27
|
+
|
28
|
+
```
|
29
|
+
$ heapy diff tmp/yesterday.dump tmp/today_morning.dump
|
30
|
+
Allocated STRING 9991 objects of size 399640/491264 (in bytes) at: scratch.rb:24
|
31
|
+
```
|
32
|
+
|
33
|
+
Run with three inputs to show the diff between the first two, but only if the objects are still retained in the third
|
34
|
+
|
35
|
+
```
|
36
|
+
$ heapy diff tmp/yesterday.dump tmp/today_morning.dump tmp/today_afternoon.dump
|
37
|
+
Retained STRING 9991 objects of size 399640/491264 (in bytes) at: scratch.rb:24
|
38
|
+
# ...
|
39
|
+
```
|
40
|
+
|
41
|
+
Pass in the name of an output file and the objects present in today.dump that aren't in yesterday.dump will be written to that file
|
42
|
+
|
43
|
+
```
|
44
|
+
$ heapy diff tmp/yesterday.dump tmp/today.dump --output_diff=output.json
|
45
|
+
Allocated STRING 9991 objects of size 399640/491264 (in bytes) at: scratch.rb:24
|
46
|
+
# ...
|
47
|
+
Writing heap dump diff to output.json
|
48
|
+
```
|
49
|
+
|
50
|
+
### Read a Heap Dump
|
51
|
+
|
24
52
|
Step 1) Generate a heap dump. You could [do this manually](http://samsaffron.com/archive/2015/03/31/debugging-memory-leaks-in-ruby). Or you can use a tool like [derailed_benchmarks](https://github.com/schneems/derailed_benchmarks)
|
25
53
|
|
26
54
|
Step 2) Once you've got the heap dump, you can analyze it using this CLI:
|
@@ -54,11 +82,10 @@ $ ruby -I ./ -r trace script_name.rb
|
|
54
82
|
|
55
83
|
If the last line of your file is invalid JSON, make sure that you are closing the file after writing the ruby heap dump to it.
|
56
84
|
|
57
|
-
|
85
|
+
### Digging into a Generation
|
58
86
|
|
59
87
|
You can drill down into a specific generation. In the previous example, the 17'th generation looks strangely large, you can drill into it:
|
60
88
|
|
61
|
-
|
62
89
|
```
|
63
90
|
$ heapy read tmp/2015-10-01T10:18:59-05:00-heap.dump 17
|
64
91
|
Analyzing Heap (Generation: 17)
|
@@ -74,6 +101,14 @@ $ heapy read tmp/2015-10-01T10:18:59-05:00-heap.dump 17
|
|
74
101
|
92200 /app/vendor/bundle/ruby/2.2.0/gems/activesupport-4.2.3/lib/active_support/core_ext/numeric/conversions.rb:131
|
75
102
|
```
|
76
103
|
|
104
|
+
You can limit the output by passing in a `--lines` value:
|
105
|
+
|
106
|
+
```
|
107
|
+
$ heapy read tmp/2015-10-01T10:18:59-05:00-heap.dump 17 --lines=6
|
108
|
+
```
|
109
|
+
|
110
|
+
> Note: Default lines value is 50
|
111
|
+
|
77
112
|
### Reviewing all generations
|
78
113
|
|
79
114
|
If you want to read all generations you can use the "all" directive
|
@@ -82,7 +117,7 @@ If you want to read all generations you can use the "all" directive
|
|
82
117
|
$ heapy read tmp/2015-10-01T10:18:59-05:00-heap.dump all
|
83
118
|
```
|
84
119
|
|
85
|
-
You can also use T-Lo's online JS based [Heap Analyzer](http://tenderlove.github.io/heap-analyzer/) for visualizations.
|
120
|
+
You can also use T-Lo's online JS based [Heap Analyzer](http://tenderlove.github.io/heap-analyzer/) for visualizations. Another tool is [HARB](https://github.com/csfrancis/harb)
|
86
121
|
|
87
122
|
## Development
|
88
123
|
|
@@ -94,7 +129,6 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
94
129
|
|
95
130
|
Bug reports and pull requests are welcome on GitHub at https://github.com/schneems/heapy. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
|
96
131
|
|
97
|
-
|
98
132
|
## License
|
99
133
|
|
100
134
|
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
data/bin/heapy
CHANGED
data/heapy.gemspec
CHANGED
@@ -19,9 +19,9 @@ Gem::Specification.new do |spec|
|
|
19
19
|
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
20
20
|
spec.require_paths = ["lib"]
|
21
21
|
|
22
|
-
spec.
|
23
|
-
|
22
|
+
spec.add_dependency "thor"
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "> 1"
|
25
|
+
spec.add_development_dependency "rake", "> 10.0"
|
24
26
|
spec.add_development_dependency "rspec"
|
25
|
-
|
26
|
-
spec.required_ruby_version = '~> 2.3'
|
27
27
|
end
|
data/lib/heapy.rb
CHANGED
@@ -1,70 +1,105 @@
|
|
1
1
|
require 'json'
|
2
|
+
require 'thor'
|
2
3
|
|
3
4
|
require "heapy/version"
|
4
5
|
|
5
6
|
module Heapy
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
@cmd = argv.shift
|
10
|
-
@file = argv.shift
|
11
|
-
@number = argv.shift
|
12
|
-
@args = argv
|
7
|
+
class CLI < Thor
|
8
|
+
def self.exit_on_failure?
|
9
|
+
true
|
13
10
|
end
|
14
11
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
When run with a file and a number it will output detailed information for that
|
30
|
-
generation:
|
31
|
-
|
32
|
-
$ heapy read tmp/2015-09-30-heap.dump 17
|
33
|
-
|
34
|
-
Analyzing Heap (Generation: 17)
|
35
|
-
-------------------------------
|
36
|
-
|
37
|
-
allocated by memory (44061517) (in bytes)
|
38
|
-
==============================
|
39
|
-
39908512 /app/vendor/ruby-2.2.3/lib/ruby/2.2.0/timeout.rb:79
|
40
|
-
1284993 /app/vendor/ruby-2.2.3/lib/ruby/2.2.0/openssl/buffering.rb:182
|
41
|
-
201068 /app/vendor/bundle/ruby/2.2.0/gems/json-1.8.3/lib/json/common.rb:223
|
42
|
-
189272 /app/vendor/bundle/ruby/2.2.0/gems/newrelic_rpm-3.13.2.302/lib/new_relic/agent/stats_engine/stats_hash.rb:39
|
43
|
-
172531 /app/vendor/ruby-2.2.3/lib/ruby/2.2.0/net/http/header.rb:172
|
44
|
-
92200 /app/vendor/bundle/ruby/2.2.0/gems/activesupport-4.2.3/lib/active_support/core_ext/numeric/conversions.rb:131
|
45
|
-
HALP
|
46
|
-
end
|
12
|
+
desc "read <file> <generation> --lines <number_of_lines>", "Read heap dump file"
|
13
|
+
long_desc <<-DESC
|
14
|
+
When run with only a file input, it will output the generation and count pairs:
|
15
|
+
|
16
|
+
$ heapy read tmp/2015-09-30-heap.dump\x5
|
17
|
+
Generation: nil object count: 209191\x5
|
18
|
+
Generation: 14 object count: 407\x5
|
19
|
+
Generation: 15 object count: 638\x5
|
20
|
+
Generation: 16 object count: 748\x5
|
21
|
+
Generation: 17 object count: 1023\x5
|
22
|
+
Generation: 18 object count: 805\x5
|
23
|
+
|
24
|
+
When run with a file and a number it will output detailed information for that\x5
|
25
|
+
generation:\x5
|
47
26
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
27
|
+
$ heapy read tmp/2015-09-30-heap.dump 17\x5
|
28
|
+
|
29
|
+
Analyzing Heap (Generation: 17)\x5
|
30
|
+
-------------------------------\x5
|
31
|
+
|
32
|
+
allocated by memory (44061517) (in bytes)\x5
|
33
|
+
==============================\x5
|
34
|
+
39908512 /app/vendor/ruby-2.2.3/lib/ruby/2.2.0/timeout.rb:79\x5
|
35
|
+
1284993 /app/vendor/ruby-2.2.3/lib/ruby/2.2.0/openssl/buffering.rb:182\x5
|
36
|
+
201068 /app/vendor/bundle/ruby/2.2.0/gems/json-1.8.3/lib/json/common.rb:223\x5
|
37
|
+
189272 /app/vendor/bundle/ruby/2.2.0/gems/newrelic_rpm-3.13.2.302/lib/new_relic/agent/stats_engine/stats_hash.rb:39\x5
|
38
|
+
172531 /app/vendor/ruby-2.2.3/lib/ruby/2.2.0/net/http/header.rb:172\x5
|
39
|
+
92200 /app/vendor/bundle/ruby/2.2.0/gems/activesupport-4.2.3/lib/active_support/core_ext/numeric/conversions.rb:131\x5
|
40
|
+
DESC
|
41
|
+
option :lines, required: false, :type => :numeric
|
42
|
+
def read(file_name, generation = nil)
|
43
|
+
if generation
|
44
|
+
Analyzer.new(file_name).drill_down(generation, options[:lines] || 50)
|
61
45
|
else
|
62
|
-
|
46
|
+
Analyzer.new(file_name).analyze
|
63
47
|
end
|
64
48
|
end
|
49
|
+
|
50
|
+
long_desc <<-DESC
|
51
|
+
Run with two inputs to output the values of today.dump that are not present in yesterday.dump
|
52
|
+
|
53
|
+
$ heapy diff tmp/yesterday.dump tmp/today.dump\x5
|
54
|
+
|
55
|
+
Run with three inputs to show the diff between the first two, but only if the objects are still retained in the third
|
56
|
+
|
57
|
+
$ heapy diff tmp/yesterday.dump tmp/today_morning.dump tmp/today_afternoon.dump\x5
|
58
|
+
|
59
|
+
Pass in the name of an output file and the objects present in today.dump that aren't in yesterday.dump will be written to that file
|
60
|
+
|
61
|
+
$ heapy diff tmp/yesterday.dump tmp/today.dump --output_diff=output.json\x5
|
62
|
+
|
63
|
+
DESC
|
64
|
+
desc "diff <before_file> <after_file> <retained_file (optional)> --output_diff=output.json", "Diffs 2 heap dumps"
|
65
|
+
option :output_diff, required: false, :type => :string
|
66
|
+
def diff(before, after, retained = nil)
|
67
|
+
Diff.new(before: before, after: after, retained: retained, output_diff: options[:output_diff] || nil).call
|
68
|
+
end
|
69
|
+
|
70
|
+
map %w[--version -v] => :version
|
71
|
+
desc "version", "Show heapy version"
|
72
|
+
def version
|
73
|
+
puts Heapy::VERSION
|
74
|
+
end
|
75
|
+
|
76
|
+
desc "wat", "Outputs instructions on how to make a manual heap dump"
|
77
|
+
def wat
|
78
|
+
puts <<-HELP
|
79
|
+
|
80
|
+
To get a heap dump do this:
|
81
|
+
|
82
|
+
require 'objspace'
|
83
|
+
ObjectSpace.trace_object_allocations_start
|
84
|
+
|
85
|
+
# Your code here
|
86
|
+
|
87
|
+
p ObjectSpace.dump_all
|
88
|
+
|
89
|
+
# => #<File:/path/to/output/heap/dump/here.json>
|
90
|
+
|
91
|
+
This will print the file name of your heap dump.
|
92
|
+
|
93
|
+
If you prefer you can manually pass in an IO object to `ObjectSpace.dump_all`
|
94
|
+
|
95
|
+
io = File.open("/tmp/my_dump.json", "w+")
|
96
|
+
ObjectSpace.dump_all(output: io);
|
97
|
+
io.close
|
98
|
+
|
99
|
+
HELP
|
100
|
+
end
|
65
101
|
end
|
66
102
|
end
|
67
103
|
|
68
104
|
require 'heapy/analyzer'
|
69
|
-
require 'heapy/
|
70
|
-
|
105
|
+
require 'heapy/diff'
|
data/lib/heapy/analyzer.rb
CHANGED
@@ -1,4 +1,14 @@
|
|
1
1
|
module Heapy
|
2
|
+
|
3
|
+
# Used for inspecting contents of a heap dump
|
4
|
+
#
|
5
|
+
# To glance all contents at a glance run:
|
6
|
+
#
|
7
|
+
# Analyzer.new(file_name).analyze
|
8
|
+
#
|
9
|
+
# To inspect contents of a specific generation run:
|
10
|
+
#
|
11
|
+
# Analyzer.new(file_name).drill_down(generation, Float::INFINITY)
|
2
12
|
class Analyzer
|
3
13
|
def initialize(filename)
|
4
14
|
@filename = filename
|
@@ -17,7 +27,7 @@ module Heapy
|
|
17
27
|
end
|
18
28
|
end
|
19
29
|
|
20
|
-
def drill_down(generation_to_inspect)
|
30
|
+
def drill_down(generation_to_inspect, max_items_to_display)
|
21
31
|
puts ""
|
22
32
|
puts "Analyzing Heap (Generation: #{generation_to_inspect})"
|
23
33
|
puts "-------------------------------"
|
@@ -25,7 +35,6 @@ module Heapy
|
|
25
35
|
|
26
36
|
generation_to_inspect = Integer(generation_to_inspect) unless generation_to_inspect == "all"
|
27
37
|
|
28
|
-
#
|
29
38
|
memsize_hash = Hash.new { |h, k| h[k] = 0 }
|
30
39
|
count_hash = Hash.new { |h, k| h[k] = 0 }
|
31
40
|
string_count = Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = 0 } }
|
@@ -58,7 +67,7 @@ module Heapy
|
|
58
67
|
# /Users/richardschneeman/Documents/projects/codetriage/app/views/layouts/application.html.slim:1"=>[{"address"=>"0x7f8a4fbf2328", "type"=>"STRING", "class"=>"0x7f8a4d5dec68", "bytesize"=>223051, "capacity"=>376832, "encoding"=>"UTF-8", "file"=>"/Users/richardschneeman/Documents/projects/codetriage/app/views/layouts/application.html.slim", "line"=>1, "method"=>"new", "generation"=>36, "memsize"=>377065, "flags"=>{"wb_protected"=>true, "old"=>true, "long_lived"=>true, "marked"=>true}}]}
|
59
68
|
puts "allocated by memory (#{total_memsize}) (in bytes)"
|
60
69
|
puts "=============================="
|
61
|
-
memsize_hash = memsize_hash.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(
|
70
|
+
memsize_hash = memsize_hash.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(max_items_to_display)
|
62
71
|
longest = memsize_hash.first[1].to_s.length
|
63
72
|
memsize_hash.each do |file_line, memsize|
|
64
73
|
puts " #{memsize.to_s.rjust(longest)} #{file_line}"
|
@@ -69,7 +78,7 @@ module Heapy
|
|
69
78
|
puts ""
|
70
79
|
puts "object count (#{total_count})"
|
71
80
|
puts "=============================="
|
72
|
-
count_hash = count_hash.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(
|
81
|
+
count_hash = count_hash.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(max_items_to_display)
|
73
82
|
longest = count_hash.first[1].to_s.length
|
74
83
|
count_hash.each do |file_line, memsize|
|
75
84
|
puts " #{memsize.to_s.rjust(longest)} #{file_line}"
|
@@ -80,7 +89,7 @@ module Heapy
|
|
80
89
|
puts "=============================="
|
81
90
|
puts ""
|
82
91
|
|
83
|
-
reference_hash = reference_hash.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(
|
92
|
+
reference_hash = reference_hash.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(max_items_to_display)
|
84
93
|
longest = count_hash.first[1].to_s.length
|
85
94
|
|
86
95
|
reference_hash.each do |file_line, count|
|
@@ -99,7 +108,7 @@ module Heapy
|
|
99
108
|
value_count[string] = location_count_hash.values.inject(&:+)
|
100
109
|
end
|
101
110
|
|
102
|
-
value_count = value_count.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(
|
111
|
+
value_count = value_count.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(max_items_to_display)
|
103
112
|
longest = value_count.first[1].to_s.length
|
104
113
|
|
105
114
|
value_count.each do |string, c1|
|
data/lib/heapy/diff.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
module Heapy
|
5
|
+
# Diff 2 dumps example:
|
6
|
+
#
|
7
|
+
# Heapy::Diff.new(before: 'my_dump_1.json', after: 'my_dump_2.json').call
|
8
|
+
#
|
9
|
+
# This will find objects that are present in my_dump_2 that are not present in my_dump_1
|
10
|
+
# this means they were allocated sometime between the two heap dumps.
|
11
|
+
#
|
12
|
+
# Diff 3 dumps example:
|
13
|
+
#
|
14
|
+
# Heapy::Diff.new(before: 'my_dump_1.json', after: 'my_dump_2.json', retained: 'my_dump_3.json').call
|
15
|
+
#
|
16
|
+
# This will find objects that are present in my_dump_2 that are not present in my_dump_1
|
17
|
+
# but only if the objects are still present at the time that my_dump_3 was taken. This does
|
18
|
+
# not guarantee that they're retained forever, but were still present at the time the last
|
19
|
+
# dump was taken.
|
20
|
+
#
|
21
|
+
# You can output the diff of heap dumps by passing in a filename as `output_diff` for example
|
22
|
+
#
|
23
|
+
# Heapy::Diff.new(before: 'my_dump_1.json', after: 'my_dump_2.json', outpu_diff: 'out.json').call
|
24
|
+
class Diff
|
25
|
+
attr_reader :diff
|
26
|
+
|
27
|
+
def initialize(before:, after:, retained: nil, io: STDOUT, output_diff: nil)
|
28
|
+
@before_file = before
|
29
|
+
@after_file = after
|
30
|
+
@retained_file = retained
|
31
|
+
@output_diff_file = output_diff ? File.open(output_diff, "w+") : nil
|
32
|
+
@io = io
|
33
|
+
@diff = Hash.new { |hash, k|
|
34
|
+
hash[k] = {}
|
35
|
+
hash[k]["count"] = 0
|
36
|
+
hash[k]["memsize"] = 0
|
37
|
+
hash[k]
|
38
|
+
}
|
39
|
+
|
40
|
+
@before_address_hash = {}
|
41
|
+
@retained_address_hash = {}
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
def call
|
46
|
+
read(@before_file) { |parsed| @before_address_hash[parsed['address']] = true }
|
47
|
+
read(@retained_file) { |parsed| @retained_address_hash[parsed['address']] = true } if @retained_file
|
48
|
+
|
49
|
+
read(@after_file) do |parsed, original_line|
|
50
|
+
address = parsed['address']
|
51
|
+
next if previously_allocated?(address)
|
52
|
+
next if not_retained?(address)
|
53
|
+
|
54
|
+
@output_diff_file.puts original_line if @output_diff_file
|
55
|
+
|
56
|
+
hash = diff["#{parsed['type']},#{parsed['file']},#{parsed['line']}"]
|
57
|
+
hash["count"] += 1
|
58
|
+
hash["memsize"] += parsed["memsize"] || 0
|
59
|
+
hash["type"] ||= parsed["type"]
|
60
|
+
hash["file"] ||= parsed["file"]
|
61
|
+
hash["line"] ||= parsed["line"]
|
62
|
+
end
|
63
|
+
|
64
|
+
@output_diff_file.close if @output_diff_file
|
65
|
+
@before_address_hash.clear
|
66
|
+
@retained_address_hash.clear
|
67
|
+
|
68
|
+
total_memsize = diff.inject(0){|sum,(_,v)| sum + v["memsize"] }
|
69
|
+
|
70
|
+
diff.sort_by do |k,v|
|
71
|
+
v["count"]
|
72
|
+
end.reverse.each do |key, data|
|
73
|
+
@io.puts "#{@retained_file ? "Retained" : "Allocated"} #{data['type']} #{data['count']} objects of size #{data['memsize']}/#{total_memsize} (in bytes) at: #{data['file']}:#{data['line']}"
|
74
|
+
end
|
75
|
+
|
76
|
+
@io.puts "\nWriting heap dump diff to #{@output_diff_file.path}\n" if @output_diff_file
|
77
|
+
end
|
78
|
+
|
79
|
+
private def is_retained?(address)
|
80
|
+
return true if @retained_file.nil?
|
81
|
+
@retained_address_hash[address]
|
82
|
+
end
|
83
|
+
|
84
|
+
private def not_retained?(address)
|
85
|
+
!is_retained?(address)
|
86
|
+
end
|
87
|
+
|
88
|
+
private def previously_allocated?(address)
|
89
|
+
@before_address_hash[address]
|
90
|
+
end
|
91
|
+
|
92
|
+
private def read(filename)
|
93
|
+
File.open(filename) do |f|
|
94
|
+
f.each_line do |line|
|
95
|
+
begin
|
96
|
+
parsed = JSON.parse(line)
|
97
|
+
yield parsed, line
|
98
|
+
rescue JSON::ParserError
|
99
|
+
puts "Could not parse #{line}"
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|