gem_footprint_analyzer 0.1.5 → 0.1.6
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 +4 -4
- data/Gemfile.lock +5 -5
- data/README.md +1 -1
- data/exe/analyze_requires +3 -3
- data/gem_footprint_analyzer.gemspec +3 -2
- data/lib/gem_footprint_analyzer/analyzer.rb +5 -4
- data/lib/gem_footprint_analyzer/average_runner.rb +3 -6
- data/lib/gem_footprint_analyzer/child_context.rb +12 -0
- data/lib/gem_footprint_analyzer/child_process.rb +11 -3
- data/lib/gem_footprint_analyzer/core_ext/file.rb +1 -1
- data/lib/gem_footprint_analyzer/formatters/text_base.rb +8 -1
- data/lib/gem_footprint_analyzer/formatters/tree.rb +25 -8
- data/lib/gem_footprint_analyzer/require_spy.rb +12 -7
- data/lib/gem_footprint_analyzer/transport.rb +1 -1
- data/lib/gem_footprint_analyzer/version.rb +1 -1
- metadata +7 -12
- data/.gitignore +0 -11
- data/.rspec +0 -3
- data/.rubocop +0 -0
- data/.rubocop.yml +0 -22
- data/.travis.yml +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4ea3f3ac4a4cdc86a560ae500fdbdc59423afe2d28ac9f16f5fdc7efe5d433fc
|
4
|
+
data.tar.gz: 5baf82c20f15679d84b56f86e303e81325aadbc4577fdcc6bc36a388d0b92baa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 54b934c3cb13d9b564f52abc70adc1286c5842acb2e0c25d178131d6d1e653732f9a1d49df807004fc434089e97f119fbf32ddee75af29e488e1df415b58bbfe
|
7
|
+
data.tar.gz: 50bb084a66ae63808ee91acde1c257cda615cb28cb2ea2ee71463306e7d8ca2b59a3fcf1e4d09d2fe3ebef8e1c6fb12e4440b4a5c3f523a6108b03bf4d44fbdc
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
gem_footprint_analyzer (0.1.
|
4
|
+
gem_footprint_analyzer (0.1.6)
|
5
5
|
|
6
6
|
GEM
|
7
7
|
remote: https://rubygems.org/
|
@@ -10,7 +10,7 @@ GEM
|
|
10
10
|
diff-lcs (1.3)
|
11
11
|
jaro_winkler (1.5.1)
|
12
12
|
parallel (1.12.1)
|
13
|
-
parser (2.5.
|
13
|
+
parser (2.5.3.0)
|
14
14
|
ast (~> 2.4.0)
|
15
15
|
powerpack (0.1.2)
|
16
16
|
rainbow (3.0.0)
|
@@ -36,8 +36,8 @@ GEM
|
|
36
36
|
rainbow (>= 2.2.2, < 4.0)
|
37
37
|
ruby-progressbar (~> 1.7)
|
38
38
|
unicode-display_width (~> 1.4.0)
|
39
|
-
rubocop-rspec (1.30.
|
40
|
-
rubocop (>= 0.
|
39
|
+
rubocop-rspec (1.30.1)
|
40
|
+
rubocop (>= 0.60.0)
|
41
41
|
ruby-progressbar (1.10.0)
|
42
42
|
unicode-display_width (1.4.0)
|
43
43
|
|
@@ -50,7 +50,7 @@ DEPENDENCIES
|
|
50
50
|
rake (~> 10.0)
|
51
51
|
rspec (~> 3.0)
|
52
52
|
rubocop (~> 0.60.0)
|
53
|
-
rubocop-rspec
|
53
|
+
rubocop-rspec (~> 1.30)
|
54
54
|
|
55
55
|
BUNDLED WITH
|
56
56
|
1.17.1
|
data/README.md
CHANGED
data/exe/analyze_requires
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#!/usr/bin/env ruby --disable=
|
1
|
+
#!/usr/bin/env ruby --disable=gem --disable=rubyopt
|
2
2
|
|
3
3
|
$LOAD_PATH.unshift(File.join(__dir__, '..', 'lib'))
|
4
4
|
$LOAD_PATH.unshift('./lib') if Dir.exist?('lib')
|
@@ -7,7 +7,7 @@ require 'gem_footprint_analyzer'
|
|
7
7
|
|
8
8
|
cli = GemFootprintAnalyzer::CLI.new
|
9
9
|
|
10
|
-
t1 =
|
10
|
+
t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
11
11
|
cli.run(ARGV)
|
12
|
-
t2 =
|
12
|
+
t2 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
13
13
|
STDERR.puts(format("\nTotal runtime %0.4fs", (t2 - t1)))
|
@@ -12,11 +12,12 @@ Gem::Specification.new do |spec|
|
|
12
12
|
spec.summary = %q{A simple tool to analyze footprint of Ruby requires.}
|
13
13
|
spec.homepage = "https://github.com/irvingwashington/gem_footprint_analyzer"
|
14
14
|
spec.license = "MIT"
|
15
|
+
spec.required_ruby_version = '>= 2.2.0'
|
15
16
|
|
16
17
|
# Specify which files should be added to the gem when it is released.
|
17
18
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
18
19
|
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
19
|
-
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(
|
20
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(spec/|\.)}) }
|
20
21
|
end
|
21
22
|
spec.bindir = "exe"
|
22
23
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
@@ -26,5 +27,5 @@ Gem::Specification.new do |spec|
|
|
26
27
|
spec.add_development_dependency "rake", "~> 10.0"
|
27
28
|
spec.add_development_dependency "rspec", "~> 3.0"
|
28
29
|
spec.add_development_dependency "rubocop", "~> 0.60.0"
|
29
|
-
spec.add_development_dependency "rubocop-rspec"
|
30
|
+
spec.add_development_dependency "rubocop-rspec", "~> 1.30"
|
30
31
|
end
|
@@ -5,16 +5,17 @@ module GemFootprintAnalyzer
|
|
5
5
|
# Require calls are interwoven with RSS checks done from the parent process, require timing
|
6
6
|
# is gathered in the child process and passed along to the parent.
|
7
7
|
class Analyzer
|
8
|
+
# @param fifos [Hash<Symbol>] Hash containing filenames of :child and :parent FIFO files
|
9
|
+
def initialize(fifos)
|
10
|
+
@fifos = fifos
|
11
|
+
end
|
12
|
+
|
8
13
|
# @param library [String] name of the library or parameter for the gem method
|
9
14
|
# (ex. 'activerecord', 'activesupport')
|
10
15
|
# @param require_string [String|nil] optional require string, if it differs from the gem name
|
11
16
|
# (ex. 'active_record', 'active_support/time')
|
12
17
|
# @return [Array<Hash>] list of require-data-hashes, first element contains base level RSS,
|
13
18
|
# last element can be treated as a summary as effectively it consists of all the previous.
|
14
|
-
def initialize(fifos)
|
15
|
-
@fifos = fifos
|
16
|
-
end
|
17
|
-
|
18
19
|
def test_library(library, require_string = nil)
|
19
20
|
child = ChildProcess.new(library, require_string, fifos)
|
20
21
|
child.start_child
|
@@ -16,10 +16,7 @@ module GemFootprintAnalyzer
|
|
16
16
|
# @return [Array<Hash>] Array of hashes that now include average metrics in place of fields
|
17
17
|
# present in {AVERAGED_FIELDS}. The rest of the columns is copied from the first sample.
|
18
18
|
def run
|
19
|
-
results =
|
20
|
-
@runs.times do
|
21
|
-
results << run_once
|
22
|
-
end
|
19
|
+
results = Array.new(@runs) { run_once }
|
23
20
|
calculate_averages(results)
|
24
21
|
end
|
25
22
|
|
@@ -50,8 +47,8 @@ module GemFootprintAnalyzer
|
|
50
47
|
sum = values.sum.to_f
|
51
48
|
mean = sum / num
|
52
49
|
|
53
|
-
stddev = Math.sqrt(values.sum { |v| (v - mean)**2 } / num)
|
54
|
-
{mean: mean,
|
50
|
+
stddev = Math.sqrt(values.sum { |v| (v - mean)**2 } / num).round(4)
|
51
|
+
{mean: mean, stddev: stddev}
|
55
52
|
end
|
56
53
|
|
57
54
|
def initialize_average_with_copied_fields(sample)
|
@@ -20,7 +20,9 @@ module GemFootprintAnalyzer
|
|
20
20
|
begin
|
21
21
|
require(require_string)
|
22
22
|
rescue LoadError => e
|
23
|
+
warn_about_load_error(e)
|
23
24
|
transport.exit_with_error(e)
|
25
|
+
exit 1
|
24
26
|
end
|
25
27
|
transport.done_and_wait_for_ack
|
26
28
|
end
|
@@ -52,6 +54,16 @@ module GemFootprintAnalyzer
|
|
52
54
|
STDOUT.puts(message)
|
53
55
|
STDOUT.flush
|
54
56
|
end
|
57
|
+
|
58
|
+
def warn_about_load_error(error)
|
59
|
+
possible_gem = error.message.split.last
|
60
|
+
STDERR.puts "Cannot load '#{possible_gem}', this might be an issue with implicit require in" \
|
61
|
+
" the '#{require_string}'"
|
62
|
+
STDERR.puts "If '#{possible_gem}' is a gem, you can try adding it to the Gemfile explicitly" \
|
63
|
+
", running `bundle install` and trying again\n\n"
|
64
|
+
STDERR.puts error.backtrace
|
65
|
+
STDERR.flush
|
66
|
+
end
|
55
67
|
end
|
56
68
|
end
|
57
69
|
|
@@ -4,6 +4,7 @@ require 'rbconfig'
|
|
4
4
|
module GemFootprintAnalyzer
|
5
5
|
# A class for starting the child process that does actual requires.
|
6
6
|
class ChildProcess
|
7
|
+
LEGACY_RUBY_CMD = [RbConfig.ruby, '--disable=gem'].freeze
|
7
8
|
RUBY_CMD = [RbConfig.ruby, '--disable=did_you_mean', '--disable=gem'].freeze
|
8
9
|
|
9
10
|
def initialize(library, require_string, fifos)
|
@@ -15,11 +16,11 @@ module GemFootprintAnalyzer
|
|
15
16
|
|
16
17
|
def start_child
|
17
18
|
@child_thread ||= Thread.new do # rubocop:disable Naming/MemoizedInstanceVariableName
|
18
|
-
Open3.popen3(child_env_vars, *
|
19
|
+
Open3.popen3(child_env_vars, *ruby_command, context_file) do |_, stdout, stderr|
|
19
20
|
@pid = stdout.gets.strip.to_i
|
20
21
|
|
21
22
|
while (line = stderr.gets)
|
22
|
-
|
23
|
+
print "!! #{line}"
|
23
24
|
end
|
24
25
|
end
|
25
26
|
end
|
@@ -29,7 +30,6 @@ module GemFootprintAnalyzer
|
|
29
30
|
return unless child_thread
|
30
31
|
|
31
32
|
sleep 0.01 while @pid.nil?
|
32
|
-
|
33
33
|
@pid
|
34
34
|
end
|
35
35
|
|
@@ -37,6 +37,14 @@ module GemFootprintAnalyzer
|
|
37
37
|
|
38
38
|
attr_reader :require_string, :child_thread, :fifos
|
39
39
|
|
40
|
+
def ruby_command
|
41
|
+
if RbConfig::CONFIG['MAJOR'].to_i >= 2 && RbConfig::CONFIG['MINOR'].to_i >= 3
|
42
|
+
RUBY_CMD
|
43
|
+
else
|
44
|
+
LEGACY_RUBY_CMD
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
40
48
|
def child_env_vars
|
41
49
|
{
|
42
50
|
'require_string' => require_string,
|
@@ -11,7 +11,8 @@ module GemFootprintAnalyzer
|
|
11
11
|
# Displays explanatory words for text formatter results
|
12
12
|
def info
|
13
13
|
lines = []
|
14
|
-
lines << "GemFootprintAnalyzer (#{GemFootprintAnalyzer::VERSION})
|
14
|
+
lines << "GemFootprintAnalyzer (#{GemFootprintAnalyzer::VERSION})"
|
15
|
+
lines << (debug? ? "(#{File.expand_path(File.join(File.dirname(__FILE__), '..'))})\n" : '')
|
15
16
|
lines << "Analyze results (average measured from #{@options[:runs]} run(s))"
|
16
17
|
lines << 'time is the amount of time given require has taken to complete'
|
17
18
|
lines << 'RSS is total memory increase up to the point after the require'
|
@@ -23,6 +24,12 @@ module GemFootprintAnalyzer
|
|
23
24
|
def dash(length)
|
24
25
|
'-' * length
|
25
26
|
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def debug?
|
31
|
+
@options[:debug]
|
32
|
+
end
|
26
33
|
end
|
27
34
|
end
|
28
35
|
end
|
@@ -7,9 +7,9 @@ module GemFootprintAnalyzer
|
|
7
7
|
INDENT = ' '.freeze
|
8
8
|
# Formatter helper class representing a single results require entry.
|
9
9
|
class Entry
|
10
|
-
def initialize(entry_hash,
|
10
|
+
def initialize(entry_hash, options = {})
|
11
11
|
@entry_hash = entry_hash
|
12
|
-
@options =
|
12
|
+
@options = options
|
13
13
|
end
|
14
14
|
|
15
15
|
def name
|
@@ -39,7 +39,7 @@ module GemFootprintAnalyzer
|
|
39
39
|
def debug_parent
|
40
40
|
return unless @options[:debug]
|
41
41
|
|
42
|
-
"(#{parent})"
|
42
|
+
" (#{parent})"
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
@@ -53,12 +53,13 @@ module GemFootprintAnalyzer
|
|
53
53
|
private
|
54
54
|
|
55
55
|
def formatted_entries(entries)
|
56
|
-
|
57
|
-
max_name_length = entries.map { |e| e.
|
56
|
+
max_indent = max_indent(entries)
|
57
|
+
max_name_length = entries.map { |e| e.formatted_name.length }.max
|
58
58
|
ljust_value = max_name_length + max_indent + 1
|
59
59
|
|
60
60
|
lines = entries.reverse.map do |entry|
|
61
|
-
|
61
|
+
indent_level = count_indent_level(entry)
|
62
|
+
format_entry(entry, indent_level, ljust_value)
|
62
63
|
end
|
63
64
|
|
64
65
|
(legend(ljust_value) + lines).join("\n")
|
@@ -71,8 +72,8 @@ module GemFootprintAnalyzer
|
|
71
72
|
]
|
72
73
|
end
|
73
74
|
|
74
|
-
def format_entry(entry,
|
75
|
-
indent = INDENT *
|
75
|
+
def format_entry(entry, indent_level, ljust_value)
|
76
|
+
indent = INDENT * indent_level
|
76
77
|
time = format('%5dms', entry.time)
|
77
78
|
rss = format('%7dKB', entry.rss)
|
78
79
|
|
@@ -85,6 +86,22 @@ module GemFootprintAnalyzer
|
|
85
86
|
end
|
86
87
|
end
|
87
88
|
|
89
|
+
def max_indent(entries)
|
90
|
+
entries.reverse_each { |entry| count_indent_level(entry) }
|
91
|
+
max_indent_level = @indent_levels.map(&:values).flatten.max
|
92
|
+
@indent_levels = []
|
93
|
+
max_indent_level * INDENT.size
|
94
|
+
end
|
95
|
+
|
96
|
+
def count_indent_level(entry)
|
97
|
+
@indent_levels ||= []
|
98
|
+
parent_hash = @indent_levels.find { |elem| elem.key?(entry.parent) }
|
99
|
+
parent_level = parent_hash ? parent_hash.values.first : -1
|
100
|
+
indent_level = parent_level + 1
|
101
|
+
@indent_levels.unshift(entry.name => indent_level)
|
102
|
+
indent_level
|
103
|
+
end
|
104
|
+
|
88
105
|
def count_indent_levels(entries)
|
89
106
|
root = entries.last
|
90
107
|
indent_levels = {root.name => 0}
|
@@ -15,11 +15,15 @@ module GemFootprintAnalyzer
|
|
15
15
|
full_path.sub(%r{\A#{load_path}/}, '')
|
16
16
|
end
|
17
17
|
|
18
|
-
def
|
19
|
-
|
20
|
-
|
18
|
+
def without_extension(name)
|
19
|
+
name.sub(/\.rb\z/, '')
|
20
|
+
end
|
21
|
+
|
22
|
+
def first_foreign_caller(caller_list)
|
23
|
+
ffc = caller_list.find do |c|
|
24
|
+
relative_path(c) !~ /gem_footprint_analyzer/
|
21
25
|
end
|
22
|
-
|
26
|
+
without_extension(relative_path(ffc)) if ffc
|
23
27
|
end
|
24
28
|
|
25
29
|
def spy_require(transport)
|
@@ -37,9 +41,9 @@ module GemFootprintAnalyzer
|
|
37
41
|
|
38
42
|
def define_timed_exec
|
39
43
|
Kernel.send :define_method, :timed_exec do |&block|
|
40
|
-
start_time =
|
44
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
41
45
|
block.call
|
42
|
-
(
|
46
|
+
(Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time).round(4)
|
43
47
|
end
|
44
48
|
end
|
45
49
|
|
@@ -53,7 +57,8 @@ module GemFootprintAnalyzer
|
|
53
57
|
t = timed_exec { result = regular_require(name) }
|
54
58
|
|
55
59
|
first_foreign_caller = GemFootprintAnalyzer::RequireSpy.first_foreign_caller(caller)
|
56
|
-
|
60
|
+
bare_name = GemFootprintAnalyzer::RequireSpy.without_extension(name)
|
61
|
+
transport.report_require(bare_name, first_foreign_caller || '', t)
|
57
62
|
result
|
58
63
|
end
|
59
64
|
end
|
@@ -9,7 +9,7 @@ module GemFootprintAnalyzer
|
|
9
9
|
@write_stream = write_stream
|
10
10
|
end
|
11
11
|
|
12
|
-
# @return [Array] A tuple with command and *payload
|
12
|
+
# @return [Array|nil] A tuple with command and *payload or nil when command not recognized
|
13
13
|
def read_one_command
|
14
14
|
case read_raw_command
|
15
15
|
when /\A(done|ack|start|ready)\z/
|
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.
|
4
|
+
version: 0.1.6
|
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-
|
11
|
+
date: 2018-11-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -70,16 +70,16 @@ dependencies:
|
|
70
70
|
name: rubocop-rspec
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
|
-
- - "
|
73
|
+
- - "~>"
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '
|
75
|
+
version: '1.30'
|
76
76
|
type: :development
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
|
-
- - "
|
80
|
+
- - "~>"
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '
|
82
|
+
version: '1.30'
|
83
83
|
description:
|
84
84
|
email:
|
85
85
|
- maciek@dubinski.net
|
@@ -88,11 +88,6 @@ executables:
|
|
88
88
|
extensions: []
|
89
89
|
extra_rdoc_files: []
|
90
90
|
files:
|
91
|
-
- ".gitignore"
|
92
|
-
- ".rspec"
|
93
|
-
- ".rubocop"
|
94
|
-
- ".rubocop.yml"
|
95
|
-
- ".travis.yml"
|
96
91
|
- CODE_OF_CONDUCT.md
|
97
92
|
- Gemfile
|
98
93
|
- Gemfile.lock
|
@@ -128,7 +123,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
128
123
|
requirements:
|
129
124
|
- - ">="
|
130
125
|
- !ruby/object:Gem::Version
|
131
|
-
version:
|
126
|
+
version: 2.2.0
|
132
127
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
128
|
requirements:
|
134
129
|
- - ">="
|
data/.gitignore
DELETED
data/.rspec
DELETED
data/.rubocop
DELETED
File without changes
|
data/.rubocop.yml
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
require: rubocop-rspec
|
2
|
-
AllCops:
|
3
|
-
Include:
|
4
|
-
- 'lib/gem_footprint_analyzer/**/*.rb'
|
5
|
-
- 'exe/*'
|
6
|
-
- 'spec/**/*'
|
7
|
-
TargetRubyVersion: 2.2
|
8
|
-
|
9
|
-
Metrics/LineLength:
|
10
|
-
Max: 100
|
11
|
-
|
12
|
-
Style/RegexpLiteral:
|
13
|
-
EnforcedStyle: slashes
|
14
|
-
|
15
|
-
Layout/SpaceInsideHashLiteralBraces:
|
16
|
-
EnforcedStyle: no_space
|
17
|
-
|
18
|
-
Style/SignalException:
|
19
|
-
EnforcedStyle: semantic
|
20
|
-
|
21
|
-
RSpec/NestedGroups:
|
22
|
-
Max: 5
|
data/.travis.yml
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
---
|
2
|
-
sudo: false
|
3
|
-
language: ruby
|
4
|
-
cache: bundler
|
5
|
-
rvm:
|
6
|
-
- 2.5.3
|
7
|
-
- 2.2
|
8
|
-
before_install:
|
9
|
-
- gem install bundler -v 1.17.1
|
10
|
-
script:
|
11
|
-
- bundle exec rubocop
|
12
|
-
- bundle exec rspec
|
13
|
-
- bundle exec analyze_requires -d rubocop
|
14
|
-
- bundle exec analyze_requires net/http
|