rack-mini-profiler 2.3.1 → 2.3.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +52 -19
- data/lib/html/includes.css +9 -11
- data/lib/html/includes.js +28 -2
- data/lib/html/includes.scss +3 -11
- data/lib/html/includes.tmpl +5 -2
- data/lib/html/profile_handler.js +1 -1
- data/lib/html/vendor.js +2 -2
- data/lib/mini_profiler/asset_version.rb +1 -1
- data/lib/mini_profiler/client_settings.rb +5 -5
- data/lib/mini_profiler/config.rb +23 -2
- data/lib/mini_profiler/profiler.rb +92 -47
- data/lib/mini_profiler/storage/abstract_store.rb +1 -1
- data/lib/mini_profiler/storage/file_store.rb +2 -0
- data/lib/mini_profiler/storage/memcache_store.rb +11 -7
- data/lib/mini_profiler/storage/redis_store.rb +18 -12
- data/lib/mini_profiler/timer_struct/page.rb +9 -3
- data/lib/mini_profiler/version.rb +2 -1
- data/lib/mini_profiler_rails/railtie.rb +1 -1
- data/lib/patches/db/riak.rb +1 -1
- data/rack-mini-profiler.gemspec +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6021effb717c193c4c70b3ac7a3550fd99c9abfd19432ac0ab9db63f62b11321
|
4
|
+
data.tar.gz: 8bce19855d2f6d908339e3108201282c519b4d8ad99b8e7e90dad9d3f718c929
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0c4354c194a6fa6018c57162e24e1cee85de7f296e45cd6182360b39abeb803b29a14674e2dfe93b3ba5d9bcb192814de8d091ad6b7db20d71af95e2cddcf4eb
|
7
|
+
data.tar.gz: e3c7f4da5fd5c79bfdb4c3582b50a38346d38784bfd92c6e7ce3fa41713104c74b6ad5622084d42624011ef850533c403002664f44ad7f6e1b1ac16a245c633b
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## 2.3.4 - 2022-02-23
|
4
|
+
|
5
|
+
- [FEATURE] Add cookie path support for subfolder sites
|
6
|
+
- [FIX] Remove deprecated uses of Redis#pipelined
|
7
|
+
|
8
|
+
## 2.3.3 - 2021-08-30
|
9
|
+
|
10
|
+
- [FEATURE] Introduce `pp=flamegraph_mode`
|
11
|
+
- [FEATURE] Richer CSP configuration options
|
12
|
+
- [FEATURE] Add support for Hotwire Turbo Drive
|
13
|
+
|
14
|
+
## 2.3.2 - 2021-04-30
|
15
|
+
|
16
|
+
- [FEATURE] Introduce `pp=async-flamegraph` for asynchronous flamegraphs
|
17
|
+
|
3
18
|
## 2.3.1 - 2021-01-29
|
4
19
|
|
5
20
|
- [FIX] compatability with Ruby 3.0
|
data/README.md
CHANGED
@@ -166,10 +166,14 @@ export RACK_MINI_PROFILER_PATCH="false"
|
|
166
166
|
|
167
167
|
### Flamegraphs
|
168
168
|
|
169
|
-
To generate [flamegraphs](http://samsaffron.com/archive/2013/03/19/flame-graphs-in-ruby-miniprofiler)
|
169
|
+
To generate [flamegraphs](http://samsaffron.com/archive/2013/03/19/flame-graphs-in-ruby-miniprofiler), add the [**stackprof**](https://rubygems.org/gems/stackprof) gem to your Gemfile.
|
170
170
|
|
171
|
-
|
172
|
-
|
171
|
+
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.
|
172
|
+
|
173
|
+
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.
|
174
|
+
|
175
|
+
Note: Mini Profiler will not record SQL timings for a request if it asks for a flamegraph. The rationale behind this is to keep
|
176
|
+
Mini Profiler's methods that are responsible for generating the timings data out of the flamegraph.
|
173
177
|
|
174
178
|
### Memory Profiling
|
175
179
|
|
@@ -197,7 +201,7 @@ There are two additional `pp` options that can be used to analyze memory which d
|
|
197
201
|
|
198
202
|
In a complex web application, it's possible for a request to trigger rare conditions that result in poor performance. Mini Profiler ships with a feature to help detect those rare conditions and fix them. It works by enabling invisible profiling on one request every N requests, and saving the performance metrics that are collected during the request (a.k.a snapshot of the request) so that they can be viewed later. To turn this feature on, set the `snapshot_every_n_requests` config to a value larger than 0. The larger the value is, the less frequently requests are profiled.
|
199
203
|
|
200
|
-
Mini Profiler will exclude requests that are made to
|
204
|
+
Mini Profiler will exclude requests that are made to skipped paths (see `skip_paths` config below) from being sampled. Additionally, if profiling is enabled for a request that later finishes with a non-2xx status code, Mini Profiler will discard the snapshot and not save it (this behavior may change in the future).
|
201
205
|
|
202
206
|
After enabling snapshots sampling, you can see the snapshots that have been collected at `/mini-profiler-resources/snapshots` (or if you changed the `base_url_path` config, substitute `mini-profiler-resources` with your value of the config). You'll see on that page a table where each row represents a group of snapshots with the duration of the worst snapshot in that group. The worst snapshot in a group is defined as the snapshot whose request took longer than all of the snapshots in the same group. Snapshots grouped by HTTP method and path of the request, and if your application is a Rails app, Mini Profiler will try to convert the path to `controller#action` and group by that instead of request path. Clicking on a group will display the snapshots of that group sorted from worst to best. From there, you can click on a snapshot's ID to see the snapshot with all the performance metrics that were collected.
|
203
207
|
|
@@ -235,21 +239,21 @@ rack-mini-profiler is designed with production profiling in mind. To enable that
|
|
235
239
|
|
236
240
|
Note:
|
237
241
|
|
238
|
-
Out-of-the-box we will initialize the `authorization_mode` to `:
|
242
|
+
Out-of-the-box we will initialize the `authorization_mode` to `:allow_authorized` in production. However, in some cases we may not be able to do it:
|
239
243
|
|
240
|
-
- If you are running in development or test we will not enable
|
244
|
+
- If you are running in development or test we will not enable the explicit authorization mode
|
241
245
|
- If you use `require: false` on rack_mini_profiler we are unlikely to be able to run the railtie
|
242
246
|
- If you are running outside of rails we will not run the railtie
|
243
247
|
|
244
248
|
In those cases use:
|
245
249
|
|
246
250
|
```ruby
|
247
|
-
Rack::MiniProfiler.config.authorization_mode = :
|
251
|
+
Rack::MiniProfiler.config.authorization_mode = :allow_authorized
|
248
252
|
```
|
249
253
|
|
250
254
|
When deciding to fully profile a page mini profiler consults with the `authorization_mode`
|
251
255
|
|
252
|
-
By default in production we attempt to set the authorization mode to `:
|
256
|
+
By default in production we attempt to set the authorization mode to `:allow_authorized` meaning that end user will only be able to see requests where somewhere `Rack::MiniProfiler.authorize_request` is invoked.
|
253
257
|
|
254
258
|
In development we run in the `:allow_all` authorization mode meaning every request is profiled and displayed to the end user.
|
255
259
|
|
@@ -343,19 +347,41 @@ Single page applications built using Ember, Angular or other frameworks need som
|
|
343
347
|
On route transition always call:
|
344
348
|
|
345
349
|
```
|
346
|
-
window.MiniProfiler
|
350
|
+
if (window.MiniProfiler !== undefined) {
|
351
|
+
window.MiniProfiler.pageTransition();
|
352
|
+
}
|
347
353
|
```
|
348
354
|
|
349
355
|
This method will remove profiling information that was related to previous page and clear aggregate statistics.
|
350
356
|
|
351
357
|
#### MiniProfiler's speed badge on pages that are not generated via Rails
|
352
|
-
You need to inject the following in your SPA to load MiniProfiler's speed badge ([extra details surrounding this script](https://github.com/MiniProfiler/rack-mini-profiler/issues/139#issuecomment-192880706)):
|
358
|
+
You need to inject the following in your SPA to load MiniProfiler's speed badge ([extra details surrounding this script](https://github.com/MiniProfiler/rack-mini-profiler/issues/139#issuecomment-192880706) and [credit for the script tag](https://github.com/MiniProfiler/rack-mini-profiler/issues/479#issue-782488320) to [@ivanyv](https://github.com/ivanyv)):
|
353
359
|
|
354
360
|
```html
|
355
|
-
<script
|
356
|
-
|
357
|
-
|
358
|
-
|
361
|
+
<script type="text/javascript" id="mini-profiler"
|
362
|
+
src="/mini-profiler-resources/includes.js?v=12b4b45a3c42e6e15503d7a03810ff33"
|
363
|
+
data-css-url="/mini-profiler-resources/includes.css?v=12b4b45a3c42e6e15503d7a03810ff33"
|
364
|
+
data-version="12b4b45a3c42e6e15503d7a03810ff33"
|
365
|
+
data-path="/mini-profiler-resources/"
|
366
|
+
data-horizontal-position="left"
|
367
|
+
data-vertical-position="top"
|
368
|
+
data-ids=""
|
369
|
+
data-trivial="false"
|
370
|
+
data-children="false"
|
371
|
+
data-max-traces="20"
|
372
|
+
data-controls="false"
|
373
|
+
data-total-sql-count="false"
|
374
|
+
data-authorized="true"
|
375
|
+
data-toggle-shortcut="alt+p"
|
376
|
+
data-start-hidden="false"
|
377
|
+
data-collapse-results="true"
|
378
|
+
data-html-container="body"
|
379
|
+
data-hidden-custom-fields></script>
|
380
|
+
```
|
381
|
+
|
382
|
+
See an [example of how to do this in a React useEffect](https://gist.github.com/katelovescode/01cfc2b962c165193b160fd10af6c4d5).
|
383
|
+
|
384
|
+
_Note:_ The GUID (`data-version` and the `?v=` parameter on the `src` and `data-css-url`) will change with each release of `rack_mini_profiler`. The MiniProfiler's speed badge will continue to work, although you will have to change the GUID to expire the script to fetch the most recent version.
|
359
385
|
|
360
386
|
#### Using MiniProfiler's built in route for apps without HTML responses
|
361
387
|
MiniProfiler also ships with a `/rack-mini-profiler/requests` route that displays the speed badge on a blank HTML page. This can be useful when profiling an application that does not render HTML.
|
@@ -394,12 +420,14 @@ toggle_shortcut|Alt+P|Keyboard shortcut to toggle the mini_profiler's visibility
|
|
394
420
|
start_hidden|`false`|`false` to make mini_profiler visible on page load.
|
395
421
|
backtrace_threshold_ms|`0`|Minimum SQL query elapsed time before a backtrace is recorded.
|
396
422
|
flamegraph_sample_rate|`0.5`|How often to capture stack traces for flamegraphs in milliseconds.
|
423
|
+
flamegraph_mode|`:wall`|The [StackProf mode](https://github.com/tmm1/stackprof#all-options) to pass to `StackProf.run`.
|
397
424
|
base_url_path|`'/mini-profiler-resources/'`|Path for assets; added as a prefix when naming assets and sought when responding to requests.
|
425
|
+
cookie_path|`'/'`|Set-Cookie header path for profile cookie
|
398
426
|
collapse_results|`true`|If multiple timing results exist in a single page, collapse them till clicked.
|
399
427
|
max_traces_to_show|20|Maximum number of mini profiler timing blocks to show on one page
|
400
428
|
html_container|`body`|The HTML container (as a jQuery selector) to inject the mini_profiler UI into
|
401
429
|
show_total_sql_count|`false`|Displays the total number of SQL executions.
|
402
|
-
enable_advanced_debugging_tools|`false`|Enables sensitive debugging tools that can be used via the UI. In production we recommend keeping this disabled as memory and environment debugging tools can expose contents of memory that may contain passwords.
|
430
|
+
enable_advanced_debugging_tools|`false`|Enables sensitive debugging tools that can be used via the UI. In production we recommend keeping this disabled as memory and environment debugging tools can expose contents of memory that may contain passwords. Defaults to `true` in development.
|
403
431
|
assets_url|`nil`|See the "Register MiniProfiler's assets in the Rails assets pipeline" section above.
|
404
432
|
snapshot_every_n_requests|`-1`|Determines how frequently snapshots are taken. See the "Snapshots Sampling" above for more details.
|
405
433
|
snapshots_limit|`1000`|Determines how many snapshots Mini Profiler is allowed to keep.
|
@@ -408,13 +436,15 @@ snapshots_transport_destination_url|`nil`|Set this config to a valid URL to enab
|
|
408
436
|
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.
|
409
437
|
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.
|
410
438
|
snapshots_transport_gzip_requests|`false`|Make the snapshots transporter gzip the requests it makes to `snapshots_transport_destination_url`.
|
439
|
+
content_security_policy_nonce|Rails: Current nonce<br>Rack: nil|Set the content security policy nonce to use when inserting MiniProfiler's script block.
|
440
|
+
enable_hotwire_turbo_drive_support| `false` | Enable support for Hotwire TurboDrive page transitions.
|
411
441
|
|
412
442
|
### Using MiniProfiler with `Rack::Deflate` middleware
|
413
443
|
|
414
444
|
If you are using `Rack::Deflate` with Rails and `rack-mini-profiler` in its default configuration,
|
415
445
|
`Rack::MiniProfiler` will be injected (as always) at position 0 in the middleware stack,
|
416
446
|
which means it will run after `Rack::Deflate` on response processing. To prevent attempting to inject
|
417
|
-
HTML in already compressed response body MiniProfiler will suppress compression by setting
|
447
|
+
HTML in already compressed response body MiniProfiler will suppress compression by setting
|
418
448
|
`identity` encoding in `Accept-Encoding` request header.
|
419
449
|
|
420
450
|
## Special query strings
|
@@ -457,20 +487,23 @@ end
|
|
457
487
|
If you want to contribute to this project, that's great, thank you! You can run the following rake task:
|
458
488
|
|
459
489
|
```
|
490
|
+
$ BUNDLE_GEMFILE=website/Gemfile bundle install
|
460
491
|
$ bundle exec rake client_dev
|
461
492
|
```
|
462
493
|
|
463
|
-
|
494
|
+
This 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.
|
495
|
+
|
496
|
+
Make sure to prepend `bundle exec` before any Rake tasks you run.
|
464
497
|
|
465
498
|
## Running the Specs
|
466
499
|
|
500
|
+
You need Memcached and Redis services running for the specs.
|
501
|
+
|
467
502
|
```
|
468
503
|
$ rake build
|
469
504
|
$ rake spec
|
470
505
|
```
|
471
506
|
|
472
|
-
Additionally you can also run `autotest` if you like.
|
473
|
-
|
474
507
|
## Licence
|
475
508
|
|
476
509
|
The MIT License (MIT)
|
data/lib/html/includes.css
CHANGED
@@ -69,8 +69,16 @@
|
|
69
69
|
.profiler-result .custom-fields-title,
|
70
70
|
.profiler-queries .custom-fields-title {
|
71
71
|
color: #555;
|
72
|
-
font: Helvetica, Arial, sans-serif;
|
72
|
+
font-family: Helvetica, Arial, sans-serif;
|
73
73
|
font-size: 14px; }
|
74
|
+
.mp-snapshots .ta-left,
|
75
|
+
.profiler-result .ta-left,
|
76
|
+
.profiler-queries .ta-left {
|
77
|
+
text-align: left; }
|
78
|
+
.mp-snapshots .ta-right,
|
79
|
+
.profiler-result .ta-right,
|
80
|
+
.profiler-queries .ta-right {
|
81
|
+
text-align: right; }
|
74
82
|
|
75
83
|
.profiler-result {
|
76
84
|
font-family: Helvetica, Arial, sans-serif; }
|
@@ -233,8 +241,6 @@
|
|
233
241
|
left: 0px; }
|
234
242
|
.profiler-results.profiler-top.profiler-left.profiler-no-controls .profiler-totals, .profiler-results.profiler-top.profiler-left.profiler-no-controls .profiler-result:last-child .profiler-button,
|
235
243
|
.profiler-results.profiler-top.profiler-left .profiler-controls {
|
236
|
-
-webkit-border-bottom-right-radius: 10px;
|
237
|
-
-moz-border-radius-bottomright: 10px;
|
238
244
|
border-bottom-right-radius: 10px; }
|
239
245
|
.profiler-results.profiler-top.profiler-left .profiler-button,
|
240
246
|
.profiler-results.profiler-top.profiler-left .profiler-controls {
|
@@ -243,8 +249,6 @@
|
|
243
249
|
right: 0px; }
|
244
250
|
.profiler-results.profiler-top.profiler-right.profiler-no-controls .profiler-totals, .profiler-results.profiler-top.profiler-right.profiler-no-controls .profiler-result:last-child .profiler-button,
|
245
251
|
.profiler-results.profiler-top.profiler-right .profiler-controls {
|
246
|
-
-webkit-border-bottom-left-radius: 10px;
|
247
|
-
-moz-border-radius-bottomleft: 10px;
|
248
252
|
border-bottom-left-radius: 10px; }
|
249
253
|
.profiler-results.profiler-top.profiler-right .profiler-button,
|
250
254
|
.profiler-results.profiler-top.profiler-right .profiler-controls {
|
@@ -255,8 +259,6 @@
|
|
255
259
|
left: 0px; }
|
256
260
|
.profiler-results.profiler-bottom.profiler-left.profiler-no-controls .profiler-totals, .profiler-results.profiler-bottom.profiler-left.profiler-no-controls .profiler-result:first-child .profiler-button,
|
257
261
|
.profiler-results.profiler-bottom.profiler-left .profiler-controls {
|
258
|
-
-webkit-border-top-right-radius: 10px;
|
259
|
-
-moz-border-radius-topright: 10px;
|
260
262
|
border-top-right-radius: 10px; }
|
261
263
|
.profiler-results.profiler-bottom.profiler-left .profiler-button,
|
262
264
|
.profiler-results.profiler-bottom.profiler-left .profiler-controls {
|
@@ -265,8 +267,6 @@
|
|
265
267
|
right: 0px; }
|
266
268
|
.profiler-results.profiler-bottom.profiler-right.profiler-no-controls .profiler-totals, .profiler-results.profiler-bottom.profiler-right.profiler-no-controls .profiler-result:first-child .profiler-button,
|
267
269
|
.profiler-results.profiler-bottom.profiler-right .profiler-controls {
|
268
|
-
-webkit-border-bottom-top-radius: 10px;
|
269
|
-
-moz-border-radius-topleft: 10px;
|
270
270
|
border-top-left-radius: 10px; }
|
271
271
|
.profiler-results.profiler-bottom.profiler-right .profiler-button,
|
272
272
|
.profiler-results.profiler-bottom.profiler-right .profiler-controls {
|
@@ -324,8 +324,6 @@
|
|
324
324
|
text-align: left;
|
325
325
|
line-height: 18px;
|
326
326
|
overflow: auto;
|
327
|
-
-moz-box-shadow: 0px 1px 15px #555;
|
328
|
-
-webkit-box-shadow: 0px 1px 15px #555;
|
329
327
|
box-shadow: 0px 1px 15px #555; }
|
330
328
|
.profiler-results .profiler-popup .profiler-info {
|
331
329
|
margin-bottom: 3px;
|
data/lib/html/includes.js
CHANGED
@@ -401,7 +401,7 @@ var _MiniProfiler = (function() {
|
|
401
401
|
var px = button.offsetTop - 1,
|
402
402
|
// position next to the button we clicked
|
403
403
|
windowHeight = window.innerHeight,
|
404
|
-
maxHeight = windowHeight - 40; // make sure the popup doesn't extend below the fold
|
404
|
+
maxHeight = windowHeight - px - 40; // make sure the popup doesn't extend below the fold
|
405
405
|
|
406
406
|
popup.style[options.renderVerticalPosition] = "".concat(px, "px");
|
407
407
|
popup.style.maxHeight = "".concat(maxHeight, "px");
|
@@ -495,6 +495,19 @@ var _MiniProfiler = (function() {
|
|
495
495
|
}, 3000);
|
496
496
|
};
|
497
497
|
|
498
|
+
var onTurboBeforeVisit = function onTurboBeforeVisit(e) {
|
499
|
+
if(!e.defaultPrevented) {
|
500
|
+
window.MiniProfilerContainer = document.querySelector('body > .profiler-results')
|
501
|
+
window.MiniProfiler.pageTransition()
|
502
|
+
}
|
503
|
+
}
|
504
|
+
|
505
|
+
var onTurboLoad = function onTurboLoad(e) {
|
506
|
+
if(window.MiniProfilerContainer) {
|
507
|
+
document.body.appendChild(window.MiniProfilerContainer)
|
508
|
+
}
|
509
|
+
}
|
510
|
+
|
498
511
|
var onClickEvents = function onClickEvents(e) {
|
499
512
|
// this happens on every keystroke, and :visible is crazy expensive in IE <9
|
500
513
|
// and in this case, the display:none check is sufficient.
|
@@ -652,6 +665,11 @@ var _MiniProfiler = (function() {
|
|
652
665
|
turbolinksSkipResultsFetch
|
653
666
|
);
|
654
667
|
}
|
668
|
+
|
669
|
+
if (options.hotwireTurboDriveSupport) {
|
670
|
+
document.addEventListener("turbo:before-visit", onTurboBeforeVisit)
|
671
|
+
document.addEventListener("turbo:load", onTurboLoad)
|
672
|
+
}
|
655
673
|
};
|
656
674
|
|
657
675
|
var unbindDocumentEvents = function unbindDocumentEvents() {
|
@@ -664,6 +682,8 @@ var _MiniProfiler = (function() {
|
|
664
682
|
"turbolinks:request-start",
|
665
683
|
turbolinksSkipResultsFetch
|
666
684
|
);
|
685
|
+
document.removeEventListener("turbo:before-visit", onTurboBeforeVisit);
|
686
|
+
document.removeEventListener("turbo:load", onTurboLoad);
|
667
687
|
};
|
668
688
|
|
669
689
|
var initFullView = function initFullView() {
|
@@ -1033,6 +1053,8 @@ var _MiniProfiler = (function() {
|
|
1033
1053
|
.getAttribute("data-hidden-custom-fields")
|
1034
1054
|
.toLowerCase()
|
1035
1055
|
.split(",");
|
1056
|
+
var hotwireTurboDriveSupport = script
|
1057
|
+
.getAttribute('data-turbo-permanent') === "true";
|
1036
1058
|
return {
|
1037
1059
|
ids: ids,
|
1038
1060
|
path: path,
|
@@ -1051,7 +1073,8 @@ var _MiniProfiler = (function() {
|
|
1051
1073
|
collapseResults: collapseResults,
|
1052
1074
|
htmlContainer: htmlContainer,
|
1053
1075
|
cssUrl: cssUrl,
|
1054
|
-
hiddenCustomFields: hiddenCustomFields
|
1076
|
+
hiddenCustomFields: hiddenCustomFields,
|
1077
|
+
hotwireTurboDriveSupport: hotwireTurboDriveSupport
|
1055
1078
|
};
|
1056
1079
|
})();
|
1057
1080
|
|
@@ -1213,6 +1236,9 @@ var _MiniProfiler = (function() {
|
|
1213
1236
|
shareUrl: function shareUrl(id) {
|
1214
1237
|
return options.path + "results?id=" + id;
|
1215
1238
|
},
|
1239
|
+
flamegraphUrl: function flamegrapgUrl(id) {
|
1240
|
+
return options.path + "flamegraph?id=" + id;
|
1241
|
+
},
|
1216
1242
|
moreUrl: function moreUrl(requestName) {
|
1217
1243
|
var requestParts = requestName.split(" ");
|
1218
1244
|
var linkSrc =
|
data/lib/html/includes.scss
CHANGED
@@ -1,6 +1,4 @@
|
|
1
1
|
@mixin box-shadow($dx, $dy, $radius, $color) {
|
2
|
-
-moz-box-shadow: $dx $dy $radius $color;
|
3
|
-
-webkit-box-shadow: $dx $dy $radius $color;
|
4
2
|
box-shadow: $dx $dy $radius $color;
|
5
3
|
}
|
6
4
|
|
@@ -58,9 +56,11 @@ $zindex: 2147483640; // near 32bit max 2147483647
|
|
58
56
|
}
|
59
57
|
.custom-fields-title {
|
60
58
|
color: $textColor;
|
61
|
-
font: $normalFonts;
|
59
|
+
font-family: $normalFonts;
|
62
60
|
font-size: 14px;
|
63
61
|
}
|
62
|
+
.ta-left { text-align: left; }
|
63
|
+
.ta-right { text-align: right; }
|
64
64
|
}
|
65
65
|
|
66
66
|
// styles shared between popup view and full view
|
@@ -326,8 +326,6 @@ $zindex: 2147483640; // near 32bit max 2147483647
|
|
326
326
|
&.profiler-no-controls .profiler-totals,
|
327
327
|
&.profiler-no-controls .profiler-result:last-child .profiler-button,
|
328
328
|
.profiler-controls {
|
329
|
-
-webkit-border-bottom-right-radius: $radius;
|
330
|
-
-moz-border-radius-bottomright: $radius;
|
331
329
|
border-bottom-right-radius: $radius;
|
332
330
|
}
|
333
331
|
|
@@ -343,8 +341,6 @@ $zindex: 2147483640; // near 32bit max 2147483647
|
|
343
341
|
&.profiler-no-controls .profiler-totals,
|
344
342
|
&.profiler-no-controls .profiler-result:last-child .profiler-button,
|
345
343
|
.profiler-controls {
|
346
|
-
-webkit-border-bottom-left-radius: $radius;
|
347
|
-
-moz-border-radius-bottomleft: $radius;
|
348
344
|
border-bottom-left-radius: $radius;
|
349
345
|
}
|
350
346
|
|
@@ -364,8 +360,6 @@ $zindex: 2147483640; // near 32bit max 2147483647
|
|
364
360
|
&.profiler-no-controls .profiler-totals,
|
365
361
|
&.profiler-no-controls .profiler-result:first-child .profiler-button,
|
366
362
|
.profiler-controls {
|
367
|
-
-webkit-border-top-right-radius: $radius;
|
368
|
-
-moz-border-radius-topright: $radius;
|
369
363
|
border-top-right-radius: $radius;
|
370
364
|
}
|
371
365
|
|
@@ -381,8 +375,6 @@ $zindex: 2147483640; // near 32bit max 2147483647
|
|
381
375
|
&.profiler-no-controls .profiler-totals,
|
382
376
|
&.profiler-no-controls .profiler-result:first-child .profiler-button,
|
383
377
|
.profiler-controls {
|
384
|
-
-webkit-border-bottom-top-radius: $radius;
|
385
|
-
-moz-border-radius-topleft: $radius;
|
386
378
|
border-top-left-radius: $radius;
|
387
379
|
}
|
388
380
|
|
data/lib/html/includes.tmpl
CHANGED
@@ -117,8 +117,8 @@
|
|
117
117
|
<table>
|
118
118
|
<thead>
|
119
119
|
<tr>
|
120
|
-
<th
|
121
|
-
<th
|
120
|
+
<th class="ta-right">step<br />time from start<br />query type<br />duration</th>
|
121
|
+
<th class="ta-left">call stack<br />query</th>
|
122
122
|
</tr>
|
123
123
|
</thead>
|
124
124
|
<tbody>
|
@@ -142,6 +142,9 @@
|
|
142
142
|
<script id="linksTemplate" type="text/x-dot-tmpl">
|
143
143
|
<a href="{{= MiniProfiler.shareUrl(it.page.id) }}" class="profiler-share-profiler-results" target="_blank">share</a>
|
144
144
|
<a href="{{= MiniProfiler.moreUrl(it.timing.name) }}" class="profiler-more-actions">more</a>
|
145
|
+
{{? it.page.has_flamegraph}}
|
146
|
+
<a href="{{= MiniProfiler.flamegraphUrl(it.page.id) }}" class="profiler-show-flamegraph" target="_blank">flamegraph</a>
|
147
|
+
{{?}}
|
145
148
|
{{? it.custom_link}}
|
146
149
|
<a href="{{= it.custom_link }}" class="profiler-custom-link" target="_blank">{{= it.custom_link_name }}</a>
|
147
150
|
{{?}}
|
data/lib/html/profile_handler.js
CHANGED
@@ -1 +1 @@
|
|
1
|
-
<script async type="text/javascript" id="mini-profiler" src="{url}" data-css-url="{cssUrl}" data-version="{version}" data-path="{path}" data-current-id="{currentId}" data-ids="{ids}" data-horizontal-position="{horizontalPosition}" data-vertical-position="{verticalPosition}" data-trivial="{showTrivial}" data-children="{showChildren}" data-max-traces="{maxTracesToShow}" data-controls="{showControls}" data-total-sql-count="{showTotalSqlCount}" data-authorized="{authorized}" data-toggle-shortcut="{toggleShortcut}" data-start-hidden="{startHidden}" data-collapse-results="{collapseResults}" data-html-container="{htmlContainer}" data-hidden-custom-fields="{hiddenCustomFields}"></script>
|
1
|
+
<script async nonce="{cspNonce}" type="text/javascript" id="mini-profiler" src="{url}" data-css-url="{cssUrl}" data-version="{version}" data-path="{path}" data-current-id="{currentId}" data-ids="{ids}" data-horizontal-position="{horizontalPosition}" data-vertical-position="{verticalPosition}" data-trivial="{showTrivial}" data-children="{showChildren}" data-max-traces="{maxTracesToShow}" data-controls="{showControls}" data-total-sql-count="{showTotalSqlCount}" data-authorized="{authorized}" data-toggle-shortcut="{toggleShortcut}" data-start-hidden="{startHidden}" data-collapse-results="{collapseResults}" data-html-container="{htmlContainer}" data-hidden-custom-fields="{hiddenCustomFields}" data-turbo-permanent="{hotwireTurboDriveSupport}"></script>
|
data/lib/html/vendor.js
CHANGED
@@ -7,11 +7,11 @@
|
|
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
|
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;
|
11
11
|
}
|
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.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 < '+( it.page.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.page.has_flamegraph){out+=' <a href="'+( MiniProfiler.flamegraphUrl(it.page.id) )+'" class="profiler-show-flamegraph" target="_blank">flamegraph</a> ';}out+=' ';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 < '+( it.page.trivial_duration_threshold_milliseconds )+' ms"> show trivial </a> ';}return out;
|
15
15
|
}
|
16
16
|
MiniProfiler.templates["timingTemplate"] = function anonymous(it
|
17
17
|
) {
|
@@ -42,7 +42,7 @@ module Rack
|
|
42
42
|
def handle_cookie(result)
|
43
43
|
status, headers, _body = result
|
44
44
|
|
45
|
-
if (MiniProfiler.config.authorization_mode == :
|
45
|
+
if (MiniProfiler.config.authorization_mode == :allow_authorized && !MiniProfiler.request_authorized?)
|
46
46
|
# this is non-obvious, don't kill the profiling cookie on errors or short requests
|
47
47
|
# this ensures that stuff that never reaches the rails stack does not kill profiling
|
48
48
|
if status.to_i >= 200 && status.to_i < 300 && ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - @start) > 0.1)
|
@@ -59,7 +59,7 @@ module Rack
|
|
59
59
|
|
60
60
|
tokens_changed = false
|
61
61
|
|
62
|
-
if MiniProfiler.request_authorized? && MiniProfiler.config.authorization_mode == :
|
62
|
+
if MiniProfiler.request_authorized? && MiniProfiler.config.authorization_mode == :allow_authorized
|
63
63
|
@allowed_tokens ||= @store.allowed_tokens
|
64
64
|
tokens_changed = !@orig_auth_tokens || ((@allowed_tokens - @orig_auth_tokens).length > 0)
|
65
65
|
end
|
@@ -74,7 +74,7 @@ module Rack
|
|
74
74
|
settings["bt"] = @backtrace_level if @backtrace_level
|
75
75
|
settings["a"] = @allowed_tokens.join("|") if @allowed_tokens && MiniProfiler.request_authorized?
|
76
76
|
settings_string = settings.map { |k, v| "#{k}=#{v}" }.join(",")
|
77
|
-
cookie = { value: settings_string, path:
|
77
|
+
cookie = { value: settings_string, path: MiniProfiler.config.cookie_path, httponly: true }
|
78
78
|
cookie[:secure] = true if @request.ssl?
|
79
79
|
cookie[:same_site] = 'Lax'
|
80
80
|
Rack::Utils.set_cookie_header!(headers, COOKIE_NAME, cookie)
|
@@ -83,14 +83,14 @@ module Rack
|
|
83
83
|
|
84
84
|
def discard_cookie!(headers)
|
85
85
|
if @cookie
|
86
|
-
Rack::Utils.delete_cookie_header!(headers, COOKIE_NAME, path:
|
86
|
+
Rack::Utils.delete_cookie_header!(headers, COOKIE_NAME, path: MiniProfiler.config.cookie_path)
|
87
87
|
end
|
88
88
|
end
|
89
89
|
|
90
90
|
def has_valid_cookie?
|
91
91
|
valid_cookie = !@cookie.nil?
|
92
92
|
|
93
|
-
if (MiniProfiler.config.authorization_mode == :
|
93
|
+
if (MiniProfiler.config.authorization_mode == :allow_authorized) && valid_cookie
|
94
94
|
begin
|
95
95
|
@allowed_tokens ||= @store.allowed_tokens
|
96
96
|
rescue => e
|
data/lib/mini_profiler/config.rb
CHANGED
@@ -17,6 +17,7 @@ module Rack
|
|
17
17
|
new.instance_eval {
|
18
18
|
@auto_inject = true # automatically inject on every html page
|
19
19
|
@base_url_path = "/mini-profiler-resources/".dup
|
20
|
+
@cookie_path = "/".dup
|
20
21
|
@disable_caching = true
|
21
22
|
# called prior to rack chain, to ensure we are allowed to profile
|
22
23
|
@pre_authorize_cb = lambda { |env| true }
|
@@ -28,6 +29,7 @@ module Rack
|
|
28
29
|
@authorization_mode = :allow_all
|
29
30
|
@backtrace_threshold_ms = 0
|
30
31
|
@flamegraph_sample_rate = 0.5
|
32
|
+
@flamegraph_mode = :wall
|
31
33
|
@storage_failure = Proc.new do |exception|
|
32
34
|
if @logger
|
33
35
|
@logger.warn("MiniProfiler storage failure: #{exception.message}")
|
@@ -57,6 +59,7 @@ module Rack
|
|
57
59
|
@snapshots_transport_auth_key = nil
|
58
60
|
@snapshots_redact_sql_queries = true
|
59
61
|
@snapshots_transport_gzip_requests = false
|
62
|
+
@enable_hotwire_turbo_drive_support = false
|
60
63
|
|
61
64
|
self
|
62
65
|
}
|
@@ -64,11 +67,13 @@ module Rack
|
|
64
67
|
|
65
68
|
attr_accessor :authorization_mode, :auto_inject, :backtrace_ignores,
|
66
69
|
:backtrace_includes, :backtrace_remove, :backtrace_threshold_ms,
|
67
|
-
:base_url_path, :disable_caching, :enabled,
|
70
|
+
:base_url_path, :cookie_path, :disable_caching, :enabled,
|
68
71
|
:flamegraph_sample_rate, :logger, :pre_authorize_cb, :skip_paths,
|
69
72
|
:skip_schema_queries, :storage, :storage_failure, :storage_instance,
|
70
73
|
:storage_options, :user_provider, :enable_advanced_debugging_tools,
|
71
|
-
:skip_sql_param_names, :suppress_encoding, :max_sql_param_length
|
74
|
+
:skip_sql_param_names, :suppress_encoding, :max_sql_param_length,
|
75
|
+
:content_security_policy_nonce, :enable_hotwire_turbo_drive_support,
|
76
|
+
:flamegraph_mode
|
72
77
|
|
73
78
|
# ui accessors
|
74
79
|
attr_accessor :collapse_results, :max_traces_to_show, :position,
|
@@ -86,6 +91,22 @@ module Rack
|
|
86
91
|
|
87
92
|
attr_reader :assets_url
|
88
93
|
|
94
|
+
# redefined - since the accessor defines it first
|
95
|
+
undef :authorization_mode=
|
96
|
+
def authorization_mode=(mode)
|
97
|
+
if mode == :whitelist
|
98
|
+
warn "[DEPRECATION] `:whitelist` authorization mode is deprecated. Please use `:allow_authorized` instead."
|
99
|
+
|
100
|
+
mode = :allow_authorized
|
101
|
+
end
|
102
|
+
|
103
|
+
warn <<~DEP unless mode == :allow_authorized || mode == :allow_all
|
104
|
+
[DEPRECATION] unknown authorization mode #{mode}. Expected `:allow_all` or `:allow_authorized`.
|
105
|
+
DEP
|
106
|
+
|
107
|
+
@authorization_mode = mode
|
108
|
+
end
|
109
|
+
|
89
110
|
def assets_url=(lmbda)
|
90
111
|
if defined?(Rack::MiniProfilerRails)
|
91
112
|
Rack::MiniProfilerRails.create_engine
|
@@ -182,6 +182,7 @@ module Rack
|
|
182
182
|
|
183
183
|
return serve_results(env) if file_name.eql?('results')
|
184
184
|
return handle_snapshots_request(env) if file_name.eql?('snapshots')
|
185
|
+
return serve_flamegraph(env) if file_name.eql?('flamegraph')
|
185
186
|
|
186
187
|
resources_env = env.dup
|
187
188
|
resources_env['PATH_INFO'] = file_name
|
@@ -213,7 +214,7 @@ module Rack
|
|
213
214
|
def call(env)
|
214
215
|
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
215
216
|
client_settings = ClientSettings.new(env, @storage, start)
|
216
|
-
MiniProfiler.deauthorize_request if @config.authorization_mode == :
|
217
|
+
MiniProfiler.deauthorize_request if @config.authorization_mode == :allow_authorized
|
217
218
|
|
218
219
|
status = headers = body = nil
|
219
220
|
query_string = env['QUERY_STRING']
|
@@ -239,7 +240,7 @@ module Rack
|
|
239
240
|
skip_it = (@config.pre_authorize_cb && !@config.pre_authorize_cb.call(env))
|
240
241
|
|
241
242
|
if skip_it || (
|
242
|
-
@config.authorization_mode == :
|
243
|
+
@config.authorization_mode == :allow_authorized &&
|
243
244
|
!client_settings.has_valid_cookie?
|
244
245
|
)
|
245
246
|
if take_snapshot?(path)
|
@@ -281,6 +282,15 @@ module Rack
|
|
281
282
|
# profile memory
|
282
283
|
if query_string =~ /pp=profile-memory/
|
283
284
|
return tool_disabled_message(client_settings) if !advanced_debugging_enabled?
|
285
|
+
|
286
|
+
unless defined?(MemoryProfiler) && MemoryProfiler.respond_to?(:report)
|
287
|
+
message = "Please install the memory_profiler gem and require it: add gem 'memory_profiler' to your Gemfile"
|
288
|
+
_, _, body = @app.call(env)
|
289
|
+
body.close if body.respond_to? :close
|
290
|
+
|
291
|
+
return client_settings.handle_cookie(text_result(message))
|
292
|
+
end
|
293
|
+
|
284
294
|
query_params = Rack::Utils.parse_nested_query(query_string)
|
285
295
|
options = {
|
286
296
|
ignore_files: query_params['memory_profiler_ignore_files'],
|
@@ -336,11 +346,12 @@ module Rack
|
|
336
346
|
# Prevent response body from being compressed
|
337
347
|
env['HTTP_ACCEPT_ENCODING'] = 'identity' if config.suppress_encoding
|
338
348
|
|
339
|
-
if query_string =~ /pp=flamegraph/
|
349
|
+
if query_string =~ /pp=(async-)?flamegraph/ || env['HTTP_REFERER'] =~ /pp=async-flamegraph/
|
340
350
|
unless defined?(StackProf) && StackProf.respond_to?(:run)
|
341
|
-
|
342
|
-
|
343
|
-
|
351
|
+
headers = { 'Content-Type' => 'text/html' }
|
352
|
+
message = "Please install the stackprof gem and require it: add gem 'stackprof' to your Gemfile"
|
353
|
+
body.close if body.respond_to? :close
|
354
|
+
return client_settings.handle_cookie([500, headers, message])
|
344
355
|
else
|
345
356
|
# do not sully our profile with mini profiler timings
|
346
357
|
current.measure = false
|
@@ -351,8 +362,17 @@ module Rack
|
|
351
362
|
else
|
352
363
|
sample_rate = config.flamegraph_sample_rate
|
353
364
|
end
|
365
|
+
|
366
|
+
mode_match_data = query_string.match(/flamegraph_mode=([a-zA-Z]+)/)
|
367
|
+
|
368
|
+
if mode_match_data && [:cpu, :wall, :object, :custom].include?(mode_match_data[1].to_sym)
|
369
|
+
mode = mode_match_data[1].to_sym
|
370
|
+
else
|
371
|
+
mode = config.flamegraph_mode
|
372
|
+
end
|
373
|
+
|
354
374
|
flamegraph = StackProf.run(
|
355
|
-
mode:
|
375
|
+
mode: mode,
|
356
376
|
raw: true,
|
357
377
|
aggregate: false,
|
358
378
|
interval: (sample_rate * 1000).to_i
|
@@ -379,7 +399,7 @@ module Rack
|
|
379
399
|
|
380
400
|
skip_it = current.discard
|
381
401
|
|
382
|
-
if (config.authorization_mode == :
|
402
|
+
if (config.authorization_mode == :allow_authorized && !MiniProfiler.request_authorized?)
|
383
403
|
skip_it = true
|
384
404
|
end
|
385
405
|
|
@@ -420,9 +440,12 @@ module Rack
|
|
420
440
|
page_struct[:user] = user(env)
|
421
441
|
page_struct[:root].record_time((Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000)
|
422
442
|
|
423
|
-
if flamegraph
|
443
|
+
if flamegraph && query_string =~ /pp=flamegraph/
|
424
444
|
body.close if body.respond_to? :close
|
425
445
|
return client_settings.handle_cookie(self.flamegraph(flamegraph, path))
|
446
|
+
elsif flamegraph # async-flamegraph
|
447
|
+
page_struct[:has_flamegraph] = true
|
448
|
+
page_struct[:flamegraph] = flamegraph
|
426
449
|
end
|
427
450
|
|
428
451
|
begin
|
@@ -616,7 +639,7 @@ module Rack
|
|
616
639
|
end
|
617
640
|
|
618
641
|
def text_result(body)
|
619
|
-
headers = { 'Content-Type' => 'text/plain' }
|
642
|
+
headers = { 'Content-Type' => 'text/plain; charset=utf-8' }
|
620
643
|
[200, headers, [body]]
|
621
644
|
end
|
622
645
|
|
@@ -629,7 +652,7 @@ module Rack
|
|
629
652
|
headers = { 'Content-Type' => 'text/html' }
|
630
653
|
body = "<html><body>
|
631
654
|
<pre style='line-height: 30px; font-size: 16px;'>
|
632
|
-
|
655
|
+
This is the help menu of the <a href='#{Rack::MiniProfiler::SOURCE_CODE_URI}'>rack-mini-profiler</a> gem, append the following to your query string for more options:
|
633
656
|
|
634
657
|
#{make_link "help", env} : display this screen
|
635
658
|
#{make_link "env", env} : display the rack environment
|
@@ -639,13 +662,15 @@ Append the following to your query string:
|
|
639
662
|
#{make_link "full-backtrace", env} #{"(*) " if client_settings.backtrace_full?}: enable full backtraces for SQL executed (use pp=normal-backtrace to disable)
|
640
663
|
#{make_link "disable", env} : disable profiling for this session
|
641
664
|
#{make_link "enable", env} : enable profiling for this session (if previously disabled)
|
642
|
-
#{make_link "profile-gc", env} : perform gc profiling on this request, analyzes ObjectSpace generated by request
|
665
|
+
#{make_link "profile-gc", env} : perform gc profiling on this request, analyzes ObjectSpace generated by request
|
643
666
|
#{make_link "profile-memory", env} : requires the memory_profiler gem, new location based report
|
644
|
-
#{make_link "flamegraph", env} :
|
667
|
+
#{make_link "flamegraph", env} : a graph representing sampled activity (requires the stackprof gem).
|
668
|
+
#{make_link "async-flamegraph", env} : store flamegraph data for this page and all its AJAX requests. Flamegraph links will be available in the mini-profiler UI (requires the stackprof gem).
|
645
669
|
#{make_link "flamegraph&flamegraph_sample_rate=1", env}: creates a flamegraph with the specified sample rate (in ms). Overrides value set in config
|
646
|
-
#{make_link "
|
647
|
-
#{make_link "
|
648
|
-
#{make_link "
|
670
|
+
#{make_link "flamegraph&flamegraph_mode=cpu", env}: creates a flamegraph with the specified mode (one of cpu, wall, object, or custom). Overrides value set in config
|
671
|
+
#{make_link "flamegraph_embed", env} : a graph representing sampled activity (requires the stackprof gem), embedded resources for use on an intranet.
|
672
|
+
#{make_link "trace-exceptions", env} : will return all the spots where your application raises exceptions
|
673
|
+
#{make_link "analyze-memory", env} : will perform basic memory analysis of heap
|
649
674
|
</pre>
|
650
675
|
</body>
|
651
676
|
</html>
|
@@ -656,35 +681,31 @@ Append the following to your query string:
|
|
656
681
|
|
657
682
|
def flamegraph(graph, path)
|
658
683
|
headers = { 'Content-Type' => 'text/html' }
|
659
|
-
|
660
|
-
html
|
661
|
-
|
662
|
-
<
|
663
|
-
<
|
664
|
-
|
665
|
-
|
666
|
-
|
667
|
-
|
668
|
-
|
669
|
-
<
|
670
|
-
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
675
|
-
|
676
|
-
|
677
|
-
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
684
|
-
[200, headers, [html]]
|
685
|
-
else
|
686
|
-
[200, headers, [graph]]
|
687
|
-
end
|
684
|
+
html = <<~HTML
|
685
|
+
<!DOCTYPE html>
|
686
|
+
<html>
|
687
|
+
<head>
|
688
|
+
<style>
|
689
|
+
body { margin: 0; height: 100vh; }
|
690
|
+
#speedscope-iframe { width: 100%; height: 100%; border: none; }
|
691
|
+
</style>
|
692
|
+
</head>
|
693
|
+
<body>
|
694
|
+
<script type="text/javascript">
|
695
|
+
var graph = #{JSON.generate(graph)};
|
696
|
+
var json = JSON.stringify(graph);
|
697
|
+
var blob = new Blob([json], { type: 'text/plain' });
|
698
|
+
var objUrl = encodeURIComponent(URL.createObjectURL(blob));
|
699
|
+
var iframe = document.createElement('IFRAME');
|
700
|
+
iframe.setAttribute('id', 'speedscope-iframe');
|
701
|
+
document.body.appendChild(iframe);
|
702
|
+
var iframeUrl = '#{@config.base_url_path}speedscope/index.html#profileURL=' + objUrl + '&title=' + 'Flamegraph for #{CGI.escape(path)}';
|
703
|
+
iframe.setAttribute('src', iframeUrl);
|
704
|
+
</script>
|
705
|
+
</body>
|
706
|
+
</html>
|
707
|
+
HTML
|
708
|
+
[200, headers, [html]]
|
688
709
|
end
|
689
710
|
|
690
711
|
def ids(env)
|
@@ -717,6 +738,10 @@ Append the following to your query string:
|
|
717
738
|
url = "#{path}includes.js?v=#{version}" if !url
|
718
739
|
css_url = "#{path}includes.css?v=#{version}" if !css_url
|
719
740
|
|
741
|
+
content_security_policy_nonce = @config.content_security_policy_nonce ||
|
742
|
+
env["action_dispatch.content_security_policy_nonce"] ||
|
743
|
+
env["secure_headers_content_security_policy_nonce"]
|
744
|
+
|
720
745
|
settings = {
|
721
746
|
path: path,
|
722
747
|
url: url,
|
@@ -734,7 +759,9 @@ Append the following to your query string:
|
|
734
759
|
startHidden: @config.start_hidden,
|
735
760
|
collapseResults: @config.collapse_results,
|
736
761
|
htmlContainer: @config.html_container,
|
737
|
-
hiddenCustomFields: @config.snapshot_hidden_custom_fields.join(',')
|
762
|
+
hiddenCustomFields: @config.snapshot_hidden_custom_fields.join(','),
|
763
|
+
cspNonce: content_security_policy_nonce,
|
764
|
+
hotwireTurboDriveSupport: @config.enable_hotwire_turbo_drive_support,
|
738
765
|
}
|
739
766
|
|
740
767
|
if current && current.page_struct
|
@@ -746,7 +773,7 @@ Append the following to your query string:
|
|
746
773
|
end
|
747
774
|
|
748
775
|
# TODO : cache this snippet
|
749
|
-
script =
|
776
|
+
script = ::File.read(::File.expand_path('../html/profile_handler.js', ::File.dirname(__FILE__)))
|
750
777
|
# replace the variables
|
751
778
|
settings.each do |k, v|
|
752
779
|
regex = Regexp.new("\\{#{k.to_s}\\}")
|
@@ -815,6 +842,24 @@ Append the following to your query string:
|
|
815
842
|
response.finish
|
816
843
|
end
|
817
844
|
|
845
|
+
def serve_flamegraph(env)
|
846
|
+
request = Rack::Request.new(env)
|
847
|
+
id = request.params['id']
|
848
|
+
page_struct = @storage.load(id)
|
849
|
+
|
850
|
+
if !page_struct
|
851
|
+
id = ERB::Util.html_escape(id)
|
852
|
+
user_info = ERB::Util.html_escape(user(env))
|
853
|
+
return [404, {}, ["Request not found: #{id} - user #{user_info}"]]
|
854
|
+
end
|
855
|
+
|
856
|
+
if !page_struct[:flamegraph]
|
857
|
+
return [404, {}, ["No flamegraph available for #{ERB::Util.html_escape(id)}"]]
|
858
|
+
end
|
859
|
+
|
860
|
+
self.flamegraph(page_struct[:flamegraph], page_struct[:request_path])
|
861
|
+
end
|
862
|
+
|
818
863
|
def rails_route_from_path(path, method)
|
819
864
|
if defined?(Rails) && defined?(ActionController::RoutingError)
|
820
865
|
hash = Rails.application.routes.recognize_path(path, method: method)
|
@@ -36,7 +36,7 @@ module Rack
|
|
36
36
|
""
|
37
37
|
end
|
38
38
|
|
39
|
-
# a list of tokens that are permitted to access profiler in
|
39
|
+
# a list of tokens that are permitted to access profiler in explicit mode
|
40
40
|
def allowed_tokens
|
41
41
|
raise NotImplementedError.new("allowed_tokens is not implemented")
|
42
42
|
end
|
@@ -24,7 +24,9 @@ module Rack
|
|
24
24
|
|
25
25
|
def load(id)
|
26
26
|
raw = @client.get("#{@prefix}#{id}")
|
27
|
-
|
27
|
+
# rubocop:disable Security/MarshalLoad
|
28
|
+
Marshal.load(raw) if raw
|
29
|
+
# rubocop:enable Security/MarshalLoad
|
28
30
|
end
|
29
31
|
|
30
32
|
def set_unviewed(user, id)
|
@@ -65,14 +67,16 @@ module Rack
|
|
65
67
|
key1, key2, cycle_at = nil
|
66
68
|
|
67
69
|
if token_info
|
68
|
-
|
70
|
+
# rubocop:disable Security/MarshalLoad
|
71
|
+
key1, key2, cycle_at = Marshal.load(token_info)
|
72
|
+
# rubocop:enable Security/MarshalLoad
|
69
73
|
|
70
|
-
|
71
|
-
|
74
|
+
key1 = nil unless key1 && key1.length == 32
|
75
|
+
key2 = nil unless key2 && key2.length == 32
|
72
76
|
|
73
|
-
|
74
|
-
|
75
|
-
|
77
|
+
if key1 && cycle_at && (cycle_at > Process.clock_gettime(Process::CLOCK_MONOTONIC))
|
78
|
+
return [key1, key2].compact
|
79
|
+
end
|
76
80
|
end
|
77
81
|
|
78
82
|
timeout = Rack::MiniProfiler::AbstractStore::MAX_TOKEN_AGE
|
@@ -25,7 +25,9 @@ module Rack
|
|
25
25
|
key = prefixed_id(id)
|
26
26
|
raw = redis.get key
|
27
27
|
begin
|
28
|
-
|
28
|
+
# rubocop:disable Security/MarshalLoad
|
29
|
+
Marshal.load(raw) if raw
|
30
|
+
# rubocop:enable Security/MarshalLoad
|
29
31
|
rescue
|
30
32
|
# bad format, junk old data
|
31
33
|
redis.del key
|
@@ -177,7 +179,9 @@ unviewed_ids: #{get_unviewed_ids(user)}
|
|
177
179
|
batch = redis.mapped_hmget(hash_key, *ids).to_a
|
178
180
|
batch.map! do |id, bytes|
|
179
181
|
begin
|
182
|
+
# rubocop:disable Security/MarshalLoad
|
180
183
|
Marshal.load(bytes)
|
184
|
+
# rubocop:enable Security/MarshalLoad
|
181
185
|
rescue
|
182
186
|
corrupt_snapshots << id
|
183
187
|
nil
|
@@ -189,9 +193,9 @@ unviewed_ids: #{get_unviewed_ids(user)}
|
|
189
193
|
iteration += 1
|
190
194
|
end
|
191
195
|
if corrupt_snapshots.size > 0
|
192
|
-
redis.pipelined do
|
193
|
-
|
194
|
-
|
196
|
+
redis.pipelined do |pipeline|
|
197
|
+
pipeline.zrem(zset_key, corrupt_snapshots)
|
198
|
+
pipeline.hdel(hash_key, corrupt_snapshots)
|
195
199
|
end
|
196
200
|
end
|
197
201
|
end
|
@@ -200,11 +204,13 @@ unviewed_ids: #{get_unviewed_ids(user)}
|
|
200
204
|
hash_key = snapshot_hash_key()
|
201
205
|
bytes = redis.hget(hash_key, id)
|
202
206
|
begin
|
207
|
+
# rubocop:disable Security/MarshalLoad
|
203
208
|
Marshal.load(bytes)
|
209
|
+
# rubocop:enable Security/MarshalLoad
|
204
210
|
rescue
|
205
|
-
redis.pipelined do
|
206
|
-
|
207
|
-
|
211
|
+
redis.pipelined do |pipeline|
|
212
|
+
pipeline.zrem(snapshot_zset_key(), id)
|
213
|
+
pipeline.hdel(hash_key, id)
|
208
214
|
end
|
209
215
|
nil
|
210
216
|
end
|
@@ -253,11 +259,11 @@ unviewed_ids: #{get_unviewed_ids(user)}
|
|
253
259
|
|
254
260
|
# only used in tests
|
255
261
|
def wipe_snapshots_data
|
256
|
-
redis.
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
262
|
+
redis.del(
|
263
|
+
snapshot_counter_key(),
|
264
|
+
snapshot_zset_key(),
|
265
|
+
snapshot_hash_key(),
|
266
|
+
)
|
261
267
|
end
|
262
268
|
end
|
263
269
|
end
|
@@ -87,7 +87,9 @@ module Rack
|
|
87
87
|
executed_non_queries: 0,
|
88
88
|
custom_timing_names: [],
|
89
89
|
custom_timing_stats: {},
|
90
|
-
custom_fields: {}
|
90
|
+
custom_fields: {},
|
91
|
+
has_flamegraph: false,
|
92
|
+
flamegraph: nil
|
91
93
|
)
|
92
94
|
self[:request_method] = env['REQUEST_METHOD']
|
93
95
|
self[:request_path] = env['PATH_INFO']
|
@@ -111,12 +113,16 @@ module Rack
|
|
111
113
|
@attributes[:root]
|
112
114
|
end
|
113
115
|
|
116
|
+
def attributes_to_serialize
|
117
|
+
@attributes.keys - [:flamegraph]
|
118
|
+
end
|
119
|
+
|
114
120
|
def to_json(*a)
|
115
|
-
::JSON.generate(@attributes.merge(
|
121
|
+
::JSON.generate(@attributes.slice(*attributes_to_serialize).merge(extra_json))
|
116
122
|
end
|
117
123
|
|
118
124
|
def as_json(options = nil)
|
119
|
-
super(options).merge!(extra_json)
|
125
|
+
super(options).slice(*attributes_to_serialize.map(&:to_s)).merge!(extra_json)
|
120
126
|
end
|
121
127
|
|
122
128
|
def extra_json
|
data/lib/patches/db/riak.rb
CHANGED
data/rack-mini-profiler.gemspec
CHANGED
@@ -24,7 +24,7 @@ Gem::Specification.new do |s|
|
|
24
24
|
s.required_ruby_version = '>= 2.4.0'
|
25
25
|
|
26
26
|
s.metadata = {
|
27
|
-
'source_code_uri' =>
|
27
|
+
'source_code_uri' => Rack::MiniProfiler::SOURCE_CODE_URI,
|
28
28
|
'changelog_uri' => 'https://github.com/MiniProfiler/rack-mini-profiler/blob/master/CHANGELOG.md'
|
29
29
|
}
|
30
30
|
|
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: 2.3.
|
4
|
+
version: 2.3.4
|
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:
|
13
|
+
date: 2022-02-22 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rack
|
@@ -354,7 +354,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
354
354
|
- !ruby/object:Gem::Version
|
355
355
|
version: '0'
|
356
356
|
requirements: []
|
357
|
-
rubygems_version: 3.
|
357
|
+
rubygems_version: 3.1.6
|
358
358
|
signing_key:
|
359
359
|
specification_version: 4
|
360
360
|
summary: Profiles loading speed for rack applications.
|