memdump 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: bb8daaffee80155c4227920118ed3793e3afe4ab
4
+ data.tar.gz: b57f30d96b9e35d8519d800cbca852ef6995d965
5
+ SHA512:
6
+ metadata.gz: 84b9700c70b49d35b7c7910e1a047d4f1fd9aba7161888090bc3190e6e983a835b9417363376c3eabe648763eb537e57d89726440f9b8ac022b0920be60bd2fd
7
+ data.tar.gz: 206a0adbfd4c9f3b8d10f21c0ad07f911384977f79dee4a22e01af9c3710ab6e430e6f4ade72a37655e24eef2137025cec050dce232d2dacb0db75d1ae8f86e3
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
@@ -0,0 +1,4 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.2.3
4
+ before_install: gem install bundler -v 1.11.2
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in memdump.gemspec
4
+ gemspec
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Sylvain Joyeux
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,123 @@
1
+ # Memdump
2
+
3
+ Memdump is a set of (basic) tools to create and manipulate Ruby object dumps.
4
+
5
+ Since Ruby 2.1, ObjectSpace can be dumped in a JSON file that represents all
6
+ allocated objects and their relationships. It is a gold mine of information if
7
+ you want to understand why your application has that many objects and/or a
8
+ memory leak.
9
+
10
+ Processing methods are available as a library, or using the `memdump`
11
+ command-line tool. Just run `memdump help` for a summary of operations.
12
+
13
+ **NOTE** running memdump under jruby really reduces processing times... If you're using rbenv, just do
14
+
15
+ ```
16
+ rbenv shell jruby-9.0.5.0
17
+ ```
18
+
19
+ in the shell where you run the memdump commands.
20
+
21
+ ## Installation
22
+
23
+ Add this line to your application's Gemfile:
24
+
25
+ ```ruby
26
+ gem 'memdump'
27
+ ```
28
+
29
+ And then execute:
30
+
31
+ $ bundle
32
+
33
+ Or install it yourself as:
34
+
35
+ $ gem install memdump
36
+
37
+
38
+ ## Creating a memory dump
39
+
40
+ ### Using rbtrace
41
+
42
+ The memdump command-line tool can connect to a process where
43
+ the [excellent rbtrace](https://github.com/tmm1/rbtrace) has been required. Just
44
+ start your Ruby application with `-r rbtrace`, e.g.
45
+
46
+ ```
47
+ ruby -rrbtrace -S syskit run
48
+ ```
49
+
50
+ and find out the process PID using e.g. top or ps (in the following, I assume
51
+ that the PID is 1234)
52
+
53
+ Memory dumps are then created with
54
+
55
+ ```
56
+ memdump dump 1234 /tmp/mydump
57
+ ```
58
+
59
+ Since `dump_all` requires very long, the rbtrace client will return before the
60
+ end of the dump with `*** timed out waiting for eval response`. Check your
61
+ application's output for a line saying `sendto(14): No such file or directory
62
+ [detaching]`
63
+
64
+ Additionally, you might want to enable allocation tracing, which adds to the
65
+ dump the line/file of the point where the object got allocated but is also very
66
+ costly from a performance point of view, do
67
+
68
+ ```
69
+ memdump enable-allocation-trace 1234
70
+ ```
71
+
72
+ ### Manually
73
+
74
+ It is sometimes more beneficial to do the dumps in specific places
75
+ in your application, something the rbtrace method does not allow you to do. In
76
+ this case, create memory dumps by calling `ObjectSpace.dump_all`
77
+
78
+ ~~~ ruby
79
+ require 'objspace'
80
+ File.open('/path/to/dump/file', 'w') do |io|
81
+ ObjectSpace.dump_all(output: io)
82
+ end
83
+ ~~~
84
+
85
+ Allocation tracing is enabled with
86
+
87
+ ~~~ ruby
88
+ require 'objspace'
89
+ ObjectSpace.trace_objects_allocation_start
90
+ ~~~
91
+
92
+ ## Analyzing the dump
93
+
94
+ The first thing you will probably want to do is to run the replace-class command
95
+ on the dump. It replaces the class attribute, which in the original dump is the
96
+ reference to the class object, by the class name. This makes reading the dump a
97
+ lot easier.
98
+
99
+ ```
100
+ memdump replace-class /tmp/mydump
101
+ ```
102
+
103
+ The most basic analysis is done by running **stats**, which outputs the object
104
+ count by class. For memory leaks, the **diff** command allows you to output the
105
+ part of the graph that involves new objects (removing the
106
+ "old-and-not-referred-to-by-new")
107
+
108
+ Beyond that, I usually go back and forth between the memory dump and
109
+ [gephi](http://gephi.org), a graph analysis application. the **gml** command
110
+ allows to convert the memory dump into a graph format that gephi can import.
111
+ From there, use gephi's layouting and filtering algorithms to get an idea of the
112
+ most likely objects. Then, you can "massage" the dump using the **root_of**,
113
+ **subgraph_of** and **remove-node** commands to narrow the dump to its most useful
114
+ parts.
115
+
116
+ ## Contributing
117
+
118
+ Bug reports and pull requests are welcome on GitHub at https://github.com/doudou/memdump.
119
+
120
+ ## License
121
+
122
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
123
+
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.libs << "lib"
7
+ t.test_files = FileList['test/**/*_test.rb']
8
+ end
9
+
10
+ task :default => :spec
@@ -0,0 +1,2 @@
1
+ require 'memdump/cli'
2
+ MemDump::CLI.start(ARGV)
@@ -0,0 +1,5 @@
1
+ require "memdump/version"
2
+
3
+ module Memdump
4
+ # Your code goes here...
5
+ end
@@ -0,0 +1,19 @@
1
+ module MemDump
2
+ def self.cleanup_references(dump)
3
+ addresses = Set.new
4
+ records = Array.new
5
+ dump.each_record do |r|
6
+ addr = (r['address'] || r['root'])
7
+ addresses << addr
8
+ records << r
9
+ end
10
+
11
+ records.each do |r|
12
+ if references = r['references']
13
+ references.delete_if { |r| !addresses.include?(r) }
14
+ end
15
+ end
16
+ records
17
+ end
18
+ end
19
+
@@ -0,0 +1,134 @@
1
+ require 'thor'
2
+ require 'pathname'
3
+ require 'memdump/json_dump'
4
+
5
+ module MemDump
6
+ class CLI < Thor
7
+ desc 'enable-allocation-trace PID', 'enable the tracing of allocations on a running process'
8
+ def enable_allocation_trace(pid)
9
+ system('rbtrace', '-p', pid.to_s, '-e', "require \"objspace\"; ObjectSpace.trace_object_allocations_start")
10
+ end
11
+
12
+ desc 'dump PID FILE', 'generate a dump file from a running process'
13
+ def dump(pid, file)
14
+ file = File.expand_path(file)
15
+ system('rbtrace', '-p', pid.to_s, '-e', "require \"objspace\"; File.open(\"#{file}\", 'w') { |io| ObjectSpace.dump_all(output: io) }")
16
+ end
17
+
18
+ desc 'diff SOURCE TARGET OUTPUT', 'generate a memory dump that contains the objects in TARGET not in SOURCE, and all their parents'
19
+ def diff(source, target, output)
20
+ require 'memdump/diff'
21
+
22
+ STDOUT.sync = true
23
+ from = MemDump::JSONDump.new(Pathname.new(source))
24
+ to = MemDump::JSONDump.new(Pathname.new(target))
25
+ records = MemDump.diff(from, to)
26
+ File.open(output, 'w') do |io|
27
+ records.each do |r|
28
+ io.puts JSON.dump(r)
29
+ end
30
+ end
31
+ end
32
+
33
+ desc 'gml DUMP GML', 'converts a memory dump into a graph in the GML format (for processing by e.g. gephi)'
34
+ def gml(dump_path, gml_path = nil)
35
+ require 'memdump/convert_to_gml'
36
+
37
+ STDOUT.sync = true
38
+ dump_path = Pathname.new(dump_path)
39
+ if gml_path
40
+ gml_path = Pathname.new(gml_path)
41
+ else
42
+ gml_path = dump_path.sub_ext('.gml')
43
+ end
44
+
45
+ dump = MemDump::JSONDump.new(dump_path)
46
+ gml_path.open('w') do |io|
47
+ MemDump.convert_to_gml(dump, io)
48
+ end
49
+ end
50
+
51
+ desc "subgraph_of DUMP ADDRESS", "traces all objects that are reachable from the given object"
52
+ option :max_depth, desc: 'depth of the subgraph to generate', type: :numeric, default: Float::INFINITY
53
+ def subgraph_of(dump, address)
54
+ require 'memdump/subgraph_of'
55
+
56
+ STDOUT.sync = true
57
+ dump = MemDump::JSONDump.new(Pathname.new(dump))
58
+ MemDump.subgraph_of(dump, address, max_depth: options[:max_depth]).each do |r|
59
+ puts JSON.dump(r)
60
+ end
61
+ end
62
+
63
+ desc "root_of DUMP ADDRESS", "traces the object with the given address to the root that's holding it alive"
64
+ def root_of(dump, address)
65
+ require 'memdump/root_of'
66
+
67
+ STDOUT.sync = true
68
+ dump = MemDump::JSONDump.new(Pathname.new(dump))
69
+ MemDump.root_of(dump, address).each do |r|
70
+ puts JSON.dump(r)
71
+ end
72
+ end
73
+
74
+ desc 'replace-class DUMP OUTPUT', 'replaces the class address by the class name'
75
+ option :add_ref, desc: 'whether a reference to the class object should be added', type: :boolean, default: false
76
+ def replace_class(dump_path, output_path = nil)
77
+ require 'memdump/replace_class_address_by_name'
78
+
79
+ STDOUT.sync = true
80
+ dump_path = Pathname.new(dump_path)
81
+ output_path =
82
+ if output_path then Pathname.new(output_path)
83
+ else dump_path
84
+ end
85
+ dump = MemDump::JSONDump.new(dump_path)
86
+ result = MemDump.replace_class_address_by_name(dump, add_reference_to_class: options[:add_ref])
87
+ output_path.open('w') do |io|
88
+ result.each do |r|
89
+ io.puts JSON.dump(r)
90
+ end
91
+ end
92
+ end
93
+
94
+ desc 'cleanup-refs DUMP OUTPUT', "removes references to deleted objects"
95
+ def cleanup_refs(dump, output)
96
+ require 'memdump/cleanup_references'
97
+
98
+ STDOUT.sync = true
99
+ dump = MemDump::JSONDump.new(Pathname.new(dump))
100
+ cleaned = MemDump.cleanup_references(dump)
101
+ Pathname.new(output).open('w') do |io|
102
+ cleaned.each do |r|
103
+ io.puts JSON.dump(r)
104
+ end
105
+ end
106
+ end
107
+
108
+ desc 'remove-node DUMP NODE', 'remove all objects that are kept alive by the given node'
109
+ def remove_node(dump, node_id)
110
+ require 'memdump/remove_node'
111
+
112
+ STDOUT.sync = true
113
+ dump = MemDump::JSONDump.new(Pathname.new(dump))
114
+ cleaned = MemDump.remove_node(dump, node_id)
115
+ cleaned.each do |r|
116
+ STDOUT.puts JSON.dump(r)
117
+ end
118
+ end
119
+
120
+ desc 'stats DUMP', 'give statistics on the objects present in the dump'
121
+ def stats(dump)
122
+ require 'pp'
123
+ require 'memdump/stats'
124
+ dump = MemDump::JSONDump.new(Pathname.new(dump))
125
+ unknown, by_type = MemDump.stats(dump)
126
+ puts "#{unknown} objects without a known type"
127
+ by_type.sort_by { |n, v| v }.reverse.each do |n, v|
128
+ puts "#{n}: #{v}"
129
+ end
130
+ end
131
+ end
132
+ end
133
+
134
+
@@ -0,0 +1,47 @@
1
+ require 'set'
2
+
3
+ module MemDump
4
+ def self.convert_to_gml(dump, io)
5
+ nodes = dump.each_record.map do |row|
6
+ if row['class_address'] # transformed with replace_class_address_by_name
7
+ name = row['class']
8
+ else
9
+ name = row['struct'] || row['root'] || row['type']
10
+ end
11
+
12
+ address = row['address'] || row['root']
13
+ refs = Hash.new
14
+ if row_refs = row['references']
15
+ row_refs.each { |r| refs[r] = nil }
16
+ end
17
+
18
+ [address, refs, name]
19
+ end
20
+
21
+ io.puts "graph"
22
+ io.puts "["
23
+ known_addresses = Set.new
24
+ nodes.each do |address, refs, name|
25
+ known_addresses << address
26
+ io.puts " node"
27
+ io.puts " ["
28
+ io.puts " id #{address}"
29
+ io.puts " label \"#{name}\""
30
+ io.puts " ]"
31
+ end
32
+
33
+ nodes.each do |address, refs, _|
34
+ refs.each do |ref_address, ref_label|
35
+ io.puts " edge"
36
+ io.puts " ["
37
+ io.puts " source #{address}"
38
+ io.puts " target #{ref_address}"
39
+ if ref_label
40
+ io.puts " label \"#{ref_label}\""
41
+ end
42
+ io.puts " ]"
43
+ end
44
+ end
45
+ io.puts "]"
46
+ end
47
+ end
@@ -0,0 +1,44 @@
1
+ require 'set'
2
+
3
+ module MemDump
4
+ def self.diff(from, to)
5
+ from_objects = Set.new
6
+ from.each_record { |r| from_objects << (r['address'] || r['root']) }
7
+ puts "#{from_objects.size} objects found in source dump"
8
+
9
+ selected_records = Hash.new
10
+ remaining_records = Array.new
11
+ to.each_record do |r|
12
+ address = (r['address'] || r['root'])
13
+ if !from_objects.include?(address)
14
+ selected_records[address] = r
15
+ r['only_in_target'] = 1
16
+ else
17
+ remaining_records << r
18
+ end
19
+ end
20
+
21
+ total = remaining_records.size + selected_records.size
22
+ count = 0
23
+ while selected_records.size != count
24
+ count = selected_records.size
25
+ puts "#{count}/#{total} records selected so far"
26
+ remaining_records.delete_if do |r|
27
+ address = (r['address'] || r['root'])
28
+ references = r['references']
29
+
30
+ if references && references.any? { |r| selected_records.has_key?(r) }
31
+ selected_records[address] = r
32
+ end
33
+ end
34
+ end
35
+ puts "#{count}/#{total} records selected"
36
+
37
+ selected_records.each_value do |r|
38
+ if references = r['references']
39
+ references.delete_if { |a| !selected_records.has_key?(a) }
40
+ end
41
+ end
42
+ selected_records.each_value
43
+ end
44
+ end
@@ -0,0 +1,23 @@
1
+ require 'json'
2
+ module MemDump
3
+ class JSONDump
4
+ def initialize(filename)
5
+ @filename = filename
6
+ end
7
+
8
+ def each_record
9
+ return enum_for(__method__) if !block_given?
10
+
11
+ if @cached_entries
12
+ @cached_entries.each(&proc)
13
+ else
14
+ @filename.open do |f|
15
+ f.each_line do |line|
16
+ yield JSON.parse(line)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,43 @@
1
+ module MemDump
2
+ def self.remove_node(dump, removed_node)
3
+ remaining_records = Hash.new
4
+ non_roots = Set.new
5
+ dump.each_record do |r|
6
+ address = (r['address'] || r['root'])
7
+ remaining_records[address] = r
8
+
9
+ if refs = r['references']
10
+ refs.each do |ref_address|
11
+ non_roots << ref_address
12
+ end
13
+ end
14
+ end
15
+
16
+ roots = remaining_records.each_key.
17
+ find_all { |a| !non_roots.include?(a) }
18
+
19
+ queue = roots.dup
20
+ selected_records = Hash.new
21
+ while !queue.empty?
22
+ address = queue.shift
23
+ next if address == removed_node
24
+
25
+ if record = remaining_records.delete(address)
26
+ selected_records[address] = record
27
+ if refs = record['references']
28
+ refs.each do |ref_address|
29
+ queue << ref_address
30
+ end
31
+ end
32
+ end
33
+ end
34
+
35
+ selected_records.values.reverse.map do |r|
36
+ if refs = r['references']
37
+ refs.delete_if { |a| !selected_records.has_key?(a) }
38
+ end
39
+ r
40
+ end
41
+ end
42
+ end
43
+
@@ -0,0 +1,25 @@
1
+ module MemDump
2
+ # Replace the address in the 'class' attribute by the class name
3
+ def self.replace_class_address_by_name(dump, add_reference_to_class: false)
4
+ class_names = Hash.new
5
+ dump.each_record do |row|
6
+ if row['type'] == 'CLASS' || row['type'] == 'MODULE'
7
+ class_names[row['address']] = row['name']
8
+ end
9
+ end
10
+
11
+ dump.each_record.map do |r|
12
+ if klass = r['class']
13
+ r['class'] = class_names[klass] || klass
14
+ r['class_address'] = klass
15
+ if add_reference_to_class
16
+ (r['references'] ||= Array.new) << klass
17
+ end
18
+ end
19
+ if r['type'] == 'ICLASS'
20
+ r['class'] = "I(#{r['class']})"
21
+ end
22
+ r
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ module MemDump
2
+ def self.root_of(dump, root_address)
3
+ remaining_records = Array.new
4
+ selected_records = Hash.new
5
+ selected_root = root_address
6
+ dump.each_record do |r|
7
+ address = (r['address'] || r['root'])
8
+ if selected_root == address
9
+ selected_records[address] = r
10
+ selected_root = nil;
11
+ else
12
+ remaining_records << r
13
+ end
14
+ end
15
+
16
+ count = 0
17
+ while count != selected_records.size
18
+ count = selected_records.size
19
+ remaining_records.delete_if do |r|
20
+ references = r['references']
21
+ if references && references.any? { |a| selected_records.has_key?(a) }
22
+ address = (r['address'] || r['root'])
23
+ selected_records[address] = r
24
+ end
25
+ end
26
+ end
27
+
28
+ selected_records.values.reverse.each do |r|
29
+ if refs = r['references']
30
+ refs.delete_if { |a| !selected_records.has_key?(a) }
31
+ end
32
+ end
33
+ end
34
+ end
35
+
@@ -0,0 +1,15 @@
1
+ module MemDump
2
+ def self.stats(memdump)
3
+ unknown_class = 0
4
+ by_class = Hash.new(0)
5
+ memdump.each_record do |r|
6
+ if klass = (r['class'] || r['type'] || r['root'])
7
+ by_class[klass] += 1
8
+ else
9
+ unknown_class += 1
10
+ end
11
+ end
12
+ return unknown_class, by_class
13
+ end
14
+ end
15
+
@@ -0,0 +1,26 @@
1
+ module MemDump
2
+ def self.subgraph_of(dump, root_address, max_depth: Float::INFINITY)
3
+ remaining_records = Hash.new
4
+ dump.each_record do |r|
5
+ address = (r['address'] || r['root'])
6
+ remaining_records[address] = r
7
+ end
8
+
9
+ selected_records = Hash.new
10
+ queue = [[root_address, 0]]
11
+ while !queue.empty?
12
+ address, depth = queue.shift
13
+ if record = remaining_records.delete(address)
14
+ selected_records[address] = record
15
+ if (depth < max_depth) && (refs = record['references'])
16
+ refs.each do |ref_address|
17
+ queue << [ref_address, depth + 1]
18
+ end
19
+ end
20
+ end
21
+ end
22
+
23
+ selected_records.values
24
+ end
25
+ end
26
+
@@ -0,0 +1,3 @@
1
+ module Memdump
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'memdump/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "memdump"
8
+ spec.version = Memdump::VERSION
9
+ spec.authors = ["Sylvain Joyeux"]
10
+ spec.email = ["sylvain.joyeux@m4x.org"]
11
+
12
+ spec.summary = %q{Tools to manipulate Ruby 2.1+ memory dumps}
13
+ spec.description = %q{}
14
+ spec.homepage = "https://github.com/doudou/memdump"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
18
+ spec.bindir = "bin"
19
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency 'thor'
23
+ spec.add_dependency 'rbtrace'
24
+ spec.add_development_dependency "bundler", "~> 1.11"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "minitest", "~> 5.0"
27
+ end
metadata ADDED
@@ -0,0 +1,136 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: memdump
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sylvain Joyeux
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2016-04-25 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rbtrace
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.11'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.11'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: minitest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '5.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '5.0'
83
+ description: ''
84
+ email:
85
+ - sylvain.joyeux@m4x.org
86
+ executables:
87
+ - memdump
88
+ extensions: []
89
+ extra_rdoc_files: []
90
+ files:
91
+ - ".gitignore"
92
+ - ".travis.yml"
93
+ - Gemfile
94
+ - LICENSE.txt
95
+ - README.md
96
+ - Rakefile
97
+ - bin/memdump
98
+ - lib/memdump.rb
99
+ - lib/memdump/cleanup_references.rb
100
+ - lib/memdump/cli.rb
101
+ - lib/memdump/convert_to_gml.rb
102
+ - lib/memdump/diff.rb
103
+ - lib/memdump/json_dump.rb
104
+ - lib/memdump/remove_node.rb
105
+ - lib/memdump/replace_class_address_by_name.rb
106
+ - lib/memdump/root_of.rb
107
+ - lib/memdump/stats.rb
108
+ - lib/memdump/subgraph_of.rb
109
+ - lib/memdump/version.rb
110
+ - memdump.gemspec
111
+ homepage: https://github.com/doudou/memdump
112
+ licenses:
113
+ - MIT
114
+ metadata: {}
115
+ post_install_message:
116
+ rdoc_options: []
117
+ require_paths:
118
+ - lib
119
+ required_ruby_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ required_rubygems_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ requirements: []
130
+ rubyforge_project:
131
+ rubygems_version: 2.2.3
132
+ signing_key:
133
+ specification_version: 4
134
+ summary: Tools to manipulate Ruby 2.1+ memory dumps
135
+ test_files: []
136
+ has_rdoc: