memory-profiler 1.0.2 → 1.0.3

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: 9dc143b51ee61fd99bfbe646490321b4dc106981
4
+ data.tar.gz: 679e188284bee7611d02e3bdf79b19a2ba2902e7
5
+ SHA512:
6
+ metadata.gz: 9ee3cde0472d1c96deb9d30ed7f180ceae46046000bb54f676f19179f5c185a1cc34ab0ce01cf497b3465c2603b03f5541632706d10cacd994a6212de3a943ba
7
+ data.tar.gz: 9002ee4f5c8d1444c833ddf25930918c9645af00f82859b839cd93a1301bc46d489b648bfd501649944d9d7494d88126c023f26e2b47e2dfc4b9f26d33b350fa
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem 'rake'
7
+ end
8
+
9
+ group :test do
10
+ gem 'rake'
11
+ end
data/LICENSE CHANGED
@@ -1,13 +1,13 @@
1
- Copyright 2011 Matthew Kerwin
2
-
3
- Licensed under the Apache License, Version 2.0 (the "License");
4
- you may not use this file except in compliance with the License.
5
- You may obtain a copy of at
6
-
7
- http://www.apache.org/licenses/LICENSE-2.0
8
-
9
- Unless required by applicable law or agreed to in writing, software
10
- distributed under the License is distributed on an "AS IS" BASES,
11
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- See the License for the specific language governing permissions and
1
+ Copyright 2011 Matthew Kerwin
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASES,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
13
  limitations under the License.
data/README CHANGED
@@ -1,71 +1,71 @@
1
- A rudimentary Ruby memory profiler that uses pure in-VM techniques to
2
- analyse the object space and attempt to determine memory usage trends.
3
-
4
- * Takes instantaneous snapshots, and can be used to determine trends
5
- between subsequent instances.
6
- * Can also be used to detect memory leaks (obscure object references,
7
- etc.) in particular blocks or sections of code.
8
-
9
- Note that this uses pure Ruby code and techniques, without patches to
10
- the VM. As such it is trivial to install and use, but it doesn't have
11
- access to raw memory management/garbage collection data, so is forced
12
- to estimate, and it will affect performance noticeably.
13
-
14
- It has been tested with the following Ruby versions (ruby -v):
15
- * ruby 1.8.7 (2009-06-12 patchlevel 174) [i486-linux]
16
- * ruby 1.9.1p243 (2009-07-16 revision 24175) [i486-linux]
17
-
18
- This project was inspired by the similar Ruby memory profiler at
19
- http://code.google.com/p/ruby-memory-profiler/ , which was apparently
20
- released under a BSD (or BSD-style) license; but since no attribution
21
- details were included, I haven't copied them here anywhere.
22
-
23
- -------------------------------------------------------------------------
24
-
25
- The simplest way to use this utility is to copy the file:
26
- lib/memory-profiler.rb
27
- to your project somewhere, and include it directly.
28
-
29
- However the correct way is to build the gem, so that you can use it in
30
- all your projects.
31
-
32
- -------------------------------------------------------------------------
33
-
34
- To build the gem, using rake:
35
-
36
- rake install
37
- rake clean
38
-
39
- Then in your code:
40
-
41
- require 'rubygems'
42
- require 'memory-profiler'
43
-
44
- -------------------------------------------------------------------------
45
-
46
- Refer to RDoc documentation for more detail, but here's an example for
47
- using the utility in your Ruby program:
48
-
49
- # start the daemon, and let us know the file to which it reports
50
- puts MemoryProfiler.start_daemon( :limit=>5, :delay=>10, :marshall_size=>true, :sort_by=>:absdelta )
51
-
52
- 5.times do
53
- blah = Hash.new([])
54
-
55
- # compare memory space before and after executing a block of code
56
- rpt = MemoryProfiler.start( :limit=>10 ) do
57
- # some activities likely to create object references
58
- 100.times{ blah[1] << 'aaaaa' }
59
- 1000.times{ blah[2] << 'bbbbb' }
60
- end
61
-
62
- # display the report in a (slightly) readable form
63
- puts MemoryProfiler.format(rpt)
64
-
65
- sleep 7
66
- end
67
-
68
- # terminate the daemon
69
- MemoryProfiler.stop_daemon
70
-
71
- -------------------------------------------------------------------------
1
+ A rudimentary Ruby memory profiler that uses pure in-VM techniques to
2
+ analyse the object space and attempt to determine memory usage trends.
3
+
4
+ * Takes instantaneous snapshots, and can be used to determine trends
5
+ between subsequent instances.
6
+ * Can also be used to detect memory leaks (obscure object references,
7
+ etc.) in particular blocks or sections of code.
8
+
9
+ Note that this uses pure Ruby code and techniques, without patches to
10
+ the VM. As such it is trivial to install and use, but it doesn't have
11
+ access to raw memory management/garbage collection data, so is forced
12
+ to estimate, and it will affect performance noticeably.
13
+
14
+ It has been tested with the following Ruby versions (ruby -v):
15
+ * ruby 1.8.7 (2009-06-12 patchlevel 174) [i486-linux]
16
+ * ruby 1.9.1p243 (2009-07-16 revision 24175) [i486-linux]
17
+
18
+ This project was inspired by the similar Ruby memory profiler at
19
+ http://code.google.com/p/ruby-memory-profiler/ , which was apparently
20
+ released under a BSD (or BSD-style) license; but since no attribution
21
+ details were included, I haven't copied them here anywhere.
22
+
23
+ -------------------------------------------------------------------------
24
+
25
+ The simplest way to use this utility is to copy the file:
26
+ lib/memory-profiler.rb
27
+ to your project somewhere, and include it directly.
28
+
29
+ However the correct way is to build the gem, so that you can use it in
30
+ all your projects.
31
+
32
+ -------------------------------------------------------------------------
33
+
34
+ To build the gem:
35
+
36
+ gem build memory-profiler.gemspec
37
+ gem install memory-profiler-1.0.2.gem
38
+
39
+ Then in your code:
40
+
41
+ require 'rubygems' # Ruby 1.8 only
42
+ require 'memory-profiler'
43
+
44
+ -------------------------------------------------------------------------
45
+
46
+ Refer to RDoc documentation for more detail, but here's an example for
47
+ using the utility in your Ruby program:
48
+
49
+ # start the daemon, and let us know the file to which it reports
50
+ puts MemoryProfiler.start_daemon( :limit=>5, :delay=>10, :marshall_size=>true, :sort_by=>:absdelta )
51
+
52
+ 5.times do
53
+ blah = Hash.new([])
54
+
55
+ # compare memory space before and after executing a block of code
56
+ rpt = MemoryProfiler.start( :limit=>10 ) do
57
+ # some activities likely to create object references
58
+ 100.times{ blah[1] << 'aaaaa' }
59
+ 1000.times{ blah[2] << 'bbbbb' }
60
+ end
61
+
62
+ # display the report in a (slightly) readable form
63
+ puts MemoryProfiler.format(rpt)
64
+
65
+ sleep 7
66
+ end
67
+
68
+ # terminate the daemon
69
+ MemoryProfiler.stop_daemon
70
+
71
+ -------------------------------------------------------------------------
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env rake
2
+ require 'bundler/gem_tasks'
3
+ require 'rake/testtask'
4
+ task :default => [:test]
5
+ Rake::TestTask.new do |tt|
6
+ tt.verbose = true
7
+ end
@@ -1,332 +1,338 @@
1
- #!/usr/bin/ruby --
2
- # vim: tabstop=2:softtabstop=2:shiftwidth=2:noexpandtab
3
- =begin
4
-
5
- Author: Matthew Kerwin <matthew@kerwin.net.au>
6
- Version: 1.0.2
7
- Date: 2011-09-27
8
-
9
-
10
- Copyright 2011 Matthew Kerwin.
11
-
12
- Licensed under the Apache License, Version 2.0 (the "License");
13
- you may not use this file except in compliance with the License.
14
- You may obtain a copy of the License at
15
-
16
- http://www.apache.org/licenses/LICENSE-2.0
17
-
18
- Unless required by applicable law or agreed to in writing, software
19
- distributed under the License is distributed on an "AS IS" BASIS,
20
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
- See the License for the specific language governing permissions and
22
- limitations under the License.
23
-
24
- =end
25
-
26
- require 'sync'
27
-
28
- module MemoryProfiler
29
- DEFAULTS = {
30
- # general options
31
- :sort_by => :current,
32
- :only => [],
33
- :ignore => [],
34
- :limit => 20,
35
- :force_gc => false,
36
- # daemon options
37
- :delay => 60,
38
- :filename => nil,
39
- # ObjectSpaceAnalyser options
40
- :string_debug => false,
41
- :marshall_size => false,
42
- }
43
- @@daemon_thread = nil
44
- @@daemon_sync = Sync.new
45
-
46
- @@start_data = nil
47
- @@start_sync = Sync.new
48
-
49
- #
50
- # Begins an analysis thread that runs periodically, reporting to a text
51
- # file at: /tmp/memory_profiler-<pid>.log
52
- #
53
- # Returns the filename used.
54
- #
55
- # Options:
56
- # :delay => 60 # number of seconds between summaries
57
- # :filename => nil # override the generated default
58
- # See: #start for other options
59
- #
60
- def self.start_daemon(opt = {})
61
- opt = DEFAULTS.merge(opt)
62
- filename = opt[:filename] || "/tmp/memory_profiler-#{Process.pid}.log"
63
- @@daemon_sync.synchronize(:EX) do
64
- raise 'daemon process already running' if @@daemon_thread
65
- @@daemon_thread = Thread.new do
66
- prev = Hash.new(0)
67
- file = File.open(filename, 'w')
68
- loop do
69
- begin
70
- GC.start if opt[:force_gc]
71
- curr = ObjectSpaceAnalyser.analyse(opt)
72
- data = self._delta(curr, prev, opt)
73
-
74
- file.puts '-'*80
75
- file.puts Time.now.to_s
76
- data.each {|k,c,d| file.printf( "%5d %+5d %s\n", c, d, k.name ) }
77
-
78
- prev = curr
79
- GC.start if opt[:force_gc]
80
- rescue ::Exception => err
81
- $stderr.puts "** MemoryProfiler daemon error: #{err}", err.backtrace.map{|b| "\t#{b}" }
82
- end
83
- sleep opt[:delay]
84
- end #loop
85
- end #Thread.new
86
- end
87
- filename
88
- end
89
-
90
- #
91
- # Terminates the analysis thread started by #start_daemon
92
- #
93
- def self.stop_daemon
94
- @@daemon_sync.synchronize(:EX) do
95
- raise 'no daemon process running' unless @@daemon_thread
96
- @@daemon_thread.kill
97
- @@daemon_thread.join
98
- @@daemon_thread = nil
99
- end
100
- self
101
- end
102
-
103
- #
104
- # Generates an instantaneous report on the current Ruby ObjectSpace, saved to
105
- # a text file at: /tmp/memory_profiler-<pid>-<time>.log
106
- #
107
- # Returns the filename used.
108
- #
109
- # See: #start for valid/default options, except that :sort_by may
110
- # only have the value :current or :none when using #report
111
- #
112
- def self.report(opt = {})
113
- opt = DEFAULTS.merge(opt)
114
- GC.start if opt[:force_gc]
115
-
116
- data = ObjectSpaceAnalyser.analyse(opt)
117
-
118
- if opt[:sort_by] == :current
119
- data = data.to_a.sort_by{|k,v| -v }
120
- data = data[0,opt[:limit]] if opt[:limit] > 0 and opt[:limit] < data.length
121
- elsif opt[:sort_by] != :none
122
- warn "MemoryProfiler: invalid option :sort_by => #{opt[:sort_by].inspect}; using :none"
123
- end
124
-
125
- filename = opt[:filename] || "/tmp/memory_profiler-#{Process.pid}-#{Time.now.to_i}.log"
126
- File.open(filename, 'w') do |f|
127
- data.each {|k,c| f.printf( "%5d %s\n", c, k.name ) }
128
- end
129
-
130
- GC.start if opt[:force_gc]
131
- filename
132
- end
133
-
134
- #
135
- # If a block is given, executes it and returns a summary. Otherwise,
136
- # starts the analyser, and waits for a call to #restart or #stop.
137
- #
138
- # Returned data is an array of:
139
- # [ [Class, current_usage, usage_delta], ... ]
140
- #
141
- # Options:
142
- # :sort_by => :current # how to order classes; :current | :delta | :absdelta | :none
143
- #
144
- # :only => [] # list of only classes to scan; if empty, scans all classes
145
- # :ignore => [] # list of classes to exclude from reports (including sub-classes and modules, but not namespaces)
146
- # :limit => 20 # how many of the top classes to report (less than 1 means 'all'); only matters if :sort_by is not :none
147
- #
148
- # :force_gc => true # if true, forces a garbage collection before and after generating report
149
- #
150
- # :string_debug => false # see ObjectSpaceAnalyser#analyse
151
- # :marshall_size => false # see ObjectSpaceAnalyser#analyse
152
- #
153
- def self.start(opt = {}, &block)
154
- opt = DEFAULTS.merge(opt)
155
- if block_given?
156
- # get pre-block analysis of ObjectSpace
157
- GC.start if opt[:force_gc]
158
- prev = ObjectSpaceAnalyser.analyse(opt)
159
- GC.start if opt[:force_gc]
160
-
161
- yield
162
-
163
- # get post-block analysis of ObjectSpace
164
- GC.start if opt[:force_gc]
165
- curr = ObjectSpaceAnalyser.analyse(opt)
166
-
167
- # calculate the differences before and after execution
168
- data = self._delta(curr, prev, opt)
169
-
170
- # return it
171
- GC.start if opt[:force_gc]
172
- data
173
- else
174
- @@start_sync.synchronize(:EX) do
175
- raise 'already started' if @@start_data
176
-
177
- GC.start if opt[:force_gc]
178
- @@start_data = [ObjectSpaceAnalyser.analyse(opt), opt]
179
- GC.start if opt[:force_gc]
180
- end
181
- self
182
- end
183
- end
184
-
185
- #
186
- # Stops the current analysis and emits the results.
187
- #
188
- # See: #start
189
- #
190
- def self.stop
191
- prev = nil
192
- opt = nil
193
- @@start_sync.synchronize(:EX) do
194
- raise 'not started' unless @@start_data
195
- prev, opt = @@start_data
196
- @@start_data = nil
197
- end
198
-
199
- # get the current state of affairs
200
- GC.start if opt[:force_gc]
201
- curr = ObjectSpaceAnalyser.analyse(opt)
202
-
203
- # calculate the differences before and after execution
204
- data = self._delta(curr, prev, opt)
205
-
206
- # return it
207
- GC.start if opt[:force_gc]
208
- data
209
- end
210
-
211
- #
212
- # Stops the current analysis, emits the results, and immediately starts
213
- # a new analysis.
214
- #
215
- # See: #stop, #start
216
- #
217
- def self.restart(opt = {})
218
- res = self.stop
219
- self.start(opt)
220
- res
221
- end
222
-
223
- # => [ [Class, current, delta], ... ]
224
- def self._delta(curr, prev, opt={}) #:nodoc:
225
- opt = DEFAULTS.merge(opt)
226
-
227
- # determine the difference between current and previous
228
- delta = Hash.new(0)
229
- (curr.keys + prev.keys).each do |k|
230
- delta[k] = curr[k] - prev[k]
231
- end
232
- data = delta.map{|k,d| [k, curr[k].to_i, d]}
233
-
234
- # organise data according to given options
235
- case opt[:sort_by]
236
- when :none
237
- opt[:limit] = -1
238
- when :current
239
- data = data.sort_by{|k,c,d| -( c ) }
240
- when :delta
241
- data = data.sort_by{|k,c,d| -( d ) }
242
- when :absdelta
243
- data = data.sort_by{|k,c,d| -( d.abs ) }
244
- else
245
- warn "MemoryProfiler: invalid option :sort_by => #{opt[:sort_by].inspect}; using :none"
246
- opt[:limit] = -1
247
- end
248
- data = data[0,opt[:limit]] if opt[:limit] > 0 and opt[:limit] < data.length
249
-
250
- # return it
251
- data
252
- end
253
-
254
- #
255
- # Formats data, such as that returned by #start , into a printable,
256
- # readable string.
257
- #
258
- def self.format(data)
259
- " Curr. Delta Class\n" +
260
- " ----- ----- -----\n" +
261
- data.map{|k,c,d| sprintf(" %5d %+5d %s\n", c, d, k.name) }.join
262
- end
263
-
264
-
265
- module ObjectSpaceAnalyser
266
- #
267
- # Returns a hash mapping each Class to its usage.
268
- #
269
- # If opt[:marshall_size] is true, the usage is estimated using Marshall.dump() for each instance;
270
- # otherwise it is a simple instance count.
271
- #
272
- # If opt[:string_debug] is true, the analyser writes a text file containing every string
273
- # in the Ruby ObjectSpace, at: /tmp/memory_profiler-<pid>-strings-<time>.log
274
- #
275
- # Uses opt[:only] and opt[:ignore] , as per MemoryProfiler#start
276
- #
277
- def self.analyse(opt = {})
278
- opt = MemoryProfiler::DEFAULTS.merge(opt)
279
- marshall_size = !!opt[:marshall_size]
280
- string_debug = !!opt[:string_debug]
281
- ign = opt[:ignore]
282
- only = opt[:only]
283
-
284
- res = Hash.new(0)
285
- str = [] if string_debug
286
- ObjectSpace.each_object do |o|
287
- if res[o.class] or ((only.empty? or only.any?{|y| o.is_a? y }) and ign.none?{|x| o.is_a? x })
288
- res[o.class] += (marshall_size ? self.__sizeof(o) : 1)
289
- end
290
- str.push o.inspect if string_debug and o.class == String
291
- end
292
- if string_debug
293
- self.__save str
294
- str = nil
295
- end
296
- res
297
- end
298
-
299
- # Estimates the size of an object using Marshall.dump()
300
- # Defaults to 1 if anything goes wrong.
301
- def self.__sizeof(o) #:nodoc:
302
- Marshall.dump(o).size
303
- rescue ::Exception
304
- 1
305
- end
306
-
307
- # a single place where the magic filename is defined
308
- def self.__save(str) #:nodoc:
309
- File.open("/tmp/memory_profiler-#{Process.pid}-strings-#{Time.now.to_i}.log", 'w') do |f|
310
- str.sort.each{|s| f.puts s }
311
- end
312
- str = nil
313
- end
314
- end
315
- end
316
-
317
-
318
- if $0 == __FILE__
319
- puts MemoryProfiler.start_daemon( :limit=>5, :delay=>10, :marshall_size=>true, :sort_by=>:absdelta )
320
-
321
- 5.times do
322
- blah = Hash.new([])
323
- rpt = MemoryProfiler.start( :limit=>10 ) do
324
- 100.times{ blah[1] << 'aaaaa' }
325
- 1000.times{ blah[2] << 'bbbbb' }
326
- end
327
- puts MemoryProfiler.format(rpt)
328
- sleep 7
329
- end
330
-
331
- MemoryProfiler.stop_daemon
332
- end
1
+ #!/usr/bin/ruby --
2
+ # vim: tabstop=2:softtabstop=2:shiftwidth=2:noexpandtab
3
+ =begin
4
+
5
+ Author: Matthew Kerwin <matthew@kerwin.net.au>
6
+ Version: 1.0.3
7
+ Date: 2014-04-19
8
+
9
+
10
+ Copyright 2011,2014 Matthew Kerwin.
11
+
12
+ Licensed under the Apache License, Version 2.0 (the "License");
13
+ you may not use this file except in compliance with the License.
14
+ You may obtain a copy of the License at
15
+
16
+ http://www.apache.org/licenses/LICENSE-2.0
17
+
18
+ Unless required by applicable law or agreed to in writing, software
19
+ distributed under the License is distributed on an "AS IS" BASIS,
20
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
+ See the License for the specific language governing permissions and
22
+ limitations under the License.
23
+
24
+ =end
25
+
26
+ require 'sync'
27
+
28
+ module MemoryProfiler
29
+ DEFAULTS = {
30
+ # general options
31
+ :sort_by => :current,
32
+ :only => [],
33
+ :ignore => [],
34
+ :limit => 20,
35
+ :force_gc => false,
36
+ # daemon options
37
+ :delay => 60,
38
+ :filename => nil,
39
+ # ObjectSpaceAnalyser options
40
+ :string_debug => false,
41
+ :marshal_size => false,
42
+ }
43
+ @@daemon_thread = nil #:nodoc:
44
+ @@daemon_sync = Sync.new #:nodoc:
45
+
46
+ @@start_data = nil #:nodoc:
47
+ @@start_sync = Sync.new #:nodoc:
48
+
49
+ #
50
+ # Begins an analysis thread that runs periodically, reporting to a text
51
+ # file at: /tmp/memory_profiler-<pid>.log
52
+ #
53
+ # Returns the filename used.
54
+ #
55
+ # Options:
56
+ # :delay => 60 # number of seconds between summaries
57
+ # :filename => nil # override the generated default
58
+ # See: #start for other options
59
+ #
60
+ def self.start_daemon(opt = {})
61
+ opt = DEFAULTS.merge(opt)
62
+ filename = opt[:filename] || "/tmp/memory_profiler-#{Process.pid}.log"
63
+ @@daemon_sync.synchronize(:EX) do
64
+ raise 'daemon process already running' if @@daemon_thread
65
+ @@daemon_thread = Thread.new do
66
+ prev = Hash.new(0)
67
+ file = File.open(filename, 'w')
68
+ loop do
69
+ begin
70
+ GC.start if opt[:force_gc]
71
+ curr = ObjectSpaceAnalyser.analyse(opt)
72
+ data = self._delta(curr, prev, opt)
73
+
74
+ file.puts '-'*80
75
+ file.puts Time.now.to_s
76
+ data.each {|k,c,d| file.printf( "%5d %+5d %s\n", c, d, k.name ) }
77
+
78
+ prev = curr
79
+ GC.start if opt[:force_gc]
80
+ rescue ::Exception => err
81
+ $stderr.puts "** MemoryProfiler daemon error: #{err}", err.backtrace.map{|b| "\t#{b}" }
82
+ end
83
+ sleep opt[:delay]
84
+ end #loop
85
+ end #Thread.new
86
+ end
87
+ filename
88
+ end
89
+
90
+ #
91
+ # Terminates the analysis thread started by #start_daemon
92
+ #
93
+ def self.stop_daemon
94
+ @@daemon_sync.synchronize(:EX) do
95
+ raise 'no daemon process running' unless @@daemon_thread
96
+ @@daemon_thread.kill
97
+ @@daemon_thread.join
98
+ @@daemon_thread = nil
99
+ end
100
+ self
101
+ end
102
+
103
+ #
104
+ # Generates an instantaneous report on the current Ruby ObjectSpace, saved to
105
+ # a text file at: /tmp/memory_profiler-<pid>-<time>.log
106
+ #
107
+ # Returns the filename used.
108
+ #
109
+ # See: #start for valid/default options, except that :sort_by may
110
+ # only have the value :current or :none when using #report
111
+ #
112
+ def self.report(opt = {})
113
+ opt = DEFAULTS.merge(opt)
114
+ GC.start if opt[:force_gc]
115
+
116
+ data = ObjectSpaceAnalyser.analyse(opt)
117
+
118
+ if opt[:sort_by] == :current
119
+ data = data.to_a.sort_by{|k,v| -v }
120
+ data = data[0,opt[:limit]] if opt[:limit] > 0 and opt[:limit] < data.length
121
+ elsif opt[:sort_by] != :none
122
+ warn "MemoryProfiler: invalid option :sort_by => #{opt[:sort_by].inspect}; using :none"
123
+ end
124
+
125
+ filename = opt[:filename] || "/tmp/memory_profiler-#{Process.pid}-#{Time.now.to_i}.log"
126
+ File.open(filename, 'w') do |f|
127
+ data.each {|k,c| f.printf( "%5d %s\n", c, k.name ) }
128
+ end
129
+
130
+ GC.start if opt[:force_gc]
131
+ filename
132
+ end
133
+
134
+ #
135
+ # If a block is given, executes it and returns a summary. Otherwise,
136
+ # starts the analyser, and waits for a call to #restart or #stop.
137
+ #
138
+ # Returned data is an array of:
139
+ # [ [Class, current_usage, usage_delta], ... ]
140
+ #
141
+ # Options:
142
+ # :sort_by => :current # how to order classes; :current | :delta | :absdelta | :none
143
+ #
144
+ # :only => [] # list of only classes to scan; if empty, scans all classes
145
+ # :ignore => [] # list of classes to exclude from reports (including sub-classes and modules, but not namespaces)
146
+ # :limit => 20 # how many of the top classes to report (less than 1 means 'all'); only matters if :sort_by is not :none
147
+ #
148
+ # :force_gc => true # if true, forces a garbage collection before and after generating report
149
+ #
150
+ # :string_debug => false # see ObjectSpaceAnalyser#analyse
151
+ # :marshal_size => false # see ObjectSpaceAnalyser#analyse
152
+ #
153
+ def self.start(opt = {}, &block)
154
+ opt = DEFAULTS.merge(opt)
155
+ if block_given?
156
+ # get pre-block analysis of ObjectSpace
157
+ GC.start if opt[:force_gc]
158
+ prev = ObjectSpaceAnalyser.analyse(opt)
159
+ GC.start if opt[:force_gc]
160
+
161
+ yield
162
+
163
+ # get post-block analysis of ObjectSpace
164
+ GC.start if opt[:force_gc]
165
+ curr = ObjectSpaceAnalyser.analyse(opt)
166
+
167
+ # calculate the differences before and after execution
168
+ data = self._delta(curr, prev, opt)
169
+
170
+ # return it
171
+ GC.start if opt[:force_gc]
172
+ data
173
+ else
174
+ @@start_sync.synchronize(:EX) do
175
+ raise 'already started' if @@start_data
176
+
177
+ GC.start if opt[:force_gc]
178
+ @@start_data = [ObjectSpaceAnalyser.analyse(opt), opt]
179
+ GC.start if opt[:force_gc]
180
+ end
181
+ self
182
+ end
183
+ end
184
+
185
+ #
186
+ # Stops the current analysis and emits the results.
187
+ #
188
+ # See: #start
189
+ #
190
+ def self.stop
191
+ prev = nil
192
+ opt = nil
193
+ @@start_sync.synchronize(:EX) do
194
+ raise 'not started' unless @@start_data
195
+ prev, opt = @@start_data
196
+ @@start_data = nil
197
+ end
198
+
199
+ # get the current state of affairs
200
+ GC.start if opt[:force_gc]
201
+ curr = ObjectSpaceAnalyser.analyse(opt)
202
+
203
+ # calculate the differences before and after execution
204
+ data = self._delta(curr, prev, opt)
205
+
206
+ # return it
207
+ GC.start if opt[:force_gc]
208
+ data
209
+ end
210
+
211
+ #
212
+ # Stops the current analysis, emits the results, and immediately starts
213
+ # a new analysis.
214
+ #
215
+ # See: #stop, #start
216
+ #
217
+ def self.restart(opt = {})
218
+ res = self.stop
219
+ self.start(opt)
220
+ res
221
+ end
222
+
223
+ # @private
224
+ # => [ [Class, current, delta], ... ]
225
+ def self._delta(curr, prev, opt={}) #:nodoc:
226
+ opt = DEFAULTS.merge(opt)
227
+
228
+ # determine the difference between current and previous
229
+ delta = Hash.new(0)
230
+ (curr.keys + prev.keys).each do |k|
231
+ delta[k] = curr[k] - prev[k]
232
+ end
233
+ data = delta.map{|k,d| [k, curr[k].to_i, d]}
234
+
235
+ # organise data according to given options
236
+ case opt[:sort_by]
237
+ when :none
238
+ opt[:limit] = -1
239
+ when :current
240
+ data = data.sort_by{|k,c,d| -( c ) }
241
+ when :delta
242
+ data = data.sort_by{|k,c,d| -( d ) }
243
+ when :absdelta
244
+ data = data.sort_by{|k,c,d| -( d.abs ) }
245
+ else
246
+ warn "MemoryProfiler: invalid option :sort_by => #{opt[:sort_by].inspect}; using :none"
247
+ opt[:limit] = -1
248
+ end
249
+ data = data[0,opt[:limit]] if opt[:limit] > 0 and opt[:limit] < data.length
250
+
251
+ # return it
252
+ data
253
+ end
254
+
255
+ #
256
+ # Formats data, such as that returned by #start , into a printable,
257
+ # readable string.
258
+ #
259
+ def self.format(data)
260
+ " Curr. Delta Class\n" +
261
+ " ----- ----- -----\n" +
262
+ data.map{|k,c,d| sprintf(" %5d %+5d %s\n", c, d, k.name) }.join
263
+ end
264
+
265
+
266
+ module ObjectSpaceAnalyser
267
+ #
268
+ # Returns a hash mapping each Class to its usage.
269
+ #
270
+ # If opt[:marshal_size] is true, the usage is estimated using Marshal.dump() for each instance;
271
+ # otherwise it is a simple instance count.
272
+ #
273
+ # If opt[:string_debug] is true, the analyser writes a text file containing every string
274
+ # in the Ruby ObjectSpace, at: /tmp/memory_profiler-<pid>-strings-<time>.log
275
+ #
276
+ # Uses opt[:only] and opt[:ignore] , as per MemoryProfiler#start
277
+ #
278
+ def self.analyse(opt = {})
279
+ opt = MemoryProfiler::DEFAULTS.merge(opt)
280
+ marshal_size = !!(opt[:marshal_size] || opt[:marshall_size])
281
+ string_debug = !!opt[:string_debug]
282
+ ign = opt[:ignore]
283
+ only = opt[:only]
284
+
285
+ res = Hash.new(0)
286
+ str = [] if string_debug
287
+ ObjectSpace.each_object do |o|
288
+ if res[o.class] or ((only.empty? or only.any?{|y| o.is_a? y }) and ign.none?{|x| o.is_a? x })
289
+ res[o.class] += (marshal_size ? self.__sizeof(o) : 1)
290
+ end
291
+ str.push o.inspect if string_debug and o.class == String
292
+ end
293
+ if string_debug
294
+ self.__save str
295
+ str = nil
296
+ end
297
+ res
298
+ end
299
+
300
+ # Estimates the size of an object using Marshal.dump()
301
+ # Defaults to 1 if anything goes wrong.
302
+ def self.__sizeof(o) #:nodoc:
303
+ if o.respond_to? :dump
304
+ Marshal.dump(o).size
305
+ else
306
+ 1
307
+ end
308
+ rescue ::Exception
309
+ 1
310
+ end
311
+
312
+ # a single place where the magic filename is defined
313
+ def self.__save(str) #:nodoc:
314
+ File.open("/tmp/memory_profiler-#{Process.pid}-strings-#{Time.now.to_i}.log", 'w') do |f|
315
+ str.sort.each{|s| f.puts s }
316
+ end
317
+ str = nil
318
+ end
319
+ end
320
+ end
321
+
322
+
323
+ if $0 == __FILE__
324
+ puts MemoryProfiler.start_daemon( :limit=>5, :delay=>10, :marshal_size=>true, :sort_by=>:absdelta )
325
+
326
+ 5.times do
327
+ blah = Hash.new([])
328
+ rpt = MemoryProfiler.start( :limit=>10 ) do
329
+ 100.times{ blah[1] << 'aaaaa' }
330
+ 1000.times{ blah[2] << 'bbbbb' }
331
+ end
332
+ puts MemoryProfiler.format(rpt)
333
+ sleep 7
334
+ end
335
+
336
+ MemoryProfiler.stop_daemon
337
+ end
338
+
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+ require 'rubygems/user_interaction' if RUBY_VERSION >= '1.9.1' and Gem::VERSION >= '1.4'
3
+ require 'rake'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = 'memory-profiler'
7
+ s.version = '1.0.3'
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ['Matthew Kerwin']
10
+ s.email = ['matthew@kerwin.net.au']
11
+ s.homepage = 'https://github.com/phluid61/memory-profiler-gem'
12
+ s.summary = 'A Ruby Memory Profiler'
13
+ s.description = 'A rudimentary memory profiler that uses pure in-VM techniques to analyse the object space and attempt to determine memory usage trends.'
14
+ s.license = 'Apache License 2.0'
15
+ s.rubyforge_project = 'mem-prof-ruby'
16
+
17
+ s.required_rubygems_version = '>= 1.3.6'
18
+
19
+ s.files = Rake::FileList['lib/**/*.rb', '[A-Z]*'].to_a
20
+ s.require_path = 'lib'
21
+
22
+ s.has_rdoc = true
23
+ s.rdoc_options << '--title' << 'Memory Profiler' <<
24
+ '--main' << 'MemoryProfiler' <<
25
+ '--line-numbers' <<
26
+ '--tab-width' << '2'
27
+ end
metadata CHANGED
@@ -1,15 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: memory-profiler
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
5
- prerelease:
4
+ version: 1.0.3
6
5
  platform: ruby
7
6
  authors:
8
7
  - Matthew Kerwin
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2013-02-27 00:00:00.000000000 Z
11
+ date: 2014-04-18 00:00:00.000000000 Z
13
12
  dependencies: []
14
13
  description: A rudimentary memory profiler that uses pure in-VM techniques to analyse
15
14
  the object space and attempt to determine memory usage trends.
@@ -20,12 +19,15 @@ extensions: []
20
19
  extra_rdoc_files: []
21
20
  files:
22
21
  - lib/memory-profiler.rb
22
+ - Gemfile
23
23
  - LICENSE
24
24
  - README
25
- - Gemfile.lock
25
+ - Rakefile
26
+ - memory-profiler.gemspec
26
27
  homepage: https://github.com/phluid61/memory-profiler-gem
27
28
  licenses:
28
29
  - Apache License 2.0
30
+ metadata: {}
29
31
  post_install_message:
30
32
  rdoc_options:
31
33
  - --title
@@ -38,21 +40,19 @@ rdoc_options:
38
40
  require_paths:
39
41
  - lib
40
42
  required_ruby_version: !ruby/object:Gem::Requirement
41
- none: false
42
43
  requirements:
43
- - - ! '>='
44
+ - - '>='
44
45
  - !ruby/object:Gem::Version
45
46
  version: '0'
46
47
  required_rubygems_version: !ruby/object:Gem::Requirement
47
- none: false
48
48
  requirements:
49
- - - ! '>='
49
+ - - '>='
50
50
  - !ruby/object:Gem::Version
51
51
  version: 1.3.6
52
52
  requirements: []
53
53
  rubyforge_project: mem-prof-ruby
54
- rubygems_version: 1.8.11
54
+ rubygems_version: 2.0.3
55
55
  signing_key:
56
- specification_version: 3
56
+ specification_version: 4
57
57
  summary: A Ruby Memory Profiler
58
58
  test_files: []
@@ -1,13 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- memory-profiler (1.0.2)
5
-
6
- GEM
7
- specs:
8
-
9
- PLATFORMS
10
- ruby
11
-
12
- DEPENDENCIES
13
- memory-profiler!