memory-profiler 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (7) hide show
  1. data/Gemfile +1 -0
  2. data/Gemfile.lock +13 -0
  3. data/LICENSE +14 -0
  4. data/README +52 -0
  5. data/Rakefile +46 -0
  6. data/lib/memory-profiler.rb +333 -0
  7. metadata +79 -0
data/Gemfile ADDED
@@ -0,0 +1 @@
1
+ gemspec
@@ -0,0 +1,13 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ memory-profiler (1.0.0)
5
+
6
+ GEM
7
+ specs:
8
+
9
+ PLATFORMS
10
+ ruby
11
+
12
+ DEPENDENCIES
13
+ memory-profiler!
data/LICENSE ADDED
@@ -0,0 +1,14 @@
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
+ limitations under the License.
14
+
data/README ADDED
@@ -0,0 +1,52 @@
1
+ -------------------------------------------------------------------------
2
+
3
+ The simplest way to use this utility is to copy the file:
4
+ lib/memory-profiler.rb
5
+ to your project somewhere, and include it directly.
6
+
7
+ However the correct way is to build the gem, so that you can use it in
8
+ all your projects.
9
+
10
+ -------------------------------------------------------------------------
11
+
12
+ To build the gem, using rake:
13
+
14
+ rake install
15
+ rake clean
16
+
17
+ Then in your code:
18
+
19
+ require 'rubygems'
20
+ require 'memory-profiler'
21
+
22
+ -------------------------------------------------------------------------
23
+
24
+ Refer to RDoc documentation for more detail, but here's an example for
25
+ using the utility in your Ruby program:
26
+
27
+ # start the daemon, and let us know the file to which it reports
28
+ puts MemoryProfiler.start_daemon( :limit=>5, :delay=>10, :marshall_size=>true, :sort_by=>:absdelta )
29
+
30
+ 5.times do
31
+ blah = Hash.new([])
32
+
33
+ # compare memory space before and after executing a block of code
34
+ rpt = MemoryProfiler.start( :limit=>10 ) do
35
+ # some activities likely to create object references
36
+ 100.times{ blah[1] << 'aaaaa' }
37
+ 1000.times{ blah[2] << 'bbbbb' }
38
+ end
39
+
40
+ # display the report in a (slightly) readable form
41
+ puts MemoryProfiler.format(rpt)
42
+
43
+ sleep 7
44
+ end
45
+
46
+ # terminate the daemon
47
+ MemoryProfiler.stop_daemon
48
+
49
+ -------------------------------------------------------------------------
50
+
51
+ Report any issues to the author <matthew@kerwin.net.au>
52
+
@@ -0,0 +1,46 @@
1
+ gemspec = eval(File.read(Dir["*.gemspec"].first))
2
+
3
+ begin
4
+ require 'bundler'
5
+ Bundler.setup
6
+ rescue LoadError => er
7
+ $stderr.puts "You need to have Bundler installed to be able to build this gem."
8
+ # Process.exit
9
+ end
10
+
11
+ # This is hacky, but I'm too lazy right now to compare all executables in the $PATH
12
+ # and try to work out which gem* command is appropriate.
13
+ if $0 =~ /\brake((?:\d+[.\d]))$/
14
+ $version = $1
15
+ else
16
+ $version = case RUBY_VERSION
17
+ when /^(1\.\d)\.0$/ then $1
18
+ when /^1\.8\./ then '1.8'
19
+ else RUBY_VERSION
20
+ end
21
+ end
22
+
23
+ desc "Validate the gemspec"
24
+ task :gemspec do
25
+ gemspec.validate
26
+ end
27
+
28
+
29
+ desc "Build gem locally"
30
+ task :build => :gemspec do
31
+ system "gem#$version build #{gemspec.name}.gemspec"
32
+ FileUtils.mkdir_p "pkg-#$version"
33
+ FileUtils.mv "#{gemspec.name}-#{gemspec.version}.gem", "pkg-#$version"
34
+ end
35
+
36
+ desc "Install gem locally"
37
+ task :install => :build do
38
+ system "gem#$version install pkg-#$version/#{gemspec.name}-#{gemspec.version}"
39
+ end
40
+
41
+
42
+ desc "Clean automatically generated files"
43
+ task :clean do
44
+ FileUtils.rm_rf "pkg-#$version"
45
+ end
46
+
@@ -0,0 +1,333 @@
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
7
+ Date: 2011-01-28
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 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" BASES,
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 in 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| file.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
+ opts = 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 emite 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 returns 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>-string-<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
333
+
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: memory-profiler
3
+ version: !ruby/object:Gem::Version
4
+ hash: 23
5
+ prerelease:
6
+ segments:
7
+ - 1
8
+ - 0
9
+ - 0
10
+ version: 1.0.0
11
+ platform: ruby
12
+ authors:
13
+ - Matthew Kerwin
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-01-30 00:00:00 +10:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: A rudimentary memory profiler that uses pure in-VM techniques to analyse the object space and attempt to determine memory usage trends.
23
+ email:
24
+ - matthew@kerwin.net.au
25
+ executables: []
26
+
27
+ extensions: []
28
+
29
+ extra_rdoc_files: []
30
+
31
+ files:
32
+ - lib/memory-profiler.rb
33
+ - Gemfile
34
+ - README
35
+ - Rakefile
36
+ - Gemfile.lock
37
+ - LICENSE
38
+ has_rdoc: true
39
+ homepage: http://code.google.com/p/memory-profiler-ruby/
40
+ licenses:
41
+ - Apache License 2.0
42
+ post_install_message:
43
+ rdoc_options:
44
+ - --title
45
+ - Memory Profiler
46
+ - --main
47
+ - MemoryProfiler
48
+ - --line-numbers
49
+ require_paths:
50
+ - lib
51
+ required_ruby_version: !ruby/object:Gem::Requirement
52
+ none: false
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ hash: 3
57
+ segments:
58
+ - 0
59
+ version: "0"
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ hash: 23
66
+ segments:
67
+ - 1
68
+ - 3
69
+ - 6
70
+ version: 1.3.6
71
+ requirements: []
72
+
73
+ rubyforge_project: mem-prof-ruby
74
+ rubygems_version: 1.4.2
75
+ signing_key:
76
+ specification_version: 3
77
+ summary: A Ruby Memory Profiler
78
+ test_files: []
79
+