memory_profiler 0.9.14 → 1.0.0

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: 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: []