fastup 1.0.0
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 +7 -0
- data/COPYING +30 -0
- data/README.md +56 -0
- data/lib/fastup/autoapply.rb +3 -0
- data/lib/fastup.rb +113 -0
- data/test/app/boot.rb +5 -0
- data/test/fastup_test.rb +150 -0
- metadata +108 -0
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.
|
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
data/test/fastup_test.rb
ADDED
@@ -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: []
|