rack-mini-profiler 1.0.2 → 3.1.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 +4 -4
- data/CHANGELOG.md +145 -21
- data/README.md +201 -94
- data/lib/enable_rails_patches.rb +5 -0
- data/lib/generators/rack_mini_profiler/USAGE +9 -0
- data/lib/generators/rack_mini_profiler/install_generator.rb +13 -0
- data/lib/generators/{rack_profiler/templates/rack_profiler.rb → rack_mini_profiler/templates/rack_mini_profiler.rb} +1 -1
- data/lib/generators/rack_profiler/install_generator.rb +6 -3
- data/lib/html/dot.1.1.2.min.js +2 -0
- data/lib/html/includes.css +144 -45
- data/lib/html/includes.js +1420 -1009
- data/lib/html/includes.scss +538 -441
- data/lib/html/includes.tmpl +231 -148
- data/lib/html/pretty-print.js +810 -0
- data/lib/html/profile_handler.js +1 -1
- data/lib/html/rack-mini-profiler.css +3 -0
- data/lib/html/rack-mini-profiler.js +2 -0
- data/lib/html/share.html +0 -1
- data/lib/html/speedscope/LICENSE +21 -0
- data/lib/html/speedscope/README.md +3 -0
- data/lib/html/speedscope/demangle-cpp.1768f4cc.js +4 -0
- data/lib/html/speedscope/favicon-16x16.f74b3187.png +0 -0
- data/lib/html/speedscope/favicon-32x32.bc503437.png +0 -0
- data/lib/html/speedscope/file-format-schema.json +324 -0
- data/lib/html/speedscope/fonts/source-code-pro-regular.css +8 -0
- data/lib/html/speedscope/fonts/source-code-pro-v13-regular.woff +0 -0
- data/lib/html/speedscope/fonts/source-code-pro-v13-regular.woff2 +0 -0
- data/lib/html/speedscope/import.cf0fa83f.js +115 -0
- data/lib/html/speedscope/index.html +2 -0
- data/lib/html/speedscope/release.txt +3 -0
- data/lib/html/speedscope/reset.8c46b7a1.css +2 -0
- data/lib/html/speedscope/source-map.438fa06b.js +24 -0
- data/lib/html/speedscope/speedscope.44364064.js +200 -0
- data/lib/html/vendor.js +848 -0
- data/lib/mini_profiler/asset_version.rb +3 -2
- data/lib/mini_profiler/client_settings.rb +15 -7
- data/lib/mini_profiler/config.rb +51 -5
- data/lib/mini_profiler/gc_profiler.rb +1 -1
- data/lib/mini_profiler/profiling_methods.rb +13 -8
- data/lib/mini_profiler/snapshots_transporter.rb +109 -0
- data/lib/mini_profiler/storage/abstract_store.rb +52 -1
- data/lib/mini_profiler/storage/file_store.rb +7 -3
- data/lib/mini_profiler/storage/memcache_store.rb +13 -7
- data/lib/mini_profiler/storage/memory_store.rb +100 -7
- data/lib/mini_profiler/storage/redis_store.rb +226 -3
- data/lib/mini_profiler/storage.rb +7 -0
- data/lib/mini_profiler/timer_struct/base.rb +2 -0
- data/lib/mini_profiler/timer_struct/custom.rb +1 -0
- data/lib/mini_profiler/timer_struct/page.rb +60 -4
- data/lib/mini_profiler/timer_struct/request.rb +53 -11
- data/lib/mini_profiler/timer_struct/sql.rb +6 -2
- data/lib/mini_profiler/timer_struct.rb +8 -0
- data/lib/mini_profiler/version.rb +2 -1
- data/lib/{mini_profiler/profiler.rb → mini_profiler.rb} +400 -83
- data/lib/mini_profiler_rails/railtie.rb +89 -7
- data/lib/mini_profiler_rails/railtie_methods.rb +61 -0
- data/lib/patches/db/activerecord.rb +1 -12
- data/lib/patches/db/mongo.rb +1 -1
- data/lib/patches/db/moped.rb +1 -1
- data/lib/patches/db/mysql2/alias_method.rb +30 -0
- data/lib/patches/db/mysql2/prepend.rb +34 -0
- data/lib/patches/db/mysql2.rb +4 -27
- data/lib/patches/db/plucky.rb +4 -4
- data/lib/patches/db/riak.rb +1 -1
- data/lib/patches/net_patches.rb +21 -10
- data/lib/patches/sql_patches.rb +13 -5
- data/lib/prepend_mysql2_patch.rb +5 -0
- data/lib/prepend_net_http_patch.rb +5 -0
- data/lib/rack-mini-profiler.rb +1 -24
- data/rack-mini-profiler.gemspec +17 -8
- metadata +156 -32
- data/lib/html/jquery.1.7.1.js +0 -4
- data/lib/html/jquery.tmpl.js +0 -486
- data/lib/html/list.css +0 -9
- data/lib/html/list.js +0 -38
- data/lib/html/list.tmpl +0 -34
@@ -1,10 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'cgi'
|
4
|
+
require 'json'
|
5
|
+
require 'erb'
|
6
|
+
|
7
|
+
require 'mini_profiler/timer_struct'
|
8
|
+
require 'mini_profiler/storage'
|
9
|
+
require 'mini_profiler/config'
|
10
|
+
require 'mini_profiler/profiling_methods'
|
11
|
+
require 'mini_profiler/context'
|
12
|
+
require 'mini_profiler/client_settings'
|
13
|
+
require 'mini_profiler/gc_profiler'
|
14
|
+
require 'mini_profiler/snapshots_transporter'
|
15
|
+
|
3
16
|
module Rack
|
4
17
|
class MiniProfiler
|
5
18
|
class << self
|
6
19
|
|
7
20
|
include Rack::MiniProfiler::ProfilingMethods
|
21
|
+
attr_accessor :subscribe_sql_active_record
|
22
|
+
|
23
|
+
def patch_rails?
|
24
|
+
!!defined?(Rack::MINI_PROFILER_ENABLE_RAILS_PATCHES)
|
25
|
+
end
|
8
26
|
|
9
27
|
def generate_id
|
10
28
|
rand(36**20).to_s(36)
|
@@ -20,11 +38,11 @@ module Rack
|
|
20
38
|
end
|
21
39
|
|
22
40
|
def resources_root
|
23
|
-
@resources_root ||= ::File.expand_path("
|
41
|
+
@resources_root ||= ::File.expand_path("../html", __FILE__)
|
24
42
|
end
|
25
43
|
|
26
44
|
def share_template
|
27
|
-
@share_template ||= ERB.new(::File.read(::File.expand_path("
|
45
|
+
@share_template ||= ERB.new(::File.read(::File.expand_path("html/share.html", ::File.dirname(__FILE__))))
|
28
46
|
end
|
29
47
|
|
30
48
|
def current
|
@@ -33,9 +51,21 @@ module Rack
|
|
33
51
|
|
34
52
|
def current=(c)
|
35
53
|
# we use TLS cause we need access to this from sql blocks and code blocks that have no access to env
|
54
|
+
Thread.current[:mini_profiler_snapshot_custom_fields] = nil
|
55
|
+
Thread.current[:mp_ongoing_snapshot] = nil
|
36
56
|
Thread.current[:mini_profiler_private] = c
|
37
57
|
end
|
38
58
|
|
59
|
+
def add_snapshot_custom_field(key, value)
|
60
|
+
thread_var_key = :mini_profiler_snapshot_custom_fields
|
61
|
+
Thread.current[thread_var_key] ||= {}
|
62
|
+
Thread.current[thread_var_key][key] = value
|
63
|
+
end
|
64
|
+
|
65
|
+
def get_snapshot_custom_fields
|
66
|
+
Thread.current[:mini_profiler_snapshot_custom_fields]
|
67
|
+
end
|
68
|
+
|
39
69
|
# discard existing results, don't track this request
|
40
70
|
def discard_results
|
41
71
|
self.current.discard = true if current
|
@@ -62,6 +92,32 @@ module Rack
|
|
62
92
|
Thread.current[:mp_authorized]
|
63
93
|
end
|
64
94
|
|
95
|
+
def advanced_tools_message
|
96
|
+
<<~TEXT
|
97
|
+
This feature is disabled by default, to enable set the enable_advanced_debugging_tools option to true in Mini Profiler config.
|
98
|
+
TEXT
|
99
|
+
end
|
100
|
+
|
101
|
+
def binds_to_params(binds)
|
102
|
+
return if binds.nil? || config.max_sql_param_length == 0
|
103
|
+
# map ActiveRecord::Relation::QueryAttribute to [name, value]
|
104
|
+
params = binds.map { |c| c.kind_of?(Array) ? [c.first, c.last] : [c.name, c.value] }
|
105
|
+
if (skip = config.skip_sql_param_names)
|
106
|
+
params.map { |(n, v)| n =~ skip ? [n, nil] : [n, v] }
|
107
|
+
else
|
108
|
+
params
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def snapshots_transporter?
|
113
|
+
!!config.snapshots_transport_destination_url &&
|
114
|
+
!!config.snapshots_transport_auth_key
|
115
|
+
end
|
116
|
+
|
117
|
+
def redact_sql_queries?
|
118
|
+
Thread.current[:mp_ongoing_snapshot] == true &&
|
119
|
+
Rack::MiniProfiler.config.snapshots_redact_sql_queries
|
120
|
+
end
|
65
121
|
end
|
66
122
|
|
67
123
|
#
|
@@ -71,7 +127,7 @@ module Rack
|
|
71
127
|
MiniProfiler.config.merge!(config)
|
72
128
|
@config = MiniProfiler.config
|
73
129
|
@app = app
|
74
|
-
@config.base_url_path
|
130
|
+
@config.base_url_path += "/" unless @config.base_url_path.end_with? "/"
|
75
131
|
unless @config.storage_instance
|
76
132
|
@config.storage_instance = @config.storage.new(@config.storage_options)
|
77
133
|
end
|
@@ -84,15 +140,24 @@ module Rack
|
|
84
140
|
|
85
141
|
def serve_results(env)
|
86
142
|
request = Rack::Request.new(env)
|
87
|
-
id = request[
|
88
|
-
|
89
|
-
|
143
|
+
id = request.params['id']
|
144
|
+
group_name = request.params['group']
|
145
|
+
is_snapshot = group_name && group_name.size > 0
|
146
|
+
if is_snapshot
|
147
|
+
page_struct = @storage.load_snapshot(id, group_name)
|
148
|
+
else
|
149
|
+
page_struct = @storage.load(id)
|
150
|
+
end
|
151
|
+
if !page_struct && is_snapshot
|
152
|
+
id = ERB::Util.html_escape(id)
|
153
|
+
return [404, {}, ["Snapshot with id '#{id}' not found"]]
|
154
|
+
elsif !page_struct
|
90
155
|
@storage.set_viewed(user(env), id)
|
91
|
-
id = ERB::Util.html_escape(
|
156
|
+
id = ERB::Util.html_escape(id)
|
92
157
|
user_info = ERB::Util.html_escape(user(env))
|
93
158
|
return [404, {}, ["Request not found: #{id} - user #{user_info}"]]
|
94
159
|
end
|
95
|
-
|
160
|
+
if !page_struct[:has_user_viewed] && !is_snapshot
|
96
161
|
page_struct[:client_timings] = TimerStruct::Client.init_from_form_data(env, page_struct)
|
97
162
|
page_struct[:has_user_viewed] = true
|
98
163
|
@storage.save(page_struct)
|
@@ -127,11 +192,13 @@ module Rack
|
|
127
192
|
file_name = path.sub(@config.base_url_path, '')
|
128
193
|
|
129
194
|
return serve_results(env) if file_name.eql?('results')
|
195
|
+
return handle_snapshots_request(env) if file_name.eql?('snapshots')
|
196
|
+
return serve_flamegraph(env) if file_name.eql?('flamegraph')
|
130
197
|
|
131
198
|
resources_env = env.dup
|
132
199
|
resources_env['PATH_INFO'] = file_name
|
133
200
|
|
134
|
-
rack_file = Rack::File.new(MiniProfiler.resources_root, 'Cache-Control' =>
|
201
|
+
rack_file = Rack::File.new(MiniProfiler.resources_root, 'Cache-Control' => "max-age=#{cache_control_value}")
|
135
202
|
rack_file.call(resources_env)
|
136
203
|
end
|
137
204
|
|
@@ -147,11 +214,18 @@ module Rack
|
|
147
214
|
@config
|
148
215
|
end
|
149
216
|
|
150
|
-
def
|
217
|
+
def advanced_debugging_enabled?
|
218
|
+
config.enable_advanced_debugging_tools
|
219
|
+
end
|
220
|
+
|
221
|
+
def tool_disabled_message(client_settings)
|
222
|
+
client_settings.handle_cookie(text_result(Rack::MiniProfiler.advanced_tools_message))
|
223
|
+
end
|
151
224
|
|
225
|
+
def call(env)
|
152
226
|
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
153
227
|
client_settings = ClientSettings.new(env, @storage, start)
|
154
|
-
MiniProfiler.deauthorize_request if @config.authorization_mode == :
|
228
|
+
MiniProfiler.deauthorize_request if @config.authorization_mode == :allow_authorized
|
155
229
|
|
156
230
|
status = headers = body = nil
|
157
231
|
query_string = env['QUERY_STRING']
|
@@ -160,15 +234,31 @@ module Rack
|
|
160
234
|
# Someone (e.g. Rails engine) could change the SCRIPT_NAME so we save it
|
161
235
|
env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME'] = ENV['PASSENGER_BASE_URI'] || env['SCRIPT_NAME']
|
162
236
|
|
163
|
-
skip_it =
|
164
|
-
|
165
|
-
|
237
|
+
skip_it = /#{@config.profile_parameter}=skip/.match?(query_string) || (
|
238
|
+
@config.skip_paths &&
|
239
|
+
@config.skip_paths.any? do |p|
|
240
|
+
if p.instance_of?(String)
|
241
|
+
path.start_with?(p)
|
242
|
+
elsif p.instance_of?(Regexp)
|
243
|
+
p.match?(path)
|
244
|
+
end
|
245
|
+
end
|
246
|
+
)
|
247
|
+
if skip_it
|
248
|
+
return client_settings.handle_cookie(@app.call(env))
|
249
|
+
end
|
250
|
+
|
251
|
+
skip_it = (@config.pre_authorize_cb && !@config.pre_authorize_cb.call(env))
|
166
252
|
|
167
253
|
if skip_it || (
|
168
|
-
@config.authorization_mode == :
|
254
|
+
@config.authorization_mode == :allow_authorized &&
|
169
255
|
!client_settings.has_valid_cookie?
|
170
256
|
)
|
171
|
-
|
257
|
+
if take_snapshot?(path)
|
258
|
+
return client_settings.handle_cookie(take_snapshot(env, start))
|
259
|
+
else
|
260
|
+
return client_settings.handle_cookie(@app.call(env))
|
261
|
+
end
|
172
262
|
end
|
173
263
|
|
174
264
|
# handle all /mini-profiler requests here
|
@@ -176,11 +266,11 @@ module Rack
|
|
176
266
|
|
177
267
|
has_disable_cookie = client_settings.disable_profiling?
|
178
268
|
# manual session disable / enable
|
179
|
-
if query_string =~
|
269
|
+
if query_string =~ /#{@config.profile_parameter}=disable/ || has_disable_cookie
|
180
270
|
skip_it = true
|
181
271
|
end
|
182
272
|
|
183
|
-
if query_string =~
|
273
|
+
if query_string =~ /#{@config.profile_parameter}=enable/
|
184
274
|
skip_it = false
|
185
275
|
config.enabled = true
|
186
276
|
end
|
@@ -194,13 +284,26 @@ module Rack
|
|
194
284
|
end
|
195
285
|
|
196
286
|
# profile gc
|
197
|
-
if query_string =~
|
287
|
+
if query_string =~ /#{@config.profile_parameter}=profile-gc/
|
288
|
+
return tool_disabled_message(client_settings) if !advanced_debugging_enabled?
|
198
289
|
current.measure = false if current
|
199
290
|
return client_settings.handle_cookie(Rack::MiniProfiler::GCProfiler.new.profile_gc(@app, env))
|
200
291
|
end
|
201
292
|
|
202
293
|
# profile memory
|
203
|
-
if query_string =~
|
294
|
+
if query_string =~ /#{@config.profile_parameter}=profile-memory/
|
295
|
+
return tool_disabled_message(client_settings) if !advanced_debugging_enabled?
|
296
|
+
|
297
|
+
unless defined?(MemoryProfiler) && MemoryProfiler.respond_to?(:report)
|
298
|
+
message = "Please install the memory_profiler gem and require it: add gem 'memory_profiler' to your Gemfile"
|
299
|
+
status, headers, body = @app.call(env)
|
300
|
+
body.close if body.respond_to? :close
|
301
|
+
|
302
|
+
return client_settings.handle_cookie(
|
303
|
+
text_result(message, status: 500, headers: headers)
|
304
|
+
)
|
305
|
+
end
|
306
|
+
|
204
307
|
query_params = Rack::Utils.parse_nested_query(query_string)
|
205
308
|
options = {
|
206
309
|
ignore_files: query_params['memory_profiler_ignore_files'],
|
@@ -218,12 +321,12 @@ module Rack
|
|
218
321
|
|
219
322
|
MiniProfiler.create_current(env, @config)
|
220
323
|
|
221
|
-
if query_string =~
|
324
|
+
if query_string =~ /#{@config.profile_parameter}=normal-backtrace/
|
222
325
|
client_settings.backtrace_level = ClientSettings::BACKTRACE_DEFAULT
|
223
|
-
elsif query_string =~
|
326
|
+
elsif query_string =~ /#{@config.profile_parameter}=no-backtrace/
|
224
327
|
current.skip_backtrace = true
|
225
328
|
client_settings.backtrace_level = ClientSettings::BACKTRACE_NONE
|
226
|
-
elsif query_string =~
|
329
|
+
elsif query_string =~ /#{@config.profile_parameter}=full-backtrace/ || client_settings.backtrace_full?
|
227
330
|
current.full_backtrace = true
|
228
331
|
client_settings.backtrace_level = ClientSettings::BACKTRACE_FULL
|
229
332
|
elsif client_settings.backtrace_none?
|
@@ -232,7 +335,7 @@ module Rack
|
|
232
335
|
|
233
336
|
flamegraph = nil
|
234
337
|
|
235
|
-
trace_exceptions = query_string =~
|
338
|
+
trace_exceptions = query_string =~ /#{@config.profile_parameter}=trace-exceptions/ && defined? TracePoint
|
236
339
|
status, headers, body, exceptions, trace = nil
|
237
340
|
|
238
341
|
if trace_exceptions
|
@@ -256,27 +359,56 @@ module Rack
|
|
256
359
|
# Prevent response body from being compressed
|
257
360
|
env['HTTP_ACCEPT_ENCODING'] = 'identity' if config.suppress_encoding
|
258
361
|
|
259
|
-
if query_string =~ /pp=flamegraph/
|
260
|
-
|
261
|
-
|
262
|
-
flamegraph = "Please install the flamegraph gem and require it: add gem 'flamegraph' to your Gemfile"
|
263
|
-
status, headers, body = @app.call(env)
|
264
|
-
else
|
362
|
+
if query_string =~ /pp=(async-)?flamegraph/ || env['HTTP_REFERER'] =~ /pp=async-flamegraph/
|
363
|
+
if defined?(StackProf) && StackProf.respond_to?(:run)
|
265
364
|
# do not sully our profile with mini profiler timings
|
266
365
|
current.measure = false
|
267
366
|
match_data = query_string.match(/flamegraph_sample_rate=([\d\.]+)/)
|
268
367
|
|
269
|
-
mode = query_string =~ /mode=c/ ? :c : :ruby
|
270
|
-
|
271
368
|
if match_data && !match_data[1].to_f.zero?
|
272
369
|
sample_rate = match_data[1].to_f
|
273
370
|
else
|
274
371
|
sample_rate = config.flamegraph_sample_rate
|
275
372
|
end
|
276
|
-
|
373
|
+
|
374
|
+
mode_match_data = query_string.match(/flamegraph_mode=([a-zA-Z]+)/)
|
375
|
+
|
376
|
+
if mode_match_data && [:cpu, :wall, :object, :custom].include?(mode_match_data[1].to_sym)
|
377
|
+
mode = mode_match_data[1].to_sym
|
378
|
+
else
|
379
|
+
mode = config.flamegraph_mode
|
380
|
+
end
|
381
|
+
|
382
|
+
flamegraph = StackProf.run(
|
383
|
+
mode: mode,
|
384
|
+
raw: true,
|
385
|
+
aggregate: false,
|
386
|
+
interval: (sample_rate * 1000).to_i
|
387
|
+
) do
|
277
388
|
status, headers, body = @app.call(env)
|
278
389
|
end
|
390
|
+
else
|
391
|
+
message = "Please install the stackprof gem and require it: add gem 'stackprof' to your Gemfile"
|
392
|
+
status, headers, body = @app.call(env)
|
393
|
+
body.close if body.respond_to? :close
|
394
|
+
|
395
|
+
return client_settings.handle_cookie(
|
396
|
+
text_result(message, status: status, headers: headers)
|
397
|
+
)
|
279
398
|
end
|
399
|
+
elsif path == '/rack-mini-profiler/requests'
|
400
|
+
blank_page_html = <<~HTML
|
401
|
+
<!DOCTYPE html>
|
402
|
+
<html>
|
403
|
+
<head>
|
404
|
+
<title>Rack::MiniProfiler Requests</title>
|
405
|
+
</head>
|
406
|
+
<body>
|
407
|
+
</body>
|
408
|
+
</html>
|
409
|
+
HTML
|
410
|
+
|
411
|
+
status, headers, body = [200, { 'Content-Type' => 'text/html' }, [blank_page_html.dup]]
|
280
412
|
else
|
281
413
|
status, headers, body = @app.call(env)
|
282
414
|
end
|
@@ -287,7 +419,7 @@ module Rack
|
|
287
419
|
|
288
420
|
skip_it = current.discard
|
289
421
|
|
290
|
-
if (config.authorization_mode == :
|
422
|
+
if (config.authorization_mode == :allow_authorized && !MiniProfiler.request_authorized?)
|
291
423
|
skip_it = true
|
292
424
|
end
|
293
425
|
|
@@ -307,17 +439,19 @@ module Rack
|
|
307
439
|
return client_settings.handle_cookie(dump_exceptions exceptions)
|
308
440
|
end
|
309
441
|
|
310
|
-
if query_string =~
|
442
|
+
if query_string =~ /#{@config.profile_parameter}=env/
|
443
|
+
return tool_disabled_message(client_settings) if !advanced_debugging_enabled?
|
311
444
|
body.close if body.respond_to? :close
|
312
445
|
return client_settings.handle_cookie(dump_env env)
|
313
446
|
end
|
314
447
|
|
315
|
-
if query_string =~
|
448
|
+
if query_string =~ /#{@config.profile_parameter}=analyze-memory/
|
449
|
+
return tool_disabled_message(client_settings) if !advanced_debugging_enabled?
|
316
450
|
body.close if body.respond_to? :close
|
317
451
|
return client_settings.handle_cookie(analyze_memory)
|
318
452
|
end
|
319
453
|
|
320
|
-
if query_string =~
|
454
|
+
if query_string =~ /#{@config.profile_parameter}=help/
|
321
455
|
body.close if body.respond_to? :close
|
322
456
|
return client_settings.handle_cookie(help(client_settings, env))
|
323
457
|
end
|
@@ -326,9 +460,12 @@ module Rack
|
|
326
460
|
page_struct[:user] = user(env)
|
327
461
|
page_struct[:root].record_time((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000)
|
328
462
|
|
329
|
-
if flamegraph
|
463
|
+
if flamegraph && query_string =~ /#{@config.profile_parameter}=flamegraph/
|
330
464
|
body.close if body.respond_to? :close
|
331
|
-
return client_settings.handle_cookie(self.flamegraph(flamegraph))
|
465
|
+
return client_settings.handle_cookie(self.flamegraph(flamegraph, path, env))
|
466
|
+
elsif flamegraph # async-flamegraph
|
467
|
+
page_struct[:has_flamegraph] = true
|
468
|
+
page_struct[:flamegraph] = flamegraph
|
332
469
|
end
|
333
470
|
|
334
471
|
begin
|
@@ -369,7 +506,7 @@ module Rack
|
|
369
506
|
|
370
507
|
# inject header
|
371
508
|
if headers.is_a? Hash
|
372
|
-
headers['X-MiniProfiler-Ids'] =
|
509
|
+
headers['X-MiniProfiler-Ids'] = ids_comma_separated(env)
|
373
510
|
end
|
374
511
|
|
375
512
|
if current.inject_js && content_type =~ /text\/html/
|
@@ -421,7 +558,7 @@ module Rack
|
|
421
558
|
|
422
559
|
body << "\nBacktraces\n"
|
423
560
|
exceptions.each_with_index do |e, i|
|
424
|
-
body << "##{i + 1}: #{e.class} - \"#{e.message}\"\n #{e.backtrace.join("\n ")}\n\n"
|
561
|
+
body << "##{i + 1}: #{e.class} - \"#{e.message.lines.first.chomp}\"\n #{e.backtrace.join("\n ")}\n\n"
|
425
562
|
end
|
426
563
|
end
|
427
564
|
text_result(body)
|
@@ -521,48 +658,82 @@ module Rack
|
|
521
658
|
text_result(body)
|
522
659
|
end
|
523
660
|
|
524
|
-
def text_result(body)
|
525
|
-
headers = {
|
526
|
-
[
|
661
|
+
def text_result(body, status: 200, headers: nil)
|
662
|
+
headers = (headers || {}).merge('Content-Type' => 'text/plain; charset=utf-8')
|
663
|
+
[status, headers, [body]]
|
527
664
|
end
|
528
665
|
|
529
666
|
def make_link(postfix, env)
|
530
|
-
link = env["PATH_INFO"] + "?" + env["QUERY_STRING"].sub("
|
531
|
-
"
|
667
|
+
link = env["PATH_INFO"] + "?" + env["QUERY_STRING"].sub("#{@config.profile_parameter}=help", "#{@config.profile_parameter}=#{postfix}")
|
668
|
+
"#{@config.profile_parameter}=<a href='#{ERB::Util.html_escape(link)}'>#{postfix}</a>"
|
532
669
|
end
|
533
670
|
|
534
671
|
def help(client_settings, env)
|
535
672
|
headers = { 'Content-Type' => 'text/html' }
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
543
|
-
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
|
557
|
-
|
558
|
-
"
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
673
|
+
html = <<~HTML
|
674
|
+
<!DOCTYPE html>
|
675
|
+
<html>
|
676
|
+
<head>
|
677
|
+
<title>Rack::MiniProfiler Help</title>
|
678
|
+
</head>
|
679
|
+
<body>
|
680
|
+
<pre style='line-height: 30px; font-size: 16px'>
|
681
|
+
This is the help menu of the <a href='#{Rack::MiniProfiler::SOURCE_CODE_URI}'>rack-mini-profiler</a> gem, append the following to your query string for more options:
|
682
|
+
|
683
|
+
#{make_link "help", env} : display this screen
|
684
|
+
#{make_link "env", env} : display the rack environment
|
685
|
+
#{make_link "skip", env} : skip mini profiler for this request
|
686
|
+
#{make_link "no-backtrace", env} #{"(*) " if client_settings.backtrace_none?}: don't collect stack traces from all the SQL executed (sticky, use #{@config.profile_parameter}=normal-backtrace to enable)
|
687
|
+
#{make_link "normal-backtrace", env} #{"(*) " if client_settings.backtrace_default?}: collect stack traces from all the SQL executed and filter normally
|
688
|
+
#{make_link "full-backtrace", env} #{"(*) " if client_settings.backtrace_full?}: enable full backtraces for SQL executed (use #{@config.profile_parameter}=normal-backtrace to disable)
|
689
|
+
#{make_link "disable", env} : disable profiling for this session
|
690
|
+
#{make_link "enable", env} : enable profiling for this session (if previously disabled)
|
691
|
+
#{make_link "profile-gc", env} : perform gc profiling on this request, analyzes ObjectSpace generated by request
|
692
|
+
#{make_link "profile-memory", env} : requires the memory_profiler gem, new location based report
|
693
|
+
#{make_link "flamegraph", env} : a graph representing sampled activity (requires the stackprof gem).
|
694
|
+
#{make_link "async-flamegraph", env} : store flamegraph data for this page and all its AJAX requests. Flamegraph links will be available in the mini-profiler UI (requires the stackprof gem).
|
695
|
+
#{make_link "flamegraph&flamegraph_sample_rate=1", env}: creates a flamegraph with the specified sample rate (in ms). Overrides value set in config
|
696
|
+
#{make_link "flamegraph&flamegraph_mode=cpu", env}: creates a flamegraph with the specified mode (one of cpu, wall, object, or custom). Overrides value set in config
|
697
|
+
#{make_link "flamegraph_embed", env} : a graph representing sampled activity (requires the stackprof gem), embedded resources for use on an intranet.
|
698
|
+
#{make_link "trace-exceptions", env} : will return all the spots where your application raises exceptions
|
699
|
+
#{make_link "analyze-memory", env} : will perform basic memory analysis of heap
|
700
|
+
</pre>
|
701
|
+
</body>
|
702
|
+
</html>
|
703
|
+
HTML
|
704
|
+
|
705
|
+
[200, headers, [html]]
|
706
|
+
end
|
707
|
+
|
708
|
+
def flamegraph(graph, path, env)
|
564
709
|
headers = { 'Content-Type' => 'text/html' }
|
565
|
-
|
710
|
+
iframe_src = "#{public_base_path(env)}speedscope/index.html"
|
711
|
+
html = <<~HTML
|
712
|
+
<!DOCTYPE html>
|
713
|
+
<html>
|
714
|
+
<head>
|
715
|
+
<title>Rack::MiniProfiler Flamegraph</title>
|
716
|
+
<style>
|
717
|
+
body { margin: 0; height: 100vh; }
|
718
|
+
#speedscope-iframe { width: 100%; height: 100%; border: none; }
|
719
|
+
</style>
|
720
|
+
</head>
|
721
|
+
<body>
|
722
|
+
<script type="text/javascript">
|
723
|
+
var graph = #{JSON.generate(graph)};
|
724
|
+
var json = JSON.stringify(graph);
|
725
|
+
var blob = new Blob([json], { type: 'text/plain' });
|
726
|
+
var objUrl = encodeURIComponent(URL.createObjectURL(blob));
|
727
|
+
var iframe = document.createElement('IFRAME');
|
728
|
+
iframe.setAttribute('id', 'speedscope-iframe');
|
729
|
+
document.body.appendChild(iframe);
|
730
|
+
var iframeUrl = '#{iframe_src}#profileURL=' + objUrl + '&title=' + 'Flamegraph for #{CGI.escape(path)}';
|
731
|
+
iframe.setAttribute('src', iframeUrl);
|
732
|
+
</script>
|
733
|
+
</body>
|
734
|
+
</html>
|
735
|
+
HTML
|
736
|
+
[200, headers, [html]]
|
566
737
|
end
|
567
738
|
|
568
739
|
def ids(env)
|
@@ -574,10 +745,6 @@ Append the following to your query string:
|
|
574
745
|
all
|
575
746
|
end
|
576
747
|
|
577
|
-
def ids_json(env)
|
578
|
-
::JSON.generate(ids(env))
|
579
|
-
end
|
580
|
-
|
581
748
|
def ids_comma_separated(env)
|
582
749
|
ids(env).join(",")
|
583
750
|
end
|
@@ -589,11 +756,25 @@ Append the following to your query string:
|
|
589
756
|
# * you have disabled auto append behaviour throught :auto_inject => false flag
|
590
757
|
# * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
|
591
758
|
def get_profile_script(env)
|
592
|
-
path =
|
759
|
+
path = public_base_path(env)
|
760
|
+
version = MiniProfiler::ASSET_VERSION
|
761
|
+
if @config.assets_url
|
762
|
+
url = @config.assets_url.call('rack-mini-profiler.js', version, env)
|
763
|
+
css_url = @config.assets_url.call('rack-mini-profiler.css', version, env)
|
764
|
+
end
|
765
|
+
|
766
|
+
url = "#{path}includes.js?v=#{version}" if !url
|
767
|
+
css_url = "#{path}includes.css?v=#{version}" if !css_url
|
768
|
+
|
769
|
+
content_security_policy_nonce = @config.content_security_policy_nonce ||
|
770
|
+
env["action_dispatch.content_security_policy_nonce"] ||
|
771
|
+
env["secure_headers_content_security_policy_nonce"]
|
593
772
|
|
594
773
|
settings = {
|
595
774
|
path: path,
|
596
|
-
|
775
|
+
url: url,
|
776
|
+
cssUrl: css_url,
|
777
|
+
version: version,
|
597
778
|
verticalPosition: @config.vertical_position,
|
598
779
|
horizontalPosition: @config.horizontal_position,
|
599
780
|
showTrivial: @config.show_trivial,
|
@@ -605,7 +786,10 @@ Append the following to your query string:
|
|
605
786
|
toggleShortcut: @config.toggle_shortcut,
|
606
787
|
startHidden: @config.start_hidden,
|
607
788
|
collapseResults: @config.collapse_results,
|
608
|
-
htmlContainer: @config.html_container
|
789
|
+
htmlContainer: @config.html_container,
|
790
|
+
hiddenCustomFields: @config.snapshot_hidden_custom_fields.join(','),
|
791
|
+
cspNonce: content_security_policy_nonce,
|
792
|
+
hotwireTurboDriveSupport: @config.enable_hotwire_turbo_drive_support,
|
609
793
|
}
|
610
794
|
|
611
795
|
if current && current.page_struct
|
@@ -617,7 +801,7 @@ Append the following to your query string:
|
|
617
801
|
end
|
618
802
|
|
619
803
|
# TODO : cache this snippet
|
620
|
-
script =
|
804
|
+
script = ::File.read(::File.expand_path('html/profile_handler.js', ::File.dirname(__FILE__)))
|
621
805
|
# replace the variables
|
622
806
|
settings.each do |k, v|
|
623
807
|
regex = Regexp.new("\\{#{k.to_s}\\}")
|
@@ -633,5 +817,138 @@ Append the following to your query string:
|
|
633
817
|
current.inject_js = false
|
634
818
|
end
|
635
819
|
|
820
|
+
def cache_control_value
|
821
|
+
86400
|
822
|
+
end
|
823
|
+
|
824
|
+
private
|
825
|
+
|
826
|
+
def handle_snapshots_request(env)
|
827
|
+
self.current = nil
|
828
|
+
MiniProfiler.authorize_request
|
829
|
+
status = 200
|
830
|
+
headers = { 'Content-Type' => 'text/html' }
|
831
|
+
qp = Rack::Utils.parse_nested_query(env['QUERY_STRING'])
|
832
|
+
if group_name = qp["group_name"]
|
833
|
+
list = @storage.snapshots_group(group_name)
|
834
|
+
list.each do |snapshot|
|
835
|
+
snapshot[:url] = url_for_snapshot(snapshot[:id], group_name)
|
836
|
+
end
|
837
|
+
data = {
|
838
|
+
group_name: group_name,
|
839
|
+
list: list
|
840
|
+
}
|
841
|
+
else
|
842
|
+
list = @storage.snapshots_overview
|
843
|
+
list.each do |group|
|
844
|
+
group[:url] = url_for_snapshots_group(group[:name])
|
845
|
+
end
|
846
|
+
data = {
|
847
|
+
page: "overview",
|
848
|
+
list: list
|
849
|
+
}
|
850
|
+
end
|
851
|
+
data_html = <<~HTML
|
852
|
+
<div style="display: none;" id="snapshots-data">
|
853
|
+
#{data.to_json}
|
854
|
+
</div>
|
855
|
+
HTML
|
856
|
+
response = Rack::Response.new([], status, headers)
|
857
|
+
|
858
|
+
response.write <<~HTML
|
859
|
+
<!DOCTYPE html>
|
860
|
+
<html>
|
861
|
+
<head>
|
862
|
+
<title>Rack::MiniProfiler Snapshots</title>
|
863
|
+
</head>
|
864
|
+
<body class="mp-snapshots">
|
865
|
+
HTML
|
866
|
+
response.write(data_html)
|
867
|
+
script = self.get_profile_script(env)
|
868
|
+
response.write(script)
|
869
|
+
response.write <<~HTML
|
870
|
+
</body>
|
871
|
+
</html>
|
872
|
+
HTML
|
873
|
+
response.finish
|
874
|
+
end
|
875
|
+
|
876
|
+
def serve_flamegraph(env)
|
877
|
+
request = Rack::Request.new(env)
|
878
|
+
id = request.params['id']
|
879
|
+
page_struct = @storage.load(id)
|
880
|
+
|
881
|
+
if !page_struct
|
882
|
+
id = ERB::Util.html_escape(id)
|
883
|
+
user_info = ERB::Util.html_escape(user(env))
|
884
|
+
return [404, {}, ["Request not found: #{id} - user #{user_info}"]]
|
885
|
+
end
|
886
|
+
|
887
|
+
if !page_struct[:flamegraph]
|
888
|
+
return [404, {}, ["No flamegraph available for #{ERB::Util.html_escape(id)}"]]
|
889
|
+
end
|
890
|
+
|
891
|
+
self.flamegraph(page_struct[:flamegraph], page_struct[:request_path], env)
|
892
|
+
end
|
893
|
+
|
894
|
+
def rails_route_from_path(path, method)
|
895
|
+
if defined?(Rails) && defined?(ActionController::RoutingError)
|
896
|
+
hash = Rails.application.routes.recognize_path(path, method: method)
|
897
|
+
if hash && hash[:controller] && hash[:action]
|
898
|
+
"#{hash[:controller]}##{hash[:action]}"
|
899
|
+
end
|
900
|
+
end
|
901
|
+
rescue ActionController::RoutingError
|
902
|
+
nil
|
903
|
+
end
|
904
|
+
|
905
|
+
def url_for_snapshots_group(group_name)
|
906
|
+
qs = Rack::Utils.build_query({ group_name: group_name })
|
907
|
+
"/#{@config.base_url_path.gsub('/', '')}/snapshots?#{qs}"
|
908
|
+
end
|
909
|
+
|
910
|
+
def url_for_snapshot(id, group_name)
|
911
|
+
qs = Rack::Utils.build_query({ id: id, group: group_name })
|
912
|
+
"/#{@config.base_url_path.gsub('/', '')}/results?#{qs}"
|
913
|
+
end
|
914
|
+
|
915
|
+
def take_snapshot?(path)
|
916
|
+
@config.snapshot_every_n_requests > 0 &&
|
917
|
+
!path.start_with?(@config.base_url_path) &&
|
918
|
+
@storage.should_take_snapshot?(@config.snapshot_every_n_requests)
|
919
|
+
end
|
920
|
+
|
921
|
+
def take_snapshot(env, start)
|
922
|
+
MiniProfiler.create_current(env, @config)
|
923
|
+
Thread.current[:mp_ongoing_snapshot] = true
|
924
|
+
results = @app.call(env)
|
925
|
+
status = results[0].to_i
|
926
|
+
if status >= 200 && status < 300
|
927
|
+
page_struct = current.page_struct
|
928
|
+
page_struct[:root].record_time(
|
929
|
+
(Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000
|
930
|
+
)
|
931
|
+
custom_fields = MiniProfiler.get_snapshot_custom_fields
|
932
|
+
page_struct[:custom_fields] = custom_fields if custom_fields
|
933
|
+
if Rack::MiniProfiler.snapshots_transporter?
|
934
|
+
Rack::MiniProfiler::SnapshotsTransporter.transport(page_struct)
|
935
|
+
else
|
936
|
+
group_name = rails_route_from_path(page_struct[:request_path], page_struct[:request_method])
|
937
|
+
group_name ||= page_struct[:request_path]
|
938
|
+
group_name = "#{page_struct[:request_method]} #{group_name}"
|
939
|
+
@storage.push_snapshot(
|
940
|
+
page_struct,
|
941
|
+
group_name,
|
942
|
+
@config
|
943
|
+
)
|
944
|
+
end
|
945
|
+
end
|
946
|
+
self.current = nil
|
947
|
+
results
|
948
|
+
end
|
949
|
+
|
950
|
+
def public_base_path(env)
|
951
|
+
"#{env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME']}#{@config.base_url_path}"
|
952
|
+
end
|
636
953
|
end
|
637
954
|
end
|