rack-mini-profiler 1.0.1 → 2.3.2
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 +115 -20
- data/README.md +126 -45
- data/lib/enable_rails_patches.rb +5 -0
- data/lib/html/dot.1.1.2.min.js +2 -0
- data/lib/html/includes.css +136 -35
- data/lib/html/includes.js +1400 -1009
- data/lib/html/includes.scss +546 -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 +13 -5
- data/lib/mini_profiler/config.rb +43 -5
- data/lib/mini_profiler/gc_profiler.rb +1 -1
- data/lib/mini_profiler/profiler.rb +310 -42
- 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 +79 -1
- data/lib/mini_profiler/storage/file_store.rb +3 -3
- data/lib/mini_profiler/storage/memcache_store.rb +2 -0
- data/lib/mini_profiler/storage/memory_store.rb +54 -5
- data/lib/mini_profiler/storage/redis_store.rb +136 -2
- 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 +4 -2
- data/lib/mini_profiler/version.rb +1 -1
- data/lib/mini_profiler_rails/railtie.rb +88 -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.rb +4 -27
- data/lib/patches/db/mysql2/alias_method.rb +30 -0
- data/lib/patches/db/mysql2/prepend.rb +34 -0
- data/lib/patches/db/plucky.rb +4 -4
- data/lib/patches/net_patches.rb +18 -8
- 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 -1
- data/rack-mini-profiler.gemspec +15 -6
- metadata +150 -31
- 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
@@ -42,7 +42,7 @@ module Rack
|
|
42
42
|
def handle_cookie(result)
|
43
43
|
status, headers, _body = result
|
44
44
|
|
45
|
-
if (MiniProfiler.config.authorization_mode == :
|
45
|
+
if (MiniProfiler.config.authorization_mode == :allow_authorized && !MiniProfiler.request_authorized?)
|
46
46
|
# this is non-obvious, don't kill the profiling cookie on errors or short requests
|
47
47
|
# this ensures that stuff that never reaches the rails stack does not kill profiling
|
48
48
|
if status.to_i >= 200 && status.to_i < 300 && ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - @start) > 0.1)
|
@@ -59,7 +59,7 @@ module Rack
|
|
59
59
|
|
60
60
|
tokens_changed = false
|
61
61
|
|
62
|
-
if MiniProfiler.request_authorized? && MiniProfiler.config.authorization_mode == :
|
62
|
+
if MiniProfiler.request_authorized? && MiniProfiler.config.authorization_mode == :allow_authorized
|
63
63
|
@allowed_tokens ||= @store.allowed_tokens
|
64
64
|
tokens_changed = !@orig_auth_tokens || ((@allowed_tokens - @orig_auth_tokens).length > 0)
|
65
65
|
end
|
@@ -76,6 +76,7 @@ module Rack
|
|
76
76
|
settings_string = settings.map { |k, v| "#{k}=#{v}" }.join(",")
|
77
77
|
cookie = { value: settings_string, path: '/', httponly: true }
|
78
78
|
cookie[:secure] = true if @request.ssl?
|
79
|
+
cookie[:same_site] = 'Lax'
|
79
80
|
Rack::Utils.set_cookie_header!(headers, COOKIE_NAME, cookie)
|
80
81
|
end
|
81
82
|
end
|
@@ -89,10 +90,17 @@ module Rack
|
|
89
90
|
def has_valid_cookie?
|
90
91
|
valid_cookie = !@cookie.nil?
|
91
92
|
|
92
|
-
if (MiniProfiler.config.authorization_mode == :
|
93
|
-
|
93
|
+
if (MiniProfiler.config.authorization_mode == :allow_authorized) && valid_cookie
|
94
|
+
begin
|
95
|
+
@allowed_tokens ||= @store.allowed_tokens
|
96
|
+
rescue => e
|
97
|
+
if MiniProfiler.config.storage_failure != nil
|
98
|
+
MiniProfiler.config.storage_failure.call(e)
|
99
|
+
end
|
100
|
+
end
|
94
101
|
|
95
|
-
valid_cookie =
|
102
|
+
valid_cookie = @allowed_tokens &&
|
103
|
+
(Array === @orig_auth_tokens) &&
|
96
104
|
((@allowed_tokens & @orig_auth_tokens).length > 0)
|
97
105
|
end
|
98
106
|
|
data/lib/mini_profiler/config.rb
CHANGED
@@ -34,9 +34,11 @@ module Rack
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
@enabled = true
|
37
|
-
@disable_env_dump = false
|
38
37
|
@max_sql_param_length = 0 # disable sql parameter collection by default
|
39
38
|
@skip_sql_param_names = /password/ # skips parameters with the name password by default
|
39
|
+
@enable_advanced_debugging_tools = false
|
40
|
+
@snapshot_every_n_requests = -1
|
41
|
+
@snapshots_limit = 1000
|
40
42
|
|
41
43
|
# ui parameters
|
42
44
|
@autorized = true
|
@@ -47,9 +49,14 @@ module Rack
|
|
47
49
|
@show_trivial = false
|
48
50
|
@show_total_sql_count = false
|
49
51
|
@start_hidden = false
|
50
|
-
@toggle_shortcut = '
|
52
|
+
@toggle_shortcut = 'alt+p'
|
51
53
|
@html_container = 'body'
|
52
54
|
@position = "top-left"
|
55
|
+
@snapshot_hidden_custom_fields = []
|
56
|
+
@snapshots_transport_destination_url = nil
|
57
|
+
@snapshots_transport_auth_key = nil
|
58
|
+
@snapshots_redact_sql_queries = true
|
59
|
+
@snapshots_transport_gzip_requests = false
|
53
60
|
|
54
61
|
self
|
55
62
|
}
|
@@ -57,20 +64,51 @@ module Rack
|
|
57
64
|
|
58
65
|
attr_accessor :authorization_mode, :auto_inject, :backtrace_ignores,
|
59
66
|
:backtrace_includes, :backtrace_remove, :backtrace_threshold_ms,
|
60
|
-
:base_url_path, :disable_caching, :
|
67
|
+
:base_url_path, :disable_caching, :enabled,
|
61
68
|
:flamegraph_sample_rate, :logger, :pre_authorize_cb, :skip_paths,
|
62
69
|
:skip_schema_queries, :storage, :storage_failure, :storage_instance,
|
63
|
-
:storage_options, :user_provider
|
64
|
-
|
70
|
+
:storage_options, :user_provider, :enable_advanced_debugging_tools,
|
71
|
+
:skip_sql_param_names, :suppress_encoding, :max_sql_param_length
|
65
72
|
|
66
73
|
# ui accessors
|
67
74
|
attr_accessor :collapse_results, :max_traces_to_show, :position,
|
68
75
|
:show_children, :show_controls, :show_trivial, :show_total_sql_count,
|
69
76
|
:start_hidden, :toggle_shortcut, :html_container
|
70
77
|
|
78
|
+
# snapshot related config
|
79
|
+
attr_accessor :snapshot_every_n_requests, :snapshots_limit,
|
80
|
+
:snapshot_hidden_custom_fields, :snapshots_transport_destination_url,
|
81
|
+
:snapshots_transport_auth_key, :snapshots_redact_sql_queries,
|
82
|
+
:snapshots_transport_gzip_requests
|
83
|
+
|
71
84
|
# Deprecated options
|
72
85
|
attr_accessor :use_existing_jquery
|
73
86
|
|
87
|
+
attr_reader :assets_url
|
88
|
+
|
89
|
+
# redefined - since the accessor defines it first
|
90
|
+
undef :authorization_mode=
|
91
|
+
def authorization_mode=(mode)
|
92
|
+
if mode == :whitelist
|
93
|
+
warn "[DEPRECATION] `:whitelist` authorization mode is deprecated. Please use `:allow_authorized` instead."
|
94
|
+
|
95
|
+
mode = :allow_authorized
|
96
|
+
end
|
97
|
+
|
98
|
+
warn <<~DEP unless mode == :allow_authorized || mode == :allow_all
|
99
|
+
[DEPRECATION] unknown authorization mode #{mode}. Expected `:allow_all` or `:allow_authorized`.
|
100
|
+
DEP
|
101
|
+
|
102
|
+
@authorization_mode = mode
|
103
|
+
end
|
104
|
+
|
105
|
+
def assets_url=(lmbda)
|
106
|
+
if defined?(Rack::MiniProfilerRails)
|
107
|
+
Rack::MiniProfilerRails.create_engine
|
108
|
+
end
|
109
|
+
@assets_url = lmbda
|
110
|
+
end
|
111
|
+
|
74
112
|
def vertical_position
|
75
113
|
position.include?('bottom') ? 'bottom' : 'top'
|
76
114
|
end
|
@@ -1,10 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'cgi'
|
4
|
+
|
3
5
|
module Rack
|
4
6
|
class MiniProfiler
|
5
7
|
class << self
|
6
8
|
|
7
9
|
include Rack::MiniProfiler::ProfilingMethods
|
10
|
+
attr_accessor :subscribe_sql_active_record
|
11
|
+
|
12
|
+
def patch_rails?
|
13
|
+
!!defined?(Rack::MINI_PROFILER_ENABLE_RAILS_PATCHES)
|
14
|
+
end
|
8
15
|
|
9
16
|
def generate_id
|
10
17
|
rand(36**20).to_s(36)
|
@@ -33,9 +40,21 @@ module Rack
|
|
33
40
|
|
34
41
|
def current=(c)
|
35
42
|
# we use TLS cause we need access to this from sql blocks and code blocks that have no access to env
|
43
|
+
Thread.current[:mini_profiler_snapshot_custom_fields] = nil
|
44
|
+
Thread.current[:mp_ongoing_snapshot] = nil
|
36
45
|
Thread.current[:mini_profiler_private] = c
|
37
46
|
end
|
38
47
|
|
48
|
+
def add_snapshot_custom_field(key, value)
|
49
|
+
thread_var_key = :mini_profiler_snapshot_custom_fields
|
50
|
+
Thread.current[thread_var_key] ||= {}
|
51
|
+
Thread.current[thread_var_key][key] = value
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_snapshot_custom_fields
|
55
|
+
Thread.current[:mini_profiler_snapshot_custom_fields]
|
56
|
+
end
|
57
|
+
|
39
58
|
# discard existing results, don't track this request
|
40
59
|
def discard_results
|
41
60
|
self.current.discard = true if current
|
@@ -62,6 +81,32 @@ module Rack
|
|
62
81
|
Thread.current[:mp_authorized]
|
63
82
|
end
|
64
83
|
|
84
|
+
def advanced_tools_message
|
85
|
+
<<~TEXT
|
86
|
+
This feature is disabled by default, to enable set the enable_advanced_debugging_tools option to true in Mini Profiler config.
|
87
|
+
TEXT
|
88
|
+
end
|
89
|
+
|
90
|
+
def binds_to_params(binds)
|
91
|
+
return if binds.nil? || config.max_sql_param_length == 0
|
92
|
+
# map ActiveRecord::Relation::QueryAttribute to [name, value]
|
93
|
+
params = binds.map { |c| c.kind_of?(Array) ? [c.first, c.last] : [c.name, c.value] }
|
94
|
+
if (skip = config.skip_sql_param_names)
|
95
|
+
params.map { |(n, v)| n =~ skip ? [n, nil] : [n, v] }
|
96
|
+
else
|
97
|
+
params
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def snapshots_transporter?
|
102
|
+
!!config.snapshots_transport_destination_url &&
|
103
|
+
!!config.snapshots_transport_auth_key
|
104
|
+
end
|
105
|
+
|
106
|
+
def redact_sql_queries?
|
107
|
+
Thread.current[:mp_ongoing_snapshot] == true &&
|
108
|
+
Rack::MiniProfiler.config.snapshots_redact_sql_queries
|
109
|
+
end
|
65
110
|
end
|
66
111
|
|
67
112
|
#
|
@@ -71,7 +116,7 @@ module Rack
|
|
71
116
|
MiniProfiler.config.merge!(config)
|
72
117
|
@config = MiniProfiler.config
|
73
118
|
@app = app
|
74
|
-
@config.base_url_path
|
119
|
+
@config.base_url_path += "/" unless @config.base_url_path.end_with? "/"
|
75
120
|
unless @config.storage_instance
|
76
121
|
@config.storage_instance = @config.storage.new(@config.storage_options)
|
77
122
|
end
|
@@ -84,15 +129,24 @@ module Rack
|
|
84
129
|
|
85
130
|
def serve_results(env)
|
86
131
|
request = Rack::Request.new(env)
|
87
|
-
id = request[
|
88
|
-
|
89
|
-
|
132
|
+
id = request.params['id']
|
133
|
+
is_snapshot = request.params['snapshot']
|
134
|
+
is_snapshot = [true, "true"].include?(is_snapshot)
|
135
|
+
if is_snapshot
|
136
|
+
page_struct = @storage.load_snapshot(id)
|
137
|
+
else
|
138
|
+
page_struct = @storage.load(id)
|
139
|
+
end
|
140
|
+
if !page_struct && is_snapshot
|
141
|
+
id = ERB::Util.html_escape(id)
|
142
|
+
return [404, {}, ["Snapshot with id '#{id}' not found"]]
|
143
|
+
elsif !page_struct
|
90
144
|
@storage.set_viewed(user(env), id)
|
91
|
-
id = ERB::Util.html_escape(
|
145
|
+
id = ERB::Util.html_escape(id)
|
92
146
|
user_info = ERB::Util.html_escape(user(env))
|
93
147
|
return [404, {}, ["Request not found: #{id} - user #{user_info}"]]
|
94
148
|
end
|
95
|
-
|
149
|
+
if !page_struct[:has_user_viewed] && !is_snapshot
|
96
150
|
page_struct[:client_timings] = TimerStruct::Client.init_from_form_data(env, page_struct)
|
97
151
|
page_struct[:has_user_viewed] = true
|
98
152
|
@storage.save(page_struct)
|
@@ -127,11 +181,13 @@ module Rack
|
|
127
181
|
file_name = path.sub(@config.base_url_path, '')
|
128
182
|
|
129
183
|
return serve_results(env) if file_name.eql?('results')
|
184
|
+
return handle_snapshots_request(env) if file_name.eql?('snapshots')
|
185
|
+
return serve_flamegraph(env) if file_name.eql?('flamegraph')
|
130
186
|
|
131
187
|
resources_env = env.dup
|
132
188
|
resources_env['PATH_INFO'] = file_name
|
133
189
|
|
134
|
-
rack_file = Rack::File.new(MiniProfiler.resources_root, 'Cache-Control' =>
|
190
|
+
rack_file = Rack::File.new(MiniProfiler.resources_root, 'Cache-Control' => "max-age=#{cache_control_value}")
|
135
191
|
rack_file.call(resources_env)
|
136
192
|
end
|
137
193
|
|
@@ -147,11 +203,18 @@ module Rack
|
|
147
203
|
@config
|
148
204
|
end
|
149
205
|
|
150
|
-
def
|
206
|
+
def advanced_debugging_enabled?
|
207
|
+
config.enable_advanced_debugging_tools
|
208
|
+
end
|
209
|
+
|
210
|
+
def tool_disabled_message(client_settings)
|
211
|
+
client_settings.handle_cookie(text_result(Rack::MiniProfiler.advanced_tools_message))
|
212
|
+
end
|
151
213
|
|
214
|
+
def call(env)
|
152
215
|
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
153
216
|
client_settings = ClientSettings.new(env, @storage, start)
|
154
|
-
MiniProfiler.deauthorize_request if @config.authorization_mode == :
|
217
|
+
MiniProfiler.deauthorize_request if @config.authorization_mode == :allow_authorized
|
155
218
|
|
156
219
|
status = headers = body = nil
|
157
220
|
query_string = env['QUERY_STRING']
|
@@ -160,15 +223,31 @@ module Rack
|
|
160
223
|
# Someone (e.g. Rails engine) could change the SCRIPT_NAME so we save it
|
161
224
|
env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME'] = ENV['PASSENGER_BASE_URI'] || env['SCRIPT_NAME']
|
162
225
|
|
163
|
-
skip_it = (
|
164
|
-
|
165
|
-
|
226
|
+
skip_it = /pp=skip/.match?(query_string) || (
|
227
|
+
@config.skip_paths &&
|
228
|
+
@config.skip_paths.any? do |p|
|
229
|
+
if p.instance_of?(String)
|
230
|
+
path.start_with?(p)
|
231
|
+
elsif p.instance_of?(Regexp)
|
232
|
+
p.match?(path)
|
233
|
+
end
|
234
|
+
end
|
235
|
+
)
|
236
|
+
if skip_it
|
237
|
+
return client_settings.handle_cookie(@app.call(env))
|
238
|
+
end
|
239
|
+
|
240
|
+
skip_it = (@config.pre_authorize_cb && !@config.pre_authorize_cb.call(env))
|
166
241
|
|
167
242
|
if skip_it || (
|
168
|
-
@config.authorization_mode == :
|
243
|
+
@config.authorization_mode == :allow_authorized &&
|
169
244
|
!client_settings.has_valid_cookie?
|
170
245
|
)
|
171
|
-
|
246
|
+
if take_snapshot?(path)
|
247
|
+
return client_settings.handle_cookie(take_snapshot(env, start))
|
248
|
+
else
|
249
|
+
return client_settings.handle_cookie(@app.call(env))
|
250
|
+
end
|
172
251
|
end
|
173
252
|
|
174
253
|
# handle all /mini-profiler requests here
|
@@ -195,12 +274,23 @@ module Rack
|
|
195
274
|
|
196
275
|
# profile gc
|
197
276
|
if query_string =~ /pp=profile-gc/
|
277
|
+
return tool_disabled_message(client_settings) if !advanced_debugging_enabled?
|
198
278
|
current.measure = false if current
|
199
279
|
return client_settings.handle_cookie(Rack::MiniProfiler::GCProfiler.new.profile_gc(@app, env))
|
200
280
|
end
|
201
281
|
|
202
282
|
# profile memory
|
203
283
|
if query_string =~ /pp=profile-memory/
|
284
|
+
return tool_disabled_message(client_settings) if !advanced_debugging_enabled?
|
285
|
+
|
286
|
+
unless defined?(MemoryProfiler) && MemoryProfiler.respond_to?(:report)
|
287
|
+
message = "Please install the memory_profiler gem and require it: add gem 'memory_profiler' to your Gemfile"
|
288
|
+
_, _, body = @app.call(env)
|
289
|
+
body.close if body.respond_to? :close
|
290
|
+
|
291
|
+
return client_settings.handle_cookie(text_result(message))
|
292
|
+
end
|
293
|
+
|
204
294
|
query_params = Rack::Utils.parse_nested_query(query_string)
|
205
295
|
options = {
|
206
296
|
ignore_files: query_params['memory_profiler_ignore_files'],
|
@@ -256,27 +346,40 @@ module Rack
|
|
256
346
|
# Prevent response body from being compressed
|
257
347
|
env['HTTP_ACCEPT_ENCODING'] = 'identity' if config.suppress_encoding
|
258
348
|
|
259
|
-
if query_string =~ /pp=flamegraph/
|
260
|
-
unless defined?(
|
261
|
-
|
262
|
-
|
263
|
-
|
349
|
+
if query_string =~ /pp=(async-)?flamegraph/ || env['HTTP_REFERER'] =~ /pp=async-flamegraph/
|
350
|
+
unless defined?(StackProf) && StackProf.respond_to?(:run)
|
351
|
+
headers = { 'Content-Type' => 'text/html' }
|
352
|
+
message = "Please install the stackprof gem and require it: add gem 'stackprof' to your Gemfile"
|
353
|
+
body.close if body.respond_to? :close
|
354
|
+
return client_settings.handle_cookie([500, headers, message])
|
264
355
|
else
|
265
356
|
# do not sully our profile with mini profiler timings
|
266
357
|
current.measure = false
|
267
358
|
match_data = query_string.match(/flamegraph_sample_rate=([\d\.]+)/)
|
268
359
|
|
269
|
-
mode = query_string =~ /mode=c/ ? :c : :ruby
|
270
|
-
|
271
360
|
if match_data && !match_data[1].to_f.zero?
|
272
361
|
sample_rate = match_data[1].to_f
|
273
362
|
else
|
274
363
|
sample_rate = config.flamegraph_sample_rate
|
275
364
|
end
|
276
|
-
flamegraph =
|
365
|
+
flamegraph = StackProf.run(
|
366
|
+
mode: :wall,
|
367
|
+
raw: true,
|
368
|
+
aggregate: false,
|
369
|
+
interval: (sample_rate * 1000).to_i
|
370
|
+
) do
|
277
371
|
status, headers, body = @app.call(env)
|
278
372
|
end
|
279
373
|
end
|
374
|
+
elsif path == '/rack-mini-profiler/requests'
|
375
|
+
blank_page_html = <<~HTML
|
376
|
+
<html>
|
377
|
+
<head></head>
|
378
|
+
<body></body>
|
379
|
+
</html>
|
380
|
+
HTML
|
381
|
+
|
382
|
+
status, headers, body = [200, { 'Content-Type' => 'text/html' }, [blank_page_html.dup]]
|
280
383
|
else
|
281
384
|
status, headers, body = @app.call(env)
|
282
385
|
end
|
@@ -287,7 +390,7 @@ module Rack
|
|
287
390
|
|
288
391
|
skip_it = current.discard
|
289
392
|
|
290
|
-
if (config.authorization_mode == :
|
393
|
+
if (config.authorization_mode == :allow_authorized && !MiniProfiler.request_authorized?)
|
291
394
|
skip_it = true
|
292
395
|
end
|
293
396
|
|
@@ -307,12 +410,14 @@ module Rack
|
|
307
410
|
return client_settings.handle_cookie(dump_exceptions exceptions)
|
308
411
|
end
|
309
412
|
|
310
|
-
if query_string =~ /pp=env/
|
413
|
+
if query_string =~ /pp=env/
|
414
|
+
return tool_disabled_message(client_settings) if !advanced_debugging_enabled?
|
311
415
|
body.close if body.respond_to? :close
|
312
416
|
return client_settings.handle_cookie(dump_env env)
|
313
417
|
end
|
314
418
|
|
315
419
|
if query_string =~ /pp=analyze-memory/
|
420
|
+
return tool_disabled_message(client_settings) if !advanced_debugging_enabled?
|
316
421
|
body.close if body.respond_to? :close
|
317
422
|
return client_settings.handle_cookie(analyze_memory)
|
318
423
|
end
|
@@ -326,9 +431,12 @@ module Rack
|
|
326
431
|
page_struct[:user] = user(env)
|
327
432
|
page_struct[:root].record_time((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000)
|
328
433
|
|
329
|
-
if flamegraph
|
434
|
+
if flamegraph && query_string =~ /pp=flamegraph/
|
330
435
|
body.close if body.respond_to? :close
|
331
|
-
return client_settings.handle_cookie(self.flamegraph(flamegraph))
|
436
|
+
return client_settings.handle_cookie(self.flamegraph(flamegraph, path))
|
437
|
+
elsif flamegraph # async-flamegraph
|
438
|
+
page_struct[:has_flamegraph] = true
|
439
|
+
page_struct[:flamegraph] = flamegraph
|
332
440
|
end
|
333
441
|
|
334
442
|
begin
|
@@ -369,7 +477,7 @@ module Rack
|
|
369
477
|
|
370
478
|
# inject header
|
371
479
|
if headers.is_a? Hash
|
372
|
-
headers['X-MiniProfiler-Ids'] =
|
480
|
+
headers['X-MiniProfiler-Ids'] = ids_comma_separated(env)
|
373
481
|
end
|
374
482
|
|
375
483
|
if current.inject_js && content_type =~ /text\/html/
|
@@ -397,7 +505,13 @@ module Rack
|
|
397
505
|
if script.respond_to?(:encoding) && script.respond_to?(:force_encoding)
|
398
506
|
script = script.force_encoding(fragment.encoding)
|
399
507
|
end
|
400
|
-
|
508
|
+
|
509
|
+
safe_script = script
|
510
|
+
if script.respond_to?(:html_safe)
|
511
|
+
safe_script = script.html_safe
|
512
|
+
end
|
513
|
+
|
514
|
+
fragment.insert(index, safe_script)
|
401
515
|
else
|
402
516
|
fragment
|
403
517
|
end
|
@@ -522,7 +636,7 @@ module Rack
|
|
522
636
|
|
523
637
|
def make_link(postfix, env)
|
524
638
|
link = env["PATH_INFO"] + "?" + env["QUERY_STRING"].sub("pp=help", "pp=#{postfix}")
|
525
|
-
"pp=<a href='#{link}'>#{postfix}</a>"
|
639
|
+
"pp=<a href='#{ERB::Util.html_escape(link)}'>#{postfix}</a>"
|
526
640
|
end
|
527
641
|
|
528
642
|
def help(client_settings, env)
|
@@ -539,13 +653,14 @@ Append the following to your query string:
|
|
539
653
|
#{make_link "full-backtrace", env} #{"(*) " if client_settings.backtrace_full?}: enable full backtraces for SQL executed (use pp=normal-backtrace to disable)
|
540
654
|
#{make_link "disable", env} : disable profiling for this session
|
541
655
|
#{make_link "enable", env} : enable profiling for this session (if previously disabled)
|
542
|
-
#{make_link "profile-gc", env} : perform gc profiling on this request, analyzes ObjectSpace generated by request
|
656
|
+
#{make_link "profile-gc", env} : perform gc profiling on this request, analyzes ObjectSpace generated by request
|
543
657
|
#{make_link "profile-memory", env} : requires the memory_profiler gem, new location based report
|
544
|
-
#{make_link "flamegraph", env} :
|
658
|
+
#{make_link "flamegraph", env} : a graph representing sampled activity (requires the stackprof gem).
|
659
|
+
#{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).
|
545
660
|
#{make_link "flamegraph&flamegraph_sample_rate=1", env}: creates a flamegraph with the specified sample rate (in ms). Overrides value set in config
|
546
|
-
#{make_link "flamegraph_embed", env} :
|
547
|
-
#{make_link "trace-exceptions", env} :
|
548
|
-
#{make_link "analyze-memory", env} :
|
661
|
+
#{make_link "flamegraph_embed", env} : a graph representing sampled activity (requires the stackprof gem), embedded resources for use on an intranet.
|
662
|
+
#{make_link "trace-exceptions", env} : will return all the spots where your application raises exceptions
|
663
|
+
#{make_link "analyze-memory", env} : will perform basic memory analysis of heap
|
549
664
|
</pre>
|
550
665
|
</body>
|
551
666
|
</html>
|
@@ -554,9 +669,33 @@ Append the following to your query string:
|
|
554
669
|
[200, headers, [body]]
|
555
670
|
end
|
556
671
|
|
557
|
-
def flamegraph(graph)
|
672
|
+
def flamegraph(graph, path)
|
558
673
|
headers = { 'Content-Type' => 'text/html' }
|
559
|
-
|
674
|
+
html = <<~HTML
|
675
|
+
<!DOCTYPE html>
|
676
|
+
<html>
|
677
|
+
<head>
|
678
|
+
<style>
|
679
|
+
body { margin: 0; height: 100vh; }
|
680
|
+
#speedscope-iframe { width: 100%; height: 100%; border: none; }
|
681
|
+
</style>
|
682
|
+
</head>
|
683
|
+
<body>
|
684
|
+
<script type="text/javascript">
|
685
|
+
var graph = #{JSON.generate(graph)};
|
686
|
+
var json = JSON.stringify(graph);
|
687
|
+
var blob = new Blob([json], { type: 'text/plain' });
|
688
|
+
var objUrl = encodeURIComponent(URL.createObjectURL(blob));
|
689
|
+
var iframe = document.createElement('IFRAME');
|
690
|
+
iframe.setAttribute('id', 'speedscope-iframe');
|
691
|
+
document.body.appendChild(iframe);
|
692
|
+
var iframeUrl = '#{@config.base_url_path}speedscope/index.html#profileURL=' + objUrl + '&title=' + 'Flamegraph for #{CGI.escape(path)}';
|
693
|
+
iframe.setAttribute('src', iframeUrl);
|
694
|
+
</script>
|
695
|
+
</body>
|
696
|
+
</html>
|
697
|
+
HTML
|
698
|
+
[200, headers, [html]]
|
560
699
|
end
|
561
700
|
|
562
701
|
def ids(env)
|
@@ -568,10 +707,6 @@ Append the following to your query string:
|
|
568
707
|
all
|
569
708
|
end
|
570
709
|
|
571
|
-
def ids_json(env)
|
572
|
-
::JSON.generate(ids(env))
|
573
|
-
end
|
574
|
-
|
575
710
|
def ids_comma_separated(env)
|
576
711
|
ids(env).join(",")
|
577
712
|
end
|
@@ -584,10 +719,20 @@ Append the following to your query string:
|
|
584
719
|
# * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
|
585
720
|
def get_profile_script(env)
|
586
721
|
path = "#{env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME']}#{@config.base_url_path}"
|
722
|
+
version = MiniProfiler::ASSET_VERSION
|
723
|
+
if @config.assets_url
|
724
|
+
url = @config.assets_url.call('rack-mini-profiler.js', version, env)
|
725
|
+
css_url = @config.assets_url.call('rack-mini-profiler.css', version, env)
|
726
|
+
end
|
727
|
+
|
728
|
+
url = "#{path}includes.js?v=#{version}" if !url
|
729
|
+
css_url = "#{path}includes.css?v=#{version}" if !css_url
|
587
730
|
|
588
731
|
settings = {
|
589
732
|
path: path,
|
590
|
-
|
733
|
+
url: url,
|
734
|
+
cssUrl: css_url,
|
735
|
+
version: version,
|
591
736
|
verticalPosition: @config.vertical_position,
|
592
737
|
horizontalPosition: @config.horizontal_position,
|
593
738
|
showTrivial: @config.show_trivial,
|
@@ -599,7 +744,8 @@ Append the following to your query string:
|
|
599
744
|
toggleShortcut: @config.toggle_shortcut,
|
600
745
|
startHidden: @config.start_hidden,
|
601
746
|
collapseResults: @config.collapse_results,
|
602
|
-
htmlContainer: @config.html_container
|
747
|
+
htmlContainer: @config.html_container,
|
748
|
+
hiddenCustomFields: @config.snapshot_hidden_custom_fields.join(',')
|
603
749
|
}
|
604
750
|
|
605
751
|
if current && current.page_struct
|
@@ -627,5 +773,127 @@ Append the following to your query string:
|
|
627
773
|
current.inject_js = false
|
628
774
|
end
|
629
775
|
|
776
|
+
def cache_control_value
|
777
|
+
86400
|
778
|
+
end
|
779
|
+
|
780
|
+
private
|
781
|
+
|
782
|
+
def handle_snapshots_request(env)
|
783
|
+
self.current = nil
|
784
|
+
MiniProfiler.authorize_request
|
785
|
+
status = 200
|
786
|
+
headers = { 'Content-Type' => 'text/html' }
|
787
|
+
qp = Rack::Utils.parse_nested_query(env['QUERY_STRING'])
|
788
|
+
if group_name = qp["group_name"]
|
789
|
+
list = @storage.find_snapshots_group(group_name)
|
790
|
+
list.each do |snapshot|
|
791
|
+
snapshot[:url] = url_for_snapshot(snapshot[:id])
|
792
|
+
end
|
793
|
+
data = {
|
794
|
+
group_name: group_name,
|
795
|
+
list: list
|
796
|
+
}
|
797
|
+
else
|
798
|
+
list = @storage.snapshot_groups_overview
|
799
|
+
list.each do |group|
|
800
|
+
group[:url] = url_for_snapshots_group(group[:name])
|
801
|
+
end
|
802
|
+
data = {
|
803
|
+
page: "overview",
|
804
|
+
list: list
|
805
|
+
}
|
806
|
+
end
|
807
|
+
data_html = <<~HTML
|
808
|
+
<div style="display: none;" id="snapshots-data">
|
809
|
+
#{data.to_json}
|
810
|
+
</div>
|
811
|
+
HTML
|
812
|
+
response = Rack::Response.new([], status, headers)
|
813
|
+
|
814
|
+
response.write <<~HTML
|
815
|
+
<html>
|
816
|
+
<head></head>
|
817
|
+
<body class="mp-snapshots">
|
818
|
+
HTML
|
819
|
+
response.write(data_html)
|
820
|
+
script = self.get_profile_script(env)
|
821
|
+
response.write(script)
|
822
|
+
response.write <<~HTML
|
823
|
+
</body>
|
824
|
+
</html>
|
825
|
+
HTML
|
826
|
+
response.finish
|
827
|
+
end
|
828
|
+
|
829
|
+
def serve_flamegraph(env)
|
830
|
+
request = Rack::Request.new(env)
|
831
|
+
id = request.params['id']
|
832
|
+
page_struct = @storage.load(id)
|
833
|
+
|
834
|
+
if !page_struct
|
835
|
+
id = ERB::Util.html_escape(id)
|
836
|
+
user_info = ERB::Util.html_escape(user(env))
|
837
|
+
return [404, {}, ["Request not found: #{id} - user #{user_info}"]]
|
838
|
+
end
|
839
|
+
|
840
|
+
if !page_struct[:flamegraph]
|
841
|
+
return [404, {}, ["No flamegraph available for #{ERB::Util.html_escape(id)}"]]
|
842
|
+
end
|
843
|
+
|
844
|
+
self.flamegraph(page_struct[:flamegraph], page_struct[:request_path])
|
845
|
+
end
|
846
|
+
|
847
|
+
def rails_route_from_path(path, method)
|
848
|
+
if defined?(Rails) && defined?(ActionController::RoutingError)
|
849
|
+
hash = Rails.application.routes.recognize_path(path, method: method)
|
850
|
+
if hash && hash[:controller] && hash[:action]
|
851
|
+
"#{method} #{hash[:controller]}##{hash[:action]}"
|
852
|
+
end
|
853
|
+
end
|
854
|
+
rescue ActionController::RoutingError
|
855
|
+
nil
|
856
|
+
end
|
857
|
+
|
858
|
+
def url_for_snapshots_group(group_name)
|
859
|
+
qs = Rack::Utils.build_query({ group_name: group_name })
|
860
|
+
"/#{@config.base_url_path.gsub('/', '')}/snapshots?#{qs}"
|
861
|
+
end
|
862
|
+
|
863
|
+
def url_for_snapshot(id)
|
864
|
+
qs = Rack::Utils.build_query({ id: id, snapshot: true })
|
865
|
+
"/#{@config.base_url_path.gsub('/', '')}/results?#{qs}"
|
866
|
+
end
|
867
|
+
|
868
|
+
def take_snapshot?(path)
|
869
|
+
@config.snapshot_every_n_requests > 0 &&
|
870
|
+
!path.start_with?(@config.base_url_path) &&
|
871
|
+
@storage.should_take_snapshot?(@config.snapshot_every_n_requests)
|
872
|
+
end
|
873
|
+
|
874
|
+
def take_snapshot(env, start)
|
875
|
+
MiniProfiler.create_current(env, @config)
|
876
|
+
Thread.current[:mp_ongoing_snapshot] = true
|
877
|
+
results = @app.call(env)
|
878
|
+
status = results[0].to_i
|
879
|
+
if status >= 200 && status < 300
|
880
|
+
page_struct = current.page_struct
|
881
|
+
page_struct[:root].record_time(
|
882
|
+
(Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000
|
883
|
+
)
|
884
|
+
custom_fields = MiniProfiler.get_snapshot_custom_fields
|
885
|
+
page_struct[:custom_fields] = custom_fields if custom_fields
|
886
|
+
if Rack::MiniProfiler.snapshots_transporter?
|
887
|
+
Rack::MiniProfiler::SnapshotsTransporter.transport(page_struct)
|
888
|
+
else
|
889
|
+
@storage.push_snapshot(
|
890
|
+
page_struct,
|
891
|
+
@config
|
892
|
+
)
|
893
|
+
end
|
894
|
+
end
|
895
|
+
self.current = nil
|
896
|
+
results
|
897
|
+
end
|
630
898
|
end
|
631
899
|
end
|