memory_profiler 0.9.14 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 187acbd9601ade1bb6ccdb60bfb4c305ada15097f02278529f8f6a6fc7d68666
4
- data.tar.gz: 33d797db658d4a4507247a0ae9c4b92da568b86e9246f4571a94774b62285c6b
3
+ metadata.gz: dea36221189a4ea796d262870a9a09acf48fcfe3b6321814fc3e86cc1f4c37db
4
+ data.tar.gz: '059ab396fc3c0e928046dd3f5c0e6271a616a369f427ee8d134521bb934e8cd8'
5
5
  SHA512:
6
- metadata.gz: 977f958e6b9f3292053ccef51bcef01828cda7d29760f131687d21b00222950fbf1ec4629a7021bfdc4665e137c3f75cfd729a29db60b879a5c30f08da0de097
7
- data.tar.gz: 1cc221f3bf20520a3063031a4d9830e3303d9d9182cafb746aa77d0059888a4a640f2d6dd39a925e31398664018c98e9324565247046310c15ef823419bb6baf
6
+ metadata.gz: 69a830fb9ac126817342cc84e70f0796417d119ae337172a6b79a26d10e78a08146a4496915c384c7f993e8c514d43168e8e9d1d925acb856ca77092bdd1535e
7
+ data.tar.gz: 6de39e21847f551fac3b53107e5fa9d8cba5c6d00cfd39fb104432bc44f83008afefe9c46138ce4560e91880f245456b00f60718593198d735a379e4d93b8d71
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.0 - 02-12-2020
4
+
5
+ - Added new CLI `ruby-memory-profiler` which can be used to profile scripts @fatkodima
6
+ - Reduced memory usage when generating reports
7
+ - Some optimizations for Ruby 2.7
8
+ - Remove EOL Rubies: 2.3 and 2.4 are no longer supported (use an earlier version of the gem if needed)
9
+
3
10
  ## 0.9.14 - 28-06-2019
4
11
 
5
12
  - Pass 'normalize_path: true' to pretty_print to have locations stripped
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![Build Status](https://travis-ci.org/SamSaffron/memory_profiler.svg?branch=master)](https://travis-ci.org/SamSaffron/memory_profiler)
1
+ [![CI](https://github.com/SamSaffron/memory_profiler/workflows/CI/badge.svg)](https://github.com/SamSaffron/memory_profiler/actions?query=workflow%3ACI)
2
2
  [![Gem Version](https://badge.fury.io/rb/memory_profiler.svg)](https://rubygems.org/gems/memory_profiler)
3
3
 
4
4
  # MemoryProfiler
@@ -7,7 +7,7 @@ A memory profiler for Ruby
7
7
 
8
8
  ## Requirements
9
9
 
10
- Ruby(MRI) Version 2.3.8 and above.
10
+ Ruby(MRI) Version 2.5.0 and above.
11
11
 
12
12
  ## Installation
13
13
 
@@ -25,6 +25,25 @@ Or install it yourself as:
25
25
 
26
26
  ## Usage
27
27
 
28
+ There are two ways to use `memory_profiler`:
29
+ * command line
30
+ * convenience API
31
+
32
+ ### Command Line
33
+
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
+ $ ruby-memory-profiler [options] <script.rb> [--] [script-options]
37
+ ```
38
+ Where `script.rb` is the program you want to profile.
39
+
40
+ For a full list of options, execute the following command:
41
+ ```
42
+ ruby-memory-profiler -h
43
+ ```
44
+
45
+ ### Convenience API
46
+
28
47
  ```ruby
29
48
  require 'memory_profiler'
30
49
  report = MemoryProfiler.report do
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ $LOAD_PATH.unshift File.expand_path("../lib", __dir__)
5
+
6
+ require "memory_profiler"
7
+
8
+ exit MemoryProfiler::CLI.new.run(ARGV)
@@ -9,6 +9,7 @@ require "memory_profiler/stat"
9
9
  require "memory_profiler/stat_hash"
10
10
  require "memory_profiler/results"
11
11
  require "memory_profiler/reporter"
12
+ require "memory_profiler/cli"
12
13
 
13
14
  module MemoryProfiler
14
15
  def self.report(opts = {}, &block)
@@ -0,0 +1,149 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "optparse"
4
+
5
+ module MemoryProfiler
6
+ class CLI
7
+ BIN_NAME = "ruby-memory-profiler"
8
+ VERSION_INFO = "#{BIN_NAME} #{MemoryProfiler::VERSION}"
9
+
10
+ STATUS_SUCCESS = 0
11
+ STATUS_ERROR = 1
12
+
13
+ DEFAULTS = {
14
+ ignore_files: "memory_profiler/lib"
15
+ }.freeze
16
+
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
+ def run(argv)
32
+ options = {}
33
+ parser = option_parser(options)
34
+ parser.parse!(argv)
35
+
36
+ options = DEFAULTS.merge(options)
37
+
38
+ # Make sure the user specified at least one file
39
+ unless (script = argv.shift)
40
+ puts parser
41
+ puts ""
42
+ puts "#{VERSION_INFO} | ERROR: Must specify a script to run"
43
+ return STATUS_ERROR
44
+ end
45
+
46
+ MemoryProfiler.start(reporter_options(options))
47
+ load script
48
+
49
+ STATUS_SUCCESS
50
+ rescue OptionParser::InvalidOption, OptionParser::InvalidArgument, OptionParser::MissingArgument => e
51
+ puts parser
52
+ puts e.message
53
+ STATUS_ERROR
54
+ ensure
55
+ report = MemoryProfiler.stop
56
+ report&.pretty_print(**results_options(options))
57
+ end
58
+
59
+ private
60
+
61
+ def option_parser(options)
62
+ OptionParser.new do |opts|
63
+ opts.banner = <<~BANNER
64
+
65
+ #{VERSION_INFO}
66
+ A Memory Profiler for Ruby
67
+
68
+ Usage:
69
+ #{BIN_NAME} [options] <script.rb> [--] [script-options]
70
+ BANNER
71
+
72
+ opts.separator ""
73
+ opts.separator "Options:"
74
+
75
+ # Reporter options
76
+ opts.on("-m", "--max=NUM", Integer, "Max number of entries to output. (Defaults to 50)") do |arg|
77
+ options[:top] = arg
78
+ end
79
+
80
+ opts.on("--classes=CLASSES", Array, "A class or list of classes you explicitly want to trace.") do |arg|
81
+ options[:trace] = arg.map { |klass| Object.const_get(klass) }
82
+ end
83
+
84
+ opts.on("--ignore-files=REGEXP", "A regular expression used to exclude certain files from tracing.") do |arg|
85
+ options[:ignore_files] = "#{arg}|memory_profiler/lib"
86
+ end
87
+
88
+ opts.on("--allow-files=FILES", Array, "A string or list of strings to selectively include in tracing.") do |arg|
89
+ options[:allow_files] = arg
90
+ end
91
+
92
+ opts.separator ""
93
+
94
+ # Results options
95
+ opts.on("-o", "--out=FILE", "Write output to a file instead of STDOUT.") do |arg|
96
+ options[:to_file] = arg
97
+ end
98
+
99
+ opts.on("--[no-]color", "Force color output on or off. (Enabled by default)") do |arg|
100
+ options[:color_output] = arg
101
+ end
102
+
103
+ opts.on("--retained-strings=NUM", Integer, "How many retained strings to print.") do |arg|
104
+ options[:retained_strings] = arg
105
+ end
106
+
107
+ opts.on("--allocated-strings=NUM", Integer, "How many allocated strings to print.") do |arg|
108
+ options[:allocated_strings] = arg
109
+ end
110
+
111
+ opts.on("--[no-]detailed", "Print detailed information. (Enabled by default)") do |arg|
112
+ options[:detailed_report] = arg
113
+ end
114
+
115
+ opts.on("--scale-bytes", "Calculates unit prefixes for the numbers of bytes.") do
116
+ options[:scale_bytes] = true
117
+ end
118
+
119
+ opts.on("--normalize-paths", "Print location paths relative to gem's source directory.") do
120
+ options[:normalize_paths] = true
121
+ end
122
+
123
+ opts.on("--pretty", "Easily enable options 'scale-bytes' and 'normalize-paths'") do
124
+ options[:scale_bytes] = options[:normalize_paths] = true
125
+ end
126
+
127
+ opts.separator ""
128
+
129
+ opts.on_tail("-h", "--help", "Show this help message.") do
130
+ puts opts
131
+ exit
132
+ end
133
+
134
+ opts.on_tail("-v", "--version", "Show program version.") do
135
+ puts VERSION_INFO
136
+ exit
137
+ end
138
+ end
139
+ end
140
+
141
+ def reporter_options(options)
142
+ options.select { |k, _v| REPORTER_KEYS.include?(k) }
143
+ end
144
+
145
+ def results_options(options)
146
+ options.select { |k, _v| RESULTS_KEYS.include?(k) }
147
+ end
148
+ end
149
+ end
@@ -29,8 +29,35 @@ module MemoryProfiler
29
29
  @location_cache[file][line] ||= "#{file}:#{line}"
30
30
  end
31
31
 
32
- def lookup_class_name(klass)
33
- @class_name_cache[klass] ||= ((klass.is_a?(Class) && klass.name) || '<<Unknown>>').to_s
32
+ KERNEL_CLASS_METHOD = Kernel.instance_method(:class)
33
+ if UnboundMethod.method_defined?(:bind_call)
34
+ def object_class(obj)
35
+ klass = obj.class rescue nil
36
+ unless Class === klass
37
+ # attempt to determine the true Class when .class returns something other than a Class
38
+ klass = KERNEL_CLASS_METHOD.bind_call(obj)
39
+ end
40
+ klass
41
+ end
42
+ else
43
+ def object_class(obj)
44
+ klass = obj.class rescue nil
45
+ unless Class === klass
46
+ # attempt to determine the true Class when .class returns something other than a Class
47
+ klass = KERNEL_CLASS_METHOD.bind(obj).call
48
+ end
49
+ klass
50
+ end
51
+ end
52
+
53
+ if Object.name.frozen? # Since Ruby 2.7 Module#name no longer allocate a new string
54
+ def lookup_class_name(klass)
55
+ ((klass.is_a?(Class) && klass.name) || '<<Unknown>>').to_s
56
+ end
57
+ else
58
+ def lookup_class_name(klass)
59
+ @class_name_cache[klass] ||= ((klass.is_a?(Class) && klass.name) || '<<Unknown>>').to_s
60
+ end
34
61
  end
35
62
 
36
63
  def lookup_string(obj)
@@ -5,7 +5,7 @@ module MemoryProfiler
5
5
  class Polychrome
6
6
 
7
7
  def path(text)
8
- gray(text)
8
+ blue(text)
9
9
  end
10
10
 
11
11
  def string(text)
@@ -13,7 +13,7 @@ module MemoryProfiler
13
13
  end
14
14
 
15
15
  def line(text)
16
- gray(text)
16
+ cyan(text)
17
17
  end
18
18
 
19
19
  private
@@ -99,11 +99,7 @@ module MemoryProfiler
99
99
  next if @ignore_files && @ignore_files =~ file
100
100
  next if @allow_files && !(@allow_files =~ file)
101
101
 
102
- klass = obj.class rescue nil
103
- unless Class === klass
104
- # attempt to determine the true Class when .class returns something other than a Class
105
- klass = Kernel.instance_method(:class).bind(obj).call
106
- end
102
+ klass = helper.object_class(obj)
107
103
  next if @trace && !trace.include?(klass)
108
104
 
109
105
  begin
@@ -57,9 +57,9 @@ module MemoryProfiler
57
57
  self.strings_retained = string_report(retained, top)
58
58
 
59
59
  self.total_allocated = allocated.size
60
- self.total_allocated_memsize = allocated.values.map!(&:memsize).inject(0, :+)
60
+ self.total_allocated_memsize = total_memsize(allocated)
61
61
  self.total_retained = retained.size
62
- self.total_retained_memsize = retained.values.map!(&:memsize).inject(0, :+)
62
+ self.total_retained_memsize = total_memsize(retained)
63
63
 
64
64
  self
65
65
  end
@@ -73,19 +73,22 @@ module MemoryProfiler
73
73
  end
74
74
 
75
75
  def string_report(data, top)
76
- grouped_strings = data.values
77
- .keep_if { |stat| stat.string_value }
78
- .group_by { |stat| stat.string_value.object_id }
79
- .values
76
+ grouped_strings = Hash.new { |hash, key| hash[key] = [] }
77
+ data.each_value do |stat|
78
+ if stat.string_value
79
+ grouped_strings[stat.string_value.object_id] << stat
80
+ end
81
+ end
82
+
83
+ grouped_strings = grouped_strings.values
80
84
 
81
85
  if grouped_strings.size > top
82
- cutoff = grouped_strings.sort_by!(&:size)[-top].size
83
- grouped_strings.keep_if { |list| list.size >= cutoff }
86
+ grouped_strings.sort_by!(&:size)
87
+ grouped_strings = grouped_strings.drop(grouped_strings.size - top)
84
88
  end
85
89
 
86
90
  grouped_strings
87
91
  .sort! { |a, b| a.size == b.size ? a[0].string_value <=> b[0].string_value : b.size <=> a.size }
88
- .first(top)
89
92
  .map! do |list|
90
93
  # Return array of [string, [[location, count], [location, count], ...]
91
94
  [
@@ -171,6 +174,14 @@ module MemoryProfiler
171
174
 
172
175
  private
173
176
 
177
+ def total_memsize(stat_hash)
178
+ sum = 0
179
+ stat_hash.each_value do |stat|
180
+ sum += stat.memsize
181
+ end
182
+ sum
183
+ end
184
+
174
185
  def print_title(io, title)
175
186
  io.puts
176
187
  io.puts title
@@ -6,24 +6,29 @@ module MemoryProfiler
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
8
 
9
- stat_totals =
10
- self.values
11
- .group_by(&metric_method)
12
- .map do |metric, stats|
13
- [metric, stats.reduce(0) { |sum, stat| sum + stat.memsize }, stats.size]
14
- end
9
+ metric_memsize = Hash.new(0)
10
+ metric_objects_count = Hash.new(0)
11
+
12
+ each_value do |value|
13
+ metric = value.send(metric_method)
14
+
15
+ metric_memsize[metric] += value.memsize
16
+ metric_objects_count[metric] += 1
17
+ end
15
18
 
16
19
  stats_by_memsize =
17
- stat_totals
18
- .sort_by! { |metric, memsize, _count| [-memsize, metric] }
20
+ metric_memsize
21
+ .to_a
22
+ .sort_by! { |metric, memsize| [-memsize, metric] }
19
23
  .take(max)
20
- .map! { |metric, memsize, _count| { data: metric, count: memsize } }
24
+ .map! { |metric, memsize| { data: metric, count: memsize } }
21
25
 
22
26
  stats_by_count =
23
- stat_totals
24
- .sort_by! { |metric, _memsize, count| [-count, metric] }
27
+ metric_objects_count
28
+ .to_a
29
+ .sort_by! { |metric, count| [-count, metric] }
25
30
  .take(max)
26
- .map! { |metric, _memsize, count| { data: metric, count: count } }
31
+ .map! { |metric, count| { data: metric, count: count } }
27
32
 
28
33
  [stats_by_memsize, stats_by_count]
29
34
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MemoryProfiler
4
- VERSION = "0.9.14"
4
+ VERSION = "1.0.0"
5
5
  end
metadata CHANGED
@@ -1,26 +1,29 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: memory_profiler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.14
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-06-27 00:00:00.000000000 Z
11
+ date: 2020-12-01 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: Memory profiling routines for Ruby 2.3+
13
+ description: Memory profiling routines for Ruby 2.5+
14
14
  email:
15
15
  - sam.saffron@gmail.com
16
- executables: []
16
+ executables:
17
+ - ruby-memory-profiler
17
18
  extensions: []
18
19
  extra_rdoc_files: []
19
20
  files:
20
21
  - CHANGELOG.md
21
22
  - LICENSE.txt
22
23
  - README.md
24
+ - bin/ruby-memory-profiler
23
25
  - lib/memory_profiler.rb
26
+ - lib/memory_profiler/cli.rb
24
27
  - lib/memory_profiler/helpers.rb
25
28
  - lib/memory_profiler/monochrome.rb
26
29
  - lib/memory_profiler/polychrome.rb
@@ -34,7 +37,7 @@ homepage: https://github.com/SamSaffron/memory_profiler
34
37
  licenses:
35
38
  - MIT
36
39
  metadata: {}
37
- post_install_message:
40
+ post_install_message:
38
41
  rdoc_options: []
39
42
  require_paths:
40
43
  - lib
@@ -42,15 +45,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
42
45
  requirements:
43
46
  - - ">="
44
47
  - !ruby/object:Gem::Version
45
- version: 2.3.0
48
+ version: 2.5.0
46
49
  required_rubygems_version: !ruby/object:Gem::Requirement
47
50
  requirements:
48
51
  - - ">="
49
52
  - !ruby/object:Gem::Version
50
53
  version: '0'
51
54
  requirements: []
52
- rubygems_version: 3.0.3
53
- signing_key:
55
+ rubygems_version: 3.1.4
56
+ signing_key:
54
57
  specification_version: 4
55
- summary: Memory profiling routines for Ruby 2.3+
58
+ summary: Memory profiling routines for Ruby 2.5+
56
59
  test_files: []