rack-mini-profiler 3.1.0 → 3.2.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.
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,9 +372,9 @@ 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
- return client_settings.handle_cookie(self.flamegraph(flamegraph, path))
377
+ return client_settings.handle_cookie(self.flamegraph(flamegraph, path, env))
466
378
  elsif flamegraph # async-flamegraph
467
379
  page_struct[:has_flamegraph] = true
468
380
  page_struct[:flamegraph] = 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,78 +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)
709
- headers = { 'Content-Type' => 'text/html' }
710
- html = <<~HTML
711
- <!DOCTYPE html>
712
- <html>
713
- <head>
714
- <title>Rack::MiniProfiler Flamegraph</title>
715
- <style>
716
- body { margin: 0; height: 100vh; }
717
- #speedscope-iframe { width: 100%; height: 100%; border: none; }
718
- </style>
719
- </head>
720
- <body>
721
- <script type="text/javascript">
722
- var graph = #{JSON.generate(graph)};
723
- var json = JSON.stringify(graph);
724
- var blob = new Blob([json], { type: 'text/plain' });
725
- var objUrl = encodeURIComponent(URL.createObjectURL(blob));
726
- var iframe = document.createElement('IFRAME');
727
- iframe.setAttribute('id', 'speedscope-iframe');
728
- document.body.appendChild(iframe);
729
- var iframeUrl = '#{@config.base_url_path}speedscope/index.html#profileURL=' + objUrl + '&title=' + 'Flamegraph for #{CGI.escape(path)}';
730
- iframe.setAttribute('src', iframeUrl);
731
- </script>
732
- </body>
733
- </html>
734
- HTML
735
- [200, headers, [html]]
736
- end
737
-
738
586
  def ids(env)
739
587
  all = ([current.page_struct[:id]] + (@storage.get_unviewed_ids(user(env)) || [])).uniq
740
588
  if all.size > @config.max_traces_to_show
@@ -748,69 +596,6 @@ module Rack
748
596
  ids(env).join(",")
749
597
  end
750
598
 
751
- # get_profile_script returns script to be injected inside current html page
752
- # By default, profile_script is appended to the end of all html requests automatically.
753
- # Calling get_profile_script cancels automatic append for the current page
754
- # Use it when:
755
- # * you have disabled auto append behaviour throught :auto_inject => false flag
756
- # * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
757
- def get_profile_script(env)
758
- path = "#{env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME']}#{@config.base_url_path}"
759
- version = MiniProfiler::ASSET_VERSION
760
- if @config.assets_url
761
- url = @config.assets_url.call('rack-mini-profiler.js', version, env)
762
- css_url = @config.assets_url.call('rack-mini-profiler.css', version, env)
763
- end
764
-
765
- url = "#{path}includes.js?v=#{version}" if !url
766
- css_url = "#{path}includes.css?v=#{version}" if !css_url
767
-
768
- content_security_policy_nonce = @config.content_security_policy_nonce ||
769
- env["action_dispatch.content_security_policy_nonce"] ||
770
- env["secure_headers_content_security_policy_nonce"]
771
-
772
- settings = {
773
- path: path,
774
- url: url,
775
- cssUrl: css_url,
776
- version: version,
777
- verticalPosition: @config.vertical_position,
778
- horizontalPosition: @config.horizontal_position,
779
- showTrivial: @config.show_trivial,
780
- showChildren: @config.show_children,
781
- maxTracesToShow: @config.max_traces_to_show,
782
- showControls: @config.show_controls,
783
- showTotalSqlCount: @config.show_total_sql_count,
784
- authorized: true,
785
- toggleShortcut: @config.toggle_shortcut,
786
- startHidden: @config.start_hidden,
787
- collapseResults: @config.collapse_results,
788
- htmlContainer: @config.html_container,
789
- hiddenCustomFields: @config.snapshot_hidden_custom_fields.join(','),
790
- cspNonce: content_security_policy_nonce,
791
- hotwireTurboDriveSupport: @config.enable_hotwire_turbo_drive_support,
792
- }
793
-
794
- if current && current.page_struct
795
- settings[:ids] = ids_comma_separated(env)
796
- settings[:currentId] = current.page_struct[:id]
797
- else
798
- settings[:ids] = []
799
- settings[:currentId] = ""
800
- end
801
-
802
- # TODO : cache this snippet
803
- script = ::File.read(::File.expand_path('html/profile_handler.js', ::File.dirname(__FILE__)))
804
- # replace the variables
805
- settings.each do |k, v|
806
- regex = Regexp.new("\\{#{k.to_s}\\}")
807
- script.gsub!(regex, v.to_s)
808
- end
809
-
810
- current.inject_js = false if current
811
- script
812
- end
813
-
814
599
  # cancels automatic injection of profile script for the current page
815
600
  def cancel_auto_inject(env)
816
601
  current.inject_js = false
@@ -822,74 +607,6 @@ module Rack
822
607
 
823
608
  private
824
609
 
825
- def handle_snapshots_request(env)
826
- self.current = nil
827
- MiniProfiler.authorize_request
828
- status = 200
829
- headers = { 'Content-Type' => 'text/html' }
830
- qp = Rack::Utils.parse_nested_query(env['QUERY_STRING'])
831
- if group_name = qp["group_name"]
832
- list = @storage.snapshots_group(group_name)
833
- list.each do |snapshot|
834
- snapshot[:url] = url_for_snapshot(snapshot[:id], group_name)
835
- end
836
- data = {
837
- group_name: group_name,
838
- list: list
839
- }
840
- else
841
- list = @storage.snapshots_overview
842
- list.each do |group|
843
- group[:url] = url_for_snapshots_group(group[:name])
844
- end
845
- data = {
846
- page: "overview",
847
- list: list
848
- }
849
- end
850
- data_html = <<~HTML
851
- <div style="display: none;" id="snapshots-data">
852
- #{data.to_json}
853
- </div>
854
- HTML
855
- response = Rack::Response.new([], status, headers)
856
-
857
- response.write <<~HTML
858
- <!DOCTYPE html>
859
- <html>
860
- <head>
861
- <title>Rack::MiniProfiler Snapshots</title>
862
- </head>
863
- <body class="mp-snapshots">
864
- HTML
865
- response.write(data_html)
866
- script = self.get_profile_script(env)
867
- response.write(script)
868
- response.write <<~HTML
869
- </body>
870
- </html>
871
- HTML
872
- response.finish
873
- end
874
-
875
- def serve_flamegraph(env)
876
- request = Rack::Request.new(env)
877
- id = request.params['id']
878
- page_struct = @storage.load(id)
879
-
880
- if !page_struct
881
- id = ERB::Util.html_escape(id)
882
- user_info = ERB::Util.html_escape(user(env))
883
- return [404, {}, ["Request not found: #{id} - user #{user_info}"]]
884
- end
885
-
886
- if !page_struct[:flamegraph]
887
- return [404, {}, ["No flamegraph available for #{ERB::Util.html_escape(id)}"]]
888
- end
889
-
890
- self.flamegraph(page_struct[:flamegraph], page_struct[:request_path])
891
- end
892
-
893
610
  def rails_route_from_path(path, method)
894
611
  if defined?(Rails) && defined?(ActionController::RoutingError)
895
612
  hash = Rails.application.routes.recognize_path(path, method: method)
@@ -901,16 +618,6 @@ module Rack
901
618
  nil
902
619
  end
903
620
 
904
- def url_for_snapshots_group(group_name)
905
- qs = Rack::Utils.build_query({ group_name: group_name })
906
- "/#{@config.base_url_path.gsub('/', '')}/snapshots?#{qs}"
907
- end
908
-
909
- def url_for_snapshot(id, group_name)
910
- qs = Rack::Utils.build_query({ id: id, group: group_name })
911
- "/#{@config.base_url_path.gsub('/', '')}/results?#{qs}"
912
- end
913
-
914
621
  def take_snapshot?(path)
915
622
  @config.snapshot_every_n_requests > 0 &&
916
623
  !path.start_with?(@config.base_url_path) &&
@@ -75,7 +75,8 @@ module Rack::MiniProfilerRails
75
75
  next if !should_measure?
76
76
 
77
77
  current = Rack::MiniProfiler.current
78
- description = "Executing action: #{payload[:action]}"
78
+ controller_name = payload[:controller].sub(/Controller\z/, '').downcase
79
+ description = "Executing: #{controller_name}##{payload[:action]}"
79
80
  Thread.current[get_key(payload)] = current.current_timer
80
81
  Rack::MiniProfiler.current.current_timer = current.current_timer.add_child(description)
81
82
  end
@@ -113,6 +114,16 @@ module Rack::MiniProfilerRails
113
114
  Rack::MiniProfiler.binds_to_params(payload[:binds])
114
115
  )
115
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
116
127
  end
117
128
  end
118
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,