rack-mini-profiler 1.0.1 → 2.3.2

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.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +115 -20
  3. data/README.md +126 -45
  4. data/lib/enable_rails_patches.rb +5 -0
  5. data/lib/html/dot.1.1.2.min.js +2 -0
  6. data/lib/html/includes.css +136 -35
  7. data/lib/html/includes.js +1400 -1009
  8. data/lib/html/includes.scss +546 -441
  9. data/lib/html/includes.tmpl +231 -148
  10. data/lib/html/pretty-print.js +810 -0
  11. data/lib/html/profile_handler.js +1 -1
  12. data/lib/html/rack-mini-profiler.css +3 -0
  13. data/lib/html/rack-mini-profiler.js +2 -0
  14. data/lib/html/share.html +0 -1
  15. data/lib/html/speedscope/LICENSE +21 -0
  16. data/lib/html/speedscope/README.md +3 -0
  17. data/lib/html/speedscope/demangle-cpp.1768f4cc.js +4 -0
  18. data/lib/html/speedscope/favicon-16x16.f74b3187.png +0 -0
  19. data/lib/html/speedscope/favicon-32x32.bc503437.png +0 -0
  20. data/lib/html/speedscope/file-format-schema.json +324 -0
  21. data/lib/html/speedscope/fonts/source-code-pro-regular.css +8 -0
  22. data/lib/html/speedscope/fonts/source-code-pro-v13-regular.woff +0 -0
  23. data/lib/html/speedscope/fonts/source-code-pro-v13-regular.woff2 +0 -0
  24. data/lib/html/speedscope/import.cf0fa83f.js +115 -0
  25. data/lib/html/speedscope/index.html +2 -0
  26. data/lib/html/speedscope/release.txt +3 -0
  27. data/lib/html/speedscope/reset.8c46b7a1.css +2 -0
  28. data/lib/html/speedscope/source-map.438fa06b.js +24 -0
  29. data/lib/html/speedscope/speedscope.44364064.js +200 -0
  30. data/lib/html/vendor.js +848 -0
  31. data/lib/mini_profiler/asset_version.rb +3 -2
  32. data/lib/mini_profiler/client_settings.rb +13 -5
  33. data/lib/mini_profiler/config.rb +43 -5
  34. data/lib/mini_profiler/gc_profiler.rb +1 -1
  35. data/lib/mini_profiler/profiler.rb +310 -42
  36. data/lib/mini_profiler/profiling_methods.rb +13 -8
  37. data/lib/mini_profiler/snapshots_transporter.rb +109 -0
  38. data/lib/mini_profiler/storage/abstract_store.rb +79 -1
  39. data/lib/mini_profiler/storage/file_store.rb +3 -3
  40. data/lib/mini_profiler/storage/memcache_store.rb +2 -0
  41. data/lib/mini_profiler/storage/memory_store.rb +54 -5
  42. data/lib/mini_profiler/storage/redis_store.rb +136 -2
  43. data/lib/mini_profiler/timer_struct/custom.rb +1 -0
  44. data/lib/mini_profiler/timer_struct/page.rb +60 -4
  45. data/lib/mini_profiler/timer_struct/request.rb +53 -11
  46. data/lib/mini_profiler/timer_struct/sql.rb +4 -2
  47. data/lib/mini_profiler/version.rb +1 -1
  48. data/lib/mini_profiler_rails/railtie.rb +88 -7
  49. data/lib/mini_profiler_rails/railtie_methods.rb +61 -0
  50. data/lib/patches/db/activerecord.rb +1 -12
  51. data/lib/patches/db/mongo.rb +1 -1
  52. data/lib/patches/db/moped.rb +1 -1
  53. data/lib/patches/db/mysql2.rb +4 -27
  54. data/lib/patches/db/mysql2/alias_method.rb +30 -0
  55. data/lib/patches/db/mysql2/prepend.rb +34 -0
  56. data/lib/patches/db/plucky.rb +4 -4
  57. data/lib/patches/net_patches.rb +18 -8
  58. data/lib/patches/sql_patches.rb +13 -5
  59. data/lib/prepend_mysql2_patch.rb +5 -0
  60. data/lib/prepend_net_http_patch.rb +5 -0
  61. data/lib/rack-mini-profiler.rb +1 -1
  62. data/rack-mini-profiler.gemspec +15 -6
  63. metadata +150 -31
  64. data/lib/html/jquery.1.7.1.js +0 -4
  65. data/lib/html/jquery.tmpl.js +0 -486
  66. data/lib/html/list.css +0 -9
  67. data/lib/html/list.js +0 -38
  68. data/lib/html/list.tmpl +0 -34
@@ -6,6 +6,7 @@ module Rack
6
6
  # Timing system for a custom timers such as cache, redis, RPC, external API
7
7
  # calls, etc.
8
8
  class Custom < TimerStruct::Base
9
+ attr_accessor :parent
9
10
  def initialize(type, duration_ms, page, parent)
10
11
  @parent = parent
11
12
  @page = page
@@ -10,6 +10,53 @@ module Rack
10
10
  # :has_many TimerStruct::Sql children
11
11
  # :has_many TimerStruct::Custom children
12
12
  class Page < TimerStruct::Base
13
+ class << self
14
+ def from_hash(hash)
15
+ hash = symbolize_hash(hash)
16
+ if hash.key?(:custom_timing_names)
17
+ hash[:custom_timing_names] = []
18
+ end
19
+ hash.delete(:started_formatted)
20
+ if hash.key?(:duration_milliseconds)
21
+ hash[:duration_milliseconds] = 0
22
+ end
23
+ page = self.allocate
24
+ page.instance_variable_set(:@attributes, hash)
25
+ page
26
+ end
27
+
28
+ private
29
+
30
+ def symbolize_hash(hash)
31
+ new_hash = {}
32
+ hash.each do |k, v|
33
+ sym_k = String === k ? k.to_sym : k
34
+ if Hash === v
35
+ new_hash[sym_k] = symbolize_hash(v)
36
+ elsif Array === v
37
+ new_hash[sym_k] = symbolize_array(v)
38
+ else
39
+ new_hash[sym_k] = v
40
+ end
41
+ end
42
+ new_hash
43
+ end
44
+
45
+ def symbolize_array(array)
46
+ array.map do |item|
47
+ if Array === item
48
+ symbolize_array(item)
49
+ elsif Hash === item
50
+ symbolize_hash(item)
51
+ else
52
+ item
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ attr_reader :attributes
59
+
13
60
  def initialize(env)
14
61
  timer_id = MiniProfiler.generate_id
15
62
  page_name = env['PATH_INFO']
@@ -39,8 +86,13 @@ module Rack
39
86
  executed_scalars: 0,
40
87
  executed_non_queries: 0,
41
88
  custom_timing_names: [],
42
- custom_timing_stats: {}
89
+ custom_timing_stats: {},
90
+ custom_fields: {},
91
+ has_flamegraph: false,
92
+ flamegraph: nil
43
93
  )
94
+ self[:request_method] = env['REQUEST_METHOD']
95
+ self[:request_path] = env['PATH_INFO']
44
96
  name = "#{env['REQUEST_METHOD']} http://#{env['SERVER_NAME']}:#{env['SERVER_PORT']}#{env['SCRIPT_NAME']}#{env['PATH_INFO']}"
45
97
  self[:root] = TimerStruct::Request.createRoot(name, self)
46
98
  end
@@ -61,17 +113,21 @@ module Rack
61
113
  @attributes[:root]
62
114
  end
63
115
 
116
+ def attributes_to_serialize
117
+ @attributes.keys - [:flamegraph]
118
+ end
119
+
64
120
  def to_json(*a)
65
- ::JSON.generate(@attributes.merge(self.extra_json))
121
+ ::JSON.generate(@attributes.slice(*attributes_to_serialize).merge(extra_json))
66
122
  end
67
123
 
68
124
  def as_json(options = nil)
69
- super(options).merge!(extra_json)
125
+ super(options).slice(*attributes_to_serialize.map(&:to_s)).merge!(extra_json)
70
126
  end
71
127
 
72
128
  def extra_json
73
129
  {
74
- started: '/Date(%d)/' % @attributes[:started_at],
130
+ started_formatted: '/Date(%d)/' % @attributes[:started_at],
75
131
  duration_milliseconds: @attributes[:root][:duration_milliseconds],
76
132
  custom_timing_names: @attributes[:custom_timing_stats].keys.sort
77
133
  }
@@ -11,7 +11,7 @@ module Rack
11
11
  end
12
12
  end
13
13
 
14
- attr_accessor :children_duration
14
+ attr_accessor :children_duration, :start, :parent
15
15
 
16
16
  def initialize(name, page, parent)
17
17
  start_millis = (Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1000).to_i - page[:started]
@@ -62,10 +62,6 @@ module Rack
62
62
  self[:start_milliseconds]
63
63
  end
64
64
 
65
- def start
66
- @start
67
- end
68
-
69
65
  def depth
70
66
  self[:depth]
71
67
  end
@@ -91,6 +87,20 @@ module Rack
91
87
  end
92
88
  end
93
89
 
90
+ def move_child(child, destination)
91
+ if index = self[:children].index(child)
92
+ self[:children].slice!(index)
93
+ self[:has_children] = self[:children].size > 0
94
+
95
+ destination[:children].push(child)
96
+ destination[:has_children] = true
97
+
98
+ child[:parent_timing_id] = destination[:id]
99
+ child.parent = destination
100
+ child.adjust_depth
101
+ end
102
+ end
103
+
94
104
  def add_sql(query, elapsed_ms, page, params = nil, skip_backtrace = false, full_backtrace = false)
95
105
  TimerStruct::Sql.new(query, elapsed_ms, page, self, params, skip_backtrace, full_backtrace).tap do |timer|
96
106
  self[:sql_timings].push(timer)
@@ -102,6 +112,19 @@ module Rack
102
112
  end
103
113
  end
104
114
 
115
+ def move_sql(sql, destination)
116
+ if index = self[:sql_timings].index(sql)
117
+ self[:sql_timings].slice!(index)
118
+ self[:has_sql_timings] = self[:sql_timings].size > 0
119
+ self[:sql_timings_duration_milliseconds] -= sql[:duration_milliseconds]
120
+ destination[:sql_timings].push(sql)
121
+ destination[:has_sql_timings] = true
122
+ destination[:sql_timings_duration_milliseconds] += sql[:duration_milliseconds]
123
+ sql[:parent_timing_id] = destination[:id]
124
+ sql.parent = destination
125
+ end
126
+ end
127
+
105
128
  def add_custom(type, elapsed_ms, page)
106
129
  TimerStruct::Custom.new(type, elapsed_ms, page, self).tap do |timer|
107
130
  timer[:parent_timing_id] = self[:id]
@@ -119,18 +142,37 @@ module Rack
119
142
  end
120
143
  end
121
144
 
145
+ def move_custom(type, custom, destination)
146
+ if index = self[:custom_timings][type]&.index(custom)
147
+ custom[:parent_timing_id] = destination[:id]
148
+ custom.parent = destination
149
+ self[:custom_timings][type].slice!(index)
150
+ if self[:custom_timings][type].size == 0
151
+ self[:custom_timings].delete(type)
152
+ self[:custom_timing_stats].delete(type)
153
+ else
154
+ self[:custom_timing_stats][type][:count] -= 1
155
+ self[:custom_timing_stats][type][:duration] -= custom[:duration_milliseconds]
156
+ end
157
+ destination[:custom_timings][type] ||= []
158
+ destination[:custom_timings][type].push(custom)
159
+ destination[:custom_timing_stats][type] ||= { count: 0, duration: 0.0 }
160
+ destination[:custom_timing_stats][type][:count] += 1
161
+ destination[:custom_timing_stats][type][:duration] += custom[:duration_milliseconds]
162
+ end
163
+ end
164
+
122
165
  def record_time(milliseconds = nil)
123
166
  milliseconds ||= (Process.clock_gettime(Process::CLOCK_MONOTONIC) - @start) * 1000
124
167
  self[:duration_milliseconds] = milliseconds
125
168
  self[:is_trivial] = true if milliseconds < self[:trivial_duration_threshold_milliseconds]
126
- self[:duration_without_children_milliseconds] = milliseconds - @children_duration
127
-
128
- if @parent
129
- @parent.children_duration += milliseconds
130
- end
131
-
169
+ self[:duration_without_children_milliseconds] = milliseconds - self[:children].sum(&:duration_ms)
132
170
  end
133
171
 
172
+ def adjust_depth
173
+ self[:depth] = self.parent ? self.parent[:depth] + 1 : 0
174
+ self[:children].each(&:adjust_depth)
175
+ end
134
176
  end
135
177
  end
136
178
  end
@@ -6,6 +6,8 @@ module Rack
6
6
  # Timing system for a SQL query
7
7
  module TimerStruct
8
8
  class Sql < TimerStruct::Base
9
+ attr_accessor :parent
10
+
9
11
  def initialize(query, duration_ms, page, parent, params = nil, skip_backtrace = false, full_backtrace = false)
10
12
 
11
13
  stack_trace = nil
@@ -36,12 +38,12 @@ module Rack
36
38
  start_millis = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1000).to_i - page[:started]) - duration_ms
37
39
  super(
38
40
  execute_type: 3, # TODO
39
- formatted_command_string: query,
41
+ formatted_command_string: query ? ERB::Util.html_escape(query) : nil,
40
42
  stack_trace_snippet: stack_trace,
41
43
  start_milliseconds: start_millis,
42
44
  duration_milliseconds: duration_ms,
43
45
  first_fetch_duration_milliseconds: duration_ms,
44
- parameters: trim_binds(params),
46
+ parameters: query ? trim_binds(params) : nil,
45
47
  parent_timing_id: nil,
46
48
  is_duplicate: false
47
49
  )
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Rack
4
4
  class MiniProfiler
5
- VERSION = '1.0.1'
5
+ VERSION = '2.3.2'
6
6
  end
7
7
  end
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'fileutils'
4
+ require_relative './railtie_methods'
4
5
 
5
6
  module Rack::MiniProfilerRails
7
+ extend Rack::MiniProfilerRailsMethods
6
8
 
7
9
  # call direct if needed to do a defer init
8
10
  def self.initialize!(app)
@@ -28,10 +30,12 @@ module Rack::MiniProfilerRails
28
30
 
29
31
  if serves_static_assets?(app)
30
32
  c.skip_paths << app.config.assets.prefix
33
+ wp_assets_path = get_webpacker_assets_path()
34
+ c.skip_paths << wp_assets_path if wp_assets_path
31
35
  end
32
36
 
33
37
  unless Rails.env.development? || Rails.env.test?
34
- c.authorization_mode = :whitelist
38
+ c.authorization_mode = :allow_authorized
35
39
  end
36
40
 
37
41
  if Rails.logger
@@ -55,16 +59,92 @@ module Rack::MiniProfilerRails
55
59
 
56
60
  # Install the Middleware
57
61
  app.middleware.insert(0, Rack::MiniProfiler)
62
+ c.enable_advanced_debugging_tools = Rails.env.development?
58
63
 
59
- # Attach to various Rails methods
60
- ActiveSupport.on_load(:action_controller) do
61
- ::Rack::MiniProfiler.profile_method(ActionController::Base, :process) { |action| "Executing action: #{action}" }
64
+ if ::Rack::MiniProfiler.patch_rails?
65
+ # Attach to various Rails methods
66
+ ActiveSupport.on_load(:action_controller) do
67
+ ::Rack::MiniProfiler.profile_method(ActionController::Base, :process) { |action| "Executing action: #{action}" }
68
+ end
69
+
70
+ ActiveSupport.on_load(:action_view) do
71
+ ::Rack::MiniProfiler.profile_method(ActionView::Template, :render) { |x, y| "Rendering: #{@virtual_path}" }
72
+ end
73
+ else
74
+ subscribe("start_processing.action_controller") do |name, start, finish, id, payload|
75
+ next if !should_measure?
76
+
77
+ current = Rack::MiniProfiler.current
78
+ description = "Executing action: #{payload[:action]}"
79
+ Thread.current[get_key(payload)] = current.current_timer
80
+ Rack::MiniProfiler.current.current_timer = current.current_timer.add_child(description)
81
+ end
82
+
83
+ subscribe("process_action.action_controller") do |name, start, finish, id, payload|
84
+ next if !should_measure?
85
+
86
+ key = get_key(payload)
87
+ parent_timer = Thread.current[key]
88
+ next if !parent_timer
89
+
90
+ Thread.current[key] = nil
91
+ Rack::MiniProfiler.current.current_timer.record_time
92
+ Rack::MiniProfiler.current.current_timer = parent_timer
93
+ end
94
+
95
+ subscribe("render_partial.action_view") do |name, start, finish, id, payload|
96
+ render_notification_handler(shorten_identifier(payload[:identifier]), finish, start)
97
+ end
98
+
99
+ subscribe("render_template.action_view") do |name, start, finish, id, payload|
100
+ render_notification_handler(shorten_identifier(payload[:identifier]), finish, start)
101
+ end
102
+
103
+ if Rack::MiniProfiler.subscribe_sql_active_record
104
+ # we don't want to subscribe if we've already patched a DB driver
105
+ # otherwise we would end up with 2 records for every query
106
+ subscribe("sql.active_record") do |name, start, finish, id, payload|
107
+ next if !should_measure?
108
+ next if payload[:name] =~ /SCHEMA/ && Rack::MiniProfiler.config.skip_schema_queries
109
+
110
+ Rack::MiniProfiler.record_sql(
111
+ payload[:sql],
112
+ (finish - start) * 1000,
113
+ Rack::MiniProfiler.binds_to_params(payload[:binds])
114
+ )
115
+ end
116
+ end
62
117
  end
63
- ActiveSupport.on_load(:action_view) do
64
- ::Rack::MiniProfiler.profile_method(ActionView::Template, :render) { |x, y| "Rendering: #{@virtual_path}" }
118
+ @already_initialized = true
119
+ end
120
+
121
+ def self.create_engine
122
+ return if defined?(Rack::MiniProfilerRails::Engine)
123
+ klass = Class.new(::Rails::Engine) do
124
+ engine_name 'rack-mini-profiler'
125
+ config.assets.paths << File.expand_path('../../html', __FILE__)
126
+ config.assets.precompile << 'rack-mini-profiler.js'
127
+ config.assets.precompile << 'rack-mini-profiler.css'
65
128
  end
129
+ Rack::MiniProfilerRails.const_set("Engine", klass)
130
+ end
66
131
 
67
- @already_initialized = true
132
+ def self.subscribe(event, &blk)
133
+ if ActiveSupport::Notifications.respond_to?(:monotonic_subscribe)
134
+ ActiveSupport::Notifications.monotonic_subscribe(event) { |*args| blk.call(*args) }
135
+ else
136
+ ActiveSupport::Notifications.subscribe(event) do |name, start, finish, id, payload|
137
+ blk.call(name, start.to_f, finish.to_f, id, payload)
138
+ end
139
+ end
140
+ end
141
+
142
+ def self.get_key(payload)
143
+ "mini_profiler_parent_timer_#{payload[:controller]}_#{payload[:action]}".to_sym
144
+ end
145
+
146
+ def self.shorten_identifier(identifier)
147
+ identifier.split('/').last(2).join('/')
68
148
  end
69
149
 
70
150
  def self.serves_static_assets?(app)
@@ -95,6 +175,7 @@ module Rack::MiniProfilerRails
95
175
  middlewares = app.middleware.middlewares
96
176
  if Rack::MiniProfiler.config.suppress_encoding.nil? &&
97
177
  middlewares.include?(Rack::Deflater) &&
178
+ middlewares.include?(Rack::MiniProfiler) &&
98
179
  middlewares.index(Rack::Deflater) > middlewares.index(Rack::MiniProfiler)
99
180
  Rack::MiniProfiler.config.suppress_encoding = true
100
181
  end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack::MiniProfilerRailsMethods
4
+ def render_notification_handler(name, finish, start, name_as_description: false)
5
+ return if !should_measure?
6
+
7
+ description = name_as_description ? name : "Rendering: #{name}"
8
+ current = Rack::MiniProfiler.current.current_timer
9
+ node = current.add_child(description)
10
+ duration = finish - start
11
+ duration_ms = duration * 1000
12
+ node.start -= duration
13
+ node[:start_milliseconds] -= duration_ms
14
+ node.record_time(duration_ms)
15
+
16
+ children_duration = 0
17
+ to_be_moved = { requests: [], sql: [], custom: {} }
18
+ current.children.each do |child|
19
+ next if child == node
20
+ if should_move?(child, node)
21
+ to_be_moved[:requests] << child
22
+ children_duration += child[:duration_milliseconds]
23
+ end
24
+ end
25
+ node[:duration_without_children_milliseconds] = duration_ms - children_duration
26
+ to_be_moved[:requests].each { |req| current.move_child(req, node) }
27
+
28
+ current.sql_timings.each do |sql|
29
+ to_be_moved[:sql] << sql if should_move?(sql, node)
30
+ end
31
+ to_be_moved[:sql].each { |sql| current.move_sql(sql, node) }
32
+
33
+ current.custom_timings.each do |type, timings|
34
+ to_be_moved[:custom] = []
35
+ timings.each do |custom|
36
+ to_be_moved[:custom] << custom if should_move?(custom, node)
37
+ end
38
+ to_be_moved[:custom].each { |custom| current.move_custom(type, custom, node) }
39
+ end
40
+ end
41
+
42
+ def should_measure?
43
+ current = Rack::MiniProfiler.current
44
+ current && current.measure
45
+ end
46
+
47
+ def should_move?(child, node)
48
+ start = :start_milliseconds
49
+ duration = :duration_milliseconds
50
+ child[start] >= node[start] &&
51
+ child[start] + child[duration] <= node[start] + node[duration]
52
+ end
53
+
54
+ def get_webpacker_assets_path
55
+ if defined?(Webpacker) && Webpacker.config.config_path.exist?
56
+ Webpacker.config.public_output_path.to_s.gsub(Webpacker.config.public_path.to_s, "")
57
+ end
58
+ end
59
+
60
+ extend self
61
+ end
@@ -15,17 +15,6 @@ module Rack
15
15
  end
16
16
  end
17
17
 
18
- def binds_to_params(binds)
19
- return if binds.nil? || Rack::MiniProfiler.config.max_sql_param_length == 0
20
- # map ActiveRecord::Relation::QueryAttribute to [name, value]
21
- params = binds.map { |c| c.kind_of?(Array) ? [c.first, c.last] : [c.name, c.value] }
22
- if (skip = Rack::MiniProfiler.config.skip_sql_param_names)
23
- params.map { |(n, v)| n =~ skip ? [n, nil] : [n, v] }
24
- else
25
- params
26
- end
27
- end
28
-
29
18
  def log_with_miniprofiler(*args, &block)
30
19
  return log_without_miniprofiler(*args, &block) unless SqlPatches.should_measure?
31
20
 
@@ -37,7 +26,7 @@ module Rack
37
26
  return rval if Rack::MiniProfiler.config.skip_schema_queries && name =~ (/SCHEMA/)
38
27
 
39
28
  elapsed_time = SqlPatches.elapsed_time(start)
40
- Rack::MiniProfiler.record_sql(sql, elapsed_time, binds_to_params(binds))
29
+ Rack::MiniProfiler.record_sql(sql, elapsed_time, Rack::MiniProfiler.binds_to_params(binds))
41
30
  rval
42
31
  end
43
32
  end