require-profiler 0.1.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/CHANGELOG.md +7 -0
- data/LICENSE.txt +23 -0
- data/README.md +131 -0
- data/lib/require-profiler.rb +4 -0
- data/lib/require_profiler/printer/call_stack.rb +21 -0
- data/lib/require_profiler/printer/json.rb +66 -0
- data/lib/require_profiler/printer/text.rb +22 -0
- data/lib/require_profiler/printer.rb +48 -0
- data/lib/require_profiler/reporter.rb +74 -0
- data/lib/require_profiler/version.rb +5 -0
- data/lib/require_profiler.rb +37 -0
- metadata +112 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: d5b1e34052677db0605367fd1940681029cffc965f7d1db3604e6964ee95a41a
|
|
4
|
+
data.tar.gz: acbc21ea3bbd7e120bf328287b216fc68ca7be9beb41260925bbca3a70c712cf
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 67480173af46da1a7dba97b15f5a252b66b20504af35a1397f66662dc65e97e64381419d0d175a71aba3d99d8646ad0be482e3423de72ca4ef4c822b49c6cb28
|
|
7
|
+
data.tar.gz: e91be6bf3e684cfb9c969e2bfa495ba71b1a782dddfa4cdaa1f9c3d7573047c50dd3b0a308c377996b4405e1f38f32c8604eea5993e7c6b0a8369ec27044515e
|
data/CHANGELOG.md
ADDED
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
Copyright (c) 2026 Vladimir Dementyev
|
|
2
|
+
|
|
3
|
+
MIT License
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
data/README.md
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
[](https://rubygems.org/gems/require-profiler)
|
|
2
|
+
[](https://github.com/palkan/require-profiler/actions)
|
|
3
|
+
|
|
4
|
+
# Require Profiler
|
|
5
|
+
|
|
6
|
+
Require Profiler is a tool for profiling Ruby's code loading—`Kernel#require`, `Kernel#require_relative`, and `Kernel#load`. It captures the call tree, measures how long each file takes to load, and can export the results as a [Speedscope][speedscope]-compatible JSON profile.
|
|
7
|
+
|
|
8
|
+
It's built on top of [Require Hooks][require-hooks], so it works anywhere Require Hooks does (MRI/JRuby/TruffleRuby).
|
|
9
|
+
|
|
10
|
+
<a href="https://evilmartians.com/">
|
|
11
|
+
<img src="https://evilmartians.com/badges/sponsored-by-evil-martians.svg" alt="Sponsored by Evil Martians" width="236" height="54"></a>
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
Add to your Gemfile:
|
|
16
|
+
|
|
17
|
+
```ruby
|
|
18
|
+
gem "require-profiler"
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Supported Ruby versions
|
|
22
|
+
|
|
23
|
+
- Ruby (MRI) >= 3.1
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
Wrap the code you want to profile with `RequireProfiler.start` and `RequireProfiler.stop`:
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
require "require_profiler"
|
|
31
|
+
|
|
32
|
+
RequireProfiler.start(output: "path/to/profile.txt")
|
|
33
|
+
require "my_app"
|
|
34
|
+
RequireProfiler.stop
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Only `require`/`load` calls that happen between `.start` and `.stop` are captured. Put `.start` as early as possible (e.g., at the top of `config/boot.rb` or an entry-point script) to see the full loading tree.
|
|
38
|
+
|
|
39
|
+
`RequireProfiler.stop` returns a totals hash: `{count: <number of files loaded>, time: <seconds>}`.
|
|
40
|
+
|
|
41
|
+
### Scoping via patterns
|
|
42
|
+
|
|
43
|
+
Use `patterns:` and `exclude_patterns:` to limit what gets profiled. Both accept globs as recognized by [`File.fnmatch`](https://rubyapi.org/4.0/o/file#method-c-fnmatch) and are passed through to Require Hooks:
|
|
44
|
+
|
|
45
|
+
```ruby
|
|
46
|
+
RequireProfiler.start(
|
|
47
|
+
patterns: ["#{Dir.pwd}/app/**/*.rb", "#{Dir.pwd}/lib/**/*.rb"],
|
|
48
|
+
exclude_patterns: ["*/vendor/*"]
|
|
49
|
+
)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Output
|
|
53
|
+
|
|
54
|
+
`RequireProfiler.start` accepts the following keyword arguments:
|
|
55
|
+
|
|
56
|
+
- `output:` — `$stdout` (default), any IO-like object, or a file path (string).
|
|
57
|
+
- `format:` — `:text` (default), `:call_stack`, or `:json`. When `output:` is a file path with a `.json` extension, the JSON format is picked automatically.
|
|
58
|
+
- `patterns:` / `exclude_patterns:` — see above.
|
|
59
|
+
|
|
60
|
+
## Output formats
|
|
61
|
+
|
|
62
|
+
### Text (default)
|
|
63
|
+
|
|
64
|
+
A human-readable indented tree, one line per file, with self+children duration in milliseconds:
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
lib/my_app.rb — 42.137ms
|
|
68
|
+
lib/my_app/config.rb — 3.211ms
|
|
69
|
+
lib/my_app/router.rb — 12.004ms
|
|
70
|
+
lib/my_app/routes.rb — 9.882ms
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Paths are printed relative to `Dir.pwd`, `Gem.dir`, or `Bundler.bundle_path` when they match—so vendored gem loads stay readable.
|
|
74
|
+
|
|
75
|
+
### Collapsed call stacks
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
RequireProfiler.start(output: "tmp/require-profile.txt", format: :call_stack)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Emits one line per stack in [Brendan Gregg's collapsed format](https://github.com/brendangregg/FlameGraph#2-fold-stacks) with per-frame self time in milliseconds. Pipe it to `flamegraph.pl`, [`inferno`](https://github.com/jonhoo/inferno), or any tool that consumes folded stacks.
|
|
82
|
+
|
|
83
|
+
### JSON / Speedscope
|
|
84
|
+
|
|
85
|
+
```ruby
|
|
86
|
+
RequireProfiler.start(output: "tmp/require-profile.json")
|
|
87
|
+
# or:
|
|
88
|
+
RequireProfiler.start(output: io, format: :json)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Emits a profile that conforms to the [Speedscope file format schema](https://www.speedscope.app/file-format-schema.json).
|
|
92
|
+
|
|
93
|
+
**Note:** Writing JSON straight to `$stdout` is not supported.
|
|
94
|
+
|
|
95
|
+
## Using with Speedscope
|
|
96
|
+
|
|
97
|
+
[Speedscope][speedscope] is an interactive flamegraph viewer that runs entirely in your browser (nothing is uploaded—the file is parsed locally).
|
|
98
|
+
|
|
99
|
+
1. Generate a JSON profile:
|
|
100
|
+
|
|
101
|
+
```ruby
|
|
102
|
+
RequireProfiler.start(output: "tmp/require-profile.json")
|
|
103
|
+
require "my_app"
|
|
104
|
+
RequireProfiler.stop
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
2. Open [https://www.speedscope.app/](https://www.speedscope.app/) and drag-and-drop `tmp/require-profile.json` onto the page (or use the "Browse" button).
|
|
108
|
+
|
|
109
|
+
3. Switch to the **Left Heavy** view to see which `require` chains cost the most time, and use **Sandwich** view to find individual files that show up repeatedly.
|
|
110
|
+
|
|
111
|
+
Prefer to stay local? Install the CLI and it will open a local viewer for you:
|
|
112
|
+
|
|
113
|
+
```sh
|
|
114
|
+
npm install -g speedscope
|
|
115
|
+
speedscope tmp/require-profile.json
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Contributing
|
|
119
|
+
|
|
120
|
+
Bug reports and pull requests are welcome on GitHub at [https://github.com/palkan/require-profiler](https://github.com/palkan/require-profiler).
|
|
121
|
+
|
|
122
|
+
## Credits
|
|
123
|
+
|
|
124
|
+
This gem is generated via [`newgem` template](https://github.com/palkan/newgem) by [@palkan](https://github.com/palkan).
|
|
125
|
+
|
|
126
|
+
## License
|
|
127
|
+
|
|
128
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
|
129
|
+
|
|
130
|
+
[speedscope]: https://www.speedscope.app/
|
|
131
|
+
[require-hooks]: https://github.com/ruby-next/require-hooks
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RequireProfiler
|
|
4
|
+
module Printer
|
|
5
|
+
# CallStack formatter prints collapsed stacks (Brendan Gregg's format)
|
|
6
|
+
class CallStack < Base
|
|
7
|
+
def flush(node, parts: [])
|
|
8
|
+
path = node.path.sub(prefix_stripper, "")
|
|
9
|
+
self_parts = path.split("/")
|
|
10
|
+
|
|
11
|
+
parts += self_parts.size.times.map { self_parts.take(_1 + 1).join("/") }
|
|
12
|
+
# We only show self-time, so exclude children
|
|
13
|
+
val = ((node.time - node.children.sum(&:time)) * 1000).round(3)
|
|
14
|
+
|
|
15
|
+
output << "#{parts.join(";")} #{val}\n"
|
|
16
|
+
|
|
17
|
+
node.children.each { flush(_1, parts:) }
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module RequireProfiler
|
|
6
|
+
module Printer
|
|
7
|
+
# JSON formatter converts call tacks into Speedscope
|
|
8
|
+
# compatible JSON on finish.
|
|
9
|
+
#
|
|
10
|
+
# Can only be used with rewindable IO.
|
|
11
|
+
class JSON < CallStack
|
|
12
|
+
def finish
|
|
13
|
+
output.rewind
|
|
14
|
+
stacks = output.read
|
|
15
|
+
output.rewind
|
|
16
|
+
|
|
17
|
+
frames = []
|
|
18
|
+
frame_index = {}
|
|
19
|
+
|
|
20
|
+
samples = []
|
|
21
|
+
weights = []
|
|
22
|
+
|
|
23
|
+
stacks.each_line do |line|
|
|
24
|
+
line = line.strip
|
|
25
|
+
next if line.empty?
|
|
26
|
+
|
|
27
|
+
sep = line.rindex(" ")
|
|
28
|
+
next unless sep
|
|
29
|
+
|
|
30
|
+
stack = line[0...sep]
|
|
31
|
+
weight = line[(sep + 1)..].to_f
|
|
32
|
+
|
|
33
|
+
sample = stack.split(";").map do |name|
|
|
34
|
+
frame_index[name] ||= begin
|
|
35
|
+
frames << {name: name}
|
|
36
|
+
frames.size - 1
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
samples << sample
|
|
41
|
+
weights << weight
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
total = weights.sum
|
|
45
|
+
|
|
46
|
+
profile = {
|
|
47
|
+
"$schema" => "https://www.speedscope.app/file-format-schema.json",
|
|
48
|
+
:shared => {frames: frames},
|
|
49
|
+
:profiles => [
|
|
50
|
+
{
|
|
51
|
+
type: "sampled",
|
|
52
|
+
unit: "milliseconds",
|
|
53
|
+
startValue: 0,
|
|
54
|
+
endValue: total,
|
|
55
|
+
samples:,
|
|
56
|
+
weights:
|
|
57
|
+
}
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
output.write(::JSON.pretty_generate(profile))
|
|
62
|
+
super
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RequireProfiler
|
|
4
|
+
module Printer
|
|
5
|
+
class Text < Base
|
|
6
|
+
PAD = " "
|
|
7
|
+
|
|
8
|
+
def flush(node, indent: 0)
|
|
9
|
+
path = node.path.sub(prefix_stripper, "")
|
|
10
|
+
output << "#{PAD * indent}#{path} — #{time_to_duration(node.time)}\n"
|
|
11
|
+
node.children.each { flush(_1, indent: indent + 1) }
|
|
12
|
+
|
|
13
|
+
output.flush
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
# Converts seconds to human-readable milliseconds
|
|
19
|
+
def time_to_duration(time) = "#{(time * 1000).round(3)}ms"
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RequireProfiler
|
|
4
|
+
module Printer
|
|
5
|
+
class Base
|
|
6
|
+
private attr_reader :output
|
|
7
|
+
private attr_reader :prefix_stripper
|
|
8
|
+
|
|
9
|
+
def initialize(output)
|
|
10
|
+
@output = output
|
|
11
|
+
|
|
12
|
+
# Identify prefixes for the project and the gems
|
|
13
|
+
prefixes = [::Dir.pwd]
|
|
14
|
+
prefixes << ::Gem.dir if defined?(::Gem.dir)
|
|
15
|
+
prefixes << ::Bundler.bundle_path if defined?(::Bundler.bundle_path)
|
|
16
|
+
|
|
17
|
+
@prefix_stripper = %r{^(#{prefixes.join("|")})/}
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def flush(node)
|
|
21
|
+
raise NotImplementedError
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def finish
|
|
25
|
+
output.close if output.respond_to?(:close) && output != $stdout
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
autoload :Text, "require_profiler/printer/text"
|
|
30
|
+
autoload :CallStack, "require_profiler/printer/call_stack"
|
|
31
|
+
autoload :JSON, "require_profiler/printer/json"
|
|
32
|
+
|
|
33
|
+
class << self
|
|
34
|
+
def resolve(output, format)
|
|
35
|
+
format ||= (output.is_a?(String) && File.extname(output) == ".json") ? :json : :text
|
|
36
|
+
output = File.open(output, "w+") if output.is_a?(String)
|
|
37
|
+
|
|
38
|
+
case format.to_sym
|
|
39
|
+
when :json then JSON.new(output)
|
|
40
|
+
when :call_stack then CallStack.new(output)
|
|
41
|
+
when :text then Text.new(output)
|
|
42
|
+
else
|
|
43
|
+
raise ArgumentError, "Unknown format specified: #{format}. Available formats: text, json, call_stack"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RequireProfiler
|
|
4
|
+
class Reporter
|
|
5
|
+
class Event < Struct.new(:type, :path, :time, keyword_init: true)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
class Node < Struct.new(:path, :time, :children, keyword_init: true)
|
|
9
|
+
def initialize(...)
|
|
10
|
+
super
|
|
11
|
+
self.children ||= []
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private attr_reader :stack, :totals, :printer, :processor, :queue
|
|
16
|
+
|
|
17
|
+
def initialize(printer:)
|
|
18
|
+
@stack = []
|
|
19
|
+
@totals = {count: 0, time: 0.0}
|
|
20
|
+
@printer = printer
|
|
21
|
+
@processor = nil
|
|
22
|
+
@queue = Queue.new
|
|
23
|
+
|
|
24
|
+
start_processor
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def handle_event(event)
|
|
28
|
+
queue << event
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def handle_event_sync(event)
|
|
32
|
+
if event.type == :start
|
|
33
|
+
node = Node.new(path: event.path, children: [])
|
|
34
|
+
stack.last&.children&.push(node)
|
|
35
|
+
|
|
36
|
+
stack << node
|
|
37
|
+
elsif event.type == :end
|
|
38
|
+
last = stack.pop
|
|
39
|
+
last.time = event.time
|
|
40
|
+
|
|
41
|
+
printer.flush(last) if stack.empty?
|
|
42
|
+
|
|
43
|
+
totals[:count] += 1
|
|
44
|
+
totals[:time] += event.time if stack.empty?
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def finish
|
|
49
|
+
handle_event(Event.new(type: :stop))
|
|
50
|
+
processor.join
|
|
51
|
+
|
|
52
|
+
warn "Finished in the middle of requiring a file" unless stack.empty?
|
|
53
|
+
|
|
54
|
+
printer.finish
|
|
55
|
+
totals
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def start_processor
|
|
61
|
+
@processor = Thread.new do
|
|
62
|
+
Thread.current.priority = -1
|
|
63
|
+
|
|
64
|
+
loop do
|
|
65
|
+
event = queue.pop
|
|
66
|
+
|
|
67
|
+
break if event.type == :stop
|
|
68
|
+
|
|
69
|
+
handle_event_sync(event)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RequireProfiler
|
|
4
|
+
autoload :Reporter, "require_profiler/reporter"
|
|
5
|
+
autoload :Printer, "require_profiler/printer"
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
attr_reader :reporter
|
|
9
|
+
|
|
10
|
+
def start(output: $stdout, format: nil, patterns: nil, exclude_patterns: nil)
|
|
11
|
+
raise ArgumentError, "There is already profiling in progress" if reporter
|
|
12
|
+
|
|
13
|
+
reporter = @reporter = Reporter.new(printer: Printer.resolve(output, format))
|
|
14
|
+
|
|
15
|
+
require "require-hooks/setup"
|
|
16
|
+
|
|
17
|
+
::RequireHooks.around_load(patterns:, exclude_patterns:) do |path, &block|
|
|
18
|
+
start = Time.now
|
|
19
|
+
|
|
20
|
+
reporter.handle_event(Reporter::Event.new(type: :start, path:))
|
|
21
|
+
|
|
22
|
+
block.call
|
|
23
|
+
ensure
|
|
24
|
+
time = Time.now - start
|
|
25
|
+
reporter.handle_event(Reporter::Event.new(type: :end, path:, time:))
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def stop
|
|
30
|
+
raise "No reporter defined. Are you sure you called RequireProfiler.start?" unless reporter
|
|
31
|
+
|
|
32
|
+
reporter.finish.tap do
|
|
33
|
+
@reporter = nil
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: require-profiler
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Vladimir Dementyev
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: require-hooks
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.3'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.3'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: bundler
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '1.15'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '1.15'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: rake
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '13.0'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '13.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: rspec
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '3.9'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '3.9'
|
|
68
|
+
description: 'Profile Ruby #require/#load/etc calls'
|
|
69
|
+
email:
|
|
70
|
+
- Vladimir Dementyev
|
|
71
|
+
executables: []
|
|
72
|
+
extensions: []
|
|
73
|
+
extra_rdoc_files: []
|
|
74
|
+
files:
|
|
75
|
+
- CHANGELOG.md
|
|
76
|
+
- LICENSE.txt
|
|
77
|
+
- README.md
|
|
78
|
+
- lib/require-profiler.rb
|
|
79
|
+
- lib/require_profiler.rb
|
|
80
|
+
- lib/require_profiler/printer.rb
|
|
81
|
+
- lib/require_profiler/printer/call_stack.rb
|
|
82
|
+
- lib/require_profiler/printer/json.rb
|
|
83
|
+
- lib/require_profiler/printer/text.rb
|
|
84
|
+
- lib/require_profiler/reporter.rb
|
|
85
|
+
- lib/require_profiler/version.rb
|
|
86
|
+
homepage: https://github.com/palkan/require-profiler
|
|
87
|
+
licenses:
|
|
88
|
+
- MIT
|
|
89
|
+
metadata:
|
|
90
|
+
bug_tracker_uri: https://github.com/palkan/require-profiler/issues
|
|
91
|
+
changelog_uri: https://github.com/palkan/require-profiler/blob/master/CHANGELOG.md
|
|
92
|
+
documentation_uri: https://github.com/palkan/require-profiler
|
|
93
|
+
homepage_uri: https://github.com/palkan/require-profiler
|
|
94
|
+
source_code_uri: https://github.com/palkan/require-profiler
|
|
95
|
+
rdoc_options: []
|
|
96
|
+
require_paths:
|
|
97
|
+
- lib
|
|
98
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - ">="
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '3.1'
|
|
103
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
104
|
+
requirements:
|
|
105
|
+
- - ">="
|
|
106
|
+
- !ruby/object:Gem::Version
|
|
107
|
+
version: '0'
|
|
108
|
+
requirements: []
|
|
109
|
+
rubygems_version: 3.6.9
|
|
110
|
+
specification_version: 4
|
|
111
|
+
summary: 'Profile Ruby #require/#load/etc calls'
|
|
112
|
+
test_files: []
|