rack-mini-profiler 3.1.1 → 3.2.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.
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
@@ -397,18 +320,7 @@ module Rack
397
320
  )
398
321
  end
399
322
  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]]
323
+ status, headers, body = [200, { 'Content-Type' => 'text/html' }, [blank_page_html]]
412
324
  else
413
325
  status, headers, body = @app.call(env)
414
326
  end
@@ -429,7 +341,7 @@ module Rack
429
341
  if trace_exceptions
430
342
  body.close if body.respond_to? :close
431
343
 
432
- query_params = Rack::Utils.parse_nested_query(query_string)
344
+ query_params = action_parameters(env)
433
345
  trace_exceptions_filter = query_params['trace_exceptions_filter']
434
346
  if trace_exceptions_filter
435
347
  trace_exceptions_regex = Regexp.new(trace_exceptions_filter)
@@ -439,19 +351,19 @@ module Rack
439
351
  return client_settings.handle_cookie(dump_exceptions exceptions)
440
352
  end
441
353
 
442
- if query_string =~ /#{@config.profile_parameter}=env/
354
+ if matches_action?("env", env)
443
355
  return tool_disabled_message(client_settings) if !advanced_debugging_enabled?
444
356
  body.close if body.respond_to? :close
445
357
  return client_settings.handle_cookie(dump_env env)
446
358
  end
447
359
 
448
- if query_string =~ /#{@config.profile_parameter}=analyze-memory/
360
+ if matches_action?("analyze-memory", env)
449
361
  return tool_disabled_message(client_settings) if !advanced_debugging_enabled?
450
362
  body.close if body.respond_to? :close
451
363
  return client_settings.handle_cookie(analyze_memory)
452
364
  end
453
365
 
454
- if query_string =~ /#{@config.profile_parameter}=help/
366
+ if matches_action?("help", env)
455
367
  body.close if body.respond_to? :close
456
368
  return client_settings.handle_cookie(help(client_settings, env))
457
369
  end
@@ -460,7 +372,7 @@ module Rack
460
372
  page_struct[:user] = user(env)
461
373
  page_struct[:root].record_time((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000)
462
374
 
463
- if flamegraph && query_string =~ /#{@config.profile_parameter}=flamegraph/
375
+ if flamegraph && matches_action?("flamegraph", env)
464
376
  body.close if body.respond_to? :close
465
377
  return client_settings.handle_cookie(self.flamegraph(flamegraph, path, env))
466
378
  elsif flamegraph # async-flamegraph
@@ -485,12 +397,20 @@ module Rack
485
397
  end
486
398
 
487
399
  client_settings.handle_cookie([status, headers, body])
488
-
489
400
  ensure
490
401
  # Make sure this always happens
491
402
  self.current = nil
492
403
  end
493
404
 
405
+ def matches_action?(action, env)
406
+ env['QUERY_STRING'] =~ /#{@config.profile_parameter}=#{action}/ ||
407
+ env['HTTP_X_RACK_MINI_PROFILER'] == action
408
+ end
409
+
410
+ def action_parameters(env)
411
+ query_params = Rack::Utils.parse_nested_query(env['QUERY_STRING'])
412
+ end
413
+
494
414
  def inject_profiler(env, status, headers, body)
495
415
  # mini profiler is meddling with stuff, we can not cache cause we will get incorrect data
496
416
  # Rack::ETag has already inserted some nonesense in the chain
@@ -663,79 +583,6 @@ module Rack
663
583
  [status, headers, [body]]
664
584
  end
665
585
 
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
586
  def ids(env)
740
587
  all = ([current.page_struct[:id]] + (@storage.get_unviewed_ids(user(env)) || [])).uniq
741
588
  if all.size > @config.max_traces_to_show
@@ -749,69 +596,6 @@ module Rack
749
596
  ids(env).join(",")
750
597
  end
751
598
 
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
599
  # cancels automatic injection of profile script for the current page
816
600
  def cancel_auto_inject(env)
817
601
  current.inject_js = false
@@ -823,74 +607,6 @@ module Rack
823
607
 
824
608
  private
825
609
 
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
610
  def rails_route_from_path(path, method)
895
611
  if defined?(Rails) && defined?(ActionController::RoutingError)
896
612
  hash = Rails.application.routes.recognize_path(path, method: method)
@@ -902,16 +618,6 @@ module Rack
902
618
  nil
903
619
  end
904
620
 
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
621
  def take_snapshot?(path)
916
622
  @config.snapshot_every_n_requests > 0 &&
917
623
  !path.start_with?(@config.base_url_path) &&
@@ -946,9 +652,5 @@ module Rack
946
652
  self.current = nil
947
653
  results
948
654
  end
949
-
950
- def public_base_path(env)
951
- "#{env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME']}#{@config.base_url_path}"
952
- end
953
655
  end
954
656
  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,