rack-mini-profiler 3.1.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,9 +381,9 @@ 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
- return client_settings.handle_cookie(self.flamegraph(flamegraph, path))
386
+ return client_settings.handle_cookie(self.flamegraph(flamegraph, path, env))
466
387
  elsif flamegraph # async-flamegraph
467
388
  page_struct[:has_flamegraph] = true
468
389
  page_struct[:flamegraph] = 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,78 +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)
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
595
  def ids(env)
739
596
  all = ([current.page_struct[:id]] + (@storage.get_unviewed_ids(user(env)) || [])).uniq
740
597
  if all.size > @config.max_traces_to_show
@@ -748,69 +605,6 @@ module Rack
748
605
  ids(env).join(",")
749
606
  end
750
607
 
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
608
  # cancels automatic injection of profile script for the current page
815
609
  def cancel_auto_inject(env)
816
610
  current.inject_js = false
@@ -822,74 +616,6 @@ module Rack
822
616
 
823
617
  private
824
618
 
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
619
  def rails_route_from_path(path, method)
894
620
  if defined?(Rails) && defined?(ActionController::RoutingError)
895
621
  hash = Rails.application.routes.recognize_path(path, method: method)
@@ -901,16 +627,6 @@ module Rack
901
627
  nil
902
628
  end
903
629
 
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
630
  def take_snapshot?(path)
915
631
  @config.snapshot_every_n_requests > 0 &&
916
632
  !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,