require-profiler 0.2.0 → 0.2.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 787aa181e1f018e66c9f8163837af8980a0549a23102361206451ca4c640675e
4
- data.tar.gz: a7a0a01e40938642a7bd2b505ae18e47e42b3f00fa482c63151c5616d8f746d6
3
+ metadata.gz: 8e11b353825774069b1dea2916f61688970a61ea00e59c54ccd68d39e311f452
4
+ data.tar.gz: 1580e8719935ec0d70d98f48fdad60207e777a475a86640e9d1cdac390993329
5
5
  SHA512:
6
- metadata.gz: f99d748f75f3333f90979bfde8082591bb77e6b56590df2424dc566ff556798a50660d2c125d4b261c37235b0cdc95ff7cf228f146bd1e8f27d88af5498d90a3
7
- data.tar.gz: b0b8aa77547e9852c91f699944877450aa03d125f4324b2efc5ea59854c293680215ee8bf272eda892a7c90c33ee5422bdecbeb3c6cf4530e2389a0aa1da8e05
6
+ metadata.gz: ce85f247ab89347bbd51a609e10254bd1aaf485ae892476a3e67fb722f5037d68b76a153560e8172ea2c9b605aafc005f7accb35a11e77913eba9b7d80616974
7
+ data.tar.gz: c0897c79cb3cf3bca1c612d9794ebd00f8ff3d295d98b2faf664190f4bc6d0ba235e01b11a06e3edb7f79197db848d2923b15ae34850cdb9b2febf0349219877
data/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## master
4
4
 
5
+ ## 0.2.1 (2026-05-26)
6
+
7
+ - Add YAML tracking. ([@palkan][])
8
+
9
+ - Add HTTP tracking via Sniffer. ([@palkan][])
10
+
5
11
  ## 0.2.0 (2026-05-25) 🔔
6
12
 
7
13
  - Add ability to run Stackprof for a particular file loading. ([@palkan][])
data/README.md CHANGED
@@ -108,6 +108,10 @@ Now you can use Speedscope to dig deeper.
108
108
 
109
109
  **NOTE:** The `stackprof` gem must be present in your Gemfile for that.
110
110
 
111
+ ### YAML and HTTP support
112
+
113
+ Require Profiler also captures HTTP requests and YAML file loading and add them to the profile, so you can find which Ruby files trigger the corresponding actions on load. NOTE: For HTTP requests tracking, you MUST add [sniffer][] gem to your Gemfile.
114
+
111
115
  ### Configuration
112
116
 
113
117
  `RequireProfiler.start` accepts the following keyword arguments:
@@ -189,3 +193,4 @@ The gem is available as open source under the terms of the [MIT License](http://
189
193
 
190
194
  [speedscope]: https://www.speedscope.app/
191
195
  [require-hooks]: https://github.com/ruby-next/require-hooks
196
+ [sniffer]: https://github.com/aderyabin/sniffer
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RequireProfiler
4
+ module Plugins
5
+ # Track HTTP calls using Sniffer and add the to the require profile
6
+ class HTTPPlugin < Base
7
+ def activate!
8
+ begin
9
+ require "sniffer"
10
+ rescue LoadError
11
+ return
12
+ end
13
+
14
+ Sniffer.config.logger = Logger.new(IO::NULL)
15
+
16
+ Sniffer.config.middleware do |chain|
17
+ chain.add HTTPPlugin, reporter
18
+ end
19
+
20
+ Sniffer::DataItem::Request.include(Module.new do
21
+ def require_path
22
+ @url ||= "#{method.to_s.upcase}:#{(port == 443) ? "https" : "http"}://#{host}#{query}"
23
+ end
24
+ end)
25
+
26
+ Sniffer.enable!
27
+ end
28
+
29
+ # Sniffer hook interface
30
+ def request(data_item)
31
+ reporter.handle_event(Reporter::Event.new(type: :start, kind: :http, path: data_item.request.require_path))
32
+ yield
33
+ end
34
+
35
+ def response(data_item)
36
+ yield
37
+ time = data_item.response.timing
38
+ reporter.handle_event(Reporter::Event.new(type: :end, path: data_item.request.require_path, time:))
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RequireProfiler
4
+ module Plugins
5
+ # Track loading YAML files
6
+ class YAMLPlugin < Base
7
+ module Patch
8
+ def load_file(path, ...)
9
+ YAMLPlugin.track(path) { super }
10
+ end
11
+
12
+ def unsafe_load_file(path, ...)
13
+ YAMLPlugin.track(path) { super }
14
+ end
15
+
16
+ def safe_load_file(path, ...)
17
+ YAMLPlugin.track(path) { super }
18
+ end
19
+ end
20
+
21
+ class << self
22
+ attr_accessor :reporter
23
+
24
+ def track(path)
25
+ reporter.handle_event(Reporter::Event.new(type: :start, kind: :yml, path:))
26
+ start = Time.now
27
+ yield
28
+ ensure
29
+ time = Time.now - start
30
+ reporter.handle_event(Reporter::Event.new(type: :end, path:, time:))
31
+ end
32
+ end
33
+
34
+ def activate!
35
+ require "yaml"
36
+
37
+ YAMLPlugin.reporter = reporter
38
+ ::YAML.singleton_class.prepend(Patch)
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RequireProfiler
4
+ module Plugins
5
+ class << self
6
+ def register_reporter(reporter)
7
+ HTTPPlugin.new(reporter).activate! unless ENV["REQUIRE_PROFILER_HTTP"] == "false"
8
+ YAMLPlugin.new(reporter).activate! unless ENV["REQUIRE_PROFILER_YAML"] == "false"
9
+ end
10
+ end
11
+
12
+ class Base
13
+ attr_reader :reporter
14
+
15
+ def initialize(reporter)
16
+ @reporter = reporter
17
+ end
18
+ end
19
+
20
+ autoload :HTTPPlugin, "require_profiler/plugins/http_plugin"
21
+ autoload :YAMLPlugin, "require_profiler/plugins/yaml_plugin"
22
+ end
23
+ end
@@ -8,7 +8,7 @@ module RequireProfiler
8
8
  return unless flush?(node)
9
9
 
10
10
  path = node.path.sub(prefix_stripper, "")
11
- self_parts = path.split("/")
11
+ self_parts = (node.kind == :path) ? path.split("/") : [path]
12
12
 
13
13
  parts += self_parts.size.times.map { self_parts.take(_1 + 1).join("/") }
14
14
  # We only show self-time, so exclude children
@@ -2,14 +2,15 @@
2
2
 
3
3
  module RequireProfiler
4
4
  class Reporter
5
- class Event < Struct.new(:type, :path, :time, keyword_init: true)
5
+ class Event < Struct.new(:type, :path, :time, :kind, keyword_init: true)
6
6
  end
7
7
 
8
- class Node < Struct.new(:path, :time, :parent, :children, :focused, keyword_init: true)
8
+ class Node < Struct.new(:path, :time, :parent, :children, :kind, :focused, keyword_init: true)
9
9
  def initialize(...)
10
10
  super
11
11
  self.children ||= []
12
12
  self.focused = false
13
+ self.kind ||= :path
13
14
  end
14
15
 
15
16
  def focused!
@@ -37,7 +38,7 @@ module RequireProfiler
37
38
 
38
39
  def handle_event_sync(event)
39
40
  if event.type == :start
40
- node = Node.new(path: event.path, children: [])
41
+ node = Node.new(path: event.path, children: [], kind: event.kind)
41
42
  parent = stack.last
42
43
 
43
44
  if parent
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RequireProfiler # :nodoc:
4
- VERSION = "0.2.0"
4
+ VERSION = "0.2.1"
5
5
  end
@@ -3,6 +3,7 @@
3
3
  module RequireProfiler
4
4
  autoload :Reporter, "require_profiler/reporter"
5
5
  autoload :Printer, "require_profiler/printer"
6
+ autoload :Plugins, "require_profiler/plugins"
6
7
 
7
8
  # Autoload doesn't work here, because we call it from the hooks for the first time
8
9
  require "require_profiler/ruby_profiling"
@@ -38,6 +39,8 @@ module RequireProfiler
38
39
  time = Time.now - start
39
40
  reporter.handle_event(Reporter::Event.new(type: :end, path:, time:))
40
41
  end
42
+
43
+ Plugins.register_reporter(reporter) unless ENV["REQUIRE_PROFILER_PLUGINS"] == "false"
41
44
  end
42
45
 
43
46
  def stop
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: require-profiler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vladimir Dementyev
@@ -37,6 +37,34 @@ dependencies:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
39
  version: '1.15'
40
+ - !ruby/object:Gem::Dependency
41
+ name: benchmark
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: logger
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
40
68
  - !ruby/object:Gem::Dependency
41
69
  name: rake
42
70
  requirement: !ruby/object:Gem::Requirement
@@ -65,6 +93,34 @@ dependencies:
65
93
  - - ">="
66
94
  - !ruby/object:Gem::Version
67
95
  version: '3.9'
96
+ - !ruby/object:Gem::Dependency
97
+ name: sniffer
98
+ requirement: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ type: :development
104
+ prerelease: false
105
+ version_requirements: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - ">="
108
+ - !ruby/object:Gem::Version
109
+ version: '0'
110
+ - !ruby/object:Gem::Dependency
111
+ name: webmock
112
+ requirement: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: '3.26'
117
+ type: :development
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: '3.26'
68
124
  description: 'Profile Ruby #require/#load/etc calls'
69
125
  email:
70
126
  - Vladimir Dementyev
@@ -78,6 +134,9 @@ files:
78
134
  - lib/equire-prof.rb
79
135
  - lib/require-profiler.rb
80
136
  - lib/require_profiler.rb
137
+ - lib/require_profiler/plugins.rb
138
+ - lib/require_profiler/plugins/http_plugin.rb
139
+ - lib/require_profiler/plugins/yaml_plugin.rb
81
140
  - lib/require_profiler/printer.rb
82
141
  - lib/require_profiler/printer/call_stack.rb
83
142
  - lib/require_profiler/printer/json.rb