gem_footprint_analyzer 0.1.5 → 0.1.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|