memory_profiler 1.0.0 → 1.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dea36221189a4ea796d262870a9a09acf48fcfe3b6321814fc3e86cc1f4c37db
4
- data.tar.gz: '059ab396fc3c0e928046dd3f5c0e6271a616a369f427ee8d134521bb934e8cd8'
3
+ metadata.gz: a33279b5aa11573ac4eb1c99c14d8ec0ac944827da175b6eef03d77bc57c652f
4
+ data.tar.gz: dc21db4963eb3840031d72de3b095a1a3414265dac0cd7df06de0c83ef0b95ad
5
5
  SHA512:
6
- metadata.gz: 69a830fb9ac126817342cc84e70f0796417d119ae337172a6b79a26d10e78a08146a4496915c384c7f993e8c514d43168e8e9d1d925acb856ca77092bdd1535e
7
- data.tar.gz: 6de39e21847f551fac3b53107e5fa9d8cba5c6d00cfd39fb104432bc44f83008afefe9c46138ce4560e91880f245456b00f60718593198d735a379e4d93b8d71
6
+ metadata.gz: 421e253a198c2bc3881f57ac9f016bf69a5644f96eef211c1073442ce45b6a79021c3af2cdd77a8f916607080127d96a991c7c4fcd7b650ef107dd93161233ee
7
+ data.tar.gz: 8582b18f48088f242734d353ddd86a09ee9e2a1b2bb1ccdd1e066dd45ed29da2ef3417f2b1a82667c3863445358464f73fdc89e1e715c621d627469fc7b39be4
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.2 - 17-06-2024
4
+
5
+ - Add ability to profile commands via CLI @fatkodima
6
+
7
+ ## 1.0.1 - 23-10-2022
8
+
9
+ - Adapts tests to Ruby 3.0 / 3.1
10
+ - Lazy report evaluation
11
+ - Tested under Truffle Ruby
12
+
3
13
  ## 1.0.0 - 02-12-2020
4
14
 
5
15
  - Added new CLI `ruby-memory-profiler` which can be used to profile scripts @fatkodima
data/README.md CHANGED
@@ -32,14 +32,23 @@ There are two ways to use `memory_profiler`:
32
32
  ### Command Line
33
33
 
34
34
  The easiest way to use memory_profiler is via the command line, which requires no modifications to your program. The basic usage is:
35
+
36
+ ```
37
+ $ ruby-memory-profiler [options] run [--] command [command-options]
35
38
  ```
36
- $ ruby-memory-profiler [options] <script.rb> [--] [script-options]
39
+
40
+ Example:
41
+
42
+ ```
43
+ $ ruby-memory-profiler --pretty run -- rubocop --cache false
44
+
45
+ $ ruby-memory-profiler --max=10 --pretty run -- ruby notify_users.rb 1 2 3 --quiet
37
46
  ```
38
- Where `script.rb` is the program you want to profile.
39
47
 
40
48
  For a full list of options, execute the following command:
49
+
41
50
  ```
42
- ruby-memory-profiler -h
51
+ $ ruby-memory-profiler -h
43
52
  ```
44
53
 
45
54
  ### Convenience API
@@ -112,7 +121,7 @@ The `pretty_print` method can take a few options:
112
121
  * `to_file`: a path to your log file - can be given a String
113
122
  * `color_output`: a flag for whether to colorize output - can be given a Boolean
114
123
  * `retained_strings`: how many retained strings to print - can be given an Integer
115
- * `allocated_strings`: how many allocated strings to print - can be given a String
124
+ * `allocated_strings`: how many allocated strings to print - can be given a Integer
116
125
  * `detailed_report`: should report include detailed information - can be given a Boolean
117
126
  * `scale_bytes`: flag to convert byte units (e.g. 183200000 is reported as 183.2 MB, rounds with a precision of 2 decimal digits) - can be given a Boolean
118
127
  * `normalize_paths`: flag to remove a gem's directory path from printed locations - can be given a Boolean
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "memory_profiler"
4
+ require "base64"
5
+
6
+ def deserialize_hash(data)
7
+ Marshal.load(Base64.urlsafe_decode64(data)) if data
8
+ end
9
+
10
+ options = deserialize_hash(ENV["MEMORY_PROFILER_OPTIONS"]) || {}
11
+
12
+ at_exit do
13
+ report = MemoryProfiler.stop
14
+ report.pretty_print(**options)
15
+ end
16
+
17
+ MemoryProfiler.start(options)
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "optparse"
4
+ require "base64"
4
5
 
5
6
  module MemoryProfiler
6
7
  class CLI
@@ -14,20 +15,6 @@ module MemoryProfiler
14
15
  ignore_files: "memory_profiler/lib"
15
16
  }.freeze
16
17
 
17
- REPORTER_KEYS = [
18
- :top, :trace, :ignore_files, :allow_files
19
- ].freeze
20
-
21
- RESULTS_KEYS = [
22
- :to_file, :color_output, :retained_strings, :allocated_strings,
23
- :detailed_report, :scale_bytes, :normalize_paths
24
- ].freeze
25
-
26
- private_constant :BIN_NAME, :VERSION_INFO,:STATUS_SUCCESS, :STATUS_ERROR,
27
- :DEFAULTS, :REPORTER_KEYS, :RESULTS_KEYS
28
-
29
- #
30
-
31
18
  def run(argv)
32
19
  options = {}
33
20
  parser = option_parser(options)
@@ -43,17 +30,24 @@ module MemoryProfiler
43
30
  return STATUS_ERROR
44
31
  end
45
32
 
46
- MemoryProfiler.start(reporter_options(options))
47
- load script
48
-
49
- STATUS_SUCCESS
33
+ if script == "run"
34
+ # We are profiling a command.
35
+ profile_command(options, argv)
36
+ else
37
+ # We are profiling a ruby file.
38
+ begin
39
+ MemoryProfiler.start(options)
40
+ load(script)
41
+ ensure
42
+ report = MemoryProfiler.stop
43
+ report.pretty_print(**options)
44
+ end
45
+ STATUS_SUCCESS
46
+ end
50
47
  rescue OptionParser::InvalidOption, OptionParser::InvalidArgument, OptionParser::MissingArgument => e
51
48
  puts parser
52
49
  puts e.message
53
50
  STATUS_ERROR
54
- ensure
55
- report = MemoryProfiler.stop
56
- report&.pretty_print(**results_options(options))
57
51
  end
58
52
 
59
53
  private
@@ -66,7 +60,7 @@ module MemoryProfiler
66
60
  A Memory Profiler for Ruby
67
61
 
68
62
  Usage:
69
- #{BIN_NAME} [options] <script.rb> [--] [script-options]
63
+ #{BIN_NAME} [options] run [--] command [command-options]
70
64
  BANNER
71
65
 
72
66
  opts.separator ""
@@ -138,12 +132,16 @@ module MemoryProfiler
138
132
  end
139
133
  end
140
134
 
141
- def reporter_options(options)
142
- options.select { |k, _v| REPORTER_KEYS.include?(k) }
135
+ def profile_command(options, argv)
136
+ env = {}
137
+ env["MEMORY_PROFILER_OPTIONS"] = serialize_hash(options) if options.any?
138
+ gem_path = File.expand_path('../', __dir__)
139
+ env["RUBYOPT"] = "-I #{gem_path} -r memory_profiler/autorun #{ENV['RUBYOPT']}"
140
+ exec(env, *argv)
143
141
  end
144
142
 
145
- def results_options(options)
146
- options.select { |k, _v| RESULTS_KEYS.include?(k) }
143
+ def serialize_hash(hash)
144
+ Base64.urlsafe_encode64(Marshal.dump(hash))
147
145
  end
148
146
  end
149
147
  end
@@ -16,7 +16,7 @@ module MemoryProfiler
16
16
  gemname
17
17
  elsif /\/rubygems[\.\/]/ =~ path
18
18
  "rubygems"
19
- elsif /ruby\/2\.[^\/]+\/(?<stdlib>[^\/\.]+)/ =~ path
19
+ elsif /ruby\/\d\.[^\/]+\/(?<stdlib>[^\/\.]+)/ =~ path
20
20
  stdlib
21
21
  elsif /(?<app>[^\/]+\/(bin|app|lib))/ =~ path
22
22
  app
@@ -34,8 +34,7 @@ module MemoryProfiler
34
34
  end
35
35
 
36
36
  def start
37
- GC.start
38
- GC.start
37
+ 3.times { GC.start }
39
38
  GC.start
40
39
  GC.disable
41
40
 
@@ -49,8 +48,11 @@ module MemoryProfiler
49
48
  retained = StatHash.new.compare_by_identity
50
49
 
51
50
  GC.enable
52
- GC.start
53
- GC.start
51
+ # for whatever reason doing GC in a block is more effective at
52
+ # freeing objects.
53
+ # full_mark: true, immediate_mark: true, immediate_sweep: true are already default
54
+ 3.times { GC.start }
55
+ # another start outside of the block to release the block
54
56
  GC.start
55
57
 
56
58
  # Caution: Do not allocate any new Objects between the call to GC.start and the completion of the retained
@@ -86,8 +88,6 @@ module MemoryProfiler
86
88
  # Iterates through objects in memory of a given generation.
87
89
  # Stores results along with meta data of objects collected.
88
90
  def object_list(generation)
89
-
90
- rvalue_size = GC::INTERNAL_CONSTANTS[:RVALUE_SIZE]
91
91
  helper = Helpers.new
92
92
 
93
93
  result = StatHash.new.compare_by_identity
@@ -114,7 +114,7 @@ module MemoryProfiler
114
114
  string = klass == String ? helper.lookup_string(obj) : nil
115
115
 
116
116
  # compensate for API bug
117
- memsize = rvalue_size if memsize > 100_000_000_000
117
+ memsize = GC::INTERNAL_CONSTANTS[:RVALUE_SIZE] if memsize > 100_000_000_000
118
118
  result[obj.__id__] = MemoryProfiler::Stat.new(class_name, gem, file, location, memsize, string)
119
119
  rescue
120
120
  # give up if any any error occurs inspecting the object
@@ -24,7 +24,16 @@ module MemoryProfiler
24
24
 
25
25
  TYPES.each do |type|
26
26
  METRICS.each do |metric|
27
- attr_accessor "#{type}_#{metric}_by_#{name}"
27
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
28
+ def #{type}_#{metric}_by_#{name} # def allocated_memory_by_file
29
+ @#{type}_#{metric}_by ||= {} # @allocated_memory_by ||= {}
30
+ #
31
+ @#{type}_#{metric}_by['#{name}'] ||= begin # @allocated_memory_by['file'] ||= begin
32
+ _, stat_attribute = @@lookups.find { |(n, _stat_attribute)| n == '#{name}' } # _, stat_attribute = @@lookups.find { |(n, _stat_attribute)| n == 'file' }
33
+ @#{type}.top_n_#{metric}(@top, stat_attribute) # @allocated.top_n_memory(@top, stat_attribute)
34
+ end # end
35
+ end # end
36
+ RUBY
28
37
  end
29
38
  end
30
39
  end
@@ -34,27 +43,20 @@ module MemoryProfiler
34
43
  register_type 'location', :location
35
44
  register_type 'class', :class_name
36
45
 
37
- attr_accessor :strings_retained, :strings_allocated
46
+ attr_writer :strings_retained, :strings_allocated
38
47
  attr_accessor :total_retained, :total_allocated
39
48
  attr_accessor :total_retained_memsize, :total_allocated_memsize
40
49
 
41
- def register_results(allocated, retained, top)
42
-
43
- @@lookups.each do |name, stat_attribute|
44
-
45
- memsize_results, count_results = allocated.top_n(top, stat_attribute)
46
-
47
- self.send("allocated_memory_by_#{name}=", memsize_results)
48
- self.send("allocated_objects_by_#{name}=", count_results)
49
-
50
- memsize_results, count_results = retained.top_n(top, stat_attribute)
51
-
52
- self.send("retained_memory_by_#{name}=", memsize_results)
53
- self.send("retained_objects_by_#{name}=", count_results)
54
- end
50
+ def initialize
51
+ @allocated = StatHash.new
52
+ @retained = StatHash.new
53
+ @top = 50
54
+ end
55
55
 
56
- self.strings_allocated = string_report(allocated, top)
57
- self.strings_retained = string_report(retained, top)
56
+ def register_results(allocated, retained, top)
57
+ @allocated = allocated
58
+ @retained = retained
59
+ @top = top
58
60
 
59
61
  self.total_allocated = allocated.size
60
62
  self.total_allocated_memsize = total_memsize(allocated)
@@ -64,6 +66,14 @@ module MemoryProfiler
64
66
  self
65
67
  end
66
68
 
69
+ def strings_allocated
70
+ @strings_allocated ||= string_report(@allocated, @top)
71
+ end
72
+
73
+ def strings_retained
74
+ @strings_retained ||= string_report(@retained, @top)
75
+ end
76
+
67
77
  def scale_bytes(bytes)
68
78
  return "0 B" if bytes.zero?
69
79
 
@@ -139,10 +149,10 @@ module MemoryProfiler
139
149
  end
140
150
  end
141
151
  end
142
- end
143
152
 
144
- io.puts
145
- print_string_reports(io, options)
153
+ io.puts
154
+ print_string_reports(io, options)
155
+ end
146
156
 
147
157
  io.close if io.is_a? File
148
158
  end
@@ -162,7 +172,7 @@ module MemoryProfiler
162
172
  @normalize_path[path] ||= begin
163
173
  if %r!(/gems/.*)*/gems/(?<gemname>[^/]+)(?<rest>.*)! =~ path
164
174
  "#{gemname}#{rest}"
165
- elsif %r!ruby/2\.[^/]+/(?<stdlib>[^/.]+)(?<rest>.*)! =~ path
175
+ elsif %r!ruby/\d\.[^/]+/(?<stdlib>[^/.]+)(?<rest>.*)! =~ path
166
176
  "ruby/lib/#{stdlib}#{rest}"
167
177
  elsif %r!(?<app>[^/]+/(bin|app|lib))(?<rest>.*)! =~ path
168
178
  "#{app}#{rest}"
@@ -5,32 +5,40 @@ module MemoryProfiler
5
5
  # Fast approach for determining the top_n entries in a Hash of Stat objects.
6
6
  # Returns results for both memory (memsize summed) and objects allocated (count) as a tuple.
7
7
  def top_n(max, metric_method)
8
+ [
9
+ top_n_memory(max, metric_method),
10
+ top_n_objects(max, metric_method)
11
+ ]
12
+ end
8
13
 
14
+ def top_n_memory(max, metric_method)
9
15
  metric_memsize = Hash.new(0)
10
- metric_objects_count = Hash.new(0)
11
16
 
12
17
  each_value do |value|
13
18
  metric = value.send(metric_method)
14
-
15
19
  metric_memsize[metric] += value.memsize
16
- metric_objects_count[metric] += 1
17
20
  end
18
21
 
19
- stats_by_memsize =
20
- metric_memsize
21
- .to_a
22
- .sort_by! { |metric, memsize| [-memsize, metric] }
23
- .take(max)
24
- .map! { |metric, memsize| { data: metric, count: memsize } }
22
+ metric_memsize
23
+ .to_a
24
+ .sort_by! { |metric, memsize| [-memsize, metric] }
25
+ .take(max)
26
+ .map! { |metric, memsize| { data: metric, count: memsize } }
27
+ end
25
28
 
26
- stats_by_count =
27
- metric_objects_count
28
- .to_a
29
- .sort_by! { |metric, count| [-count, metric] }
30
- .take(max)
31
- .map! { |metric, count| { data: metric, count: count } }
29
+ def top_n_objects(max, metric_method)
30
+ metric_objects_count = Hash.new(0)
31
+
32
+ each_value do |value|
33
+ metric = value.send(metric_method)
34
+ metric_objects_count[metric] += 1
35
+ end
32
36
 
33
- [stats_by_memsize, stats_by_count]
37
+ metric_objects_count
38
+ .to_a
39
+ .sort_by! { |metric, count| [-count, metric] }
40
+ .take(max)
41
+ .map! { |metric, count| { data: metric, count: count } }
34
42
  end
35
43
  end
36
44
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MemoryProfiler
4
- VERSION = "1.0.0"
4
+ VERSION = "1.0.2"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: memory_profiler
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-01 00:00:00.000000000 Z
11
+ date: 2024-06-17 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Memory profiling routines for Ruby 2.5+
14
14
  email:
@@ -23,6 +23,7 @@ files:
23
23
  - README.md
24
24
  - bin/ruby-memory-profiler
25
25
  - lib/memory_profiler.rb
26
+ - lib/memory_profiler/autorun.rb
26
27
  - lib/memory_profiler/cli.rb
27
28
  - lib/memory_profiler/helpers.rb
28
29
  - lib/memory_profiler/monochrome.rb
@@ -52,7 +53,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
52
53
  - !ruby/object:Gem::Version
53
54
  version: '0'
54
55
  requirements: []
55
- rubygems_version: 3.1.4
56
+ rubygems_version: 3.5.11
56
57
  signing_key:
57
58
  specification_version: 4
58
59
  summary: Memory profiling routines for Ruby 2.5+