gem_footprint_analyzer 0.1.6 → 0.1.7

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: 4ea3f3ac4a4cdc86a560ae500fdbdc59423afe2d28ac9f16f5fdc7efe5d433fc
4
- data.tar.gz: 5baf82c20f15679d84b56f86e303e81325aadbc4577fdcc6bc36a388d0b92baa
3
+ metadata.gz: 13ad6f4a90d9f22d88152496807371138eecc8456b195ecf63597771c3e5f43d
4
+ data.tar.gz: 6f78c45ebac12fb4602c61d3ff59b71b9c8dc0dff0fb88a7344ce56506e0c4f1
5
5
  SHA512:
6
- metadata.gz: 54b934c3cb13d9b564f52abc70adc1286c5842acb2e0c25d178131d6d1e653732f9a1d49df807004fc434089e97f119fbf32ddee75af29e488e1df415b58bbfe
7
- data.tar.gz: 50bb084a66ae63808ee91acde1c257cda615cb28cb2ea2ee71463306e7d8ca2b59a3fcf1e4d09d2fe3ebef8e1c6fb12e4440b4a5c3f523a6108b03bf4d44fbdc
6
+ metadata.gz: 5aa544dacfe1ac6d74622d07ed884e92ddeb40203f460701a2afe05f691bd0df2d495bac22efb3dba8b32d0b678e925ede6d00fbfaf66fe1fcef472c541f83b0
7
+ data.tar.gz: 52ee797d7c5a24d16effc451eb9d64b5bfd577601767e2efd974d9b7fe013cddceca41d9d89aaffcefb7931f8ac75920869a63fb53e89696dc97b4d169e64459
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- gem_footprint_analyzer (0.1.6)
4
+ gem_footprint_analyzer (0.1.7)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -1,32 +1,28 @@
1
1
  # GemFootprintAnalyzer
2
2
  [![Gem Version](https://badge.fury.io/rb/gem_footprint_analyzer.svg)](https://badge.fury.io/rb/gem_footprint_analyzer)
3
- [![Build Status](https://travis-ci.com/irvingwashington/gem_footprint_analyzer.svg?branch=master)](https://travis-ci.com/irvingwashington/gem_footprint_analyzer)
3
+ [![Build Status](https://api.travis-ci.org/irvingwashington/gem_footprint_analyzer.svg?branch=master)](https://travis-ci.org/irvingwashington/gem_footprint_analyzer)
4
4
 
5
5
  A tool for analyzing time and RSS footprint of gems or standard library requires.
6
6
  Requires Ruby >= 2.2.0, works on Linux and MacOS.
7
7
 
8
- ## Installation
9
-
10
- Add this line to your application's Gemfile:
11
-
12
- ```ruby
13
- gem 'gem_footprint_analyzer', require: false
14
- ```
15
-
16
- And then execute:
17
-
18
- $ bundle
19
-
20
- Or install it yourself as:
21
-
22
- $ gem install gem_footprint_analyzer
23
-
24
8
  ## Usage
25
9
 
26
10
  You can use this gem with or without Bundler. Using Bundler is more convenient and does not have any
27
11
  impact on results, however you have to have `gem_footprint_analyzer` present in your Gemfile
28
12
  (use the development section).
29
13
 
14
+ Banner
15
+ ```bash
16
+ GemFootprintAnalyzer (0.1.6)
17
+ Usage: bundle exec analyze_requires library_to_analyze [require]
18
+ -f, --formatter FORMATTER Format output using selected formatter (json tree)
19
+ -n, --runs-num NUMBER Number of runs
20
+ -r, --rubygems Require rubygems before the actual analyze
21
+ -g, --gemfile Analyze current Gemfile
22
+ -d, --debug Show debug information
23
+ -h, --help Show this message
24
+ ```
25
+
30
26
  1) with Bundler
31
27
  ```bash
32
28
  # Standard library
@@ -45,6 +41,57 @@ $ analyze_requires net/http
45
41
  $ RUBYLIB=/Users/irving/.gem/ruby/2.5.3/gems/activerecord-5.2.1/lib:/Users/irving/.gem/ruby/2.5.3/gems/activesupport-5.2.1/lib/:/Users/irving/.gem/ruby/2.5.3/gems/concurrent-ruby-1.0.5/lib:/Users/irving/.gem/ruby/2.5.3/gems/i18n-1.1.1/lib:/Users/irving/.gem/ruby/2.5.3/gems/activemodel-5.2.1/lib:/Users/irving/.gem/ruby/2.5.3/gems/arel-9.0.0/lib:/Users/irving/.gem/ruby/2.5.3/gems/tzinfo-1.2.5/lib:/Users/irving/.gem/ruby/2.5.3/gems/thread_safe-0.3.6/lib analyze_requries activerecord active_record
46
42
  ```
47
43
 
44
+ ## Analyzing Gemfile
45
+
46
+ GemFootprintAnalyzer can be used to analyze the whole Gemfile of a given project.
47
+ This way, it is cleary visible which gems take most time or consume most RSS on application start.
48
+ You can also see what kind of impact does adding a new dependency have on the the project footprint.
49
+ As processing of all gems might take a lot of time, it is advisable to set the number of runs to 1.
50
+
51
+ ```bash
52
+ # Analyze the entire Gemfile, with a single run
53
+ irving:~/Workspace/motoperf (master)$ bundle exec analyze_requires -gn1
54
+ GemFootprintAnalyzer (0.1.6)
55
+
56
+ Analyze results (average measured from 1 run(s))
57
+ time is the amount of time given require has taken to complete
58
+ RSS is total memory increase up to the point after the require
59
+
60
+ name time RSS after
61
+ --------------------------------------------
62
+ newrelic_rpm 1540ms 38540KB
63
+ draper 242ms 31544KB
64
+ activemodel-serializers-xml 46ms 29248KB
65
+ slim 261ms 29088KB
66
+ dalli 107ms 26036KB
67
+ bcrypt 31ms 25872KB
68
+ will_paginate 45ms 25848KB
69
+ jbuilder 190ms 25844KB
70
+ jquery-rails 17ms 25808KB
71
+ uglifier 135ms 25808KB
72
+ sass-rails 3379ms 25676KB
73
+ pg 108ms 13260KB
74
+ rails 3516ms 12612KB
75
+
76
+ Total runtime 10.0740s
77
+ ```
78
+
79
+ ## Installation
80
+
81
+ Add this line to your application's Gemfile:
82
+
83
+ ```ruby
84
+ gem 'gem_footprint_analyzer', require: false
85
+ ```
86
+
87
+ And then execute:
88
+
89
+ $ bundle
90
+
91
+ Or install it yourself as:
92
+
93
+ $ gem install gem_footprint_analyzer
94
+
48
95
  ## Example analyses (Ruby 2.5.3):
49
96
 
50
97
  ### timeout
@@ -397,7 +444,7 @@ Total runtime 0.9878s
397
444
 
398
445
  ## Contributing
399
446
 
400
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/gem_footprint_analyzer. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
447
+ Bug reports and pull requests are welcome on GitHub at https://github.com/irvingwashington/gem_footprint_analyzer. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
401
448
 
402
449
  ## License
403
450
 
@@ -3,6 +3,8 @@
3
3
  $LOAD_PATH.unshift(File.join(__dir__, '..', 'lib'))
4
4
  $LOAD_PATH.unshift('./lib') if Dir.exist?('lib')
5
5
 
6
+ require 'bundler/setup' if ENV.key?('BUNDLER_VERSION')
7
+
6
8
  require 'gem_footprint_analyzer'
7
9
 
8
10
  cli = GemFootprintAnalyzer::CLI.new
@@ -4,6 +4,8 @@ require 'gem_footprint_analyzer/child_process'
4
4
  require 'gem_footprint_analyzer/analyzer'
5
5
  require 'gem_footprint_analyzer/average_runner'
6
6
  require 'gem_footprint_analyzer/cli'
7
+ require 'gem_footprint_analyzer/cli/opts'
8
+ require 'gem_footprint_analyzer/cli/utils'
7
9
  require 'gem_footprint_analyzer/core_ext/array'
8
10
  require 'gem_footprint_analyzer/core_ext/hash'
9
11
  require 'gem_footprint_analyzer/core_ext/file'
@@ -6,18 +6,20 @@ module GemFootprintAnalyzer
6
6
  # is gathered in the child process and passed along to the parent.
7
7
  class Analyzer
8
8
  # @param fifos [Hash<Symbol>] Hash containing filenames of :child and :parent FIFO files
9
- def initialize(fifos)
9
+ def initialize(fifos, options = {})
10
+ @options = options
10
11
  @fifos = fifos
11
12
  end
12
13
 
13
- # @param library [String] name of the library or parameter for the gem method
14
- # (ex. 'activerecord', 'activesupport')
14
+ # @param library [String|nil] name of the library or parameter for the gem method
15
+ # (ex. 'activerecord', 'activesupport').
16
+ # Nil if user wants to analyze the whole Gemfile
15
17
  # @param require_string [String|nil] optional require string, if it differs from the gem name
16
18
  # (ex. 'active_record', 'active_support/time')
17
19
  # @return [Array<Hash>] list of require-data-hashes, first element contains base level RSS,
18
20
  # last element can be treated as a summary as effectively it consists of all the previous.
19
- def test_library(library, require_string = nil)
20
- child = ChildProcess.new(library, require_string, fifos)
21
+ def test_library(library = nil, require_string = nil)
22
+ child = ChildProcess.new(library, require_string, fifos, options)
21
23
  child.start_child
22
24
  parent_transport = init_transport
23
25
  requires = collect_requires(parent_transport, child.pid)
@@ -28,7 +30,7 @@ module GemFootprintAnalyzer
28
30
 
29
31
  private
30
32
 
31
- attr_reader :fifos
33
+ attr_reader :fifos, :options
32
34
 
33
35
  def collect_requires(transport, process_id)
34
36
  requires_context = {base_rss: nil, requires: [], process_id: process_id, transport: transport}
@@ -12,19 +12,18 @@ module GemFootprintAnalyzer
12
12
  def initialize
13
13
  output Process.pid
14
14
  init_transport
15
+ require_rubygems_if_needed
16
+ require_bundler_if_needed
15
17
  end
16
18
 
17
19
  # Installs the require-spying code and starts requiring
18
20
  def start
19
21
  RequireSpy.spy_require(transport)
20
- begin
21
- require(require_string)
22
- rescue LoadError => e
23
- warn_about_load_error(e)
24
- transport.exit_with_error(e)
25
- exit 1
26
- end
27
- transport.done_and_wait_for_ack
22
+ error = try_require(require_string)
23
+ return transport.done_and_wait_for_ack unless error
24
+
25
+ transport.exit_with_error(error)
26
+ exit(1)
28
27
  end
29
28
 
30
29
  private
@@ -43,6 +42,26 @@ module GemFootprintAnalyzer
43
42
  ENV['require_string']
44
43
  end
45
44
 
45
+ def analyze_gemfile
46
+ ENV['analyze_gemfile']
47
+ end
48
+
49
+ def require_rubygems
50
+ ENV['require_rubygems']
51
+ end
52
+
53
+ def try_require(require_string)
54
+ error = nil
55
+ begin
56
+ analyze_gemfile ? Bundler.require : require(require_string)
57
+ rescue LoadError => error
58
+ warn_about_load_error(error)
59
+ rescue Exception => error # rubocop:disable Lint/RescueException
60
+ warn_about_error(error)
61
+ end
62
+ error
63
+ end
64
+
46
65
  def init_transport
47
66
  write_stream = File.open(child_fifo, 'w')
48
67
  read_stream = File.open(parent_fifo, 'r')
@@ -55,12 +74,28 @@ module GemFootprintAnalyzer
55
74
  STDOUT.flush
56
75
  end
57
76
 
77
+ def require_rubygems_if_needed
78
+ return unless require_rubygems
79
+
80
+ require 'rubygems'
81
+ end
82
+
83
+ def require_bundler_if_needed
84
+ return unless analyze_gemfile
85
+
86
+ require 'bundler/setup'
87
+ end
88
+
58
89
  def warn_about_load_error(error)
59
90
  possible_gem = error.message.split.last
60
91
  STDERR.puts "Cannot load '#{possible_gem}', this might be an issue with implicit require in" \
61
92
  " the '#{require_string}'"
62
93
  STDERR.puts "If '#{possible_gem}' is a gem, you can try adding it to the Gemfile explicitly" \
63
94
  ", running `bundle install` and trying again\n\n"
95
+ warn_about_error(error)
96
+ end
97
+
98
+ def warn_about_error(error)
64
99
  STDERR.puts error.backtrace
65
100
  STDERR.flush
66
101
  end
@@ -7,11 +7,12 @@ module GemFootprintAnalyzer
7
7
  LEGACY_RUBY_CMD = [RbConfig.ruby, '--disable=gem'].freeze
8
8
  RUBY_CMD = [RbConfig.ruby, '--disable=did_you_mean', '--disable=gem'].freeze
9
9
 
10
- def initialize(library, require_string, fifos)
10
+ def initialize(library, require_string, fifos, options = {})
11
11
  @library = library
12
12
  @require_string = require_string || library
13
13
  @fifos = fifos
14
14
  @pid = nil
15
+ @options = options
15
16
  end
16
17
 
17
18
  def start_child
@@ -35,7 +36,7 @@ module GemFootprintAnalyzer
35
36
 
36
37
  private
37
38
 
38
- attr_reader :require_string, :child_thread, :fifos
39
+ attr_reader :require_string, :child_thread, :fifos, :options
39
40
 
40
41
  def ruby_command
41
42
  if RbConfig::CONFIG['MAJOR'].to_i >= 2 && RbConfig::CONFIG['MINOR'].to_i >= 3
@@ -48,6 +49,8 @@ module GemFootprintAnalyzer
48
49
  def child_env_vars
49
50
  {
50
51
  'require_string' => require_string,
52
+ 'require_rubygems' => options.key?(:rubygems) && 'true' || nil,
53
+ 'analyze_gemfile' => options.key?(:analyze_gemfile) && 'true' || nil,
51
54
  'start_child_context' => 'true',
52
55
  'child_fifo' => fifos[:child],
53
56
  'parent_fifo' => fifos[:parent],
@@ -1,4 +1,3 @@
1
- require 'optparse'
2
1
  require 'tmpdir'
3
2
 
4
3
  module GemFootprintAnalyzer
@@ -11,16 +10,16 @@ module GemFootprintAnalyzer
11
10
  @options[:debug] = false
12
11
  @options[:formatter] = 'tree'
13
12
 
14
- try_require_bundler
13
+ @opts = Opts.new(@options)
15
14
  end
16
15
 
17
16
  # @param args [Array<String>] runs the analyzer with parsed args taken as options
18
17
  # @return [void]
19
18
  def run(args = ARGV)
20
- opts_parser.parse!(args)
19
+ opts.parse!(args)
21
20
 
22
- if args.empty?
23
- puts opts_parser
21
+ if !analyze_gemfile? && args.empty?
22
+ puts opts.parser
24
23
  exit 1
25
24
  end
26
25
 
@@ -31,18 +30,19 @@ module GemFootprintAnalyzer
31
30
 
32
31
  def print_requires(options, args)
33
32
  requires_list_average = capture_requires(options, args)
34
- at_exit { clean_up }
35
33
  formatter = formatter_instance(options)
36
- puts formatter.new(options).format_list(requires_list_average)
34
+ output = formatter.new(options).format_list(requires_list_average)
35
+
36
+ Utils.safe_puts(output)
37
37
  end
38
38
 
39
- attr_reader :options
39
+ attr_reader :options, :opts
40
40
 
41
41
  def capture_requires(options, args)
42
42
  GemFootprintAnalyzer::AverageRunner.new(options[:runs]) do
43
43
  fifos = init_fifos
44
44
 
45
- GemFootprintAnalyzer::Analyzer.new(fifos).test_library(*args).tap do
45
+ GemFootprintAnalyzer::Analyzer.new(fifos, options).test_library(*args).tap do
46
46
  clean_up_fifos(fifos)
47
47
  end
48
48
  end.run
@@ -68,60 +68,8 @@ module GemFootprintAnalyzer
68
68
  GemFootprintAnalyzer::Formatters.const_get(options[:formatter].capitalize)
69
69
  end
70
70
 
71
- def opts_parser # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
72
- @opts_parser ||= OptionParser.new do |opts|
73
- opts.banner = banner
74
- opts.on('-f', '--formatter FORMATTER', %w[json tree],
75
- 'Format output using selected formatter (json tree)') do |formatter|
76
-
77
- options[:formatter] = formatter
78
- end
79
-
80
- opts.on('-n', '--runs-num NUMBER', OptParse::DecimalInteger, 'Number of runs') do |runs|
81
- fail OptionParser::InvalidArgument, 'must be a number greater than 0' if runs < 1
82
-
83
- options[:runs] = runs
84
- end
85
-
86
- opts.on('-d', '--debug', 'Show debug information') do |debug|
87
- opts.banner += debug_banner if debug
88
-
89
- options[:debug] = debug
90
- end
91
-
92
- opts.on_tail('-h', '--help', 'Show this message') do
93
- puts opts
94
- exit
95
- end
96
- end
97
- end
98
-
99
- def banner
100
- script_name = "bundle exec #{File.basename($PROGRAM_NAME)}"
101
-
102
- "GemFootprintAnalyzer (#{GemFootprintAnalyzer::VERSION})\n" \
103
- "Usage: #{script_name} library_to_analyze [require]"
104
- end
105
-
106
- def debug_banner
107
- "\n(#{File.expand_path(File.join(File.dirname(__FILE__), '..'))})"
108
- end
109
-
110
- def clean_up
111
- fork_waiters = Thread.list.select { |th| th.is_a?(Process::Waiter) }
112
- fork_waiters.each do |waiter|
113
- begin
114
- Process.kill('TERM', waiter.pid)
115
- rescue Errno::ESRCH
116
- nil
117
- end
118
- end
119
- end
120
-
121
- def try_require_bundler
122
- require 'bundler/setup'
123
- rescue LoadError
124
- nil
71
+ def analyze_gemfile?
72
+ options[:analyze_gemfile]
125
73
  end
126
74
  end
127
75
  end
@@ -0,0 +1,87 @@
1
+ require 'optparse'
2
+
3
+ module GemFootprintAnalyzer
4
+ class CLI
5
+ # A class dealing with command line options parsing, validation and displaying banner and
6
+ # help messages.
7
+ class Opts
8
+ # @param options [Hash<Symbol>]
9
+ def initialize(options)
10
+ @options = options
11
+ end
12
+
13
+ # @param args [Array<String>]
14
+ def parse!(args)
15
+ parser.parse!(args)
16
+ end
17
+
18
+ # @return [OptionParser]
19
+ def parser # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
20
+ @parser ||= OptionParser.new do |opts|
21
+ opts.banner = banner
22
+ opts.on('-f', '--formatter FORMATTER', %w[json tree],
23
+ 'Format output using selected formatter (json tree)') do |formatter|
24
+
25
+ options[:formatter] = formatter
26
+ end
27
+
28
+ opts.on('-n', '--runs-num NUMBER', OptParse::DecimalInteger, 'Number of runs') do |runs|
29
+ fail OptionParser::InvalidArgument, 'must be a number greater than 0' if runs < 1
30
+
31
+ options[:runs] = runs
32
+ end
33
+
34
+ opts.on('-r', '--rubygems', 'Require rubygems before the actual analyze') do |rubygems|
35
+ options[:rubygems] = rubygems
36
+ end
37
+
38
+ opts.on('-g', '--gemfile', 'Analyze current Gemfile') do
39
+ validate_bundler_presence
40
+
41
+ options[:rubygems] = true
42
+ options[:analyze_gemfile] = true
43
+ end
44
+
45
+ opts.on('-d', '--debug', 'Show debug information') do |debug|
46
+ opts.banner += debug_banner if debug
47
+
48
+ options[:debug] = debug
49
+ end
50
+
51
+ opts.on_tail('-h', '--help', 'Show this message') do
52
+ puts opts
53
+ exit
54
+ end
55
+ end
56
+ end
57
+
58
+ attr_reader :options
59
+
60
+ def banner
61
+ script_name = "bundle exec #{File.basename($PROGRAM_NAME)}"
62
+
63
+ "GemFootprintAnalyzer (#{GemFootprintAnalyzer::VERSION})\n" \
64
+ "Usage: #{script_name} library_to_analyze [require]"
65
+ end
66
+
67
+ def debug_banner
68
+ "\n(#{File.expand_path(File.join(File.dirname(__FILE__), '..'))})"
69
+ end
70
+
71
+ def validate_bundler_presence
72
+ require 'rubygems'
73
+ require 'bundler/setup'
74
+
75
+ Bundler.root
76
+ rescue LoadError => e
77
+ puts "Bundler gem is not available, please install it first (#{e})"
78
+
79
+ exit 1
80
+ rescue GemfileNotFound => e
81
+ puts "No Gemfile found (#{e})"
82
+
83
+ exit 1
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,16 @@
1
+ module GemFootprintAnalyzer
2
+ class CLI
3
+ # A module containing helper methods for CLI
4
+ module Utils
5
+ def self.safe_puts(output)
6
+ output ||= "\n"
7
+
8
+ string_output = output.is_a?(String) ? output : output.join("\n")
9
+
10
+ puts string_output
11
+ rescue Errno::EPIPE
12
+ exit
13
+ end
14
+ end
15
+ end
16
+ end
@@ -3,12 +3,12 @@ module GemFootprintAnalyzer
3
3
  # A formatter class outputting bare JSON.
4
4
  # Useful for integrating with other tools.
5
5
  class Json
6
- require 'json'
7
-
8
6
  def initialize(*); end
9
7
 
10
8
  # @return [String] A JSON form of the requires_list array, last entry is the cumulated result.
11
9
  def format_list(requires_list)
10
+ require 'json'
11
+
12
12
  JSON.dump(requires_list)
13
13
  end
14
14
  end
@@ -16,8 +16,8 @@ module GemFootprintAnalyzer
16
16
  lines << "Analyze results (average measured from #{@options[:runs]} run(s))"
17
17
  lines << 'time is the amount of time given require has taken to complete'
18
18
  lines << 'RSS is total memory increase up to the point after the require'
19
- lines << "\n"
20
- lines.join("\n")
19
+ lines << ''
20
+ lines
21
21
  end
22
22
 
23
23
  # @return [String] Awesome text separator
@@ -7,6 +7,8 @@ module GemFootprintAnalyzer
7
7
  INDENT = ' '.freeze
8
8
  # Formatter helper class representing a single results require entry.
9
9
  class Entry
10
+ BUNDLER_RUNTIME = 'bundler/runtime'.freeze
11
+
10
12
  def initialize(entry_hash, options = {})
11
13
  @entry_hash = entry_hash
12
14
  @options = options
@@ -34,6 +36,10 @@ module GemFootprintAnalyzer
34
36
  "#{name}#{debug_parent}"
35
37
  end
36
38
 
39
+ def top_level?
40
+ parent.nil? || parent == BUNDLER_RUNTIME
41
+ end
42
+
37
43
  private
38
44
 
39
45
  def debug_parent
@@ -62,7 +68,7 @@ module GemFootprintAnalyzer
62
68
  format_entry(entry, indent_level, ljust_value)
63
69
  end
64
70
 
65
- (legend(ljust_value) + lines).join("\n")
71
+ (legend(ljust_value) + lines)
66
72
  end
67
73
 
68
74
  def legend(ljust_value)
@@ -81,9 +87,13 @@ module GemFootprintAnalyzer
81
87
  end
82
88
 
83
89
  def init_entries(requires_list)
84
- requires_list.last(requires_list.size - 1).map do |entry_hash|
90
+ entries = requires_list.last(requires_list.size - 1).map do |entry_hash|
85
91
  Entry.new(entry_hash, @options)
86
92
  end
93
+
94
+ entries.select!(&:top_level?) if @options[:analyze_gemfile]
95
+
96
+ entries
87
97
  end
88
98
 
89
99
  def max_indent(entries)
@@ -2,6 +2,10 @@ module GemFootprintAnalyzer
2
2
  # A module keeping hacks required to hijack {Kernel.require} and {Kernel.require_relative}
3
3
  # and plug in versions of them that communicate meta data to the {Analyzer}.
4
4
  module RequireSpy
5
+ # Suitable for versions 3.0.stable up to 5.2.1
6
+ ACTIVESUPPORT_REQUIRE_DEPENDENCY =
7
+ %r{active_support/dependencies\.rb.+(`require'|`load_dependency'|`block in require')\z}.freeze
8
+
5
9
  class << self
6
10
  def relative_path(caller_entry, require_name = nil)
7
11
  caller_file = caller_entry.split(':')[0]
@@ -11,67 +15,81 @@ module GemFootprintAnalyzer
11
15
  else
12
16
  full_path = caller_file
13
17
  end
14
- load_path = $LOAD_PATH.find { |lp| full_path.start_with?(lp) }
18
+ load_path = load_paths.find { |lp| full_path.start_with?(lp) }
15
19
  full_path.sub(%r{\A#{load_path}/}, '')
16
20
  end
17
21
 
22
+ def load_paths
23
+ @load_paths ||= $LOAD_PATH.map { |path| File.expand_path(path) }
24
+ end
25
+
18
26
  def without_extension(name)
19
27
  name.sub(/\.rb\z/, '')
20
28
  end
21
29
 
22
30
  def first_foreign_caller(caller_list)
23
31
  ffc = caller_list.find do |c|
24
- relative_path(c) !~ /gem_footprint_analyzer/
32
+ c !~ ACTIVESUPPORT_REQUIRE_DEPENDENCY &&
33
+ relative_path(c) !~ /gem_footprint_analyzer/
25
34
  end
26
35
  without_extension(relative_path(ffc)) if ffc
27
36
  end
28
37
 
29
38
  def spy_require(transport)
30
39
  alias_require_methods
31
- define_timed_exec
32
40
 
33
- define_require_relative
34
- define_require(transport)
41
+ define_require_relatives
42
+ define_requires(transport)
35
43
  end
36
44
 
37
- def alias_require_methods
38
- Kernel.send :alias_method, :regular_require, :require
39
- Kernel.send :alias_method, :regular_require_relative, :require_relative
45
+ def timed_exec
46
+ start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
47
+ result = yield
48
+ duration = (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time).round(4)
49
+ [duration, result]
40
50
  end
41
51
 
42
- def define_timed_exec
43
- Kernel.send :define_method, :timed_exec do |&block|
44
- start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
45
- block.call
46
- (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time).round(4)
52
+ def alias_require_methods
53
+ kernels.each do |k|
54
+ k.send :alias_method, :regular_require, :require
55
+ k.send :alias_method, :regular_require_relative, :require_relative
47
56
  end
48
57
  end
49
58
 
50
- def define_require(transport)
51
- Kernel.send :define_method, :require do |name|
52
- result = nil
59
+ def kernels
60
+ @kernels ||= [(class << ::Kernel; self; end), Kernel]
61
+ end
62
+
63
+ def define_requires(transport)
64
+ kernels.each { |k| define_require(k, transport) }
65
+ end
53
66
 
54
- transport.ready
55
- transport.wait_for_start
67
+ def define_require(klass, transport)
68
+ klass.send :define_method, :require do |name|
69
+ transport.ready_and_wait_for_start
70
+ duration, result = GemFootprintAnalyzer::RequireSpy.timed_exec { regular_require(name) }
71
+ bare_name = GemFootprintAnalyzer::RequireSpy.relative_path(name)
56
72
 
57
- t = timed_exec { result = regular_require(name) }
73
+ transport.report_require(GemFootprintAnalyzer::RequireSpy.without_extension(bare_name),
74
+ GemFootprintAnalyzer::RequireSpy.first_foreign_caller(caller),
75
+ duration)
58
76
 
59
- first_foreign_caller = GemFootprintAnalyzer::RequireSpy.first_foreign_caller(caller)
60
- bare_name = GemFootprintAnalyzer::RequireSpy.without_extension(name)
61
- transport.report_require(bare_name, first_foreign_caller || '', t)
62
77
  result
63
78
  end
64
79
  end
65
80
 
66
- def define_require_relative
81
+ def define_require_relatives
67
82
  # As of Ruby 2.5.1, both :require and :require_relative use an unexposed native method
68
83
  # rb_safe_require, however it's challenging to plug into it and using original
69
84
  # :require_relative is not really possible (it does path calculation magic) so instead
70
85
  # we're redirecting :require_relative to the regular :require
71
- Kernel.send :define_method, :require_relative do |name|
72
- last_caller = caller(1..1).first
73
- relative_path = GemFootprintAnalyzer::RequireSpy.relative_path(last_caller, name)
74
- return require(relative_path)
86
+ kernels.each do |k|
87
+ k.send :define_method, :require_relative do |name|
88
+ last_caller = caller(1..1).first
89
+ relative_path = GemFootprintAnalyzer::RequireSpy.relative_path(last_caller, name)
90
+
91
+ require(relative_path)
92
+ end
75
93
  end
76
94
  end
77
95
  end
@@ -24,7 +24,9 @@ module GemFootprintAnalyzer
24
24
  end
25
25
 
26
26
  # Blocks until a :start command is received from the read stream
27
- def wait_for_start
27
+ def ready_and_wait_for_start
28
+ write_raw_command 'ready'
29
+
28
30
  while (cmd = read_one_command)
29
31
  msg, = cmd
30
32
  break if msg == :start
@@ -40,11 +42,6 @@ module GemFootprintAnalyzer
40
42
  end
41
43
  end
42
44
 
43
- # Sends a ready command
44
- def ready
45
- write_raw_command 'ready'
46
- end
47
-
48
45
  # Sends a start command
49
46
  def start
50
47
  write_raw_command 'start'
@@ -59,7 +56,7 @@ module GemFootprintAnalyzer
59
56
  # @param source [String] Name of the source file that required the library
60
57
  # @param duration [Float] Time which it took to complete the require
61
58
  def report_require(library, source, duration)
62
- write_raw_command "rq: #{library.inspect},#{source.inspect},#{duration.inspect}"
59
+ write_raw_command "rq: #{library.inspect},#{(source || '').inspect},#{duration.inspect}"
63
60
  end
64
61
 
65
62
  # @param library [String] Name of the library that was required, but was already required before
@@ -1,3 +1,3 @@
1
1
  module GemFootprintAnalyzer
2
- VERSION = '0.1.6'.freeze
2
+ VERSION = '0.1.7'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gem_footprint_analyzer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.6
4
+ version: 0.1.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Maciek Dubiński
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-11-15 00:00:00.000000000 Z
11
+ date: 2018-11-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -102,6 +102,8 @@ files:
102
102
  - lib/gem_footprint_analyzer/child_context.rb
103
103
  - lib/gem_footprint_analyzer/child_process.rb
104
104
  - lib/gem_footprint_analyzer/cli.rb
105
+ - lib/gem_footprint_analyzer/cli/opts.rb
106
+ - lib/gem_footprint_analyzer/cli/utils.rb
105
107
  - lib/gem_footprint_analyzer/core_ext/array.rb
106
108
  - lib/gem_footprint_analyzer/core_ext/file.rb
107
109
  - lib/gem_footprint_analyzer/core_ext/hash.rb