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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 287f0fe8da1845abde81f60250c960808168f0d278cf905786293c584fe78c23
4
- data.tar.gz: c43613852efe89a7e59c642220b4609b086190473a0c2c5a83ead3b1abbdce9c
3
+ metadata.gz: 6021effb717c193c4c70b3ac7a3550fd99c9abfd19432ac0ab9db63f62b11321
4
+ data.tar.gz: 8bce19855d2f6d908339e3108201282c519b4d8ad99b8e7e90dad9d3f718c929
5
5
  SHA512:
6
- metadata.gz: c4944a4bbf9021b4639dc18d5e13d6f901eef47785b1e66ac37b261307cf82f70dd5ebc242fb099fecada4c450594155e600150baedfd7cb2b1d7f7b7527ef69
7
- data.tar.gz: ba0ee401a63f3fe3a27acc1812d790a1e11de26f6d6ffd38d34730bfec3ecf26c96f20f9912fe071917a31a346115c10cd959bff35516624d3f816c5aa84426c
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
- * add the [**stackprof**](https://rubygems.org/gems/stackprof) gem to your Gemfile
172
- * visit a page in your app with `?pp=flamegraph`
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 skippd 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).
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 `:whitelist` in production. However, in some cases we may not be able to do it:
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 whitelist mode
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 = :whitelist
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 `:whitelist` meaning that end user will only be able to see requests where somewhere `Rack::MiniProfiler.authorize_request` is invoked.
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.pageTransition();
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 async type="text/javascript" id="mini-profiler" src="/mini-profiler-resources/includes.js?v=12b4b45a3c42e6e15503d7a03810ff33" data-version="12b4b45a3c42e6e15503d7a03810ff33" data-path="/mini-profiler-resources/" data-current-id="redo66j4g1077kto8uh3" data-ids="redo66j4g1077kto8uh3" data-horizontal-position="left" data-vertical-position="top" data-trivial="false" data-children="false" data-max-traces="10" data-controls="false" data-authorized="true" data-toggle-shortcut="Alt+P" data-start-hidden="false" data-collapse-results="true"></script>
356
- ```
357
-
358
- _Note:_ The GUID (`data-version` and the `?v=` parameter on the `src`) 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.
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
- which will start a local Sinatra server at `http://localhost:9292` where you'll be able to preview your changes. Refreshing the page should be enough to see any changes you make to files in the `lib/html` directory.
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)
@@ -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 =
@@ -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
 
@@ -117,8 +117,8 @@
117
117
  <table>
118
118
  <thead>
119
119
  <tr>
120
- <th style="text-align:right">step<br />time from start<br />query type<br />duration</th>
121
- <th style="text-align:left">call stack<br />query</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
  {{?}}
@@ -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 style="text-align:right">step<br />time from start<br />query type<br />duration</th> <th style="text-align: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"> '+( 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 &lt; '+( 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 &lt; '+( it.page.trivial_duration_threshold_milliseconds )+' ms"> show trivial </a> ';}return out;
15
15
  }
16
16
  MiniProfiler.templates["timingTemplate"] = function anonymous(it
17
17
  ) {
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Rack
3
3
  class MiniProfiler
4
- ASSET_VERSION = '644e88e41aaa4b3ea7e36f7c445b7bfd'
4
+ ASSET_VERSION = '35a79b300ab5afa978cb59af0b05e059'
5
5
  end
6
6
  end
@@ -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 == :whitelist && !MiniProfiler.request_authorized?)
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 == :whitelist
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: '/', httponly: true }
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 == :whitelist) && valid_cookie
93
+ if (MiniProfiler.config.authorization_mode == :allow_authorized) && valid_cookie
94
94
  begin
95
95
  @allowed_tokens ||= @store.allowed_tokens
96
96
  rescue => e
@@ -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 == :whitelist
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 == :whitelist &&
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
- flamegraph = "Please install the stackprof gem and require it: add gem 'stackprof' to your Gemfile"
343
- status, headers, body = @app.call(env)
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: :wall,
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 == :whitelist && !MiniProfiler.request_authorized?)
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
- Append the following to your query string:
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 (ruby 1.9.3 only)
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} : requires Ruby 2.2, a graph representing sampled activity (requires the stackprof gem).
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 "flamegraph_embed", env} : requires Ruby 2.2, a graph representing sampled activity (requires the stackprof gem), embedded resources for use on an intranet.
647
- #{make_link "trace-exceptions", env} : requires Ruby 2.0, will return all the spots where your application raises exceptions
648
- #{make_link "analyze-memory", env} : requires Ruby 2.0, will perform basic memory analysis of heap
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
- if Hash === graph
660
- html = <<~HTML
661
- <!DOCTYPE html>
662
- <html>
663
- <head>
664
- <style>
665
- body { margin: 0; height: 100vh; }
666
- #speedscope-iframe { width: 100%; height: 100%; border: none; }
667
- </style>
668
- </head>
669
- <body>
670
- <script type="text/javascript">
671
- var graph = #{JSON.generate(graph)};
672
- var json = JSON.stringify(graph);
673
- var blob = new Blob([json], { type: 'text/plain' });
674
- var objUrl = encodeURIComponent(URL.createObjectURL(blob));
675
- var iframe = document.createElement('IFRAME');
676
- iframe.setAttribute('id', 'speedscope-iframe');
677
- document.body.appendChild(iframe);
678
- var iframeUrl = '#{@config.base_url_path}speedscope/index.html#profileURL=' + objUrl + '&title=' + 'Flamegraph for #{CGI.escape(path)}';
679
- iframe.setAttribute('src', iframeUrl);
680
- </script>
681
- </body>
682
- </html>
683
- HTML
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 = IO.read(::File.expand_path('../html/profile_handler.js', ::File.dirname(__FILE__)))
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 whitelist mode
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
@@ -17,7 +17,9 @@ module Rack
17
17
  def [](key)
18
18
  begin
19
19
  data = ::File.open(path(key), "rb") { |f| f.read }
20
+ # rubocop:disable Security/MarshalLoad
20
21
  Marshal.load data
22
+ # rubocop:enable Security/MarshalLoad
21
23
  rescue
22
24
  nil
23
25
  end
@@ -24,7 +24,9 @@ module Rack
24
24
 
25
25
  def load(id)
26
26
  raw = @client.get("#{@prefix}#{id}")
27
- Marshal::load(raw) if raw
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
- key1, key2, cycle_at = Marshal::load(token_info)
70
+ # rubocop:disable Security/MarshalLoad
71
+ key1, key2, cycle_at = Marshal.load(token_info)
72
+ # rubocop:enable Security/MarshalLoad
69
73
 
70
- key1 = nil unless key1 && key1.length == 32
71
- key2 = nil unless key2 && key2.length == 32
74
+ key1 = nil unless key1 && key1.length == 32
75
+ key2 = nil unless key2 && key2.length == 32
72
76
 
73
- if key1 && cycle_at && (cycle_at > Process.clock_gettime(Process::CLOCK_MONOTONIC))
74
- return [key1, key2].compact
75
- end
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
- Marshal::load(raw) if raw
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
- redis.zrem(zset_key, corrupt_snapshots)
194
- redis.hdel(hash_key, corrupt_snapshots)
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
- redis.zrem(snapshot_zset_key(), id)
207
- redis.hdel(hash_key, id)
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.pipelined do
257
- redis.del(snapshot_counter_key())
258
- redis.del(snapshot_zset_key())
259
- redis.del(snapshot_hash_key())
260
- end
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(self.extra_json))
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
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Rack
4
4
  class MiniProfiler
5
- VERSION = '2.3.1'
5
+ VERSION = '2.3.4'
6
+ SOURCE_CODE_URI = 'https://github.com/MiniProfiler/rack-mini-profiler'
6
7
  end
7
8
  end
@@ -35,7 +35,7 @@ module Rack::MiniProfilerRails
35
35
  end
36
36
 
37
37
  unless Rails.env.development? || Rails.env.test?
38
- c.authorization_mode = :whitelist
38
+ c.authorization_mode = :allow_authorized
39
39
  end
40
40
 
41
41
  if Rails.logger
@@ -2,7 +2,7 @@
2
2
 
3
3
  # riak-client 2.2.2 patches
4
4
  class Riak::Multiget
5
- class <<self
5
+ class << self
6
6
  alias_method :get_all_without_profiling, :get_all
7
7
  def get_all(client, fetch_list)
8
8
  return get_all_without_profiling(client, fetch_list) unless SqlPatches.should_measure?
@@ -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' => 'https://github.com/MiniProfiler/rack-mini-profiler',
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.1
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: 2021-01-28 00:00:00.000000000 Z
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.2.2
357
+ rubygems_version: 3.1.6
358
358
  signing_key:
359
359
  specification_version: 4
360
360
  summary: Profiles loading speed for rack applications.