rack-mini-profiler 3.1.0 → 3.2.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
@@ -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,