rack-mini-profiler 3.1.1 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/lib/mini_profiler.rb CHANGED
@@ -12,11 +12,15 @@ require 'mini_profiler/context'
12
12
  require 'mini_profiler/client_settings'
13
13
  require 'mini_profiler/gc_profiler'
14
14
  require 'mini_profiler/snapshots_transporter'
15
+ require 'mini_profiler/views'
16
+ require 'mini_profiler/actions'
15
17
 
16
18
  module Rack
17
19
  class MiniProfiler
18
- class << self
20
+ include Actions
21
+ include Views
19
22
 
23
+ class << self
20
24
  include Rack::MiniProfiler::ProfilingMethods
21
25
  attr_accessor :subscribe_sql_active_record
22
26
 
@@ -37,14 +41,6 @@ module Rack
37
41
  @config ||= Config.default
38
42
  end
39
43
 
40
- def resources_root
41
- @resources_root ||= ::File.expand_path("../html", __FILE__)
42
- end
43
-
44
- def share_template
45
- @share_template ||= ERB.new(::File.read(::File.expand_path("html/share.html", ::File.dirname(__FILE__))))
46
- end
47
-
48
44
  def current
49
45
  Thread.current[:mini_profiler_private]
50
46
  end
@@ -138,70 +134,6 @@ module Rack
138
134
  @config.user_provider.call(env)
139
135
  end
140
136
 
141
- def serve_results(env)
142
- request = Rack::Request.new(env)
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
155
- @storage.set_viewed(user(env), id)
156
- id = ERB::Util.html_escape(id)
157
- user_info = ERB::Util.html_escape(user(env))
158
- return [404, {}, ["Request not found: #{id} - user #{user_info}"]]
159
- end
160
- if !page_struct[:has_user_viewed] && !is_snapshot
161
- page_struct[:client_timings] = TimerStruct::Client.init_from_form_data(env, page_struct)
162
- page_struct[:has_user_viewed] = true
163
- @storage.save(page_struct)
164
- @storage.set_viewed(user(env), id)
165
- end
166
-
167
- # If we're an XMLHttpRequest, serve up the contents as JSON
168
- if request.xhr?
169
- result_json = page_struct.to_json
170
- [200, { 'Content-Type' => 'application/json' }, [result_json]]
171
- else
172
- # Otherwise give the HTML back
173
- html = generate_html(page_struct, env)
174
- [200, { 'Content-Type' => 'text/html' }, [html]]
175
- end
176
- end
177
-
178
- def generate_html(page_struct, env, result_json = page_struct.to_json)
179
- # double-assigning to suppress "assigned but unused variable" warnings
180
- path = path = "#{env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME']}#{@config.base_url_path}"
181
- version = version = MiniProfiler::ASSET_VERSION
182
- json = json = result_json
183
- includes = includes = get_profile_script(env)
184
- name = name = page_struct[:name]
185
- duration = duration = page_struct.duration_ms.round(1).to_s
186
-
187
- MiniProfiler.share_template.result(binding)
188
- end
189
-
190
- def serve_html(env)
191
- path = env['PATH_INFO'].sub('//', '/')
192
- file_name = path.sub(@config.base_url_path, '')
193
-
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')
197
-
198
- resources_env = env.dup
199
- resources_env['PATH_INFO'] = file_name
200
-
201
- rack_file = Rack::File.new(MiniProfiler.resources_root, 'Cache-Control' => "max-age=#{cache_control_value}")
202
- rack_file.call(resources_env)
203
- end
204
-
205
137
  def current
206
138
  MiniProfiler.current
207
139
  end
@@ -228,13 +160,12 @@ module Rack
228
160
  MiniProfiler.deauthorize_request if @config.authorization_mode == :allow_authorized
229
161
 
230
162
  status = headers = body = nil
231
- query_string = env['QUERY_STRING']
232
163
  path = env['PATH_INFO'].sub('//', '/')
233
164
 
234
165
  # Someone (e.g. Rails engine) could change the SCRIPT_NAME so we save it
235
166
  env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME'] = ENV['PASSENGER_BASE_URI'] || env['SCRIPT_NAME']
236
167
 
237
- skip_it = /#{@config.profile_parameter}=skip/.match?(query_string) || (
168
+ skip_it = matches_action?('skip', env) || (
238
169
  @config.skip_paths &&
239
170
  @config.skip_paths.any? do |p|
240
171
  if p.instance_of?(String)
@@ -262,15 +193,29 @@ module Rack
262
193
  end
263
194
 
264
195
  # handle all /mini-profiler requests here
265
- return client_settings.handle_cookie(serve_html(env)) if path.start_with? @config.base_url_path
196
+ if path.start_with? @config.base_url_path
197
+ file_name = path.sub(@config.base_url_path, '')
198
+
199
+ case file_name
200
+ when 'results'
201
+ return serve_results(env)
202
+ when 'snapshots'
203
+ self.current = nil
204
+ return serve_snapshot(env)
205
+ when 'flamegraph'
206
+ return serve_flamegraph(env)
207
+ end
208
+
209
+ return client_settings.handle_cookie(serve_file(env, file_name: file_name))
210
+ end
266
211
 
267
212
  has_disable_cookie = client_settings.disable_profiling?
268
213
  # manual session disable / enable
269
- if query_string =~ /#{@config.profile_parameter}=disable/ || has_disable_cookie
214
+ if matches_action?('disable', env) || has_disable_cookie
270
215
  skip_it = true
271
216
  end
272
217
 
273
- if query_string =~ /#{@config.profile_parameter}=enable/
218
+ if matches_action?('enable', env)
274
219
  skip_it = false
275
220
  config.enabled = true
276
221
  end
@@ -279,54 +224,32 @@ module Rack
279
224
  status, headers, body = @app.call(env)
280
225
  client_settings.disable_profiling = true
281
226
  return client_settings.handle_cookie([status, headers, body])
282
- else
283
- client_settings.disable_profiling = false
284
227
  end
285
228
 
229
+ # remember that profiling is not disabled (ie enabled)
230
+ client_settings.disable_profiling = false
231
+
286
232
  # profile gc
287
- if query_string =~ /#{@config.profile_parameter}=profile-gc/
288
- return tool_disabled_message(client_settings) if !advanced_debugging_enabled?
233
+ if matches_action?('profile-gc', env)
289
234
  current.measure = false if current
290
- return client_settings.handle_cookie(Rack::MiniProfiler::GCProfiler.new.profile_gc(@app, env))
235
+ return serve_profile_gc(env, client_settings)
291
236
  end
292
237
 
293
238
  # profile memory
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
-
307
- query_params = Rack::Utils.parse_nested_query(query_string)
308
- options = {
309
- ignore_files: query_params['memory_profiler_ignore_files'],
310
- allow_files: query_params['memory_profiler_allow_files'],
311
- }
312
- options[:top] = Integer(query_params['memory_profiler_top']) if query_params.key?('memory_profiler_top')
313
- result = StringIO.new
314
- report = MemoryProfiler.report(options) do
315
- _, _, body = @app.call(env)
316
- body.close if body.respond_to? :close
317
- end
318
- report.pretty_print(result)
319
- return client_settings.handle_cookie(text_result(result.string))
239
+ if matches_action?('profile-memory', env)
240
+ return serve_profile_memory(env, client_settings)
320
241
  end
321
242
 
243
+ # any other requests past this point are going to the app to be profiled
244
+
322
245
  MiniProfiler.create_current(env, @config)
323
246
 
324
- if query_string =~ /#{@config.profile_parameter}=normal-backtrace/
247
+ if matches_action?('normal-backtrace', env)
325
248
  client_settings.backtrace_level = ClientSettings::BACKTRACE_DEFAULT
326
- elsif query_string =~ /#{@config.profile_parameter}=no-backtrace/
249
+ elsif matches_action?('no-backtrace', env)
327
250
  current.skip_backtrace = true
328
251
  client_settings.backtrace_level = ClientSettings::BACKTRACE_NONE
329
- elsif query_string =~ /#{@config.profile_parameter}=full-backtrace/ || client_settings.backtrace_full?
252
+ elsif matches_action?('full-backtrace', env) || client_settings.backtrace_full?
330
253
  current.full_backtrace = true
331
254
  client_settings.backtrace_level = ClientSettings::BACKTRACE_FULL
332
255
  elsif client_settings.backtrace_none?
@@ -335,7 +258,7 @@ module Rack
335
258
 
336
259
  flamegraph = nil
337
260
 
338
- trace_exceptions = query_string =~ /#{@config.profile_parameter}=trace-exceptions/ && defined? TracePoint
261
+ trace_exceptions = matches_action?('trace-exceptions', env) && defined? TracePoint
339
262
  status, headers, body, exceptions, trace = nil
340
263
 
341
264
  if trace_exceptions
@@ -359,11 +282,11 @@ module Rack
359
282
  # Prevent response body from being compressed
360
283
  env['HTTP_ACCEPT_ENCODING'] = 'identity' if config.suppress_encoding
361
284
 
362
- if query_string =~ /pp=(async-)?flamegraph/ || env['HTTP_REFERER'] =~ /pp=async-flamegraph/
285
+ if matches_action?('flamegraph', env) || matches_action?('async-flamegraph', env) || env['HTTP_REFERER'] =~ /pp=async-flamegraph/
363
286
  if defined?(StackProf) && StackProf.respond_to?(:run)
364
287
  # do not sully our profile with mini profiler timings
365
288
  current.measure = false
366
- match_data = query_string.match(/flamegraph_sample_rate=([\d\.]+)/)
289
+ match_data = action_parameters(env)['flamegraph_sample_rate']
367
290
 
368
291
  if match_data && !match_data[1].to_f.zero?
369
292
  sample_rate = match_data[1].to_f
@@ -371,7 +294,7 @@ module Rack
371
294
  sample_rate = config.flamegraph_sample_rate
372
295
  end
373
296
 
374
- mode_match_data = query_string.match(/flamegraph_mode=([a-zA-Z]+)/)
297
+ mode_match_data = action_parameters(env)['flamegraph_mode']
375
298
 
376
299
  if mode_match_data && [:cpu, :wall, :object, :custom].include?(mode_match_data[1].to_sym)
377
300
  mode = mode_match_data[1].to_sym
@@ -379,11 +302,20 @@ module Rack
379
302
  mode = config.flamegraph_mode
380
303
  end
381
304
 
305
+ ignore_gc_match_data = action_parameters(env)['flamegraph_ignore_gc']
306
+
307
+ if ignore_gc_match_data
308
+ ignore_gc = ignore_gc_match_data == 'true'
309
+ else
310
+ ignore_gc = config.flamegraph_ignore_gc
311
+ end
312
+
382
313
  flamegraph = StackProf.run(
383
314
  mode: mode,
384
315
  raw: true,
385
316
  aggregate: false,
386
- interval: (sample_rate * 1000).to_i
317
+ interval: (sample_rate * 1000).to_i,
318
+ ignore_gc: ignore_gc
387
319
  ) do
388
320
  status, headers, body = @app.call(env)
389
321
  end
@@ -397,18 +329,7 @@ module Rack
397
329
  )
398
330
  end
399
331
  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]]
332
+ status, headers, body = [200, { 'Content-Type' => 'text/html' }, [blank_page_html]]
412
333
  else
413
334
  status, headers, body = @app.call(env)
414
335
  end
@@ -429,7 +350,7 @@ module Rack
429
350
  if trace_exceptions
430
351
  body.close if body.respond_to? :close
431
352
 
432
- query_params = Rack::Utils.parse_nested_query(query_string)
353
+ query_params = action_parameters(env)
433
354
  trace_exceptions_filter = query_params['trace_exceptions_filter']
434
355
  if trace_exceptions_filter
435
356
  trace_exceptions_regex = Regexp.new(trace_exceptions_filter)
@@ -439,19 +360,19 @@ module Rack
439
360
  return client_settings.handle_cookie(dump_exceptions exceptions)
440
361
  end
441
362
 
442
- if query_string =~ /#{@config.profile_parameter}=env/
363
+ if matches_action?("env", env)
443
364
  return tool_disabled_message(client_settings) if !advanced_debugging_enabled?
444
365
  body.close if body.respond_to? :close
445
366
  return client_settings.handle_cookie(dump_env env)
446
367
  end
447
368
 
448
- if query_string =~ /#{@config.profile_parameter}=analyze-memory/
369
+ if matches_action?("analyze-memory", env)
449
370
  return tool_disabled_message(client_settings) if !advanced_debugging_enabled?
450
371
  body.close if body.respond_to? :close
451
372
  return client_settings.handle_cookie(analyze_memory)
452
373
  end
453
374
 
454
- if query_string =~ /#{@config.profile_parameter}=help/
375
+ if matches_action?("help", env)
455
376
  body.close if body.respond_to? :close
456
377
  return client_settings.handle_cookie(help(client_settings, env))
457
378
  end
@@ -460,7 +381,7 @@ module Rack
460
381
  page_struct[:user] = user(env)
461
382
  page_struct[:root].record_time((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000)
462
383
 
463
- if flamegraph && query_string =~ /#{@config.profile_parameter}=flamegraph/
384
+ if flamegraph && matches_action?("flamegraph", env)
464
385
  body.close if body.respond_to? :close
465
386
  return client_settings.handle_cookie(self.flamegraph(flamegraph, path, env))
466
387
  elsif flamegraph # async-flamegraph
@@ -485,12 +406,20 @@ module Rack
485
406
  end
486
407
 
487
408
  client_settings.handle_cookie([status, headers, body])
488
-
489
409
  ensure
490
410
  # Make sure this always happens
491
411
  self.current = nil
492
412
  end
493
413
 
414
+ def matches_action?(action, env)
415
+ env['QUERY_STRING'] =~ /#{@config.profile_parameter}=#{action}/ ||
416
+ env['HTTP_X_RACK_MINI_PROFILER'] == action
417
+ end
418
+
419
+ def action_parameters(env)
420
+ query_params = Rack::Utils.parse_nested_query(env['QUERY_STRING'])
421
+ end
422
+
494
423
  def inject_profiler(env, status, headers, body)
495
424
  # mini profiler is meddling with stuff, we can not cache cause we will get incorrect data
496
425
  # Rack::ETag has already inserted some nonesense in the chain
@@ -663,79 +592,6 @@ module Rack
663
592
  [status, headers, [body]]
664
593
  end
665
594
 
666
- def make_link(postfix, env)
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>"
669
- end
670
-
671
- def help(client_settings, env)
672
- headers = { 'Content-Type' => 'text/html' }
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)
709
- headers = { 'Content-Type' => 'text/html' }
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]]
737
- end
738
-
739
595
  def ids(env)
740
596
  all = ([current.page_struct[:id]] + (@storage.get_unviewed_ids(user(env)) || [])).uniq
741
597
  if all.size > @config.max_traces_to_show
@@ -749,69 +605,6 @@ module Rack
749
605
  ids(env).join(",")
750
606
  end
751
607
 
752
- # get_profile_script returns script to be injected inside current html page
753
- # By default, profile_script is appended to the end of all html requests automatically.
754
- # Calling get_profile_script cancels automatic append for the current page
755
- # Use it when:
756
- # * you have disabled auto append behaviour throught :auto_inject => false flag
757
- # * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
758
- def get_profile_script(env)
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"]
772
-
773
- settings = {
774
- path: path,
775
- url: url,
776
- cssUrl: css_url,
777
- version: version,
778
- verticalPosition: @config.vertical_position,
779
- horizontalPosition: @config.horizontal_position,
780
- showTrivial: @config.show_trivial,
781
- showChildren: @config.show_children,
782
- maxTracesToShow: @config.max_traces_to_show,
783
- showControls: @config.show_controls,
784
- showTotalSqlCount: @config.show_total_sql_count,
785
- authorized: true,
786
- toggleShortcut: @config.toggle_shortcut,
787
- startHidden: @config.start_hidden,
788
- collapseResults: @config.collapse_results,
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,
793
- }
794
-
795
- if current && current.page_struct
796
- settings[:ids] = ids_comma_separated(env)
797
- settings[:currentId] = current.page_struct[:id]
798
- else
799
- settings[:ids] = []
800
- settings[:currentId] = ""
801
- end
802
-
803
- # TODO : cache this snippet
804
- script = ::File.read(::File.expand_path('html/profile_handler.js', ::File.dirname(__FILE__)))
805
- # replace the variables
806
- settings.each do |k, v|
807
- regex = Regexp.new("\\{#{k.to_s}\\}")
808
- script.gsub!(regex, v.to_s)
809
- end
810
-
811
- current.inject_js = false if current
812
- script
813
- end
814
-
815
608
  # cancels automatic injection of profile script for the current page
816
609
  def cancel_auto_inject(env)
817
610
  current.inject_js = false
@@ -823,74 +616,6 @@ module Rack
823
616
 
824
617
  private
825
618
 
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
619
  def rails_route_from_path(path, method)
895
620
  if defined?(Rails) && defined?(ActionController::RoutingError)
896
621
  hash = Rails.application.routes.recognize_path(path, method: method)
@@ -902,16 +627,6 @@ module Rack
902
627
  nil
903
628
  end
904
629
 
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
630
  def take_snapshot?(path)
916
631
  @config.snapshot_every_n_requests > 0 &&
917
632
  !path.start_with?(@config.base_url_path) &&
@@ -946,9 +661,5 @@ module Rack
946
661
  self.current = nil
947
662
  results
948
663
  end
949
-
950
- def public_base_path(env)
951
- "#{env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME']}#{@config.base_url_path}"
952
- end
953
664
  end
954
665
  end
@@ -114,6 +114,16 @@ module Rack::MiniProfilerRails
114
114
  Rack::MiniProfiler.binds_to_params(payload[:binds])
115
115
  )
116
116
  end
117
+
118
+ subscribe("instantiation.active_record") do |name, start, finish, id, payload|
119
+ next if !should_measure?
120
+
121
+ Rack::MiniProfiler.report_reader_duration(
122
+ (finish - start) * 1000,
123
+ payload[:record_count],
124
+ payload[:class_name]
125
+ )
126
+ end
117
127
  end
118
128
  end
119
129
  @already_initialized = true
@@ -52,7 +52,7 @@ module Rack::MiniProfilerRailsMethods
52
52
  end
53
53
 
54
54
  def get_webpacker_assets_path
55
- if defined?(Webpacker) && Webpacker.config.config_path.exist?
55
+ if defined?(Webpacker) && Webpacker.try(:config)&.config_path&.exist?
56
56
  Webpacker.config.public_output_path.to_s.gsub(Webpacker.config.public_path.to_s, "")
57
57
  end
58
58
  end
@@ -30,8 +30,8 @@ class SqlPatches
30
30
  def self.sql_patches
31
31
  patches = []
32
32
 
33
- patches << 'mysql2' if defined?(Mysql2::Client) && Mysql2::Client.class == Class
34
- patches << 'pg' if defined?(PG::Result) && PG::Result.class == Class
33
+ patches << 'mysql2' if defined?(Mysql2::Client) && Mysql2::Client.class == Class && patch_rails?
34
+ patches << 'pg' if defined?(PG::Result) && PG::Result.class == Class && patch_rails?
35
35
  patches << 'oracle_enhanced' if defined?(ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter) && ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class == Class &&
36
36
  SqlPatches.correct_version?('~> 1.5.0', ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter) &&
37
37
  patch_rails?
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
21
21
  "CHANGELOG.md"
22
22
  ]
23
23
  s.add_runtime_dependency 'rack', '>= 1.2.0'
24
- s.required_ruby_version = '>= 2.6.0'
24
+ s.required_ruby_version = '>= 2.7.0'
25
25
 
26
26
  s.metadata = {
27
27
  'source_code_uri' => Rack::MiniProfiler::SOURCE_CODE_URI,