rails-profiler 0.31.1 → 0.31.3
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 +4 -4
- data/CHANGELOG.md +122 -0
- data/app/assets/builds/profiler-toolbar.js +13 -6
- data/app/assets/builds/profiler.js +73 -37
- data/app/controllers/profiler/api/profiles_controller.rb +2 -0
- data/app/controllers/profiler/api/toolbar_controller.rb +2 -0
- data/app/controllers/profiler/profiles_controller.rb +2 -0
- data/lib/profiler/allocation_counter.rb +21 -0
- data/lib/profiler/collectors/base_collector.rb +39 -3
- data/lib/profiler/collectors/cache_collector.rb +8 -3
- data/lib/profiler/collectors/database_collector.rb +133 -10
- data/lib/profiler/collectors/env_collector.rb +12 -4
- data/lib/profiler/collectors/exception_collector.rb +12 -9
- data/lib/profiler/collectors/flamegraph_collector.rb +10 -5
- data/lib/profiler/collectors/function_profiler_collector.rb +28 -4
- data/lib/profiler/collectors/http_collector.rb +5 -0
- data/lib/profiler/collectors/log_collector.rb +5 -0
- data/lib/profiler/collectors/mailer_collector.rb +2 -8
- data/lib/profiler/collectors/request_collector.rb +14 -11
- data/lib/profiler/collectors/routes_collector.rb +7 -50
- data/lib/profiler/collectors/scoped_notifications.rb +229 -0
- data/lib/profiler/collectors/view_collector.rb +7 -2
- data/lib/profiler/configuration.rb +27 -4
- data/lib/profiler/console_profiler.rb +2 -17
- data/lib/profiler/instrumentation/executor_context_propagation.rb +47 -0
- data/lib/profiler/instrumentation/request_context.rb +77 -0
- data/lib/profiler/instrumentation/thread_context_propagation.rb +11 -16
- data/lib/profiler/job_profiler.rb +6 -17
- data/lib/profiler/mcp/resources/recent_requests.rb +2 -0
- data/lib/profiler/mcp/tools/get_profile_detail.rb +21 -2
- data/lib/profiler/mcp/tools/get_test_profile_detail.rb +1 -1
- data/lib/profiler/middleware/capturing_body.rb +112 -0
- data/lib/profiler/middleware/profiler_middleware.rb +261 -53
- data/lib/profiler/middleware/streamed_profile.rb +135 -0
- data/lib/profiler/middleware/toolbar_injector.rb +3 -10
- data/lib/profiler/models/profile.rb +69 -5
- data/lib/profiler/process_snapshot.rb +157 -0
- data/lib/profiler/redaction.rb +10 -0
- data/lib/profiler/test_profiler.rb +2 -17
- data/lib/profiler/version.rb +1 -1
- data/lib/profiler.rb +6 -0
- metadata +12 -5
|
@@ -129,17 +129,10 @@ module Profiler
|
|
|
129
129
|
end
|
|
130
130
|
end
|
|
131
131
|
|
|
132
|
+
# The middleware hands over the page it has already read: an Array of parts, or a String.
|
|
133
|
+
# Never a body to iterate, which would drain a stream and close it.
|
|
132
134
|
def extract_content(body)
|
|
133
|
-
|
|
134
|
-
body.body
|
|
135
|
-
elsif body.respond_to?(:each)
|
|
136
|
-
parts = []
|
|
137
|
-
body.each { |part| parts << part }
|
|
138
|
-
body.close if body.respond_to?(:close)
|
|
139
|
-
parts.join
|
|
140
|
-
else
|
|
141
|
-
body.to_s
|
|
142
|
-
end
|
|
135
|
+
body.is_a?(String) ? body : Array(body).join
|
|
143
136
|
end
|
|
144
137
|
|
|
145
138
|
def toolbar_html
|
|
@@ -5,17 +5,21 @@ require "securerandom"
|
|
|
5
5
|
require "json"
|
|
6
6
|
require "zlib"
|
|
7
7
|
require_relative "../redaction"
|
|
8
|
+
require_relative "../allocation_counter"
|
|
8
9
|
|
|
9
10
|
module Profiler
|
|
10
11
|
module Models
|
|
11
12
|
class Profile
|
|
12
13
|
attr_reader :path
|
|
13
|
-
attr_accessor :token, :method, :status, :duration, :
|
|
14
|
+
attr_accessor :token, :method, :status, :duration, :allocated_objects,
|
|
14
15
|
:started_at, :finished_at, :params, :headers,
|
|
15
16
|
:response_headers, :collectors_data, :collectors_metadata,
|
|
16
17
|
:parent_token, :is_ajax, :profile_type,
|
|
17
18
|
:request_body, :request_body_encoding,
|
|
18
19
|
:response_body, :response_body_encoding,
|
|
20
|
+
:request_body_size, :request_body_truncated, :request_body_size_is_minimum,
|
|
21
|
+
:response_body_size, :response_body_truncated, :response_body_size_is_minimum,
|
|
22
|
+
:collectors_released_after_seconds,
|
|
19
23
|
:gem_version
|
|
20
24
|
|
|
21
25
|
def initialize(request = nil)
|
|
@@ -35,13 +39,32 @@ module Profiler
|
|
|
35
39
|
end
|
|
36
40
|
end
|
|
37
41
|
|
|
38
|
-
|
|
42
|
+
# The sizes are those of the whole bodies, when a body was cut at max_captured_body_bytes;
|
|
43
|
+
# *_size_is_minimum when the whole size is not known (no Content-Length, a stream that
|
|
44
|
+
# stopped), only that it is at least that.
|
|
45
|
+
def set_bodies(request_body:, response_body:, req_content_type:, resp_content_type:,
|
|
46
|
+
request_body_size: nil, response_body_size: nil,
|
|
47
|
+
request_body_size_is_minimum: false, response_body_size_is_minimum: false)
|
|
39
48
|
req = process_body(request_body, req_content_type)
|
|
40
49
|
resp = process_body(response_body, resp_content_type)
|
|
41
50
|
@request_body = req[:body]
|
|
42
51
|
@request_body_encoding = req[:encoding]
|
|
43
52
|
@response_body = resp[:body]
|
|
44
53
|
@response_body_encoding = resp[:encoding]
|
|
54
|
+
@request_body_size, @request_body_truncated = body_size(request_body, request_body_size)
|
|
55
|
+
@response_body_size, @response_body_truncated = body_size(response_body, response_body_size)
|
|
56
|
+
@request_body_size_is_minimum = request_body_size_is_minimum ? true : false
|
|
57
|
+
@response_body_size_is_minimum = response_body_size_is_minimum ? true : false
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Deprecated: the number of allocated objects times 40, the figure earlier versions
|
|
61
|
+
# reported as bytes. Still written in to_h, for the readers that only know it.
|
|
62
|
+
def memory
|
|
63
|
+
@allocated_objects&.*(AllocationCounter::LEGACY_BYTES_PER_OBJECT)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def memory=(value)
|
|
67
|
+
@allocated_objects = value && value / AllocationCounter::LEGACY_BYTES_PER_OBJECT
|
|
45
68
|
end
|
|
46
69
|
|
|
47
70
|
def finish(status, response_headers = {})
|
|
@@ -67,8 +90,20 @@ module Profiler
|
|
|
67
90
|
end
|
|
68
91
|
|
|
69
92
|
def add_collector_metadata(collector)
|
|
93
|
+
@collectors_metadata << collector_metadata(collector)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# A collector collected again, after the profile's tabs were listed (a streamed response
|
|
97
|
+
# that failed part way): its tab keeps its place, with what it now has.
|
|
98
|
+
def refresh_collector_metadata(collector)
|
|
99
|
+
entry = collector_metadata(collector)
|
|
100
|
+
index = @collectors_metadata.index { |tab| tab[:key] == entry[:key] }
|
|
101
|
+
index ? @collectors_metadata[index] = entry : @collectors_metadata << entry
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def collector_metadata(collector)
|
|
70
105
|
config = collector.tab_config
|
|
71
|
-
|
|
106
|
+
{
|
|
72
107
|
key: config[:key],
|
|
73
108
|
label: config[:label],
|
|
74
109
|
icon: config[:icon],
|
|
@@ -79,6 +114,7 @@ module Profiler
|
|
|
79
114
|
has_data: collector.has_data?
|
|
80
115
|
}
|
|
81
116
|
end
|
|
117
|
+
private :collector_metadata
|
|
82
118
|
|
|
83
119
|
def to_h
|
|
84
120
|
req_body, req_enc = decode_body(@request_body, @request_body_encoding)
|
|
@@ -92,7 +128,8 @@ module Profiler
|
|
|
92
128
|
method: @method,
|
|
93
129
|
status: @status,
|
|
94
130
|
duration: @duration,
|
|
95
|
-
|
|
131
|
+
allocated_objects: @allocated_objects,
|
|
132
|
+
memory: memory,
|
|
96
133
|
started_at: @started_at&.iso8601,
|
|
97
134
|
finished_at: @finished_at&.iso8601,
|
|
98
135
|
params: @params,
|
|
@@ -102,6 +139,13 @@ module Profiler
|
|
|
102
139
|
request_body_encoding: req_enc,
|
|
103
140
|
response_body: resp_body,
|
|
104
141
|
response_body_encoding: resp_enc,
|
|
142
|
+
request_body_size: @request_body_size,
|
|
143
|
+
request_body_truncated: @request_body_truncated,
|
|
144
|
+
request_body_size_is_minimum: @request_body_size_is_minimum,
|
|
145
|
+
response_body_size: @response_body_size,
|
|
146
|
+
response_body_truncated: @response_body_truncated,
|
|
147
|
+
response_body_size_is_minimum: @response_body_size_is_minimum,
|
|
148
|
+
collectors_released_after_seconds: @collectors_released_after_seconds,
|
|
105
149
|
collectors_data: @collectors_data,
|
|
106
150
|
tabs: @collectors_metadata,
|
|
107
151
|
parent_token: @parent_token,
|
|
@@ -125,7 +169,12 @@ module Profiler
|
|
|
125
169
|
profile.method = data[:method]
|
|
126
170
|
profile.status = data[:status]
|
|
127
171
|
profile.duration = data[:duration]
|
|
128
|
-
profile
|
|
172
|
+
# A profile saved before allocated_objects only carries memory, the count times 40.
|
|
173
|
+
if data.key?(:allocated_objects)
|
|
174
|
+
profile.allocated_objects = data[:allocated_objects]
|
|
175
|
+
else
|
|
176
|
+
profile.memory = data[:memory]
|
|
177
|
+
end
|
|
129
178
|
profile.started_at = data[:started_at] ? Time.parse(data[:started_at]) : nil
|
|
130
179
|
profile.finished_at = data[:finished_at] ? Time.parse(data[:finished_at]) : nil
|
|
131
180
|
profile.params = data[:params]
|
|
@@ -135,6 +184,15 @@ module Profiler
|
|
|
135
184
|
profile.request_body_encoding = data[:request_body_encoding] || "text"
|
|
136
185
|
profile.response_body = data[:response_body]
|
|
137
186
|
profile.response_body_encoding = data[:response_body_encoding] || "text"
|
|
187
|
+
profile.request_body_size = data[:request_body_size]
|
|
188
|
+
# Left nil when the store did not keep them (SqliteStore rebuilds a profile from its
|
|
189
|
+
# columns): the request collector's data still has them.
|
|
190
|
+
profile.request_body_truncated = data[:request_body_truncated]
|
|
191
|
+
profile.request_body_size_is_minimum = data[:request_body_size_is_minimum]
|
|
192
|
+
profile.response_body_size = data[:response_body_size]
|
|
193
|
+
profile.response_body_truncated = data[:response_body_truncated]
|
|
194
|
+
profile.response_body_size_is_minimum = data[:response_body_size_is_minimum]
|
|
195
|
+
profile.collectors_released_after_seconds = data[:collectors_released_after_seconds]
|
|
138
196
|
profile.parent_token = data[:parent_token]
|
|
139
197
|
profile.is_ajax = data[:is_ajax] || false
|
|
140
198
|
profile.profile_type = data[:profile_type] || "http"
|
|
@@ -181,6 +239,12 @@ module Profiler
|
|
|
181
239
|
end
|
|
182
240
|
end
|
|
183
241
|
|
|
242
|
+
def body_size(captured, total)
|
|
243
|
+
captured_size = captured.to_s.bytesize
|
|
244
|
+
total ||= captured_size
|
|
245
|
+
[total, total > captured_size]
|
|
246
|
+
end
|
|
247
|
+
|
|
184
248
|
def compress_body?(text)
|
|
185
249
|
Profiler.configuration.compress_bodies &&
|
|
186
250
|
text.bytesize > Profiler.configuration.compress_body_threshold
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "redaction"
|
|
4
|
+
|
|
5
|
+
module Profiler
|
|
6
|
+
# What is the same for every request of a process, kept out of the profiles: the route table
|
|
7
|
+
# and ENV. A profile stores the route its request matched; the Routes and Env tabs, the
|
|
8
|
+
# toolbar and the MCP tools get the table and the variables from the process that displays the
|
|
9
|
+
# profile (#hydrate).
|
|
10
|
+
#
|
|
11
|
+
# The route table is built once and built again when the routes are reloaded, in development
|
|
12
|
+
# (reload_routes!, or a change picked up by the reloader): the routes are new objects then.
|
|
13
|
+
# ENV is read when the profile is displayed, masked as it always was: it can change while the
|
|
14
|
+
# process runs, through the profiler's own env overrides among others.
|
|
15
|
+
module ProcessSnapshot
|
|
16
|
+
@mutex = Mutex.new
|
|
17
|
+
@routes = nil # [fingerprint, table, routes by [controller, action]]
|
|
18
|
+
|
|
19
|
+
class << self
|
|
20
|
+
# The application's routes, the engine's and Rails' own left out, as the Routes tab lists
|
|
21
|
+
# them: { name:, pattern:, verb:, controller_action: }.
|
|
22
|
+
def route_table
|
|
23
|
+
routes_snapshot[1]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# The route of the table a request to +path+ with +method+ is dispatched to, or nil.
|
|
27
|
+
def matched_route(path, method)
|
|
28
|
+
return nil unless rails_routes?
|
|
29
|
+
|
|
30
|
+
recognized = Rails.application.routes.recognize_path(path, method: method)
|
|
31
|
+
candidates = routes_snapshot[2][[recognized[:controller], recognized[:action]]]
|
|
32
|
+
return nil unless candidates
|
|
33
|
+
|
|
34
|
+
# A PUT goes to the action of the PATCH route too: give the route of the request's verb.
|
|
35
|
+
verb = method.to_s.upcase == "HEAD" ? "GET" : method.to_s.upcase
|
|
36
|
+
candidates.find { |route| route[:verb].split("|").include?(verb) } ||
|
|
37
|
+
candidates.find { |route| route[:verb] == "ANY" } ||
|
|
38
|
+
candidates.first
|
|
39
|
+
rescue StandardError
|
|
40
|
+
nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# ENV as the Env tab shows it.
|
|
44
|
+
def env_variables
|
|
45
|
+
Redaction.hide_credentials(Redaction.env_snapshot)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Puts the route table and ENV back into +profile+'s data for display. A profile saved by
|
|
49
|
+
# an earlier version has its own table and variables, and is left as it is.
|
|
50
|
+
def hydrate(profile)
|
|
51
|
+
# A job, a console expression or a test ran in another process, whose ENV its profile keeps.
|
|
52
|
+
return profile unless profile && profile.profile_type.to_s == "http"
|
|
53
|
+
|
|
54
|
+
data = profile.collectors_data
|
|
55
|
+
if (routes = data["routes"]) && !key?(routes, :routes)
|
|
56
|
+
data["routes"] = with_route_table(routes)
|
|
57
|
+
end
|
|
58
|
+
if (env = data["env"]) && !key?(env, :variables)
|
|
59
|
+
variables = env_variables
|
|
60
|
+
data["env"] = merge(env, variables: variables, total: variables.size)
|
|
61
|
+
end
|
|
62
|
+
profile
|
|
63
|
+
rescue StandardError => e
|
|
64
|
+
warn "Profiler: could not add the routes and ENV to profile #{profile.token}: #{e.message}"
|
|
65
|
+
profile
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def rails_routes?
|
|
71
|
+
defined?(Rails) && Rails.respond_to?(:application) && Rails.application
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def routes_snapshot
|
|
75
|
+
return [nil, [].freeze, {}.freeze] unless rails_routes?
|
|
76
|
+
|
|
77
|
+
journey = Rails.application.routes.routes
|
|
78
|
+
list = journey.respond_to?(:routes) ? journey.routes : journey.to_a
|
|
79
|
+
fingerprint = [journey.object_id, list.size, list.first&.object_id, list.last&.object_id]
|
|
80
|
+
snapshot = @routes
|
|
81
|
+
return snapshot if snapshot && snapshot[0] == fingerprint
|
|
82
|
+
|
|
83
|
+
@mutex.synchronize do
|
|
84
|
+
@routes = build_snapshot(fingerprint, list) unless @routes && @routes[0] == fingerprint
|
|
85
|
+
@routes
|
|
86
|
+
end
|
|
87
|
+
rescue StandardError
|
|
88
|
+
[nil, [].freeze, {}.freeze]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def build_snapshot(fingerprint, list)
|
|
92
|
+
index = {}
|
|
93
|
+
table = list.filter_map do |route|
|
|
94
|
+
next if route.respond_to?(:internal) && route.internal
|
|
95
|
+
|
|
96
|
+
controller = route.defaults[:controller]
|
|
97
|
+
action = route.defaults[:action]
|
|
98
|
+
next if controller.nil?
|
|
99
|
+
next if controller.start_with?("rails/", "profiler/")
|
|
100
|
+
|
|
101
|
+
verb = route.verb
|
|
102
|
+
entry = {
|
|
103
|
+
name: route.name,
|
|
104
|
+
pattern: route.path.spec.to_s.sub(/\(\.:format\)$/, ""),
|
|
105
|
+
verb: (verb && !verb.empty?) ? verb : "ANY",
|
|
106
|
+
controller_action: controller_action(controller, action)
|
|
107
|
+
}.freeze
|
|
108
|
+
(index[[controller, action]] ||= []) << entry
|
|
109
|
+
entry
|
|
110
|
+
end
|
|
111
|
+
[fingerprint, table.freeze, index.each_value(&:freeze).freeze]
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def controller_action(controller, action)
|
|
115
|
+
return nil unless action
|
|
116
|
+
|
|
117
|
+
"#{controller.split("/").map { |s| ActiveSupport::Inflector.camelize(s) }.join("::")}Controller##{action}"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def with_route_table(routes)
|
|
121
|
+
matched = fetch(routes, :matched)
|
|
122
|
+
table = route_table.map { |route| route.merge(matched: same_route?(route, matched)) }
|
|
123
|
+
merge(routes, routes: table, total: table.size)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Every route of the matched action, as the table always marked them.
|
|
127
|
+
def same_route?(route, matched)
|
|
128
|
+
return false unless matched.is_a?(Hash)
|
|
129
|
+
|
|
130
|
+
action = fetch(matched, :controller_action)
|
|
131
|
+
action ? route[:controller_action] == action : route[:pattern] == fetch(matched, :pattern)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Collector data has symbol keys as collected, string keys once read back from storage.
|
|
135
|
+
def key?(hash, key)
|
|
136
|
+
hash.key?(key) || hash.key?(key.to_s)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def fetch(hash, key)
|
|
140
|
+
hash.key?(key) ? hash[key] : hash[key.to_s]
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def merge(hash, values)
|
|
144
|
+
strings = hash.keys.first.is_a?(String)
|
|
145
|
+
hash.merge(strings ? deep_stringify(values) : values)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def deep_stringify(value)
|
|
149
|
+
case value
|
|
150
|
+
when Hash then value.to_h { |k, v| [k.to_s, deep_stringify(v)] }
|
|
151
|
+
when Array then value.map { |v| deep_stringify(v) }
|
|
152
|
+
else value
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
data/lib/profiler/redaction.rb
CHANGED
|
@@ -88,6 +88,16 @@ module Profiler
|
|
|
88
88
|
text.length > max ? "#{text[0, max]}#{ellipsis}" : text
|
|
89
89
|
end
|
|
90
90
|
|
|
91
|
+
# The first +max+ bytes of +text+, a body kept only in part. A credential cut in two by the
|
|
92
|
+
# limit would escape the masking by value, which runs later on what is kept: the bytes
|
|
93
|
+
# that could start one are left out as well. Text no longer than +max+ is returned whole.
|
|
94
|
+
def cut_bytes(text, max)
|
|
95
|
+
return text if text.bytesize <= max
|
|
96
|
+
|
|
97
|
+
cut = text.byteslice(0, max)
|
|
98
|
+
cut.byteslice(0, cut.bytesize - held_back_bytes(cut))
|
|
99
|
+
end
|
|
100
|
+
|
|
91
101
|
# How many trailing bytes of +text+, a stream read in pieces, could be
|
|
92
102
|
# the start of a credential and have to wait for the next piece: the
|
|
93
103
|
# longest end of the text that is also the beginning of a credential,
|
|
@@ -56,7 +56,7 @@ module Profiler
|
|
|
56
56
|
|
|
57
57
|
exception_collector = collectors.find { |c| c.is_a?(Collectors::ExceptionCollector) }
|
|
58
58
|
|
|
59
|
-
|
|
59
|
+
allocations_before = AllocationCounter.current if Profiler.configuration.track_memory
|
|
60
60
|
|
|
61
61
|
test_status = "passed"
|
|
62
62
|
error_message = nil
|
|
@@ -103,7 +103,7 @@ module Profiler
|
|
|
103
103
|
Profiler::CurrentContext.token = previous_token
|
|
104
104
|
|
|
105
105
|
if Profiler.configuration.track_memory
|
|
106
|
-
profile.
|
|
106
|
+
profile.allocated_objects = AllocationCounter.current - allocations_before
|
|
107
107
|
end
|
|
108
108
|
|
|
109
109
|
test_collector.update_status(test_status, error_message)
|
|
@@ -124,20 +124,5 @@ module Profiler
|
|
|
124
124
|
# After collect, and also when collect or the storage failed.
|
|
125
125
|
Collectors::Lifecycle.release_all(collectors)
|
|
126
126
|
end
|
|
127
|
-
|
|
128
|
-
private
|
|
129
|
-
|
|
130
|
-
def current_memory
|
|
131
|
-
return 0 unless defined?(GC.stat)
|
|
132
|
-
|
|
133
|
-
stats = GC.stat
|
|
134
|
-
if stats.key?(:total_allocated_size)
|
|
135
|
-
stats[:total_allocated_size]
|
|
136
|
-
elsif stats.key?(:total_allocated_objects)
|
|
137
|
-
stats[:total_allocated_objects] * 40
|
|
138
|
-
else
|
|
139
|
-
0
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
127
|
end
|
|
143
128
|
end
|
data/lib/profiler/version.rb
CHANGED
data/lib/profiler.rb
CHANGED
|
@@ -5,6 +5,7 @@ require_relative "profiler/boot_env"
|
|
|
5
5
|
require_relative "profiler/version"
|
|
6
6
|
require_relative "profiler/configuration"
|
|
7
7
|
require_relative "profiler/redaction"
|
|
8
|
+
require_relative "profiler/allocation_counter"
|
|
8
9
|
|
|
9
10
|
module Profiler
|
|
10
11
|
class Error < StandardError; end
|
|
@@ -15,6 +16,9 @@ module Profiler
|
|
|
15
16
|
attr_accessor :function_profiling_max_frames
|
|
16
17
|
attr_accessor :function_profiling_mode
|
|
17
18
|
attr_accessor :function_profiling_clock
|
|
19
|
+
# Without stackprof, the function profiler stays off unless this is true: it then traces
|
|
20
|
+
# every method call of the request's thread with a TracePoint, several times slower.
|
|
21
|
+
attr_accessor :function_profiling_tracepoint_fallback
|
|
18
22
|
|
|
19
23
|
def configuration
|
|
20
24
|
@configuration ||= Configuration.new
|
|
@@ -113,6 +117,7 @@ module Profiler
|
|
|
113
117
|
self.function_profiling_max_frames = 2000
|
|
114
118
|
self.function_profiling_mode = "lite"
|
|
115
119
|
self.function_profiling_clock = "wall"
|
|
120
|
+
self.function_profiling_tracepoint_fallback = false
|
|
116
121
|
end
|
|
117
122
|
|
|
118
123
|
# Require core components
|
|
@@ -137,6 +142,7 @@ require_relative "profiler/storage/token"
|
|
|
137
142
|
require_relative "profiler/storage/private_files"
|
|
138
143
|
require_relative "profiler/env_override_store"
|
|
139
144
|
require_relative "profiler/instrumentation/thread_context_propagation"
|
|
145
|
+
require_relative "profiler/instrumentation/executor_context_propagation"
|
|
140
146
|
require_relative "profiler/sse/event_bus"
|
|
141
147
|
require_relative "profiler/sse/redis_event_bus"
|
|
142
148
|
require_relative "profiler/sse/bus"
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rails-profiler
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.31.
|
|
4
|
+
version: 0.31.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Sébastien Duplessy
|
|
@@ -133,6 +133,7 @@ files:
|
|
|
133
133
|
- config/routes.rb
|
|
134
134
|
- exe/profiler-mcp
|
|
135
135
|
- lib/profiler.rb
|
|
136
|
+
- lib/profiler/allocation_counter.rb
|
|
136
137
|
- lib/profiler/boot_env.rb
|
|
137
138
|
- lib/profiler/cluster/master_client.rb
|
|
138
139
|
- lib/profiler/cluster/security.rb
|
|
@@ -157,6 +158,7 @@ files:
|
|
|
157
158
|
- lib/profiler/collectors/mailer_collector.rb
|
|
158
159
|
- lib/profiler/collectors/request_collector.rb
|
|
159
160
|
- lib/profiler/collectors/routes_collector.rb
|
|
161
|
+
- lib/profiler/collectors/scoped_notifications.rb
|
|
160
162
|
- lib/profiler/collectors/test_collector.rb
|
|
161
163
|
- lib/profiler/collectors/view_collector.rb
|
|
162
164
|
- lib/profiler/configuration.rb
|
|
@@ -166,8 +168,10 @@ files:
|
|
|
166
168
|
- lib/profiler/env_override_store.rb
|
|
167
169
|
- lib/profiler/explain_runner.rb
|
|
168
170
|
- lib/profiler/instrumentation/active_job_instrumentation.rb
|
|
171
|
+
- lib/profiler/instrumentation/executor_context_propagation.rb
|
|
169
172
|
- lib/profiler/instrumentation/irb_instrumentation.rb
|
|
170
173
|
- lib/profiler/instrumentation/net_http_instrumentation.rb
|
|
174
|
+
- lib/profiler/instrumentation/request_context.rb
|
|
171
175
|
- lib/profiler/instrumentation/sidekiq_middleware.rb
|
|
172
176
|
- lib/profiler/instrumentation/thread_context_propagation.rb
|
|
173
177
|
- lib/profiler/job_profiler.rb
|
|
@@ -206,12 +210,15 @@ files:
|
|
|
206
210
|
- lib/profiler/mcp/tools/reset_env_var.rb
|
|
207
211
|
- lib/profiler/mcp/tools/run_tests.rb
|
|
208
212
|
- lib/profiler/mcp/tools/set_env_var.rb
|
|
213
|
+
- lib/profiler/middleware/capturing_body.rb
|
|
209
214
|
- lib/profiler/middleware/cors_middleware.rb
|
|
210
215
|
- lib/profiler/middleware/profiler_middleware.rb
|
|
216
|
+
- lib/profiler/middleware/streamed_profile.rb
|
|
211
217
|
- lib/profiler/middleware/toolbar_injector.rb
|
|
212
218
|
- lib/profiler/models/profile.rb
|
|
213
219
|
- lib/profiler/models/sql_query.rb
|
|
214
220
|
- lib/profiler/models/timeline_event.rb
|
|
221
|
+
- lib/profiler/process_snapshot.rb
|
|
215
222
|
- lib/profiler/railtie.rb
|
|
216
223
|
- lib/profiler/redaction.rb
|
|
217
224
|
- lib/profiler/sql_statement.rb
|
|
@@ -235,13 +242,13 @@ files:
|
|
|
235
242
|
- lib/profiler/test_runner/run_store.rb
|
|
236
243
|
- lib/profiler/test_runner/runner.rb
|
|
237
244
|
- lib/profiler/version.rb
|
|
238
|
-
homepage: https://
|
|
245
|
+
homepage: https://github.com/Pepsit36/rails-profiler
|
|
239
246
|
licenses:
|
|
240
247
|
- MIT
|
|
241
248
|
metadata:
|
|
242
|
-
homepage_uri: https://
|
|
243
|
-
source_code_uri: https://
|
|
244
|
-
changelog_uri: https://
|
|
249
|
+
homepage_uri: https://github.com/Pepsit36/rails-profiler
|
|
250
|
+
source_code_uri: https://github.com/Pepsit36/rails-profiler
|
|
251
|
+
changelog_uri: https://github.com/Pepsit36/rails-profiler/blob/master/CHANGELOG.md
|
|
245
252
|
post_install_message:
|
|
246
253
|
rdoc_options: []
|
|
247
254
|
require_paths:
|