rack-mini-profiler 1.1.4 → 2.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 23beddd8a924076759b5f25229f0e9a2c91dee5b916f2e4712381c247ad4922d
4
- data.tar.gz: af7fc0fbe8008e5159e86b4cdf4fb19d694e15ea125eae2c776b0815df3dca0f
3
+ metadata.gz: b7fcca7a28de2898e8e320f2f94db5d96eee3128b9be7a48053e472036a6ef95
4
+ data.tar.gz: 3a21bb993d77ef33baca78135716ab0e44dfbd28eefbb8874bae0b9a715ccb3f
5
5
  SHA512:
6
- metadata.gz: bc6a2e360c45f76f9a66f12e649932ced7a2fdad4e6102eb551df688065970ef47d6b3a4871fa3f642c6b86884b236b547c98fc14de5ba93ee44c4fdbf4a6f90
7
- data.tar.gz: f6d0ea244eac7b8a40adb7556a7185a245d378402248bd9317be18a616ed2d4c4ba4203401600bb9416f38f875762d688a1191a52a17c643793d4a5a4607f6b5
6
+ metadata.gz: 2b4a31ef4e964fbd76646425d1ff16007c56d77360c6ad1f6923e753cd8830614098867ce06d5a554810564674b654b8868b1cdddc9c815942e25314459b3de4
7
+ data.tar.gz: fb3a9d99bb23a5e5d7b1518cd122a4240658b6c0c5d0c06f1852ddf472e9761cf9e46db5ce6cdcd92d574ec81ec3789f42aee51fe9ee20d8f587b51059cd55e1
@@ -1,5 +1,28 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 2.0.2 - 2020-05-25
4
+
5
+ - [FIX] client timings were not showing up when you clicked show trivial
6
+
7
+ ## 2.0.1 - 2020-03-17
8
+
9
+ - [REVERT] Prepend Net::HTTP patch instead of class_eval and aliasing (#429) (technique clashes with New Relic and Skylight agents)
10
+
11
+ ## 2.0.0 - 2020-03-11
12
+
13
+ - [FEATURE] Prepend Net::HTTP patch instead of class_eval and aliasing (#429)
14
+ - [FEATURE] Stop patching Rails and use `ActiveSupport::Notifications` by default (see README.md for details)
15
+
16
+ ## 1.1.6 - 2020-01-30
17
+
18
+ - [FIX] edge condition on page transition function could lead to exceptions
19
+
20
+ ## 1.1.5 - 2020-01-28
21
+
22
+ - [FIX] correct custom counter regression
23
+ - [FIX] respect max_traces_to_show
24
+ - [FIX] handle storage engine failures in whitelist mode
25
+
3
26
  ## 1.1.4 - 2019-12-12
4
27
 
5
28
  - [SECURITY] carefully crafted SQL could cause an XSS on sites that do not use CSPs
data/README.md CHANGED
@@ -51,6 +51,20 @@ gem 'stackprof'
51
51
 
52
52
  All you have to do is to include the Gem and you're good to go in development. See notes below for use in production.
53
53
 
54
+ #### Upgrading to version 2.0.0
55
+
56
+ Prior to version 2.0.0, Mini Profiler patched various Rails methods to get the information it needed such as template rendering time. Starting from version 2.0.0, Mini Profiler doesn't patch any Rails methods by default and relies on `ActiveSupport::Notifications` to get the information it needs from Rails. If you want Mini Profiler to keep using its patches in version 2.0.0 and later, change the gem line in your `Gemfile` to the following:
57
+
58
+ If you want to manually require Mini Profiler:
59
+ ```ruby
60
+ gem 'rack-mini-profiler', require: ['enable_rails_patches']
61
+ ```
62
+
63
+ If you don't want to manually require Mini Profiler:
64
+ ```ruby
65
+ gem 'rack-mini-profiler', require: ['enable_rails_patches', 'rack-mini-profiler']
66
+ ```
67
+
54
68
  #### Rails and manual initialization
55
69
 
56
70
  In case you need to make sure rack_mini_profiler is initialized after all other gems, or you want to execute some code before rack_mini_profiler required:
@@ -393,6 +407,16 @@ if JSON.const_defined?(:Pure)
393
407
  end
394
408
  ```
395
409
 
410
+ ## Development
411
+
412
+ If you want to contribute to this project, that's great, thank you! You can run the following rake task:
413
+
414
+ ```
415
+ $ bundle exec rake client_dev
416
+ ```
417
+
418
+ which will start a local Sinatra server at `http://localhost:9292` where you'll be able to preview your changes. Refreshing the page should be enough to see any changes you make to files in the `lib/html` directory.
419
+
396
420
  ## Running the Specs
397
421
 
398
422
  ```
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+ MINI_PROFILER_ENABLE_RAILS_PATCHES = true
5
+ end
@@ -79,20 +79,6 @@ var MiniProfiler = (function() {
79
79
  return localStorage[getVersionedKey(keyPrefix)];
80
80
  };
81
81
 
82
- var compileTemplates = function compileTemplates(data) {
83
- var element = document.createElement("DIV");
84
- element.innerHTML = data;
85
- var templates = {};
86
- var children = element.children;
87
-
88
- for (var i = 0; i < children.length; i++) {
89
- var child = children[i];
90
- templates[child.id] = doT.compile(child.innerHTML);
91
- }
92
-
93
- MiniProfiler.templates = templates;
94
- };
95
-
96
82
  var getClientPerformance = function getClientPerformance() {
97
83
  return window.performance === null ? null : window.performance;
98
84
  };
@@ -122,18 +108,8 @@ var MiniProfiler = (function() {
122
108
  // ie is buggy strip out functions
123
109
  var copy = {
124
110
  navigation: {},
125
- timing: {}
111
+ timing: clientPerformance.timing.toJSON()
126
112
  };
127
- var timing = extend({}, clientPerformance.timing);
128
-
129
- for (p in timing) {
130
- if (
131
- timing.hasOwnProperty(p) &&
132
- !(typeof timing[p] === "function")
133
- ) {
134
- copy.timing[p] = timing[p];
135
- }
136
- }
137
113
 
138
114
  if (clientPerformance.navigation) {
139
115
  copy.navigation.redirectCount =
@@ -161,10 +137,13 @@ var MiniProfiler = (function() {
161
137
  (function() {
162
138
  var request = new XMLHttpRequest();
163
139
  var url = options.path + "results";
164
- var params = "id="
165
- .concat(id, "&clientPerformance=")
166
- .concat(clientPerformance, "&clientProbes=")
167
- .concat(clientProbes, "&popup=1");
140
+ var params = {
141
+ id: id,
142
+ clientPerformance: clientPerformance,
143
+ clientProbes: clientProbes,
144
+ popup: 1
145
+ };
146
+ var queryParam = toQueryString(params);
168
147
  request.open("POST", url, true);
169
148
 
170
149
  request.onload = function() {
@@ -186,24 +165,45 @@ var MiniProfiler = (function() {
186
165
  "Content-Type",
187
166
  "application/x-www-form-urlencoded"
188
167
  );
189
- request.send(params);
168
+ request.send(queryParam);
190
169
  })();
191
170
  }
192
171
  }
193
172
  };
194
173
 
195
- var extend = function extend(out) {
196
- out = out || {};
197
-
198
- for (var i = 1; i < _arguments.length; i++) {
199
- if (!_arguments[i]) continue;
200
-
201
- for (var key in _arguments[i]) {
202
- if (_arguments[i].hasOwnProperty(key)) out[key] = _arguments[i][key];
174
+ var toQueryString = function toQueryString(data, parentKey) {
175
+ var result = [];
176
+ for (var key in data) {
177
+ var val = data[key];
178
+ var newKey = !parentKey ? key : parentKey + "[" + key + "]";
179
+ if (
180
+ typeof val === "object" &&
181
+ !Array.isArray(val) &&
182
+ val !== null &&
183
+ val !== undefined
184
+ ) {
185
+ result[result.length] = toQueryString(val, newKey);
186
+ } else {
187
+ if (Array.isArray(val)) {
188
+ val.forEach(function(v) {
189
+ result[result.length] =
190
+ encodeURIComponent(newKey + "[]") + "=" + encodeURIComponent(v);
191
+ });
192
+ } else if (val === null || val === undefined) {
193
+ result[result.length] = encodeURIComponent(newKey) + "=";
194
+ } else {
195
+ result[result.length] =
196
+ encodeURIComponent(newKey) +
197
+ "=" +
198
+ encodeURIComponent(val.toString());
199
+ }
203
200
  }
204
201
  }
205
-
206
- return out;
202
+ return result
203
+ .filter(function(element) {
204
+ return element && element.length > 0;
205
+ })
206
+ .join("&");
207
207
  };
208
208
 
209
209
  var renderTemplate = function renderTemplate(json) {
@@ -284,7 +284,7 @@ var MiniProfiler = (function() {
284
284
  }); // limit count
285
285
 
286
286
  if (
287
- container.querySelector(".profiler-result").length >
287
+ container.querySelectorAll(".profiler-result").length >
288
288
  options.maxTracesToShow
289
289
  ) {
290
290
  var elem = container.querySelector(".profiler-result");
@@ -746,7 +746,24 @@ var MiniProfiler = (function() {
746
746
  XMLHttpRequest.prototype.send = function(data) {
747
747
  ajaxStartTime = new Date();
748
748
  this.addEventListener("load", function() {
749
- // should be an array of strings, e.g. ["008c4813-9bd7-443d-9376-9441ec4d6a8c","16ff377b-8b9c-4c20-a7b5-97cd9fa7eea7"]
749
+ // responseURL isn't available in IE11
750
+ if (
751
+ this.responseURL &&
752
+ this.responseURL.indexOf(window.location.origin) !== 0
753
+ ) {
754
+ return;
755
+ }
756
+ // getAllResponseHeaders isn't available in Edge.
757
+ var allHeaders = this.getAllResponseHeaders
758
+ ? this.getAllResponseHeaders()
759
+ : null;
760
+ if (
761
+ allHeaders &&
762
+ allHeaders.toLowerCase().indexOf("x-miniprofiler-ids") === -1
763
+ ) {
764
+ return;
765
+ }
766
+ // should be a string of comma-separated ids
750
767
  var stringIds = this.getResponseHeader("X-MiniProfiler-Ids");
751
768
 
752
769
  if (stringIds) {
@@ -1069,7 +1086,9 @@ var MiniProfiler = (function() {
1069
1086
  },
1070
1087
  pageTransition: function pageTransition() {
1071
1088
  if (totalsControl) {
1072
- totalsControl.parentElement.removeChild(totalsControl);
1089
+ if (totalsControl.parentElement) {
1090
+ totalsControl.parentElement.removeChild(totalsControl);
1091
+ }
1073
1092
  totalsControl = null;
1074
1093
  }
1075
1094
 
@@ -128,8 +128,8 @@
128
128
  {{? it.custom_link}}
129
129
  <a href="{{= it.custom_link }}" class="profiler-custom-link" target="_blank">{{= it.custom_link_name }}</a>
130
130
  {{?}}
131
- {{? it.has_trivial_timings}}
132
- <a class="profiler-toggle-trivial" data-show-on-load="{{= it.has_all_trivial_timings }}" title="toggles any rows with &lt; {{= it.trivial_duration_threshold_milliseconds }} ms">
131
+ {{? it.page.has_trivial_timings}}
132
+ <a class="profiler-toggle-trivial" data-show-on-load="{{= it.page.has_all_trivial_timings }}" title="toggles any rows with &lt; {{= it.page.trivial_duration_threshold_milliseconds }} ms">
133
133
  show trivial
134
134
  </a>
135
135
  {{?}}
@@ -181,7 +181,7 @@
181
181
 
182
182
  {{? it.timing.has_children}}
183
183
  {{~ it.timing.children :value}}
184
- {{= MiniProfiler.templates.timingTemplate({timing: value, page: it}) }}
184
+ {{= MiniProfiler.templates.timingTemplate({timing: value, page: it.page}) }}
185
185
  {{~}}
186
186
  {{?}}
187
187
  </script>
@@ -11,11 +11,11 @@ var out='
  }
12
12
  MiniProfiler.templates["linksTemplate"] = function anonymous(it
13
13
  ) {
14
- var out=' <a href="'+( MiniProfiler.shareUrl(it.page.id) )+'" class="profiler-share-profiler-results" target="_blank">share</a> <a href="'+( MiniProfiler.moreUrl(it.timing.name) )+'" class="profiler-more-actions">more</a> ';if(it.custom_link){out+=' <a href="'+( it.custom_link )+'" class="profiler-custom-link" target="_blank">'+( it.custom_link_name )+'</a> ';}out+=' ';if(it.has_trivial_timings){out+=' <a class="profiler-toggle-trivial" data-show-on-load="'+( it.has_all_trivial_timings )+'" title="toggles any rows with &lt; '+( it.trivial_duration_threshold_milliseconds )+' ms"> show trivial </a> ';}return out;
14
+ var out=' <a href="'+( MiniProfiler.shareUrl(it.page.id) )+'" class="profiler-share-profiler-results" target="_blank">share</a> <a href="'+( MiniProfiler.moreUrl(it.timing.name) )+'" class="profiler-more-actions">more</a> ';if(it.custom_link){out+=' <a href="'+( it.custom_link )+'" class="profiler-custom-link" target="_blank">'+( it.custom_link_name )+'</a> ';}out+=' ';if(it.page.has_trivial_timings){out+=' <a class="profiler-toggle-trivial" data-show-on-load="'+( it.page.has_all_trivial_timings )+'" title="toggles any rows with &lt; '+( it.page.trivial_duration_threshold_milliseconds )+' ms"> show trivial </a> ';}return out;
15
15
  }
16
16
  MiniProfiler.templates["timingTemplate"] = function anonymous(it
17
17
  ) {
18
- var out=' <tr class="';if(it.timing.is_trivial){out+='profiler-trivial';}out+='" data-timing-id="'+( it.timing.id )+'"> <td class="profiler-label" title="';if(it.timing.name && it.timing.name.length > 45){out+=''+( it.timing.name );}out+='"> <span class="profiler-indent">'+( MiniProfiler.renderIndent(it.timing.depth) )+'</span> '+( it.timing.name.slice(0,45) );if(it.timing.name && it.timing.name.length > 45){out+='...';}out+=' </td> <td class="profiler-duration" title="duration of this step without any children\'s durations"> '+( MiniProfiler.formatDuration(it.timing.duration_without_children_milliseconds) )+' </td> <td class="profiler-duration profiler-duration-with-children" title="duration of this step and its children"> '+( MiniProfiler.formatDuration(it.timing.duration_milliseconds) )+' </td> <td class="profiler-duration time-from-start" title="time elapsed since profiling started"> <span class="profiler-unit">+</span>'+( MiniProfiler.formatDuration(it.timing.start_milliseconds) )+' </td> ';if(it.timing.has_sql_timings){out+=' <td class="profiler-duration ';if(it.timing.has_duplicate_sql_timings){out+='profiler-warning';}out+='" title="';if(it.timing.has_duplicate_sql_timings){out+='duplicate queries detected - ';}if(it.timing.executed_readers > 0 || it.timing.executed_scalars > 0 || it.timing.executed_non_queries > 0){out+=''+( it.timing.executed_readers )+' reader, '+( it.timing.executed_scalars )+' scalar, '+( it.timing.executed_non_queries )+' non-query statements executed';}out+='"> <a class="profiler-queries-show"> ';if(it.timing.has_duplicate_sql_timings){out+='<span class="profiler-nuclear">!</span>';}out+=' '+( it.timing.sql_timings.length )+' <span class="profiler-unit">sql</span> </a> </td> <td class="profiler-duration" title="aggregate duration of all queries in this step (excludes children)"> '+( MiniProfiler.formatDuration(it.timing.sql_timings_duration_milliseconds) )+' </td> ';}else{out+=' <td colspan="2"></td> ';}out+=' ';var arr1=it.page.custom_timing_names;if(arr1){var value,i1=-1,l1=arr1.length-1;while(i1<l1){value=arr1[i1+=1];out+=' ';if(it.timing.custom_timings && it.timing.custom_timings[value]){out+=' <td class="profiler-duration" title="aggregate number of all '+( value.toLowerCase() )+' invocations in this step (excludes children)"> '+( it.timing.custom_timings[value].length )+' '+( value.toLowerCase() )+' </td> <td class="profiler-duration" title="aggregate duration of all '+( value.toLowerCase() )+' invocations in this step (excludes children)"> '+( MiniProfiler.formatDuration(it.timing.custom_timing_stats[value].duration) )+' </td> ';}else{out+=' <td colspan="2"></td> ';}out+=' ';} } out+=' </tr> ';if(it.timing.has_children){out+=' ';var arr2=it.timing.children;if(arr2){var value,i2=-1,l2=arr2.length-1;while(i2<l2){value=arr2[i2+=1];out+=' '+( MiniProfiler.templates.timingTemplate({timing: value, page: it}) )+' ';} } out+=' ';}return out;
18
+ var out=' <tr class="';if(it.timing.is_trivial){out+='profiler-trivial';}out+='" data-timing-id="'+( it.timing.id )+'"> <td class="profiler-label" title="';if(it.timing.name && it.timing.name.length > 45){out+=''+( it.timing.name );}out+='"> <span class="profiler-indent">'+( MiniProfiler.renderIndent(it.timing.depth) )+'</span> '+( it.timing.name.slice(0,45) );if(it.timing.name && it.timing.name.length > 45){out+='...';}out+=' </td> <td class="profiler-duration" title="duration of this step without any children\'s durations"> '+( MiniProfiler.formatDuration(it.timing.duration_without_children_milliseconds) )+' </td> <td class="profiler-duration profiler-duration-with-children" title="duration of this step and its children"> '+( MiniProfiler.formatDuration(it.timing.duration_milliseconds) )+' </td> <td class="profiler-duration time-from-start" title="time elapsed since profiling started"> <span class="profiler-unit">+</span>'+( MiniProfiler.formatDuration(it.timing.start_milliseconds) )+' </td> ';if(it.timing.has_sql_timings){out+=' <td class="profiler-duration ';if(it.timing.has_duplicate_sql_timings){out+='profiler-warning';}out+='" title="';if(it.timing.has_duplicate_sql_timings){out+='duplicate queries detected - ';}if(it.timing.executed_readers > 0 || it.timing.executed_scalars > 0 || it.timing.executed_non_queries > 0){out+=''+( it.timing.executed_readers )+' reader, '+( it.timing.executed_scalars )+' scalar, '+( it.timing.executed_non_queries )+' non-query statements executed';}out+='"> <a class="profiler-queries-show"> ';if(it.timing.has_duplicate_sql_timings){out+='<span class="profiler-nuclear">!</span>';}out+=' '+( it.timing.sql_timings.length )+' <span class="profiler-unit">sql</span> </a> </td> <td class="profiler-duration" title="aggregate duration of all queries in this step (excludes children)"> '+( MiniProfiler.formatDuration(it.timing.sql_timings_duration_milliseconds) )+' </td> ';}else{out+=' <td colspan="2"></td> ';}out+=' ';var arr1=it.page.custom_timing_names;if(arr1){var value,i1=-1,l1=arr1.length-1;while(i1<l1){value=arr1[i1+=1];out+=' ';if(it.timing.custom_timings && it.timing.custom_timings[value]){out+=' <td class="profiler-duration" title="aggregate number of all '+( value.toLowerCase() )+' invocations in this step (excludes children)"> '+( it.timing.custom_timings[value].length )+' '+( value.toLowerCase() )+' </td> <td class="profiler-duration" title="aggregate duration of all '+( value.toLowerCase() )+' invocations in this step (excludes children)"> '+( MiniProfiler.formatDuration(it.timing.custom_timing_stats[value].duration) )+' </td> ';}else{out+=' <td colspan="2"></td> ';}out+=' ';} } out+=' </tr> ';if(it.timing.has_children){out+=' ';var arr2=it.timing.children;if(arr2){var value,i2=-1,l2=arr2.length-1;while(i2<l2){value=arr2[i2+=1];out+=' '+( MiniProfiler.templates.timingTemplate({timing: value, page: it.page}) )+' ';} } out+=' ';}return out;
19
19
  }
20
20
  MiniProfiler.templates["sqlTimingTemplate"] = function anonymous(it
21
21
  ) {
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Rack
3
3
  class MiniProfiler
4
- ASSET_VERSION = '7e0146095db5a4bd0c9ae1b165d69e56'
4
+ ASSET_VERSION = '22e813e9a683ebee90a5afa948cffb0b'
5
5
  end
6
6
  end
@@ -89,10 +89,17 @@ module Rack
89
89
  def has_valid_cookie?
90
90
  valid_cookie = !@cookie.nil?
91
91
 
92
- if (MiniProfiler.config.authorization_mode == :whitelist)
93
- @allowed_tokens ||= @store.allowed_tokens
92
+ if (MiniProfiler.config.authorization_mode == :whitelist) && valid_cookie
93
+ begin
94
+ @allowed_tokens ||= @store.allowed_tokens
95
+ rescue => e
96
+ if @config.storage_failure != nil
97
+ @config.storage_failure.call(e)
98
+ end
99
+ end
94
100
 
95
- valid_cookie = (Array === @orig_auth_tokens) &&
101
+ valid_cookie = @allowed_tokens &&
102
+ (Array === @orig_auth_tokens) &&
96
103
  ((@allowed_tokens & @orig_auth_tokens).length > 0)
97
104
  end
98
105
 
@@ -5,6 +5,11 @@ module Rack
5
5
  class << self
6
6
 
7
7
  include Rack::MiniProfiler::ProfilingMethods
8
+ attr_accessor :subscribe_sql_active_record
9
+
10
+ def patch_rails?
11
+ !!defined?(Rack::MINI_PROFILER_ENABLE_RAILS_PATCHES)
12
+ end
8
13
 
9
14
  def generate_id
10
15
  rand(36**20).to_s(36)
@@ -67,6 +72,17 @@ module Rack
67
72
  This feature is disabled by default, to enable set the enable_advanced_debugging_tools option to true in Mini Profiler config.
68
73
  TEXT
69
74
  end
75
+
76
+ def binds_to_params(binds)
77
+ return if binds.nil? || config.max_sql_param_length == 0
78
+ # map ActiveRecord::Relation::QueryAttribute to [name, value]
79
+ params = binds.map { |c| c.kind_of?(Array) ? [c.first, c.last] : [c.name, c.value] }
80
+ if (skip = config.skip_sql_param_names)
81
+ params.map { |(n, v)| n =~ skip ? [n, nil] : [n, v] }
82
+ else
83
+ params
84
+ end
85
+ end
70
86
  end
71
87
 
72
88
  #
@@ -136,7 +152,7 @@ module Rack
136
152
  resources_env = env.dup
137
153
  resources_env['PATH_INFO'] = file_name
138
154
 
139
- rack_file = Rack::File.new(MiniProfiler.resources_root, 'Cache-Control' => 'max-age:86400')
155
+ rack_file = Rack::File.new(MiniProfiler.resources_root, 'Cache-Control' => "max-age:#{cache_control_value}")
140
156
  rack_file.call(resources_env)
141
157
  end
142
158
 
@@ -657,5 +673,8 @@ Append the following to your query string:
657
673
  current.inject_js = false
658
674
  end
659
675
 
676
+ def cache_control_value
677
+ 86400
678
+ end
660
679
  end
661
680
  end
@@ -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
@@ -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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Rack
4
4
  class MiniProfiler
5
- VERSION = '1.1.4'
5
+ VERSION = '2.0.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)
@@ -55,17 +57,81 @@ module Rack::MiniProfilerRails
55
57
 
56
58
  # Install the Middleware
57
59
  app.middleware.insert(0, Rack::MiniProfiler)
60
+ c.enable_advanced_debugging_tools = Rails.env.development?
61
+
62
+ if ::Rack::MiniProfiler.patch_rails?
63
+ # Attach to various Rails methods
64
+ ActiveSupport.on_load(:action_controller) do
65
+ ::Rack::MiniProfiler.profile_method(ActionController::Base, :process) { |action| "Executing action: #{action}" }
66
+ end
67
+
68
+ ActiveSupport.on_load(:action_view) do
69
+ ::Rack::MiniProfiler.profile_method(ActionView::Template, :render) { |x, y| "Rendering: #{@virtual_path}" }
70
+ end
71
+ else
72
+ subscribe("start_processing.action_controller") do |name, start, finish, id, payload|
73
+ next if !should_measure?
74
+
75
+ current = Rack::MiniProfiler.current
76
+ description = "Executing action: #{payload[:action]}"
77
+ Thread.current[get_key(payload)] = current.current_timer
78
+ Rack::MiniProfiler.current.current_timer = current.current_timer.add_child(description)
79
+ end
80
+
81
+ subscribe("process_action.action_controller") do |name, start, finish, id, payload|
82
+ next if !should_measure?
83
+
84
+ key = get_key(payload)
85
+ parent_timer = Thread.current[key]
86
+ next if !parent_timer
87
+
88
+ Thread.current[key] = nil
89
+ Rack::MiniProfiler.current.current_timer.record_time
90
+ Rack::MiniProfiler.current.current_timer = parent_timer
91
+ end
92
+
93
+ subscribe("render_partial.action_view") do |name, start, finish, id, payload|
94
+ render_notification_handler(shorten_identifier(payload[:identifier]), finish, start)
95
+ end
58
96
 
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}" }
97
+ subscribe("render_template.action_view") do |name, start, finish, id, payload|
98
+ render_notification_handler(shorten_identifier(payload[:identifier]), finish, start)
99
+ end
100
+
101
+ if Rack::MiniProfiler.subscribe_sql_active_record
102
+ # we don't want to subscribe if we've already patched a DB driver
103
+ # otherwise we would end up with 2 records for every query
104
+ subscribe("sql.active_record") do |name, start, finish, id, payload|
105
+ next if !should_measure?
106
+ next if payload[:name] =~ /SCHEMA/ && Rack::MiniProfiler.config.skip_schema_queries
107
+
108
+ Rack::MiniProfiler.record_sql(
109
+ payload[:sql],
110
+ (finish - start) * 1000,
111
+ Rack::MiniProfiler.binds_to_params(payload[:binds])
112
+ )
113
+ end
114
+ end
62
115
  end
63
- ActiveSupport.on_load(:action_view) do
64
- ::Rack::MiniProfiler.profile_method(ActionView::Template, :render) { |x, y| "Rendering: #{@virtual_path}" }
116
+ @already_initialized = true
117
+ end
118
+
119
+ def self.subscribe(event, &blk)
120
+ if ActiveSupport::Notifications.respond_to?(:monotonic_subscribe)
121
+ ActiveSupport::Notifications.monotonic_subscribe(event) { |*args| blk.call(*args) }
122
+ else
123
+ ActiveSupport::Notifications.subscribe(event) do |name, start, finish, id, payload|
124
+ blk.call(name, start.to_f, finish.to_f, id, payload)
125
+ end
65
126
  end
127
+ end
66
128
 
67
- c.enable_advanced_debugging_tools = Rails.env.development?
68
- @already_initialized = true
129
+ def self.get_key(payload)
130
+ "mini_profiler_parent_timer_#{payload[:controller]}_#{payload[:action]}".to_sym
131
+ end
132
+
133
+ def self.shorten_identifier(identifier)
134
+ identifier.split('/').last(2).join('/')
69
135
  end
70
136
 
71
137
  def self.serves_static_assets?(app)
@@ -0,0 +1,55 @@
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
+ extend self
55
+ 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
@@ -23,18 +23,26 @@ class SqlPatches
23
23
  ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time).to_f * 1000).round(1)
24
24
  end
25
25
 
26
+ def self.patch_rails?
27
+ ::Rack::MiniProfiler.patch_rails?
28
+ end
29
+
26
30
  def self.sql_patches
27
31
  patches = []
28
32
 
29
33
  patches << 'mysql2' if defined?(Mysql2::Client) && Mysql2::Client.class == Class
30
34
  patches << 'pg' if defined?(PG::Result) && PG::Result.class == Class
31
35
  patches << 'oracle_enhanced' if defined?(ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter) && ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class == Class &&
32
- SqlPatches.correct_version?('~> 1.5.0', ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter)
36
+ SqlPatches.correct_version?('~> 1.5.0', ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter) &&
37
+ patch_rails?
33
38
  # if the adapters were directly patched, don't patch again
34
- return patches unless patches.empty?
39
+ if !patches.empty?
40
+ Rack::MiniProfiler.subscribe_sql_active_record = false
41
+ return patches
42
+ end
35
43
  patches << 'sequel' if defined?(Sequel::Database) && Sequel::Database.class == Class
36
- patches << 'activerecord' if defined?(ActiveRecord) && ActiveRecord.class == Module
37
-
44
+ patches << 'activerecord' if defined?(ActiveRecord) && ActiveRecord.class == Module && patch_rails?
45
+ Rack::MiniProfiler.subscribe_sql_active_record = patches.empty? && !patch_rails?
38
46
  patches
39
47
  end
40
48
 
@@ -27,7 +27,6 @@ require 'mini_profiler/context'
27
27
  require 'mini_profiler/client_settings'
28
28
  require 'mini_profiler/gc_profiler'
29
29
  require 'mini_profiler/profiler'
30
-
31
30
  require 'patches/sql_patches'
32
31
  require 'patches/net_patches'
33
32
 
@@ -11,7 +11,7 @@ Gem::Specification.new do |s|
11
11
  s.authors = ["Sam Saffron", "Robin Ward", "Aleks Totic"]
12
12
  s.description = "Profiling toolkit for Rack applications with Rails integration. Client Side profiling, DB profiling and Server profiling."
13
13
  s.email = "sam.saffron@gmail.com"
14
- s.homepage = "http://miniprofiler.com"
14
+ s.homepage = "https://miniprofiler.com"
15
15
  s.license = "MIT"
16
16
  s.files = [
17
17
  'rack-mini-profiler.gemspec',
@@ -39,6 +39,8 @@ Gem::Specification.new do |s|
39
39
  s.add_development_dependency 'rubocop'
40
40
  s.add_development_dependency 'mini_racer'
41
41
  s.add_development_dependency 'nokogiri'
42
+ s.add_development_dependency 'rubocop-discourse'
43
+ s.add_development_dependency 'listen'
42
44
 
43
45
  s.require_paths = ["lib"]
44
46
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-mini-profiler
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.4
4
+ version: 2.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2019-12-12 00:00:00.000000000 Z
13
+ date: 2020-05-25 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rack
@@ -180,6 +180,34 @@ dependencies:
180
180
  - - ">="
181
181
  - !ruby/object:Gem::Version
182
182
  version: '0'
183
+ - !ruby/object:Gem::Dependency
184
+ name: rubocop-discourse
185
+ requirement: !ruby/object:Gem::Requirement
186
+ requirements:
187
+ - - ">="
188
+ - !ruby/object:Gem::Version
189
+ version: '0'
190
+ type: :development
191
+ prerelease: false
192
+ version_requirements: !ruby/object:Gem::Requirement
193
+ requirements:
194
+ - - ">="
195
+ - !ruby/object:Gem::Version
196
+ version: '0'
197
+ - !ruby/object:Gem::Dependency
198
+ name: listen
199
+ requirement: !ruby/object:Gem::Requirement
200
+ requirements:
201
+ - - ">="
202
+ - !ruby/object:Gem::Version
203
+ version: '0'
204
+ type: :development
205
+ prerelease: false
206
+ version_requirements: !ruby/object:Gem::Requirement
207
+ requirements:
208
+ - - ">="
209
+ - !ruby/object:Gem::Version
210
+ version: '0'
183
211
  description: Profiling toolkit for Rack applications with Rails integration. Client
184
212
  Side profiling, DB profiling and Server profiling.
185
213
  email: sam.saffron@gmail.com
@@ -191,6 +219,7 @@ extra_rdoc_files:
191
219
  files:
192
220
  - CHANGELOG.md
193
221
  - README.md
222
+ - lib/enable_rails_patches.rb
194
223
  - lib/generators/rack_profiler/USAGE
195
224
  - lib/generators/rack_profiler/install_generator.rb
196
225
  - lib/generators/rack_profiler/templates/rack_profiler.rb
@@ -223,6 +252,7 @@ files:
223
252
  - lib/mini_profiler/timer_struct/sql.rb
224
253
  - lib/mini_profiler/version.rb
225
254
  - lib/mini_profiler_rails/railtie.rb
255
+ - lib/mini_profiler_rails/railtie_methods.rb
226
256
  - lib/patches/db/activerecord.rb
227
257
  - lib/patches/db/mongo.rb
228
258
  - lib/patches/db/moped.rb
@@ -239,7 +269,7 @@ files:
239
269
  - lib/patches/sql_patches.rb
240
270
  - lib/rack-mini-profiler.rb
241
271
  - rack-mini-profiler.gemspec
242
- homepage: http://miniprofiler.com
272
+ homepage: https://miniprofiler.com
243
273
  licenses:
244
274
  - MIT
245
275
  metadata: