fastup 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 187041c365e33a8fb054fcbe1abec1dccf3b75e7
4
+ data.tar.gz: 9a8915b07b587b342f586f472c8abab0054d73ae
5
+ SHA512:
6
+ metadata.gz: 6c36d9f60a561d1c701bee9c97658cfcf45babf0fd3bcd32226ddf0180c41ddc5d7a97868676c9068b4edf51fbf53da1369ac1bd2a064cbf1a5bc55d66e39ffd
7
+ data.tar.gz: 12b04e54a23dab14c32e26b84a5c7570e7764885430d01ab6979cf6f0a8d399baf89cb795fd9da1d80cb34a1d17f792fd9caca143417da35f56421bc6727c55d
data/COPYING ADDED
@@ -0,0 +1,30 @@
1
+ The MIT License
2
+
3
+ Copyright (c) 2018 Raise Marketplace, Inc. https://www.raise.com/
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ ------------------------------------------------------------
24
+
25
+ Portions of this software are derived from third-party works licensed
26
+ under terms compatible with the above MIT license:
27
+
28
+ read_ipv4 function is Copyright © 2005-2014 Rich Felker, et al. and
29
+ licensed under the MIT license (see ext/subnets/ipaddr.c). The
30
+ function is a modified version of part of inet_pton from musl libc.
data/README.md ADDED
@@ -0,0 +1,56 @@
1
+ # Fastup
2
+
3
+ Fastup builds an index from `$LOAD_PATH` and patches `require` to use
4
+ that index to significantly speed up booting up large Rails apps with
5
+ many dependencies.
6
+
7
+ ```
8
+ $ bundle show | wc -l
9
+ 375
10
+
11
+ ### before fastup
12
+
13
+ $ time echo 'puts "loaded #{$LOADED_FEATURES.size} features"' | bundle exec rails c
14
+ loaded 5095 features
15
+
16
+ real 0m23.652s
17
+ user 0m14.300s
18
+ sys 0m9.237s
19
+
20
+ ### with fastup enabled
21
+
22
+ $ time echo 'puts "loaded #{$LOADED_FEATURES.size} features"' | bundle exec rails c
23
+ loaded 5097 features
24
+
25
+ real 0m15.142s
26
+ user 0m11.005s
27
+ sys 0m4.058s
28
+ ```
29
+
30
+ With fewer dependencies, the speedup is smaller. At some point, the
31
+ overhead of building the index means `fastup` will cause a small
32
+ slowdown. Test the speedup first, to see if `fastup` is worth it or
33
+ not for any particular application.
34
+
35
+ ## Usage
36
+
37
+ `fastup/autoapply` should be required after `bundler/setup` and before
38
+ `Bundler.require`, or more generally, after `$LOAD_PATH` has been
39
+ populated with all dependencies and before most of them have been
40
+ `require`'d.
41
+
42
+ For example in `config/boot.rb` of a Rails app:
43
+
44
+ ```
45
+ require 'rubygems'
46
+
47
+ # Set up gems listed in the Gemfile.
48
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
49
+
50
+ require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
51
+ require 'fastup/autoapply'
52
+ ```
53
+
54
+ ## Production Ready?
55
+
56
+ This has not been used in production.
@@ -0,0 +1,3 @@
1
+ require 'fastup'
2
+ Fastup.apply!
3
+
data/lib/fastup.rb ADDED
@@ -0,0 +1,113 @@
1
+ module Fastup
2
+ extend self
3
+
4
+ def apply!
5
+ warn "fastup: building load path index"
6
+ sp = SearchPath.new($LOAD_PATH)
7
+ suffixes = Gem.suffixes.lazy
8
+
9
+ warn "fastup: patching require"
10
+ mod = Module.new do
11
+ define_method(:require) do |name|
12
+ path = suffixes.map{ |s| sp.lookup(name.to_s + s) rescue nil }.find do |p|
13
+ p && File.file?(p)
14
+ end
15
+
16
+ # require the absolute path if found, otherwise fallback to original name
17
+ ret = super(path || name)
18
+
19
+ if ret && ENV['FASTUP_DEBUG']
20
+ if path
21
+ warn "fastup: loaded #{name} => #{path}"
22
+ else
23
+ warn "fastup: super #{name}"
24
+ end
25
+ end
26
+
27
+ ret
28
+ end
29
+ end
30
+ Object.prepend mod
31
+ end
32
+
33
+ module XFile
34
+ def directory?(path)
35
+ File.directory?(path)
36
+ end
37
+
38
+ def each_entry(dir, &block)
39
+ Dir.new(dir).each do |e|
40
+ next if e == '.' || e == '..'
41
+ yield(e)
42
+ end
43
+ end
44
+ end
45
+
46
+ class SearchPath
47
+ include XFile
48
+
49
+ # Flatten the search path +paths+ by creating a tree of symlinks
50
+ # at +dest+. For example, if the paths include +/usr/bin+, and
51
+ # +/usr/lib+, then +dest+ will be a directory with symlinks +bin+
52
+ # and +lib+ to the respective directories.
53
+ def initialize(paths)
54
+ @root = {}
55
+ paths.each{ |path| @root = insert!(path) }
56
+ end
57
+
58
+ # given a path like 'a/b/c', determine if it exists in the tree, by trying root['a']['b']['c']
59
+ #
60
+ # if root['a'] is a string, then it should be 'somedir/a'
61
+ #
62
+ # if root['a']['b'] is a string, then it should be 'somedir/a/b'
63
+ #
64
+ # if root['a']['b']['c'] is a string, then it should be 'somedir/a/b/c'
65
+ #
66
+ # in all cases, the return value is 'somedir/a/b/c'
67
+ def lookup(path, root=@root)
68
+ case root
69
+ when String
70
+ if path.nil?
71
+ return root
72
+ else
73
+ File.join(root, path)
74
+ end
75
+ when Hash
76
+ head, rest = path.split(File::SEPARATOR, 2)
77
+ lookup(rest, root[head])
78
+ end
79
+ end
80
+
81
+ private
82
+
83
+ # Assumption: target is the shortest path not yet tried. If the
84
+ # shortest path cannot be linked directly, then if it's a
85
+ # directory, each element of the directory will be inserted,
86
+ # recursively.
87
+ def insert!(target, root=@root)
88
+ case root
89
+ when String
90
+ # conflict; can't insert ontop of non-directory
91
+ return root unless directory?(root) && directory?(target)
92
+
93
+ newroot = {}
94
+ each_entry(root) do |entry|
95
+ newroot[entry] = File.join(root, entry)
96
+ end
97
+ insert! target, newroot
98
+ when Hash
99
+ # conflict; can't insert non-directory ontop of directory
100
+ return root unless directory?(target)
101
+
102
+ each_entry(target) do |entry|
103
+ if root.has_key?(entry)
104
+ root[entry] = insert! File.join(target, entry), root[entry]
105
+ else
106
+ root[entry] = File.join(target, entry)
107
+ end
108
+ end
109
+ root
110
+ end
111
+ end
112
+ end
113
+ end
data/test/app/boot.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'bundler/setup'
2
+ require 'fastup/autoapply' if ENV['USE_FASTUP']
3
+ Bundler.require
4
+
5
+ puts $LOADED_FEATURES.join("\n")
@@ -0,0 +1,150 @@
1
+ require 'benchmark'
2
+ require 'tmpdir'
3
+
4
+ require 'minitest/autorun'
5
+
6
+ require 'fastup'
7
+
8
+ module Fastup
9
+ class TestAppBoot < Minitest::Test
10
+ def test_app_boot
11
+ results = {}
12
+
13
+ Bundler.with_original_env do
14
+ Dir.chdir(File.expand_path('../app', __FILE__)) do
15
+ results[:setup] = `bundle --quiet`
16
+ raise "bundle exited with error: #{results[:setup]}" unless $?.success?
17
+
18
+ results[:total_fastup] = Benchmark.measure {
19
+ out = `USE_FASTUP=1 bundle exec ruby boot.rb 2>/dev/null`
20
+ raise "fastup exited with error: #{out}" unless $?.success?
21
+ results[:output_fastup] = out.lines.reject{ |p| p =~ %r{fastup/lib/fastup} }
22
+ }
23
+ results[:total_nofastup] = Benchmark.measure {
24
+ out = `bundle exec ruby boot.rb`
25
+ raise "nofastup exited with error: #{out}" unless $?.success?
26
+ results[:output_nofastup] = out.lines.reject{ |p| p =~ %r{fastup/lib/fastup} }
27
+ }
28
+ end
29
+ end
30
+
31
+ results[:output_fastup].reject!{ |p| p =~ %r{fastup/lib/fastup} }
32
+ results[:output_nofastup].reject!{ |p| p =~ %r{fastup/lib/fastup} }
33
+
34
+ # warn("\nfastup: %.2f nofastup: %.2f" % [results[:total_fastup].total, results[:total_nofastup].total])
35
+
36
+ assert results[:total_fastup].total < results[:total_nofastup].total,
37
+ "expected fastup (#{results[:total_fastup]}) to be faster than without (#{results[:total_nofastup]})"
38
+
39
+ assert_equal results[:output_fastup], results[:output_nofastup]
40
+ end
41
+ end
42
+
43
+ class TestSearchPath < Minitest::Test
44
+ def setup
45
+ end
46
+
47
+ Scenario = Struct.new(:name, :source, :paths, :links)
48
+ Source = Array
49
+ Paths = Array
50
+ Links = Hash
51
+
52
+ Scenarios = [
53
+
54
+ Scenario.new(
55
+ 'simple_one_binary',
56
+ Source[
57
+ 'ls/bin/ls',
58
+ ],
59
+ Paths['ls/bin'],
60
+ Links[
61
+ 'ls' => 'ls/bin/ls'
62
+ ]),
63
+
64
+ Scenario.new(
65
+ 'simple_nested',
66
+ Source[
67
+ 'ls/bin/a/b/c/ls',
68
+ ],
69
+ Paths['ls/bin'],
70
+ Links[
71
+ 'a' => 'ls/bin/a'
72
+ ]),
73
+
74
+ Scenario.new(
75
+ 'simple_two_binaries',
76
+ Source[
77
+ 'ls/bin/ls',
78
+ 'ps/bin/ps'
79
+ ],
80
+ Paths['ls/bin', 'ps/bin'],
81
+ Links[
82
+ 'ls' => 'ls/bin/ls',
83
+ 'ps' => 'ps/bin/ps'
84
+ ]),
85
+
86
+ Scenario.new(
87
+ 'simple_overlap',
88
+ Source[
89
+ 'gems/rspec/lib/rspec.rb',
90
+ 'gems/rspec/lib/rspec/something.rb',
91
+
92
+ 'gems/rspec-core/lib/rspec/core.rb'
93
+ ],
94
+ Paths['gems/rspec/lib', 'gems/rspec-core/lib'],
95
+ Links[
96
+ 'rspec.rb' => 'gems/rspec/lib/rspec.rb',
97
+ 'rspec/something.rb' => 'gems/rspec/lib/rspec/something.rb',
98
+ 'rspec/core.rb' => 'gems/rspec-core/lib/rspec/core.rb'
99
+ ]),
100
+
101
+ Scenario.new(
102
+ 'conflict',
103
+ Source[
104
+ 'rspec-1.0/lib/rspec.rb',
105
+ 'rspec-2.0/lib/rspec.rb'
106
+ ],
107
+ Paths['rspec-1.0/lib', 'rspec-2.0/lib'],
108
+ Links['rspec.rb' => 'rspec-1.0/lib/rspec.rb']),
109
+
110
+ Scenario.new(
111
+ 'conflict_file_vs_directory',
112
+ Source[
113
+ 'a/lib/something',
114
+ 'b/lib/something/sub'
115
+ ],
116
+ Paths['a/lib', 'b/lib'],
117
+ Links['something' => 'a/lib/something']),
118
+
119
+ Scenario.new(
120
+ 'conflict_directory_vs_file',
121
+ Source[
122
+ 'a/lib/something/sub',
123
+ 'b/lib/something'
124
+ ],
125
+ Paths['a/lib/', 'b/lib'],
126
+ Links['something' => 'a/lib/something']),
127
+
128
+ ]
129
+
130
+ Scenarios.each do |s|
131
+ define_method("test_scenario_#{s.name}") do
132
+ Dir.mktmpdir do |source|
133
+ s.source.each do |path|
134
+ FileUtils.mkdir_p(File.join(source, File.dirname(path)))
135
+ FileUtils.touch(File.join(source, path))
136
+ end
137
+
138
+ paths = s.paths.map{ |p| File.join(source, p) }
139
+
140
+ sp = Fastup::SearchPath.new(paths)
141
+
142
+ s.links.each do |(key, target)|
143
+ actual_target = sp.lookup(key).slice(source.length+1..-1)
144
+ assert_equal target, actual_target, "expected #{key} -> #{target} but found #{key} -> #{actual_target}"
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
metadata ADDED
@@ -0,0 +1,108 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fastup
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Patrick Mahoney
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-08-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: minitest-reporters
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: yard
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: index load path to accelerate boot of bundler-based apps with lots of
70
+ gem dependencies
71
+ email: patrick.mahoney@raise.com
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files:
75
+ - README.md
76
+ - COPYING
77
+ files:
78
+ - COPYING
79
+ - README.md
80
+ - lib/fastup.rb
81
+ - lib/fastup/autoapply.rb
82
+ - test/app/boot.rb
83
+ - test/fastup_test.rb
84
+ homepage: https://github.com/raisemarketplace/fastup
85
+ licenses:
86
+ - MIT
87
+ metadata: {}
88
+ post_install_message:
89
+ rdoc_options: []
90
+ require_paths:
91
+ - lib
92
+ required_ruby_version: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ required_rubygems_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ requirements: []
103
+ rubyforge_project:
104
+ rubygems_version: 2.6.13
105
+ signing_key:
106
+ specification_version: 4
107
+ summary: index load path to accelerate boot
108
+ test_files: []