rack-mini-profiler 0.10.6 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGELOG.md +129 -16
- data/README.md +116 -63
- data/lib/enable_rails_patches.rb +5 -0
- data/lib/generators/rack_profiler/install_generator.rb +2 -0
- data/lib/generators/rack_profiler/templates/rack_profiler.rb +2 -0
- data/lib/html/dot.1.1.2.min.js +2 -0
- data/lib/html/includes.css +141 -40
- data/lib/html/includes.js +1398 -970
- data/lib/html/includes.scss +547 -442
- data/lib/html/includes.tmpl +227 -142
- 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/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 +27 -16
- data/lib/mini_profiler/config.rb +73 -46
- data/lib/mini_profiler/context.rb +5 -3
- data/lib/mini_profiler/gc_profiler.rb +17 -16
- data/lib/mini_profiler/profiler.rb +332 -94
- data/lib/mini_profiler/profiling_methods.rb +20 -15
- data/lib/mini_profiler/snapshots_transporter.rb +109 -0
- data/lib/mini_profiler/storage/abstract_store.rb +80 -0
- data/lib/mini_profiler/storage/file_store.rb +18 -13
- data/lib/mini_profiler/storage/memcache_store.rb +10 -7
- data/lib/mini_profiler/storage/memory_store.rb +63 -13
- data/lib/mini_profiler/storage/redis_store.rb +143 -7
- data/lib/mini_profiler/timer_struct/base.rb +4 -2
- data/lib/mini_profiler/timer_struct/client.rb +9 -8
- data/lib/mini_profiler/timer_struct/custom.rb +8 -5
- data/lib/mini_profiler/timer_struct/page.rb +79 -24
- data/lib/mini_profiler/timer_struct/request.rb +83 -38
- data/lib/mini_profiler/timer_struct/sql.rb +25 -22
- data/lib/mini_profiler/version.rb +3 -1
- data/lib/mini_profiler_rails/railtie.rb +91 -8
- data/lib/mini_profiler_rails/railtie_methods.rb +61 -0
- data/lib/patches/db/activerecord.rb +5 -14
- data/lib/patches/db/mongo.rb +3 -1
- data/lib/patches/db/moped.rb +5 -3
- data/lib/patches/db/mysql2.rb +8 -6
- data/lib/patches/db/neo4j.rb +3 -1
- data/lib/patches/db/nobrainer.rb +4 -2
- data/lib/patches/db/oracle_enhanced.rb +4 -2
- data/lib/patches/db/pg.rb +41 -21
- data/lib/patches/db/plucky.rb +7 -5
- data/lib/patches/db/riak.rb +15 -13
- data/lib/patches/db/rsolr.rb +6 -4
- data/lib/patches/db/sequel.rb +2 -0
- data/lib/patches/net_patches.rb +20 -8
- data/lib/patches/sql_patches.rb +17 -7
- data/lib/prepend_net_http_patch.rb +5 -0
- data/lib/rack-mini-profiler.rb +3 -3
- data/rack-mini-profiler.gemspec +23 -9
- metadata +146 -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
@@ -1,8 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cgi'
|
4
|
+
|
1
5
|
module Rack
|
2
6
|
class MiniProfiler
|
3
7
|
class << self
|
4
8
|
|
5
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
|
6
15
|
|
7
16
|
def generate_id
|
8
17
|
rand(36**20).to_s(36)
|
@@ -31,15 +40,27 @@ module Rack
|
|
31
40
|
|
32
41
|
def current=(c)
|
33
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
|
34
45
|
Thread.current[:mini_profiler_private] = c
|
35
46
|
end
|
36
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
|
+
|
37
58
|
# discard existing results, don't track this request
|
38
59
|
def discard_results
|
39
60
|
self.current.discard = true if current
|
40
61
|
end
|
41
62
|
|
42
|
-
def create_current(env={}, options={})
|
63
|
+
def create_current(env = {}, options = {})
|
43
64
|
# profiling the request
|
44
65
|
context = Context.new
|
45
66
|
context.inject_js = config.auto_inject && (!env['HTTP_X_REQUESTED_WITH'].eql? 'XMLHttpRequest')
|
@@ -60,6 +81,32 @@ module Rack
|
|
60
81
|
Thread.current[:mp_authorized]
|
61
82
|
end
|
62
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
|
63
110
|
end
|
64
111
|
|
65
112
|
#
|
@@ -69,7 +116,7 @@ module Rack
|
|
69
116
|
MiniProfiler.config.merge!(config)
|
70
117
|
@config = MiniProfiler.config
|
71
118
|
@app = app
|
72
|
-
@config.base_url_path
|
119
|
+
@config.base_url_path += "/" unless @config.base_url_path.end_with? "/"
|
73
120
|
unless @config.storage_instance
|
74
121
|
@config.storage_instance = @config.storage.new(@config.storage_options)
|
75
122
|
end
|
@@ -82,15 +129,24 @@ module Rack
|
|
82
129
|
|
83
130
|
def serve_results(env)
|
84
131
|
request = Rack::Request.new(env)
|
85
|
-
id = request[
|
86
|
-
|
87
|
-
|
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
|
88
144
|
@storage.set_viewed(user(env), id)
|
89
|
-
id = ERB::Util.html_escape(
|
145
|
+
id = ERB::Util.html_escape(id)
|
90
146
|
user_info = ERB::Util.html_escape(user(env))
|
91
147
|
return [404, {}, ["Request not found: #{id} - user #{user_info}"]]
|
92
148
|
end
|
93
|
-
|
149
|
+
if !page_struct[:has_user_viewed] && !is_snapshot
|
94
150
|
page_struct[:client_timings] = TimerStruct::Client.init_from_form_data(env, page_struct)
|
95
151
|
page_struct[:has_user_viewed] = true
|
96
152
|
@storage.save(page_struct)
|
@@ -100,21 +156,22 @@ module Rack
|
|
100
156
|
# If we're an XMLHttpRequest, serve up the contents as JSON
|
101
157
|
if request.xhr?
|
102
158
|
result_json = page_struct.to_json
|
103
|
-
[200, { 'Content-Type' => 'application/json'}, [result_json]]
|
159
|
+
[200, { 'Content-Type' => 'application/json' }, [result_json]]
|
104
160
|
else
|
105
161
|
# Otherwise give the HTML back
|
106
162
|
html = generate_html(page_struct, env)
|
107
|
-
[200, {'Content-Type' => 'text/html'}, [html]]
|
163
|
+
[200, { 'Content-Type' => 'text/html' }, [html]]
|
108
164
|
end
|
109
165
|
end
|
110
166
|
|
111
167
|
def generate_html(page_struct, env, result_json = page_struct.to_json)
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
168
|
+
# double-assigning to suppress "assigned but unused variable" warnings
|
169
|
+
path = path = "#{env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME']}#{@config.base_url_path}"
|
170
|
+
version = version = MiniProfiler::ASSET_VERSION
|
171
|
+
json = json = result_json
|
172
|
+
includes = includes = get_profile_script(env)
|
173
|
+
name = name = page_struct[:name]
|
174
|
+
duration = duration = page_struct.duration_ms.round(1).to_s
|
118
175
|
|
119
176
|
MiniProfiler.share_template.result(binding)
|
120
177
|
end
|
@@ -124,15 +181,15 @@ module Rack
|
|
124
181
|
file_name = path.sub(@config.base_url_path, '')
|
125
182
|
|
126
183
|
return serve_results(env) if file_name.eql?('results')
|
184
|
+
return handle_snapshots_request(env) if file_name.eql?('snapshots')
|
127
185
|
|
128
186
|
resources_env = env.dup
|
129
187
|
resources_env['PATH_INFO'] = file_name
|
130
188
|
|
131
|
-
rack_file = Rack::File.new(MiniProfiler.resources_root,
|
189
|
+
rack_file = Rack::File.new(MiniProfiler.resources_root, 'Cache-Control' => "max-age=#{cache_control_value}")
|
132
190
|
rack_file.call(resources_env)
|
133
191
|
end
|
134
192
|
|
135
|
-
|
136
193
|
def current
|
137
194
|
MiniProfiler.current
|
138
195
|
end
|
@@ -141,15 +198,20 @@ module Rack
|
|
141
198
|
MiniProfiler.current = c
|
142
199
|
end
|
143
200
|
|
144
|
-
|
145
201
|
def config
|
146
202
|
@config
|
147
203
|
end
|
148
204
|
|
205
|
+
def advanced_debugging_enabled?
|
206
|
+
config.enable_advanced_debugging_tools
|
207
|
+
end
|
149
208
|
|
150
|
-
def
|
209
|
+
def tool_disabled_message(client_settings)
|
210
|
+
client_settings.handle_cookie(text_result(Rack::MiniProfiler.advanced_tools_message))
|
211
|
+
end
|
151
212
|
|
152
|
-
|
213
|
+
def call(env)
|
214
|
+
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
153
215
|
client_settings = ClientSettings.new(env, @storage, start)
|
154
216
|
MiniProfiler.deauthorize_request if @config.authorization_mode == :whitelist
|
155
217
|
|
@@ -160,15 +222,31 @@ module Rack
|
|
160
222
|
# Someone (e.g. Rails engine) could change the SCRIPT_NAME so we save it
|
161
223
|
env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME'] = ENV['PASSENGER_BASE_URI'] || env['SCRIPT_NAME']
|
162
224
|
|
163
|
-
skip_it = (
|
164
|
-
|
165
|
-
|
225
|
+
skip_it = /pp=skip/.match?(query_string) || (
|
226
|
+
@config.skip_paths &&
|
227
|
+
@config.skip_paths.any? do |p|
|
228
|
+
if p.instance_of?(String)
|
229
|
+
path.start_with?(p)
|
230
|
+
elsif p.instance_of?(Regexp)
|
231
|
+
p.match?(path)
|
232
|
+
end
|
233
|
+
end
|
234
|
+
)
|
235
|
+
if skip_it
|
236
|
+
return client_settings.handle_cookie(@app.call(env))
|
237
|
+
end
|
238
|
+
|
239
|
+
skip_it = (@config.pre_authorize_cb && !@config.pre_authorize_cb.call(env))
|
166
240
|
|
167
241
|
if skip_it || (
|
168
242
|
@config.authorization_mode == :whitelist &&
|
169
243
|
!client_settings.has_valid_cookie?
|
170
244
|
)
|
171
|
-
|
245
|
+
if take_snapshot?(path)
|
246
|
+
return client_settings.handle_cookie(take_snapshot(env, start))
|
247
|
+
else
|
248
|
+
return client_settings.handle_cookie(@app.call(env))
|
249
|
+
end
|
172
250
|
end
|
173
251
|
|
174
252
|
# handle all /mini-profiler requests here
|
@@ -186,30 +264,32 @@ module Rack
|
|
186
264
|
end
|
187
265
|
|
188
266
|
if skip_it || !config.enabled
|
189
|
-
status,headers,body = @app.call(env)
|
267
|
+
status, headers, body = @app.call(env)
|
190
268
|
client_settings.disable_profiling = true
|
191
|
-
return client_settings.handle_cookie([status,headers,body])
|
269
|
+
return client_settings.handle_cookie([status, headers, body])
|
192
270
|
else
|
193
271
|
client_settings.disable_profiling = false
|
194
272
|
end
|
195
273
|
|
196
274
|
# profile gc
|
197
275
|
if query_string =~ /pp=profile-gc/
|
276
|
+
return tool_disabled_message(client_settings) if !advanced_debugging_enabled?
|
198
277
|
current.measure = false if current
|
199
278
|
return client_settings.handle_cookie(Rack::MiniProfiler::GCProfiler.new.profile_gc(@app, env))
|
200
279
|
end
|
201
280
|
|
202
281
|
# profile memory
|
203
282
|
if query_string =~ /pp=profile-memory/
|
283
|
+
return tool_disabled_message(client_settings) if !advanced_debugging_enabled?
|
204
284
|
query_params = Rack::Utils.parse_nested_query(query_string)
|
205
285
|
options = {
|
206
|
-
:
|
207
|
-
:
|
286
|
+
ignore_files: query_params['memory_profiler_ignore_files'],
|
287
|
+
allow_files: query_params['memory_profiler_allow_files'],
|
208
288
|
}
|
209
|
-
options[:top]= Integer(query_params['memory_profiler_top']) if query_params.key?('memory_profiler_top')
|
289
|
+
options[:top] = Integer(query_params['memory_profiler_top']) if query_params.key?('memory_profiler_top')
|
210
290
|
result = StringIO.new
|
211
291
|
report = MemoryProfiler.report(options) do
|
212
|
-
_,_,body = @app.call(env)
|
292
|
+
_, _, body = @app.call(env)
|
213
293
|
body.close if body.respond_to? :close
|
214
294
|
end
|
215
295
|
report.pretty_print(result)
|
@@ -233,8 +313,7 @@ module Rack
|
|
233
313
|
flamegraph = nil
|
234
314
|
|
235
315
|
trace_exceptions = query_string =~ /pp=trace-exceptions/ && defined? TracePoint
|
236
|
-
status, headers, body, exceptions,trace = nil
|
237
|
-
|
316
|
+
status, headers, body, exceptions, trace = nil
|
238
317
|
|
239
318
|
if trace_exceptions
|
240
319
|
exceptions = []
|
@@ -258,28 +337,40 @@ module Rack
|
|
258
337
|
env['HTTP_ACCEPT_ENCODING'] = 'identity' if config.suppress_encoding
|
259
338
|
|
260
339
|
if query_string =~ /pp=flamegraph/
|
261
|
-
unless defined?(
|
340
|
+
unless defined?(StackProf) && StackProf.respond_to?(:run)
|
262
341
|
|
263
|
-
flamegraph = "Please install the
|
264
|
-
status,headers,body = @app.call(env)
|
342
|
+
flamegraph = "Please install the stackprof gem and require it: add gem 'stackprof' to your Gemfile"
|
343
|
+
status, headers, body = @app.call(env)
|
265
344
|
else
|
266
345
|
# do not sully our profile with mini profiler timings
|
267
346
|
current.measure = false
|
268
347
|
match_data = query_string.match(/flamegraph_sample_rate=([\d\.]+)/)
|
269
348
|
|
270
|
-
mode = query_string =~ /mode=c/ ? :c : :ruby
|
271
|
-
|
272
349
|
if match_data && !match_data[1].to_f.zero?
|
273
350
|
sample_rate = match_data[1].to_f
|
274
351
|
else
|
275
352
|
sample_rate = config.flamegraph_sample_rate
|
276
353
|
end
|
277
|
-
flamegraph =
|
278
|
-
|
354
|
+
flamegraph = StackProf.run(
|
355
|
+
mode: :wall,
|
356
|
+
raw: true,
|
357
|
+
aggregate: false,
|
358
|
+
interval: (sample_rate * 1000).to_i
|
359
|
+
) do
|
360
|
+
status, headers, body = @app.call(env)
|
279
361
|
end
|
280
362
|
end
|
363
|
+
elsif path == '/rack-mini-profiler/requests'
|
364
|
+
blank_page_html = <<~HTML
|
365
|
+
<html>
|
366
|
+
<head></head>
|
367
|
+
<body></body>
|
368
|
+
</html>
|
369
|
+
HTML
|
370
|
+
|
371
|
+
status, headers, body = [200, { 'Content-Type' => 'text/html' }, [blank_page_html.dup]]
|
281
372
|
else
|
282
|
-
status,headers,body = @app.call(env)
|
373
|
+
status, headers, body = @app.call(env)
|
283
374
|
end
|
284
375
|
ensure
|
285
376
|
trace.disable if trace
|
@@ -292,7 +383,7 @@ module Rack
|
|
292
383
|
skip_it = true
|
293
384
|
end
|
294
385
|
|
295
|
-
return client_settings.handle_cookie([status,headers,body]) if skip_it
|
386
|
+
return client_settings.handle_cookie([status, headers, body]) if skip_it
|
296
387
|
|
297
388
|
# we must do this here, otherwise current[:discard] is not being properly treated
|
298
389
|
if trace_exceptions
|
@@ -308,12 +399,14 @@ module Rack
|
|
308
399
|
return client_settings.handle_cookie(dump_exceptions exceptions)
|
309
400
|
end
|
310
401
|
|
311
|
-
if query_string =~ /pp=env/
|
402
|
+
if query_string =~ /pp=env/
|
403
|
+
return tool_disabled_message(client_settings) if !advanced_debugging_enabled?
|
312
404
|
body.close if body.respond_to? :close
|
313
405
|
return client_settings.handle_cookie(dump_env env)
|
314
406
|
end
|
315
407
|
|
316
408
|
if query_string =~ /pp=analyze-memory/
|
409
|
+
return tool_disabled_message(client_settings) if !advanced_debugging_enabled?
|
317
410
|
body.close if body.respond_to? :close
|
318
411
|
return client_settings.handle_cookie(analyze_memory)
|
319
412
|
end
|
@@ -325,14 +418,13 @@ module Rack
|
|
325
418
|
|
326
419
|
page_struct = current.page_struct
|
327
420
|
page_struct[:user] = user(env)
|
328
|
-
page_struct[:root].record_time((
|
421
|
+
page_struct[:root].record_time((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000)
|
329
422
|
|
330
423
|
if flamegraph
|
331
424
|
body.close if body.respond_to? :close
|
332
|
-
return client_settings.handle_cookie(self.flamegraph(flamegraph))
|
425
|
+
return client_settings.handle_cookie(self.flamegraph(flamegraph, path))
|
333
426
|
end
|
334
427
|
|
335
|
-
|
336
428
|
begin
|
337
429
|
@storage.save(page_struct)
|
338
430
|
# no matter what it is, it should be unviewed, otherwise we will miss POST
|
@@ -340,7 +432,7 @@ module Rack
|
|
340
432
|
|
341
433
|
# inject headers, script
|
342
434
|
if status >= 200 && status < 300
|
343
|
-
result = inject_profiler(env,status,headers,body)
|
435
|
+
result = inject_profiler(env, status, headers, body)
|
344
436
|
return client_settings.handle_cookie(result) if result
|
345
437
|
end
|
346
438
|
rescue Exception => e
|
@@ -356,7 +448,7 @@ module Rack
|
|
356
448
|
self.current = nil
|
357
449
|
end
|
358
450
|
|
359
|
-
def inject_profiler(env,status,headers,body)
|
451
|
+
def inject_profiler(env, status, headers, body)
|
360
452
|
# mini profiler is meddling with stuff, we can not cache cause we will get incorrect data
|
361
453
|
# Rack::ETag has already inserted some nonesense in the chain
|
362
454
|
content_type = headers['Content-Type']
|
@@ -366,12 +458,12 @@ module Rack
|
|
366
458
|
headers.delete('Date')
|
367
459
|
end
|
368
460
|
|
369
|
-
headers['X-MiniProfiler-Original-Cache-Control'] = headers['Cache-Control']
|
461
|
+
headers['X-MiniProfiler-Original-Cache-Control'] = headers['Cache-Control'] unless headers['Cache-Control'].nil?
|
370
462
|
headers['Cache-Control'] = "#{"no-store, " if config.disable_caching}must-revalidate, private, max-age=0"
|
371
463
|
|
372
464
|
# inject header
|
373
465
|
if headers.is_a? Hash
|
374
|
-
headers['X-MiniProfiler-Ids'] =
|
466
|
+
headers['X-MiniProfiler-Ids'] = ids_comma_separated(env)
|
375
467
|
end
|
376
468
|
|
377
469
|
if current.inject_js && content_type =~ /text\/html/
|
@@ -379,7 +471,7 @@ module Rack
|
|
379
471
|
script = self.get_profile_script(env)
|
380
472
|
|
381
473
|
if String === body
|
382
|
-
response.write inject(body,script)
|
474
|
+
response.write inject(body, script)
|
383
475
|
else
|
384
476
|
body.each { |fragment| response.write inject(fragment, script) }
|
385
477
|
end
|
@@ -399,38 +491,44 @@ module Rack
|
|
399
491
|
if script.respond_to?(:encoding) && script.respond_to?(:force_encoding)
|
400
492
|
script = script.force_encoding(fragment.encoding)
|
401
493
|
end
|
402
|
-
|
494
|
+
|
495
|
+
safe_script = script
|
496
|
+
if script.respond_to?(:html_safe)
|
497
|
+
safe_script = script.html_safe
|
498
|
+
end
|
499
|
+
|
500
|
+
fragment.insert(index, safe_script)
|
403
501
|
else
|
404
502
|
fragment
|
405
503
|
end
|
406
504
|
end
|
407
505
|
|
408
506
|
def dump_exceptions(exceptions)
|
409
|
-
body = "Exceptions raised during request\n\n"
|
507
|
+
body = "Exceptions raised during request\n\n".dup
|
410
508
|
if exceptions.empty?
|
411
509
|
body << "No exceptions raised"
|
412
510
|
else
|
413
511
|
body << "Exceptions: (#{exceptions.size} total)\n"
|
414
|
-
exceptions.group_by(&:class).each do |klass,
|
415
|
-
body << " #{klass.name} (#{
|
512
|
+
exceptions.group_by(&:class).each do |klass, exceptions_per_class|
|
513
|
+
body << " #{klass.name} (#{exceptions_per_class.size})\n"
|
416
514
|
end
|
417
515
|
|
418
516
|
body << "\nBacktraces\n"
|
419
517
|
exceptions.each_with_index do |e, i|
|
420
|
-
body << "##{i+1}: #{e.class} - \"#{e.message}\"\n #{e.backtrace.join("\n ")}\n\n"
|
518
|
+
body << "##{i + 1}: #{e.class} - \"#{e.message}\"\n #{e.backtrace.join("\n ")}\n\n"
|
421
519
|
end
|
422
520
|
end
|
423
521
|
text_result(body)
|
424
522
|
end
|
425
523
|
|
426
524
|
def dump_env(env)
|
427
|
-
body = "Rack Environment\n---------------\n"
|
428
|
-
env.each do |k,v|
|
525
|
+
body = "Rack Environment\n---------------\n".dup
|
526
|
+
env.each do |k, v|
|
429
527
|
body << "#{k}: #{v}\n"
|
430
528
|
end
|
431
529
|
|
432
530
|
body << "\n\nEnvironment\n---------------\n"
|
433
|
-
ENV.each do |k,v|
|
531
|
+
ENV.each do |k, v|
|
434
532
|
body << "#{k}: #{v}\n"
|
435
533
|
end
|
436
534
|
|
@@ -446,9 +544,9 @@ module Rack
|
|
446
544
|
end
|
447
545
|
|
448
546
|
def trim_strings(strings, max_size)
|
449
|
-
strings.sort!{|a,b| b[1] <=> a[1]}
|
547
|
+
strings.sort! { |a, b| b[1] <=> a[1] }
|
450
548
|
i = 0
|
451
|
-
strings.delete_if{|_| (i+=1) > max_size}
|
549
|
+
strings.delete_if { |_| (i += 1) > max_size }
|
452
550
|
end
|
453
551
|
|
454
552
|
def analyze_memory
|
@@ -467,22 +565,22 @@ module Rack
|
|
467
565
|
|
468
566
|
unless str.valid_encoding?
|
469
567
|
# work around bust string with a double conversion
|
470
|
-
str.encode!("utf-16","utf-8"
|
471
|
-
str.encode!("utf-8","utf-16")
|
568
|
+
str.encode!("utf-16", "utf-8", invalid: :replace)
|
569
|
+
str.encode!("utf-8", "utf-16")
|
472
570
|
end
|
473
571
|
end
|
474
572
|
|
475
573
|
str
|
476
574
|
end
|
477
575
|
|
478
|
-
body = "ObjectSpace stats:\n\n"
|
576
|
+
body = "ObjectSpace stats:\n\n".dup
|
479
577
|
|
480
578
|
counts = ObjectSpace.count_objects
|
481
579
|
total_strings = counts[:T_STRING]
|
482
580
|
|
483
581
|
body << counts
|
484
|
-
.sort{|a,b| b[1] <=> a[1]}
|
485
|
-
.map{|k,v| "#{k}: #{v}"}
|
582
|
+
.sort { |a, b| b[1] <=> a[1] }
|
583
|
+
.map { |k, v| "#{k}: #{v}" }
|
486
584
|
.join("\n")
|
487
585
|
|
488
586
|
strings = []
|
@@ -506,29 +604,29 @@ module Rack
|
|
506
604
|
trim_strings(strings, max_size)
|
507
605
|
|
508
606
|
body << "\n\n\n1000 Largest strings:\n\n"
|
509
|
-
body << strings.map{|s,len| "#{s[0..1000]}\n(len: #{len})\n\n"}.join("\n")
|
607
|
+
body << strings.map { |s, len| "#{s[0..1000]}\n(len: #{len})\n\n" }.join("\n")
|
510
608
|
|
511
609
|
body << "\n\n\n1000 Sample strings:\n\n"
|
512
|
-
body << sample_strings.map{|s,len| "#{s[0..1000]}\n(len: #{len})\n\n"}.join("\n")
|
610
|
+
body << sample_strings.map { |s, len| "#{s[0..1000]}\n(len: #{len})\n\n" }.join("\n")
|
513
611
|
|
514
612
|
body << "\n\n\n1000 Most common strings:\n\n"
|
515
|
-
body << string_counts.sort{|a,b| b[1] <=> a[1]}[0..max_size].map{|s,len| "#{trunc.call(s)}\n(x #{len})\n\n"}.join("\n")
|
613
|
+
body << string_counts.sort { |a, b| b[1] <=> a[1] }[0..max_size].map { |s, len| "#{trunc.call(s)}\n(x #{len})\n\n" }.join("\n")
|
516
614
|
|
517
615
|
text_result(body)
|
518
616
|
end
|
519
617
|
|
520
618
|
def text_result(body)
|
521
|
-
headers = {'Content-Type' => 'text/plain'}
|
619
|
+
headers = { 'Content-Type' => 'text/plain' }
|
522
620
|
[200, headers, [body]]
|
523
621
|
end
|
524
622
|
|
525
623
|
def make_link(postfix, env)
|
526
624
|
link = env["PATH_INFO"] + "?" + env["QUERY_STRING"].sub("pp=help", "pp=#{postfix}")
|
527
|
-
"pp=<a href='#{link}'>#{postfix}</a>"
|
625
|
+
"pp=<a href='#{ERB::Util.html_escape(link)}'>#{postfix}</a>"
|
528
626
|
end
|
529
627
|
|
530
628
|
def help(client_settings, env)
|
531
|
-
headers = {'Content-Type' => 'text/html'}
|
629
|
+
headers = { 'Content-Type' => 'text/html' }
|
532
630
|
body = "<html><body>
|
533
631
|
<pre style='line-height: 30px; font-size: 16px;'>
|
534
632
|
Append the following to your query string:
|
@@ -543,9 +641,9 @@ Append the following to your query string:
|
|
543
641
|
#{make_link "enable", env} : enable profiling for this session (if previously disabled)
|
544
642
|
#{make_link "profile-gc", env} : perform gc profiling on this request, analyzes ObjectSpace generated by request (ruby 1.9.3 only)
|
545
643
|
#{make_link "profile-memory", env} : requires the memory_profiler gem, new location based report
|
546
|
-
#{make_link "flamegraph", env} :
|
644
|
+
#{make_link "flamegraph", env} : requires Ruby 2.2, a graph representing sampled activity (requires the stackprof gem).
|
547
645
|
#{make_link "flamegraph&flamegraph_sample_rate=1", env}: creates a flamegraph with the specified sample rate (in ms). Overrides value set in config
|
548
|
-
#{make_link "flamegraph_embed", env} :
|
646
|
+
#{make_link "flamegraph_embed", env} : requires Ruby 2.2, a graph representing sampled activity (requires the stackprof gem), embedded resources for use on an intranet.
|
549
647
|
#{make_link "trace-exceptions", env} : requires Ruby 2.0, will return all the spots where your application raises exceptions
|
550
648
|
#{make_link "analyze-memory", env} : requires Ruby 2.0, will perform basic memory analysis of heap
|
551
649
|
</pre>
|
@@ -556,9 +654,37 @@ Append the following to your query string:
|
|
556
654
|
[200, headers, [body]]
|
557
655
|
end
|
558
656
|
|
559
|
-
def flamegraph(graph)
|
560
|
-
headers = {'Content-Type' => 'text/html'}
|
561
|
-
|
657
|
+
def flamegraph(graph, path)
|
658
|
+
headers = { 'Content-Type' => 'text/html' }
|
659
|
+
if Hash === graph
|
660
|
+
html = <<~HTML
|
661
|
+
<!DOCTYPE html>
|
662
|
+
<html>
|
663
|
+
<head>
|
664
|
+
<style>
|
665
|
+
body { margin: 0; height: 100vh; }
|
666
|
+
#speedscope-iframe { width: 100%; height: 100%; border: none; }
|
667
|
+
</style>
|
668
|
+
</head>
|
669
|
+
<body>
|
670
|
+
<script type="text/javascript">
|
671
|
+
var graph = #{JSON.generate(graph)};
|
672
|
+
var json = JSON.stringify(graph);
|
673
|
+
var blob = new Blob([json], { type: 'text/plain' });
|
674
|
+
var objUrl = encodeURIComponent(URL.createObjectURL(blob));
|
675
|
+
var iframe = document.createElement('IFRAME');
|
676
|
+
iframe.setAttribute('id', 'speedscope-iframe');
|
677
|
+
document.body.appendChild(iframe);
|
678
|
+
var iframeUrl = '#{@config.base_url_path}speedscope/index.html#profileURL=' + objUrl + '&title=' + 'Flamegraph for #{CGI.escape(path)}';
|
679
|
+
iframe.setAttribute('src', iframeUrl);
|
680
|
+
</script>
|
681
|
+
</body>
|
682
|
+
</html>
|
683
|
+
HTML
|
684
|
+
[200, headers, [html]]
|
685
|
+
else
|
686
|
+
[200, headers, [graph]]
|
687
|
+
end
|
562
688
|
end
|
563
689
|
|
564
690
|
def ids(env)
|
@@ -570,10 +696,6 @@ Append the following to your query string:
|
|
570
696
|
all
|
571
697
|
end
|
572
698
|
|
573
|
-
def ids_json(env)
|
574
|
-
::JSON.generate(ids(env))
|
575
|
-
end
|
576
|
-
|
577
699
|
def ids_comma_separated(env)
|
578
700
|
ids(env).join(",")
|
579
701
|
end
|
@@ -586,21 +708,33 @@ Append the following to your query string:
|
|
586
708
|
# * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
|
587
709
|
def get_profile_script(env)
|
588
710
|
path = "#{env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME']}#{@config.base_url_path}"
|
711
|
+
version = MiniProfiler::ASSET_VERSION
|
712
|
+
if @config.assets_url
|
713
|
+
url = @config.assets_url.call('rack-mini-profiler.js', version, env)
|
714
|
+
css_url = @config.assets_url.call('rack-mini-profiler.css', version, env)
|
715
|
+
end
|
716
|
+
|
717
|
+
url = "#{path}includes.js?v=#{version}" if !url
|
718
|
+
css_url = "#{path}includes.css?v=#{version}" if !css_url
|
589
719
|
|
590
720
|
settings = {
|
591
|
-
:
|
592
|
-
:
|
593
|
-
:
|
594
|
-
:
|
595
|
-
:
|
596
|
-
:
|
597
|
-
:
|
598
|
-
:
|
599
|
-
:
|
600
|
-
:
|
601
|
-
:
|
602
|
-
:
|
603
|
-
:
|
721
|
+
path: path,
|
722
|
+
url: url,
|
723
|
+
cssUrl: css_url,
|
724
|
+
version: version,
|
725
|
+
verticalPosition: @config.vertical_position,
|
726
|
+
horizontalPosition: @config.horizontal_position,
|
727
|
+
showTrivial: @config.show_trivial,
|
728
|
+
showChildren: @config.show_children,
|
729
|
+
maxTracesToShow: @config.max_traces_to_show,
|
730
|
+
showControls: @config.show_controls,
|
731
|
+
showTotalSqlCount: @config.show_total_sql_count,
|
732
|
+
authorized: true,
|
733
|
+
toggleShortcut: @config.toggle_shortcut,
|
734
|
+
startHidden: @config.start_hidden,
|
735
|
+
collapseResults: @config.collapse_results,
|
736
|
+
htmlContainer: @config.html_container,
|
737
|
+
hiddenCustomFields: @config.snapshot_hidden_custom_fields.join(',')
|
604
738
|
}
|
605
739
|
|
606
740
|
if current && current.page_struct
|
@@ -614,7 +748,7 @@ Append the following to your query string:
|
|
614
748
|
# TODO : cache this snippet
|
615
749
|
script = IO.read(::File.expand_path('../html/profile_handler.js', ::File.dirname(__FILE__)))
|
616
750
|
# replace the variables
|
617
|
-
settings.each do |k,v|
|
751
|
+
settings.each do |k, v|
|
618
752
|
regex = Regexp.new("\\{#{k.to_s}\\}")
|
619
753
|
script.gsub!(regex, v.to_s)
|
620
754
|
end
|
@@ -628,5 +762,109 @@ Append the following to your query string:
|
|
628
762
|
current.inject_js = false
|
629
763
|
end
|
630
764
|
|
765
|
+
def cache_control_value
|
766
|
+
86400
|
767
|
+
end
|
768
|
+
|
769
|
+
private
|
770
|
+
|
771
|
+
def handle_snapshots_request(env)
|
772
|
+
self.current = nil
|
773
|
+
MiniProfiler.authorize_request
|
774
|
+
status = 200
|
775
|
+
headers = { 'Content-Type' => 'text/html' }
|
776
|
+
qp = Rack::Utils.parse_nested_query(env['QUERY_STRING'])
|
777
|
+
if group_name = qp["group_name"]
|
778
|
+
list = @storage.find_snapshots_group(group_name)
|
779
|
+
list.each do |snapshot|
|
780
|
+
snapshot[:url] = url_for_snapshot(snapshot[:id])
|
781
|
+
end
|
782
|
+
data = {
|
783
|
+
group_name: group_name,
|
784
|
+
list: list
|
785
|
+
}
|
786
|
+
else
|
787
|
+
list = @storage.snapshot_groups_overview
|
788
|
+
list.each do |group|
|
789
|
+
group[:url] = url_for_snapshots_group(group[:name])
|
790
|
+
end
|
791
|
+
data = {
|
792
|
+
page: "overview",
|
793
|
+
list: list
|
794
|
+
}
|
795
|
+
end
|
796
|
+
data_html = <<~HTML
|
797
|
+
<div style="display: none;" id="snapshots-data">
|
798
|
+
#{data.to_json}
|
799
|
+
</div>
|
800
|
+
HTML
|
801
|
+
response = Rack::Response.new([], status, headers)
|
802
|
+
|
803
|
+
response.write <<~HTML
|
804
|
+
<html>
|
805
|
+
<head></head>
|
806
|
+
<body class="mp-snapshots">
|
807
|
+
HTML
|
808
|
+
response.write(data_html)
|
809
|
+
script = self.get_profile_script(env)
|
810
|
+
response.write(script)
|
811
|
+
response.write <<~HTML
|
812
|
+
</body>
|
813
|
+
</html>
|
814
|
+
HTML
|
815
|
+
response.finish
|
816
|
+
end
|
817
|
+
|
818
|
+
def rails_route_from_path(path, method)
|
819
|
+
if defined?(Rails) && defined?(ActionController::RoutingError)
|
820
|
+
hash = Rails.application.routes.recognize_path(path, method: method)
|
821
|
+
if hash && hash[:controller] && hash[:action]
|
822
|
+
"#{method} #{hash[:controller]}##{hash[:action]}"
|
823
|
+
end
|
824
|
+
end
|
825
|
+
rescue ActionController::RoutingError
|
826
|
+
nil
|
827
|
+
end
|
828
|
+
|
829
|
+
def url_for_snapshots_group(group_name)
|
830
|
+
qs = Rack::Utils.build_query({ group_name: group_name })
|
831
|
+
"/#{@config.base_url_path.gsub('/', '')}/snapshots?#{qs}"
|
832
|
+
end
|
833
|
+
|
834
|
+
def url_for_snapshot(id)
|
835
|
+
qs = Rack::Utils.build_query({ id: id, snapshot: true })
|
836
|
+
"/#{@config.base_url_path.gsub('/', '')}/results?#{qs}"
|
837
|
+
end
|
838
|
+
|
839
|
+
def take_snapshot?(path)
|
840
|
+
@config.snapshot_every_n_requests > 0 &&
|
841
|
+
!path.start_with?(@config.base_url_path) &&
|
842
|
+
@storage.should_take_snapshot?(@config.snapshot_every_n_requests)
|
843
|
+
end
|
844
|
+
|
845
|
+
def take_snapshot(env, start)
|
846
|
+
MiniProfiler.create_current(env, @config)
|
847
|
+
Thread.current[:mp_ongoing_snapshot] = true
|
848
|
+
results = @app.call(env)
|
849
|
+
status = results[0].to_i
|
850
|
+
if status >= 200 && status < 300
|
851
|
+
page_struct = current.page_struct
|
852
|
+
page_struct[:root].record_time(
|
853
|
+
(Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000
|
854
|
+
)
|
855
|
+
custom_fields = MiniProfiler.get_snapshot_custom_fields
|
856
|
+
page_struct[:custom_fields] = custom_fields if custom_fields
|
857
|
+
if Rack::MiniProfiler.snapshots_transporter?
|
858
|
+
Rack::MiniProfiler::SnapshotsTransporter.transport(page_struct)
|
859
|
+
else
|
860
|
+
@storage.push_snapshot(
|
861
|
+
page_struct,
|
862
|
+
@config
|
863
|
+
)
|
864
|
+
end
|
865
|
+
end
|
866
|
+
self.current = nil
|
867
|
+
results
|
868
|
+
end
|
631
869
|
end
|
632
870
|
end
|