memory-profiler 1.0.2 → 1.0.3

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