memory-profiler 1.0.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.
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
+