rack-mini-profiler 3.3.0 → 4.0.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 569f89edd8e16a6577ec6e22ca4a889026678d288d9282114b8fdeaedc6be2bf
4
- data.tar.gz: 7ec71935c344ff9113d3b4b9dc70dfaf3430fa30796075391d7d65bc008cb390
3
+ metadata.gz: fb35c9ff911dd9dcc3fc5cccf15d62f21f0c04ed79ff5dec3e88746fcb90a43f
4
+ data.tar.gz: a8cd39cdb11b4bb2ed154e9c3269de66b6f9c351c8cdcdb775e73e0c25e5d5a2
5
5
  SHA512:
6
- metadata.gz: dcb3c67be45ee1b7dc7516f7ddef1f4f9b6fc1513db927009aac3ef11c504caa6d9b11f3d9a8b28028c7f35f2e6e450cdc0a6b124939d1901679129a758d4499
7
- data.tar.gz: 28b0c19d557485c1abf1e636bebe911e42c8f3706de174021af6e543c0c52c4a6fa0ecdef39518acb58d18d5284147c4b57c950186a8b630e9bd2e69acbc72c9
6
+ metadata.gz: 1c1123a2fc22b5f20ea63fa089b4914f214a6da3d88dbba2fe1b3f597c92c653a166dc1c97d8a68e51a71893b9787ca11b99db1a68aa19da04cad494d8f33af9
7
+ data.tar.gz: b21baa7055d5252cf3f57031be17e67810d898e4b40428d12409027454bfa15b414e2346f07e49d09b139945eb177f8951a34f41c765773ac543c49c2f0efd81
data/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 4.0 - 2025-06-11
4
+
5
+ - [BREAKING CHANGE] Ruby version 3.1.0 or later is required. [#632](https://github.com/MiniProfiler/rack-mini-profiler/pull/632)
6
+ - [FEATURE] Implement prepend patch for postgres [#625](https://github.com/MiniProfiler/rack-mini-profiler/pull/625)
7
+ - [FEATURE] Show Active Record QueryCache hits in UI. [#640](https://github.com/MiniProfiler/rack-mini-profiler/pull/640)
8
+ - [FEATURE] Show record type and count in SQL query UI. [#638](https://github.com/MiniProfiler/rack-mini-profiler/pull/638)
9
+ - [FIX] Requests page fails due to trying to modify a frozen string. [#630](https://github.com/MiniProfiler/rack-mini-profiler/pull/630)
10
+ - [FIX] alignment of SQL query start times. [#627](https://github.com/MiniProfiler/rack-mini-profiler/pull/627)
11
+ - [FIX] Lower case HTTP response headers to be compatible with Rack 3 [#628](https://github.com/MiniProfiler/rack-mini-profiler/pull/628)
12
+ - [FIX] Truncate long profiler name in profiler popup. [#634](https://github.com/MiniProfiler/rack-mini-profiler/pull/634)
13
+ - [FIX] `flamegraph_mode` query param having no effect. [#635](https://github.com/MiniProfiler/rack-mini-profiler/pull/635)
14
+ - [FIX] max_traces_to_show had chance to break the profiler frontend [#297](https://github.com/MiniProfiler/rack-mini-profiler/issues/297)
15
+
16
+ ## 3.3.1 - 2024-02-15
17
+ - [FEATURE] Support dynamic `config.content_security_policy_nonce` [#609](https://github.com/MiniProfiler/rack-mini-profiler/pull/609)
18
+ - [FEATURE] Add flamgraph path to response header: [#601](https://github.com/MiniProfiler/rack-mini-profiler/pull/601)
19
+
3
20
  ## 3.3.0 - 2023-12-07
4
21
  - [FEATURE] Use `?pp=flamegraph?ignore_gc=true` or `config.flamegraph_ignore_gc` to ignore gc in flamegraphs. [#599](https://github.com/MiniProfiler/rack-mini-profiler/pull/599)
5
22
 
data/README.md CHANGED
@@ -90,6 +90,20 @@ gem 'rack-mini-profiler', require: ['prepend_mysql2_patch', 'rack-mini-profiler'
90
90
 
91
91
  This should not be necessary with Rails < 5 because peek-mysql2 hooks into mysql2 gem in different ways depending on your Rails version.
92
92
 
93
+ #### `pg` stack level too deep errors
94
+
95
+ If you encounter `SystemStackError (stack level too deep)` from PG, you'll need to use this gem spec in your Gemfile:
96
+
97
+ ```ruby
98
+ gem 'rack-mini-profiler', require: ['prepend_pg_patch', 'rack-mini-profiler']
99
+ ```
100
+
101
+ Or if you initially have `require: false`, then use
102
+
103
+ ```ruby
104
+ gem 'rack-mini-profiler', require: ['prepend_pg_patch']
105
+ ```
106
+
93
107
  #### Rails and manual initialization
94
108
 
95
109
  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:
@@ -180,7 +194,7 @@ To generate [flamegraphs](http://samsaffron.com/archive/2013/03/19/flame-graphs-
180
194
 
181
195
  Then, to view the flamegraph as a direct HTML response from your request, just visit any page in your app with `?pp=flamegraph` appended to the URL, or add the header `X-Rack-Mini-Profiler` to the request with the value `flamegraph`.
182
196
 
183
- Conversely, if you want your regular response instead (which is specially useful for JSON and/or XHR requests), just append the `?pp=async-flamegraph` parameter to your request/fetch URL; the request will then return as normal, and the flamegraph data will be stored for later *async* viewing, both for this request and for all subsequent requests made by this page (based on the `REFERER` header). For viewing these async flamegraphs, use the 'flamegraph' link that will appear inside the MiniProfiler UI for these requests.
197
+ Conversely, if you want your regular response instead (which is specially useful for JSON and/or XHR requests), just append the `?pp=async-flamegraph` parameter to your request/fetch URL; the request will then return as normal, and the flamegraph data will be stored for later *async* viewing, both for this request and for all subsequent requests made by this page (based on the `REFERER` header). For viewing these async flamegraphs, use the 'flamegraph' link that will appear inside the MiniProfiler UI for these requests or path returned in the `X-MiniProfiler-Flamegraph-Path` header.
184
198
 
185
199
  Note: Mini Profiler will not record SQL timings for a request if it asks for a flamegraph. The rationale behind this is to keep
186
200
  Mini Profiler's methods that are responsible for generating the timings data out of the flamegraph.
@@ -245,6 +259,7 @@ rack-mini-profiler is designed with production profiling in mind. To enable that
245
259
  end
246
260
  ```
247
261
 
262
+ > [!WARNING]
248
263
  > If your production application is running on more than one server (or more than one dyno) you will need to configure rack mini profiler's storage to use Redis or Memcache. See [storage](#storage) for information on changing the storage backend.
249
264
 
250
265
  Note:
@@ -448,7 +463,7 @@ snapshots_transport_destination_url | `nil`
448
463
  snapshots_transport_auth_key | `nil` | `POST` requests made by the snapshots transporter to the destination URL will have a `Mini-Profiler-Transport-Auth` header with the value of this config. Make sure you use a secure and random key for this config.
449
464
  snapshots_redact_sql_queries | `true` | When this is true, SQL queries will be redacted from sampling snapshots, but the backtrace and duration of each SQL query will be saved with the snapshot to keep debugging performance issues possible.
450
465
  snapshots_transport_gzip_requests | `false` | Make the snapshots transporter gzip the requests it makes to `snapshots_transport_destination_url`.
451
- content_security_policy_nonce | Rails: Current nonce<br>Rack: nil | Set the content security policy nonce to use when inserting MiniProfiler's script block.
466
+ content_security_policy_nonce | Rails: Current nonce<br>Rack: nil | Set the content security policy nonce to use when inserting MiniProfiler's script block. Can be set to a static string, or a Proc which receives `env` and `response_headers` as arguments and returns the nonce.
452
467
  enable_hotwire_turbo_drive_support | `false` | Enable support for Hotwire TurboDrive page transitions.
453
468
  profile_parameter | `'pp'` | The query parameter used to interact with this gem.
454
469
 
@@ -466,7 +481,7 @@ If you are using Heroku Redis, you may need to add the following to your `config
466
481
 
467
482
  ```ruby
468
483
  if Rails.env.production?
469
- Rack::MiniProfiler.config.storage_options = {
484
+ Rack::MiniProfiler.config.storage_options = {
470
485
  url: ENV["REDIS_URL"],
471
486
  ssl_params: { verify_mode: OpenSSL::SSL::VERIFY_NONE }
472
487
  }
@@ -105,13 +105,16 @@
105
105
  .profiler-result .profiler-unit {
106
106
  font-family: Consolas, monospace, serif; }
107
107
  .profiler-result .profiler-number {
108
- color: #111; }
108
+ color: #111;
109
+ display: inline-block; }
109
110
  .profiler-result .profiler-info {
110
111
  text-align: right; }
111
112
  .profiler-result .profiler-info .profiler-name {
112
113
  float: left; }
113
114
  .profiler-result .profiler-info .profiler-server-time {
114
115
  white-space: nowrap; }
116
+ .profiler-result .profiler-info .profiler-number {
117
+ display: block; }
115
118
  .profiler-result .profiler-timings th {
116
119
  background-color: #fff;
117
120
  color: #767676;
@@ -132,19 +135,21 @@
132
135
  .profiler-result .profiler-timings .profiler-queries-duration {
133
136
  padding-left: 6px; }
134
137
  .profiler-result .profiler-timings .profiler-percent-in-sql {
135
- white-space: nowrap;
136
- text-align: right; }
137
- .profiler-result .profiler-timings tfoot td {
138
- padding-top: 10px;
138
+ white-space: nowrap; }
139
+ .profiler-result .profiler-timings tfoot tr td:last-child {
139
140
  text-align: right; }
140
- .profiler-result .profiler-timings tfoot td a {
141
+ .profiler-result .profiler-timings-summary {
142
+ display: flex;
143
+ justify-content: space-between;
144
+ padding-top: 10px; }
145
+ .profiler-result .profiler-timings-summary a {
141
146
  font-size: 95%;
142
147
  display: inline-block;
143
148
  margin-left: 12px; }
144
- .profiler-result .profiler-timings tfoot td a:first-child {
149
+ .profiler-result .profiler-timings-summary a:first-child {
145
150
  float: left;
146
151
  margin-left: 0px; }
147
- .profiler-result .profiler-timings tfoot td a.profiler-custom-link {
152
+ .profiler-result .profiler-timings-summary a.profiler-custom-link {
148
153
  float: left; }
149
154
  .profiler-result .profiler-queries {
150
155
  font-family: Helvetica, Arial, sans-serif; }
@@ -162,6 +167,12 @@
162
167
  background-color: #fdd; }
163
168
  .profiler-result .profiler-queries tr.very-very-slow {
164
169
  background-color: #fcc; }
170
+ .profiler-result .profiler-queries tr.cached {
171
+ background-color: #f2f0ef; }
172
+ .profiler-result .profiler-queries span.cached {
173
+ color: #818589; }
174
+ .profiler-result .profiler-queries span.cached + pre {
175
+ display: inline; }
165
176
  .profiler-result .profiler-queries pre {
166
177
  font-family: Consolas, monospace, serif;
167
178
  white-space: pre-wrap; }
@@ -235,6 +246,19 @@
235
246
  .profiler-results {
236
247
  z-index: 2147483643;
237
248
  position: fixed; }
249
+ .profiler-results.profiler-left .profiler-button {
250
+ text-align: left; }
251
+ .profiler-results.profiler-right .profiler-button {
252
+ text-align: right; }
253
+ .profiler-results .profiler-button > .profiler-duration-milliseconds {
254
+ min-width: 70px; }
255
+ .profiler-results .profiler-button > .profiler-sql-count {
256
+ min-width: 55px; }
257
+ .profiler-results .profiler-button > .profiler-name {
258
+ min-width: 190px;
259
+ margin-left: 3px; }
260
+ .profiler-results .profiler-button > .profiler-number {
261
+ text-align: right; }
238
262
  .profiler-results.profiler-top {
239
263
  top: 0px; }
240
264
  .profiler-results.profiler-top.profiler-left {
@@ -285,8 +309,10 @@
285
309
  background-color: maroon; }
286
310
  .profiler-results .profiler-button.profiler-button-active .profiler-number,
287
311
  .profiler-results .profiler-button.profiler-button-active .profiler-nuclear,
312
+ .profiler-results .profiler-button.profiler-button-active .profiler-name,
288
313
  .profiler-results .profiler-controls.profiler-button-active .profiler-number,
289
- .profiler-results .profiler-controls.profiler-button-active .profiler-nuclear {
314
+ .profiler-results .profiler-controls.profiler-button-active .profiler-nuclear,
315
+ .profiler-results .profiler-controls.profiler-button-active .profiler-name {
290
316
  color: #fff;
291
317
  font-weight: bold; }
292
318
  .profiler-results .profiler-button.profiler-button-active .profiler-unit,
@@ -324,14 +350,22 @@
324
350
  text-align: left;
325
351
  line-height: 18px;
326
352
  overflow: auto;
353
+ max-width: 800px;
327
354
  box-shadow: 0px 1px 15px #555; }
328
355
  .profiler-results .profiler-popup .profiler-info {
329
356
  margin-bottom: 3px;
330
357
  padding-bottom: 2px;
331
- border-bottom: 1px solid #ddd; }
358
+ border-bottom: 1px solid #ddd;
359
+ display: flex;
360
+ width: inherit; }
332
361
  .profiler-results .profiler-popup .profiler-info .profiler-name {
362
+ overflow: hidden;
363
+ text-overflow: ellipsis;
364
+ text-align: left;
365
+ white-space: nowrap;
333
366
  font-size: 110%;
334
- font-weight: bold; }
367
+ font-weight: bold;
368
+ padding-right: 10px; }
335
369
  .profiler-results .profiler-popup .profiler-info .profiler-name .profiler-overall-duration {
336
370
  display: none; }
337
371
  .profiler-results .profiler-popup .profiler-info .profiler-server-time {
data/lib/html/includes.js CHANGED
@@ -220,7 +220,7 @@ var _MiniProfiler = (function() {
220
220
  totalSqlCount += parseInt(json.sql_count);
221
221
  reqs++;
222
222
 
223
- if (!controls && reqs > 1 && options.collapseResults && !expandedResults) {
223
+ if (!controls && options.collapseResults && !expandedResults) {
224
224
  if (!totalsControl) {
225
225
  toArray(container.querySelectorAll(".profiler-result")).forEach(
226
226
  function(el) {
@@ -285,10 +285,10 @@ var _MiniProfiler = (function() {
285
285
  }); // limit count
286
286
 
287
287
  if (
288
- container.querySelectorAll(".profiler-result").length >
288
+ container.querySelectorAll(".profiler-result:not(:has(.profiler-totals))").length >
289
289
  options.maxTracesToShow
290
290
  ) {
291
- var elem = container.querySelector(".profiler-result");
291
+ var elem = container.querySelector('.profiler-result:not(:has(.profiler-totals))');
292
292
 
293
293
  if (elem) {
294
294
  elem.parentElement.removeChild(elem);
@@ -1179,6 +1179,7 @@ var _MiniProfiler = (function() {
1179
1179
 
1180
1180
  reqs = 0;
1181
1181
  totalTime = 0;
1182
+ totalSqlCount = 0;
1182
1183
  expandedResults = false;
1183
1184
  toArray(
1184
1185
  document.querySelectorAll(".profiler-results .profiler-result")
@@ -1288,6 +1289,10 @@ var _MiniProfiler = (function() {
1288
1289
 
1289
1290
  sqlTiming.parent_timing_name = timing.name;
1290
1291
 
1292
+ if (sqlTiming.cached) {
1293
+ sqlTiming.row_class = "cached";
1294
+ }
1295
+
1291
1296
  if (sqlTiming.duration_milliseconds > 50) {
1292
1297
  sqlTiming.row_class = "slow";
1293
1298
  }
@@ -103,6 +103,7 @@ $zindex: 2147483640; // near 32bit max 2147483647
103
103
 
104
104
  .profiler-number {
105
105
  color: $numberColor;
106
+ display: inline-block;
106
107
  }
107
108
 
108
109
  .profiler-info {
@@ -113,6 +114,9 @@ $zindex: 2147483640; // near 32bit max 2147483647
113
114
  .profiler-server-time {
114
115
  white-space: nowrap;
115
116
  }
117
+ .profiler-number {
118
+ display: block;
119
+ }
116
120
  }
117
121
 
118
122
  .profiler-timings {
@@ -146,27 +150,28 @@ $zindex: 2147483640; // near 32bit max 2147483647
146
150
  }
147
151
  .profiler-percent-in-sql {
148
152
  white-space: nowrap;
153
+ }
154
+ tfoot tr td:last-child {
149
155
  text-align: right;
150
156
  }
157
+ }
151
158
 
152
- tfoot {
153
- td {
154
- padding-top: 10px;
155
- text-align: right;
159
+ .profiler-timings-summary {
160
+ display: flex;
161
+ justify-content: space-between;
162
+ padding-top: 10px;
156
163
 
157
- a {
158
- font-size: 95%;
159
- display: inline-block;
160
- margin-left: 12px;
164
+ a {
165
+ font-size: 95%;
166
+ display: inline-block;
167
+ margin-left: 12px;
161
168
 
162
- &:first-child {
163
- float: left;
164
- margin-left: 0px;
165
- }
166
- &.profiler-custom-link {
167
- float: left;
168
- }
169
- }
169
+ &:first-child {
170
+ float: left;
171
+ margin-left: 0px;
172
+ }
173
+ &.profiler-custom-link {
174
+ float: left;
170
175
  }
171
176
  }
172
177
  }
@@ -201,6 +206,18 @@ $zindex: 2147483640; // near 32bit max 2147483647
201
206
  background-color: #fcc;
202
207
  }
203
208
 
209
+ tr.cached {
210
+ background-color: #f2f0ef;
211
+ }
212
+
213
+ span.cached {
214
+ color: #818589;
215
+ }
216
+
217
+ span.cached + pre {
218
+ display: inline;
219
+ }
220
+
204
221
  pre {
205
222
  font-family: $codeFonts;
206
223
  white-space: pre-wrap;
@@ -317,6 +334,34 @@ $zindex: 2147483640; // near 32bit max 2147483647
317
334
 
318
335
  $radius: 10px;
319
336
 
337
+ &.profiler-left {
338
+ .profiler-button {
339
+ text-align: left;
340
+ }
341
+ }
342
+
343
+ &.profiler-right {
344
+ .profiler-button {
345
+ text-align: right;
346
+ }
347
+ }
348
+
349
+ .profiler-button {
350
+ > .profiler-duration-milliseconds {
351
+ min-width: 70px;
352
+ }
353
+ > .profiler-sql-count {
354
+ min-width: 55px;
355
+ }
356
+ > .profiler-name {
357
+ min-width: 190px;
358
+ margin-left: 3px;
359
+ }
360
+ > .profiler-number {
361
+ text-align: right;
362
+ }
363
+ }
364
+
320
365
  &.profiler-top {
321
366
  top: 0px;
322
367
 
@@ -399,7 +444,8 @@ $zindex: 2147483640; // near 32bit max 2147483647
399
444
  background-color: maroon;
400
445
 
401
446
  .profiler-number,
402
- .profiler-nuclear {
447
+ .profiler-nuclear,
448
+ .profiler-name {
403
449
  color: #fff;
404
450
  font-weight: bold;
405
451
  }
@@ -452,6 +498,7 @@ $zindex: 2147483640; // near 32bit max 2147483647
452
498
  text-align: left;
453
499
  line-height: 18px;
454
500
  overflow: auto;
501
+ max-width: 800px;
455
502
 
456
503
  @include box-shadow(0px, 1px, 15px, $textColor);
457
504
 
@@ -459,14 +506,23 @@ $zindex: 2147483640; // near 32bit max 2147483647
459
506
  margin-bottom: 3px;
460
507
  padding-bottom: 2px;
461
508
  border-bottom: 1px solid #ddd;
509
+ display: flex;
510
+ width: inherit;
462
511
 
463
512
  .profiler-name {
513
+ overflow: hidden;
514
+ text-overflow: ellipsis;
515
+ text-align: left;
516
+ white-space: nowrap;
464
517
  font-size: 110%;
465
518
  font-weight: bold;
519
+ padding-right: 10px;
520
+
466
521
  .profiler-overall-duration {
467
522
  display: none;
468
523
  }
469
524
  }
525
+
470
526
  .profiler-server-time {
471
527
  font-size: 95%;
472
528
  }
@@ -2,20 +2,27 @@
2
2
  <div class="profiler-result">
3
3
  <div class="profiler-button {{? it.has_duplicate_sql_timings}}profiler-warning{{?}}">
4
4
  {{? it.has_duplicate_sql_timings}}<span class="profiler-nuclear">!</span>{{?}}
5
- <span class="profiler-number">
5
+ <span class="profiler-number profiler-duration-milliseconds">
6
6
  {{= MiniProfiler.formatDuration(it.duration_milliseconds)}} <span class="profiler-unit">ms</span>
7
7
  </span>
8
8
  {{? MiniProfiler.showTotalSqlCount()}}
9
- <span class="profiler-number">
9
+ <span class="profiler-number profiler-sql-count">
10
10
  {{= it.sql_count}} <span class="profiler-unit">sql</span>
11
11
  </span>
12
12
  {{?}}
13
+ <span class="profiler-name">
14
+ {{? it.name.length >= 30 }}
15
+ {{= it.name.substring(0,15) + "..." + it.name.slice(-15) }}
16
+ {{??}}
17
+ {{= it.name}}
18
+ {{?}}
19
+ </span>
13
20
  </div>
14
21
 
15
22
  <div class="profiler-popup">
16
23
  <div class="profiler-info">
17
- <span class="profiler-name">
18
- {{= it.name}} <span class="profiler-overall-duration">({{= MiniProfiler.formatDuration(it.duration_milliseconds)}} ms)</span>
24
+ <span class="profiler-name" title="{{= it.name}}">
25
+ {{= it.name }} <span class="profiler-overall-duration">({{= MiniProfiler.formatDuration(it.duration_milliseconds)}} ms)</span>
19
26
  </span>
20
27
  <span class="profiler-server-time">{{= it.machine_name}} on {{= MiniProfiler.renderDate(it.started_formatted)}}</span>
21
28
  </div>
@@ -38,33 +45,42 @@
38
45
  <tbody>
39
46
  {{= MiniProfiler.templates.timingTemplate({timing: it.root, page: it}) }}
40
47
  </tbody>
41
- <tfoot>
42
- <tr>
43
- <td colspan="3">
44
- {{? !it.client_timings}}
45
- {{= MiniProfiler.templates.linksTemplate({timing: it.root, page: it}) }}
46
- {{?}}
47
- <a class="profiler-toggle-duration-with-children" title="toggles column with aggregate child durations">show time with children</a>
48
- <a
49
- class="profiler-snapshots-page-link"
50
- title="Go to snapshots page"
51
- href="{{= MiniProfiler.options.path }}snapshots">snapshots</a>
52
- </td>
53
- {{? it.has_sql_timings}}
54
- <td colspan="2" class="profiler-number profiler-percent-in-sql" title="{{= MiniProfiler.getSqlTimingsCount(it.root) }} queries spent {{= MiniProfiler.formatDuration(it.duration_milliseconds_in_sql) }} ms of total request time">
55
- {{= MiniProfiler.formatDuration(it.duration_milliseconds_in_sql / it.duration_milliseconds * 100) }}
56
- <span class="profiler-unit">% in sql</span>
48
+ {{? it.has_sql_timings}}
49
+ <tfoot>
50
+ <tr>
51
+ <td colspan="1">
52
+ SQL Summary:
57
53
  </td>
58
- {{?}}
59
- {{~ it.custom_timing_names :value}}
60
- <td colspan="2" class="profiler-number profiler-percentage-in-sql" title="{{= it.custom_timing_stats[value].count }} {{= value.toLowerCase() }} invocations spent {{= MiniProfiler.formatDuration(it.custom_timing_stats[value].duration) }} ms of total request time">
61
- {{= MiniProfiler.formatDuration(it.custom_timing_stats[value].duration / it.duration_milliseconds * 100) }}
62
- <span class="profiler-unit">% in {{= value.toLowerCase() }}</span>
54
+ <td colspan="5" title="percent of total request time spent in SQL">
55
+ {{=it.sql_count}} {{? it.cached_sql_count > 0 }} ({{=it.cached_sql_count}} cached) {{?}}
56
+ <span class="profiler-unit" title="percent of total request time spent in SQL"> -
57
+ {{= MiniProfiler.formatDuration(it.duration_milliseconds_in_sql / it.duration_milliseconds * 100) }}% in sql
58
+ </span>
63
59
  </td>
64
- {{~}}
65
- </tr>
66
- </tfoot>
60
+ </tr>
61
+ </tfoot>
62
+ {{?}}
67
63
  </table>
64
+
65
+ <div class="profiler-timings-summary">
66
+ <div>
67
+ {{? !it.client_timings}}
68
+ {{= MiniProfiler.templates.linksTemplate({timing: it.root, page: it}) }}
69
+ {{?}}
70
+ <a class="profiler-toggle-duration-with-children" title="toggles column with aggregate child durations">show time with children</a>
71
+ <a
72
+ class="profiler-snapshots-page-link"
73
+ title="Go to snapshots page"
74
+ href="{{= MiniProfiler.options.path }}snapshots">snapshots</a>
75
+ </div>
76
+ {{~ it.custom_timing_names :value}}
77
+ <div class="profiler-number profiler-percentage-in-sql" title="{{= it.custom_timing_stats[value].count }} {{= value.toLowerCase() }} invocations spent {{= MiniProfiler.formatDuration(it.custom_timing_stats[value].duration) }} ms of total request time">
78
+ {{= MiniProfiler.formatDuration(it.custom_timing_stats[value].duration / it.duration_milliseconds * 100) }}
79
+ <span class="profiler-unit">% in {{= value.toLowerCase() }}</span>
80
+ </div>
81
+ {{~}}
82
+ </div>
83
+
68
84
  {{? it.client_timings}}
69
85
  <table class="profiler-timings profiler-client-timings">
70
86
  <thead>
@@ -117,7 +133,7 @@
117
133
  <table>
118
134
  <thead>
119
135
  <tr>
120
- <th class="ta-right">step<br />time from start<br />query type<br />duration</th>
136
+ <th class="ta-right">step<br />time from start<br />query type<br />duration<br />records</th>
121
137
  <th class="ta-left">call stack<br />query</th>
122
138
  </tr>
123
139
  </thead>
@@ -216,10 +232,16 @@
216
232
  {{= MiniProfiler.renderExecuteType(it.s.execute_type) }}
217
233
  </div>
218
234
  <div title="{{? it.s.execute_type == 3}}first result fetched: {{= it.s.first_fetch_duration_milliseconds }}ms{{?}}">{{= MiniProfiler.formatDuration(it.s.duration_milliseconds) }} <span class="profiler-unit">ms</span></div>
235
+ {{? it.s.row_count > 0 }}
236
+ <div title="number and type of records instantiated by query">{{= it.s.class_name }}: {{= it.s.row_count }}</div>
237
+ {{?}}
219
238
  </td>
220
239
  <td>
221
240
  <div class="query">
222
241
  <pre class="profiler-stack-trace">{{= it.s.stack_trace_snippet }}</pre>
242
+ {{? it.s.cached }}
243
+ <span class="cached"> [CACHE] </span>
244
+ {{?}}
223
245
  {{? it.s.formatted_command_string}}
224
246
  <pre class="prettyprint lang-sql"><code>{{= it.s.formatted_command_string }}; {{= MiniProfiler.formatParameters(it.s.parameters) }}</code></pre>
225
247
  {{??}}
data/lib/html/vendor.js CHANGED
@@ -7,7 +7,7 @@
7
7
  MiniProfiler.templates = {};
8
8
  MiniProfiler.templates["profilerTemplate"] = function anonymous(it
9
9
  ) {
10
- var out=' <div class="profiler-result"> <div class="profiler-button ';if(it.has_duplicate_sql_timings){out+='profiler-warning';}out+='"> ';if(it.has_duplicate_sql_timings){out+='<span class="profiler-nuclear">!</span>';}out+=' <span class="profiler-number"> '+( MiniProfiler.formatDuration(it.duration_milliseconds))+' <span class="profiler-unit">ms</span> </span> ';if(MiniProfiler.showTotalSqlCount()){out+=' <span class="profiler-number"> '+( it.sql_count)+' <span class="profiler-unit">sql</span> </span> ';}out+=' </div> <div class="profiler-popup"> <div class="profiler-info"> <span class="profiler-name"> '+( it.name)+' <span class="profiler-overall-duration">('+( MiniProfiler.formatDuration(it.duration_milliseconds))+' ms)</span> </span> <span class="profiler-server-time">'+( it.machine_name)+' on '+( MiniProfiler.renderDate(it.started_formatted))+'</span> </div> <div class="profiler-output"> <table class="profiler-timings"> <thead> <tr> <th>event</th> <th>duration (ms)</th> <th class="profiler-duration-with-children">with children (ms)</th> <th class="time-from-start">from start (ms)</th> ';if(it.has_sql_timings){out+=' <th colspan="2">query time (ms)</th> ';}out+=' ';var arr1=it.custom_timing_names;if(arr1){var value,i1=-1,l1=arr1.length-1;while(i1<l1){value=arr1[i1+=1];out+=' <th colspan="2">'+( value.toLowerCase() )+' (ms)</th> ';} } out+=' </tr> </thead> <tbody> '+( MiniProfiler.templates.timingTemplate({timing: it.root, page: it}) )+' </tbody> <tfoot> <tr> <td colspan="3"> ';if(!it.client_timings){out+=' '+( MiniProfiler.templates.linksTemplate({timing: it.root, page: it}) )+' ';}out+=' <a class="profiler-toggle-duration-with-children" title="toggles column with aggregate child durations">show time with children</a> <a class="profiler-snapshots-page-link" title="Go to snapshots page" href="'+( MiniProfiler.options.path )+'snapshots">snapshots</a> </td> ';if(it.has_sql_timings){out+=' <td colspan="2" class="profiler-number profiler-percent-in-sql" title="'+( MiniProfiler.getSqlTimingsCount(it.root) )+' queries spent '+( MiniProfiler.formatDuration(it.duration_milliseconds_in_sql) )+' ms of total request time"> '+( MiniProfiler.formatDuration(it.duration_milliseconds_in_sql / it.duration_milliseconds * 100) )+' <span class="profiler-unit">% in sql</span> </td> ';}out+=' ';var arr2=it.custom_timing_names;if(arr2){var value,i2=-1,l2=arr2.length-1;while(i2<l2){value=arr2[i2+=1];out+=' <td colspan="2" class="profiler-number profiler-percentage-in-sql" title="'+( it.custom_timing_stats[value].count )+' '+( value.toLowerCase() )+' invocations spent '+( MiniProfiler.formatDuration(it.custom_timing_stats[value].duration) )+' ms of total request time"> '+( MiniProfiler.formatDuration(it.custom_timing_stats[value].duration / it.duration_milliseconds * 100) )+' <span class="profiler-unit">% in '+( value.toLowerCase() )+'</span> </td> ';} } out+=' </tr> </tfoot> </table> ';if(it.client_timings){out+=' <table class="profiler-timings profiler-client-timings"> <thead> <tr> <th>client event</th> <th>duration (ms)</th> <th>from start (ms)</th> </tr> </thead> <tbody> ';var arr3=MiniProfiler.getClientTimings(it.client_timings);if(arr3){var value,i3=-1,l3=arr3.length-1;while(i3<l3){value=arr3[i3+=1];out+=' <tr class="';if(value.isTrivial){out+='profiler-trivial';}out+='"> <td class="profiler-label">'+( value.name )+'</td> <td class="profiler-duration"> ';if(value.duration >= 0){out+=' <span class="profiler-unit"></span>'+( MiniProfiler.formatDuration(value.duration) )+' ';}out+=' </td> <td class="profiler-duration time-from-start"> <span class="profiler-unit">+</span>'+( MiniProfiler.formatDuration(value.start) )+' </td> </tr> ';} } out+=' </tbody> <tfoot> <td colspan="3"> '+( MiniProfiler.templates.linksTemplate({timing: it.root, page: it}) )+' </td> </tfoot> </table> ';}out+=' ';if(it.custom_fields && Object.keys(it.custom_fields).length > 0){out+=' <p class="custom-fields-title">Snapshot custom fields</p> <table class="profiler-timings"> <tbody> ';var arr4=Object.keys(it.custom_fields);if(arr4){var key,i4=-1,l4=arr4.length-1;while(i4<l4){key=arr4[i4+=1];out+=' <tr> <td class="profiler-label">'+( key )+'</td> <td class="profiler-label">'+( it.custom_fields[key] )+'</td> </tr> ';} } out+=' </tbody> </table> ';}out+=' </div> </div> ';if(it.has_sql_timings){out+=' <div class="profiler-queries"> <table> <thead> <tr> <th class="ta-right">step<br />time from start<br />query type<br />duration</th> <th class="ta-left">call stack<br />query</th> </tr> </thead> <tbody> ';var arr5=MiniProfiler.getSqlTimings(it.root);if(arr5){var value,index=-1,l5=arr5.length-1;while(index<l5){value=arr5[index+=1];out+=' '+( MiniProfiler.templates.sqlGapTemplate({g: value.prevGap}) )+' '+( MiniProfiler.templates.sqlTimingTemplate({i: index, s: value}) )+' ';if(value.nextGap){out+=' '+( MiniProfiler.templates.sqlGapTemplate({g: value.nextGap}) )+' ';}out+=' ';} } out+=' </tbody> </table> <p class="profiler-trivial-gap-container"> <a class="profiler-toggle-trivial-gaps">show trivial gaps</a> </p> </div> ';}out+=' </div>';return out;
10
+ var out=' <div class="profiler-result"> <div class="profiler-button ';if(it.has_duplicate_sql_timings){out+='profiler-warning';}out+='"> ';if(it.has_duplicate_sql_timings){out+='<span class="profiler-nuclear">!</span>';}out+=' <span class="profiler-number profiler-duration-milliseconds"> '+( MiniProfiler.formatDuration(it.duration_milliseconds))+' <span class="profiler-unit">ms</span> </span> ';if(MiniProfiler.showTotalSqlCount()){out+=' <span class="profiler-number profiler-sql-count"> '+( it.sql_count)+' <span class="profiler-unit">sql</span> </span> ';}out+=' <span class="profiler-name"> ';if(it.name.length >= 30){out+=' '+( it.name.substring(0,15) + "..." + it.name.slice(-15) )+' ';}else{out+=' '+( it.name)+' ';}out+=' </span> </div> <div class="profiler-popup"> <div class="profiler-info"> <span class="profiler-name" title="'+( it.name)+'"> '+( it.name )+' <span class="profiler-overall-duration">('+( MiniProfiler.formatDuration(it.duration_milliseconds))+' ms)</span> </span> <span class="profiler-server-time">'+( it.machine_name)+' on '+( MiniProfiler.renderDate(it.started_formatted))+'</span> </div> <div class="profiler-output"> <table class="profiler-timings"> <thead> <tr> <th>event</th> <th>duration (ms)</th> <th class="profiler-duration-with-children">with children (ms)</th> <th class="time-from-start">from start (ms)</th> ';if(it.has_sql_timings){out+=' <th colspan="2">query time (ms)</th> ';}out+=' ';var arr1=it.custom_timing_names;if(arr1){var value,i1=-1,l1=arr1.length-1;while(i1<l1){value=arr1[i1+=1];out+=' <th colspan="2">'+( value.toLowerCase() )+' (ms)</th> ';} } out+=' </tr> </thead> <tbody> '+( MiniProfiler.templates.timingTemplate({timing: it.root, page: it}) )+' </tbody> ';if(it.has_sql_timings){out+=' <tfoot> <tr> <td colspan="1"> SQL Summary: </td> <td colspan="5" title="percent of total request time spent in SQL"> '+(it.sql_count)+' ';if(it.cached_sql_count > 0){out+=' ('+(it.cached_sql_count)+' cached) ';}out+=' <span class="profiler-unit" title="percent of total request time spent in SQL"> - '+( MiniProfiler.formatDuration(it.duration_milliseconds_in_sql / it.duration_milliseconds * 100) )+'% in sql </span> </td> </tr> </tfoot> ';}out+=' </table> <div class="profiler-timings-summary"> <div> ';if(!it.client_timings){out+=' '+( MiniProfiler.templates.linksTemplate({timing: it.root, page: it}) )+' ';}out+=' <a class="profiler-toggle-duration-with-children" title="toggles column with aggregate child durations">show time with children</a> <a class="profiler-snapshots-page-link" title="Go to snapshots page" href="'+( MiniProfiler.options.path )+'snapshots">snapshots</a> </div> ';var arr2=it.custom_timing_names;if(arr2){var value,i2=-1,l2=arr2.length-1;while(i2<l2){value=arr2[i2+=1];out+=' <div class="profiler-number profiler-percentage-in-sql" title="'+( it.custom_timing_stats[value].count )+' '+( value.toLowerCase() )+' invocations spent '+( MiniProfiler.formatDuration(it.custom_timing_stats[value].duration) )+' ms of total request time"> '+( MiniProfiler.formatDuration(it.custom_timing_stats[value].duration / it.duration_milliseconds * 100) )+' <span class="profiler-unit">% in '+( value.toLowerCase() )+'</span> </div> ';} } out+=' </div> ';if(it.client_timings){out+=' <table class="profiler-timings profiler-client-timings"> <thead> <tr> <th>client event</th> <th>duration (ms)</th> <th>from start (ms)</th> </tr> </thead> <tbody> ';var arr3=MiniProfiler.getClientTimings(it.client_timings);if(arr3){var value,i3=-1,l3=arr3.length-1;while(i3<l3){value=arr3[i3+=1];out+=' <tr class="';if(value.isTrivial){out+='profiler-trivial';}out+='"> <td class="profiler-label">'+( value.name )+'</td> <td class="profiler-duration"> ';if(value.duration >= 0){out+=' <span class="profiler-unit"></span>'+( MiniProfiler.formatDuration(value.duration) )+' ';}out+=' </td> <td class="profiler-duration time-from-start"> <span class="profiler-unit">+</span>'+( MiniProfiler.formatDuration(value.start) )+' </td> </tr> ';} } out+=' </tbody> <tfoot> <td colspan="3"> '+( MiniProfiler.templates.linksTemplate({timing: it.root, page: it}) )+' </td> </tfoot> </table> ';}out+=' ';if(it.custom_fields && Object.keys(it.custom_fields).length > 0){out+=' <p class="custom-fields-title">Snapshot custom fields</p> <table class="profiler-timings"> <tbody> ';var arr4=Object.keys(it.custom_fields);if(arr4){var key,i4=-1,l4=arr4.length-1;while(i4<l4){key=arr4[i4+=1];out+=' <tr> <td class="profiler-label">'+( key )+'</td> <td class="profiler-label">'+( it.custom_fields[key] )+'</td> </tr> ';} } out+=' </tbody> </table> ';}out+=' </div> </div> ';if(it.has_sql_timings){out+=' <div class="profiler-queries"> <table> <thead> <tr> <th class="ta-right">step<br />time from start<br />query type<br />duration<br />records</th> <th class="ta-left">call stack<br />query</th> </tr> </thead> <tbody> ';var arr5=MiniProfiler.getSqlTimings(it.root);if(arr5){var value,index=-1,l5=arr5.length-1;while(index<l5){value=arr5[index+=1];out+=' '+( MiniProfiler.templates.sqlGapTemplate({g: value.prevGap}) )+' '+( MiniProfiler.templates.sqlTimingTemplate({i: index, s: value}) )+' ';if(value.nextGap){out+=' '+( MiniProfiler.templates.sqlGapTemplate({g: value.nextGap}) )+' ';}out+=' ';} } out+=' </tbody> </table> <p class="profiler-trivial-gap-container"> <a class="profiler-toggle-trivial-gaps">show trivial gaps</a> </p> </div> ';}out+=' </div>';return out;
11
11
  }
12
12
  MiniProfiler.templates["linksTemplate"] = function anonymous(it
13
13
  ) {
@@ -19,7 +19,7 @@ var out=' <tr class="';if(it.timing.is_trivial){out+='profiler-trivial';}out+='"
19
19
  }
20
20
  MiniProfiler.templates["sqlTimingTemplate"] = function anonymous(it
21
21
  ) {
22
- var out=' <tr class="'+( it.s.row_class || '' )+'" data-timing-id="'+( it.s.parent_timing_id )+'"> <td class="profiler-info"> <div>'+( it.s.parent_timing_name )+'</div> <div class="profiler-number"><span class="profiler-unit">T+</span>'+( MiniProfiler.formatDuration(it.s.start_milliseconds) )+' <span class="profiler-unit">ms</span></div> <div> ';if(it.s.is_duplicate){out+='<span class="profiler-warning">DUPLICATE</span>';}out+=' '+( MiniProfiler.renderExecuteType(it.s.execute_type) )+' </div> <div title="';if(it.s.execute_type == 3){out+='first result fetched: '+( it.s.first_fetch_duration_milliseconds )+'ms';}out+='">'+( MiniProfiler.formatDuration(it.s.duration_milliseconds) )+' <span class="profiler-unit">ms</span></div> </td> <td> <div class="query"> <pre class="profiler-stack-trace">'+( it.s.stack_trace_snippet )+'</pre> ';if(it.s.formatted_command_string){out+=' <pre class="prettyprint lang-sql"><code>'+( it.s.formatted_command_string )+'; '+( MiniProfiler.formatParameters(it.s.parameters) )+'</code></pre> ';}else{out+=' <i>Query redacted</i> ';}out+=' </div> </td> </tr>';return out;
22
+ var out=' <tr class="'+( it.s.row_class || '' )+'" data-timing-id="'+( it.s.parent_timing_id )+'"> <td class="profiler-info"> <div>'+( it.s.parent_timing_name )+'</div> <div class="profiler-number"><span class="profiler-unit">T+</span>'+( MiniProfiler.formatDuration(it.s.start_milliseconds) )+' <span class="profiler-unit">ms</span></div> <div> ';if(it.s.is_duplicate){out+='<span class="profiler-warning">DUPLICATE</span>';}out+=' '+( MiniProfiler.renderExecuteType(it.s.execute_type) )+' </div> <div title="';if(it.s.execute_type == 3){out+='first result fetched: '+( it.s.first_fetch_duration_milliseconds )+'ms';}out+='">'+( MiniProfiler.formatDuration(it.s.duration_milliseconds) )+' <span class="profiler-unit">ms</span></div> ';if(it.s.row_count > 0){out+=' <div title="number and type of records instantiated by query">'+( it.s.class_name )+': '+( it.s.row_count )+'</div> ';}out+=' </td> <td> <div class="query"> <pre class="profiler-stack-trace">'+( it.s.stack_trace_snippet )+'</pre> ';if(it.s.cached){out+=' <span class="cached"> [CACHE] </span> ';}out+=' ';if(it.s.formatted_command_string){out+=' <pre class="prettyprint lang-sql"><code>'+( it.s.formatted_command_string )+'; '+( MiniProfiler.formatParameters(it.s.parameters) )+'</code></pre> ';}else{out+=' <i>Query redacted</i> ';}out+=' </div> </td> </tr>';return out;
23
23
  }
24
24
  MiniProfiler.templates["sqlGapTemplate"] = function anonymous(it
25
25
  ) {
@@ -5,7 +5,7 @@ module Rack
5
5
  def serve_snapshot(env)
6
6
  MiniProfiler.authorize_request
7
7
  status = 200
8
- headers = { 'Content-Type' => 'text/html' }
8
+ headers = { 'content-type' => 'text/html' }
9
9
  qp = Rack::Utils.parse_nested_query(env['QUERY_STRING'])
10
10
  if group_name = qp["group_name"]
11
11
  list = @storage.snapshots_group(group_name)
@@ -55,7 +55,12 @@ module Rack
55
55
  resources_env = env.dup
56
56
  resources_env['PATH_INFO'] = file_name
57
57
 
58
- rack_file = Rack::File.new(resources_root, 'Cache-Control' => "max-age=#{cache_control_value}")
58
+ if Gem::Version.new(Rack.release) >= Gem::Version.new("2.1.0")
59
+ rack_file = Rack::Files.new(resources_root, 'cache-control' => "max-age=#{cache_control_value}")
60
+ else
61
+ rack_file = Rack::File.new(resources_root, 'cache-control' => "max-age=#{cache_control_value}")
62
+ end
63
+
59
64
  rack_file.call(resources_env)
60
65
  end
61
66
 
@@ -88,11 +93,11 @@ module Rack
88
93
  # If we're an XMLHttpRequest, serve up the contents as JSON
89
94
  if request.xhr?
90
95
  result_json = page_struct.to_json
91
- [200, { 'Content-Type' => 'application/json' }, [result_json]]
96
+ [200, { 'content-type' => 'application/json' }, [result_json]]
92
97
  else
93
98
  # Otherwise give the HTML back
94
99
  html = generate_html(page_struct, env)
95
- [200, { 'Content-Type' => 'text/html' }, [html]]
100
+ [200, { 'content-type' => 'text/html' }, [html]]
96
101
  end
97
102
  end
98
103
 
@@ -125,7 +130,7 @@ module Rack
125
130
 
126
131
  unless defined?(MemoryProfiler) && MemoryProfiler.respond_to?(:report)
127
132
  message = "Please install the memory_profiler gem and require it: add gem 'memory_profiler' to your Gemfile"
128
- status, headers, body = @app.call(env)
133
+ _status, headers, body = @app.call(env)
129
134
  body.close if body.respond_to? :close
130
135
 
131
136
  return client_settings.handle_cookie(
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Rack
3
3
  class MiniProfiler
4
- ASSET_VERSION = 'c336b1303a50b21f5222bc23262ca7bf'
4
+ ASSET_VERSION = 'e0bcc9ce0ae3bb5d6b736b6f282f601f'
5
5
  end
6
6
  end
@@ -97,7 +97,9 @@ module Rack
97
97
 
98
98
  # redefined - since the accessor defines it first
99
99
  undef :authorization_mode=
100
+ # rubocop:disable Lint/DuplicateMethods
100
101
  def authorization_mode=(mode)
102
+ # rubocop:enable Lint/DuplicateMethods
101
103
  if mode == :whitelist
102
104
  warn "[DEPRECATION] `:whitelist` authorization mode is deprecated. Please use `:allow_authorized` instead."
103
105