memory_profiler 1.0.0 → 1.0.2

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.
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+