rack-mini-profiler 2.3.2 → 3.0.0
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 +49 -19
- data/lib/html/includes.css +9 -11
- data/lib/html/includes.js +24 -1
- data/lib/html/includes.scss +3 -11
- data/lib/html/includes.tmpl +2 -2
- data/lib/html/profile_handler.js +1 -1
- data/lib/html/vendor.js +1 -1
- data/lib/mini_profiler/asset_version.rb +1 -1
- data/lib/mini_profiler/client_settings.rb +2 -2
- data/lib/mini_profiler/config.rb +11 -5
- data/lib/mini_profiler/profiler.rb +34 -14
- data/lib/mini_profiler/storage/abstract_store.rb +30 -57
- 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/memory_store.rb +54 -12
- data/lib/mini_profiler/storage/redis_store.rb +150 -62
- data/lib/mini_profiler/version.rb +2 -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: cde7281c7a63d3d3ac5bc7605ba2cf81dedf6b598ff17ed568610a54a50b517f
|
4
|
+
data.tar.gz: 7af763d5136493c71cc4321fe54db79372e5b9aaac4c10d31bc2f27d22f324b7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: abb415f975552a1256753e15ba9d6623aeb55fe4b058c775701f2b7d3a955f62d2a906a93929f1a22545f8558750d48c78f3b7e185f774c224d12e6a256a69a6
|
7
|
+
data.tar.gz: f5ff03170537e5dc17a826909d9b77609bc8d6f2390f42b29be9ff1af538332eaa33c269f7433d87142ba7e05315741b3ea5b4c69fcc76980a56c225338d843c
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## 3.0.0 - 2022-02-24
|
4
|
+
|
5
|
+
- PERF: Improve snapshots page performance (#518) (introduces breaking changes to the API of `AbstractStore`, `MemoryStore` and `RedisStore`, and removes the `snapshots_limit` config option.)
|
6
|
+
|
7
|
+
## 2.3.4 - 2022-02-23
|
8
|
+
|
9
|
+
- [FEATURE] Add cookie path support for subfolder sites
|
10
|
+
- [FIX] Remove deprecated uses of Redis#pipelined
|
11
|
+
|
12
|
+
## 2.3.3 - 2021-08-30
|
13
|
+
|
14
|
+
- [FEATURE] Introduce `pp=flamegraph_mode`
|
15
|
+
- [FEATURE] Richer CSP configuration options
|
16
|
+
- [FEATURE] Add support for Hotwire Turbo Drive
|
17
|
+
|
3
18
|
## 2.3.2 - 2021-04-30
|
4
19
|
|
5
20
|
- [FEATURE] Introduce `pp=async-flamegraph` for asynchronous flamegraphs
|
data/README.md
CHANGED
@@ -166,14 +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
|
-
* 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.
|
173
172
|
|
174
|
-
|
175
|
-
|
176
|
-
|
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.
|
177
177
|
|
178
178
|
### Memory Profiling
|
179
179
|
|
@@ -207,7 +207,7 @@ After enabling snapshots sampling, you can see the snapshots that have been coll
|
|
207
207
|
|
208
208
|
Access to the snapshots page is restricted to only those who can see the speed badge on their own requests, see the section below this one about access control.
|
209
209
|
|
210
|
-
Mini Profiler will keep a maximum of
|
210
|
+
Mini Profiler will keep a maximum of 50 snapshot groups and a maximum of 15 snapshots per group making the default maximum number of snapshots in the system 750. The default group and per group limits can be changed via the `max_snapshot_groups` and `max_snapshots_per_group` configuration options, see the configurations table below.
|
211
211
|
|
212
212
|
#### Snapshots Transporter
|
213
213
|
|
@@ -347,19 +347,41 @@ Single page applications built using Ember, Angular or other frameworks need som
|
|
347
347
|
On route transition always call:
|
348
348
|
|
349
349
|
```
|
350
|
-
window.MiniProfiler
|
350
|
+
if (window.MiniProfiler !== undefined) {
|
351
|
+
window.MiniProfiler.pageTransition();
|
352
|
+
}
|
351
353
|
```
|
352
354
|
|
353
355
|
This method will remove profiling information that was related to previous page and clear aggregate statistics.
|
354
356
|
|
355
357
|
#### MiniProfiler's speed badge on pages that are not generated via Rails
|
356
|
-
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)):
|
357
359
|
|
358
360
|
```html
|
359
|
-
<script
|
360
|
-
|
361
|
-
|
362
|
-
|
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.
|
363
385
|
|
364
386
|
#### Using MiniProfiler's built in route for apps without HTML responses
|
365
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.
|
@@ -398,27 +420,32 @@ toggle_shortcut|Alt+P|Keyboard shortcut to toggle the mini_profiler's visibility
|
|
398
420
|
start_hidden|`false`|`false` to make mini_profiler visible on page load.
|
399
421
|
backtrace_threshold_ms|`0`|Minimum SQL query elapsed time before a backtrace is recorded.
|
400
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`.
|
401
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
|
402
426
|
collapse_results|`true`|If multiple timing results exist in a single page, collapse them till clicked.
|
403
427
|
max_traces_to_show|20|Maximum number of mini profiler timing blocks to show on one page
|
404
428
|
html_container|`body`|The HTML container (as a jQuery selector) to inject the mini_profiler UI into
|
405
429
|
show_total_sql_count|`false`|Displays the total number of SQL executions.
|
406
|
-
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.
|
407
431
|
assets_url|`nil`|See the "Register MiniProfiler's assets in the Rails assets pipeline" section above.
|
408
432
|
snapshot_every_n_requests|`-1`|Determines how frequently snapshots are taken. See the "Snapshots Sampling" above for more details.
|
409
|
-
|
433
|
+
max_snapshot_groups|`50`|Determines how many snapshot groups Mini Profiler is allowed to keep.
|
434
|
+
max_snapshots_per_group|`15`|Determines how many snapshots per group Mini Profiler is allowed to keep.
|
410
435
|
snapshot_hidden_custom_fields|`[]`|Each snapshot custom field will have a dedicated column in the UI by default. Use this config to exclude certain custom fields from having their own columns.
|
411
436
|
snapshots_transport_destination_url|`nil`|Set this config to a valid URL to enable snapshots transporter which will `POST` snapshots to the given URL. The transporter requires `snapshots_transport_auth_key` config to be set as well.
|
412
437
|
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.
|
413
438
|
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.
|
414
439
|
snapshots_transport_gzip_requests|`false`|Make the snapshots transporter gzip the requests it makes to `snapshots_transport_destination_url`.
|
440
|
+
content_security_policy_nonce|Rails: Current nonce<br>Rack: nil|Set the content security policy nonce to use when inserting MiniProfiler's script block.
|
441
|
+
enable_hotwire_turbo_drive_support| `false` | Enable support for Hotwire TurboDrive page transitions.
|
415
442
|
|
416
443
|
### Using MiniProfiler with `Rack::Deflate` middleware
|
417
444
|
|
418
445
|
If you are using `Rack::Deflate` with Rails and `rack-mini-profiler` in its default configuration,
|
419
446
|
`Rack::MiniProfiler` will be injected (as always) at position 0 in the middleware stack,
|
420
447
|
which means it will run after `Rack::Deflate` on response processing. To prevent attempting to inject
|
421
|
-
HTML in already compressed response body MiniProfiler will suppress compression by setting
|
448
|
+
HTML in already compressed response body MiniProfiler will suppress compression by setting
|
422
449
|
`identity` encoding in `Accept-Encoding` request header.
|
423
450
|
|
424
451
|
## Special query strings
|
@@ -461,20 +488,23 @@ end
|
|
461
488
|
If you want to contribute to this project, that's great, thank you! You can run the following rake task:
|
462
489
|
|
463
490
|
```
|
491
|
+
$ BUNDLE_GEMFILE=website/Gemfile bundle install
|
464
492
|
$ bundle exec rake client_dev
|
465
493
|
```
|
466
494
|
|
467
|
-
|
495
|
+
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.
|
496
|
+
|
497
|
+
Make sure to prepend `bundle exec` before any Rake tasks you run.
|
468
498
|
|
469
499
|
## Running the Specs
|
470
500
|
|
501
|
+
You need Memcached and Redis services running for the specs.
|
502
|
+
|
471
503
|
```
|
472
504
|
$ rake build
|
473
505
|
$ rake spec
|
474
506
|
```
|
475
507
|
|
476
|
-
Additionally you can also run `autotest` if you like.
|
477
|
-
|
478
508
|
## Licence
|
479
509
|
|
480
510
|
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
@@ -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
|
|
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>
|
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,7 +7,7 @@
|
|
7
7
|
MiniProfiler.templates = {};
|
8
8
|
MiniProfiler.templates["profilerTemplate"] = function anonymous(it
|
9
9
|
) {
|
10
|
-
var out=' <div class="profiler-result"> <div class="profiler-button ';if(it.has_duplicate_sql_timings){out+='profiler-warning';}out+='"> ';if(it.has_duplicate_sql_timings){out+='<span class="profiler-nuclear">!</span>';}out+=' <span class="profiler-number"> '+( MiniProfiler.formatDuration(it.duration_milliseconds))+' <span class="profiler-unit">ms</span> </span> ';if(MiniProfiler.showTotalSqlCount()){out+=' <span class="profiler-number"> '+( it.sql_count)+' <span class="profiler-unit">sql</span> </span> ';}out+=' </div> <div class="profiler-popup"> <div class="profiler-info"> <span class="profiler-name"> '+( it.name)+' <span class="profiler-overall-duration">('+( MiniProfiler.formatDuration(it.duration_milliseconds))+' ms)</span> </span> <span class="profiler-server-time">'+( it.machine_name)+' on '+( MiniProfiler.renderDate(it.started_formatted))+'</span> </div> <div class="profiler-output"> <table class="profiler-timings"> <thead> <tr> <th>event</th> <th>duration (ms)</th> <th class="profiler-duration-with-children">with children (ms)</th> <th class="time-from-start">from start (ms)</th> ';if(it.has_sql_timings){out+=' <th colspan="2">query time (ms)</th> ';}out+=' ';var arr1=it.custom_timing_names;if(arr1){var value,i1=-1,l1=arr1.length-1;while(i1<l1){value=arr1[i1+=1];out+=' <th colspan="2">'+( value.toLowerCase() )+' (ms)</th> ';} } out+=' </tr> </thead> <tbody> '+( MiniProfiler.templates.timingTemplate({timing: it.root, page: it}) )+' </tbody> <tfoot> <tr> <td colspan="3"> ';if(!it.client_timings){out+=' '+( MiniProfiler.templates.linksTemplate({timing: it.root, page: it}) )+' ';}out+=' <a class="profiler-toggle-duration-with-children" title="toggles column with aggregate child durations">show time with children</a> <a class="profiler-snapshots-page-link" title="Go to snapshots page" href="'+( MiniProfiler.options.path )+'snapshots">snapshots</a> </td> ';if(it.has_sql_timings){out+=' <td colspan="2" class="profiler-number profiler-percent-in-sql" title="'+( MiniProfiler.getSqlTimingsCount(it.root) )+' queries spent '+( MiniProfiler.formatDuration(it.duration_milliseconds_in_sql) )+' ms of total request time"> '+( MiniProfiler.formatDuration(it.duration_milliseconds_in_sql / it.duration_milliseconds * 100) )+' <span class="profiler-unit">% in sql</span> </td> ';}out+=' ';var arr2=it.custom_timing_names;if(arr2){var value,i2=-1,l2=arr2.length-1;while(i2<l2){value=arr2[i2+=1];out+=' <td colspan="2" class="profiler-number profiler-percentage-in-sql" title="'+( it.custom_timing_stats[value].count )+' '+( value.toLowerCase() )+' invocations spent '+( MiniProfiler.formatDuration(it.custom_timing_stats[value].duration) )+' ms of total request time"> '+( MiniProfiler.formatDuration(it.custom_timing_stats[value].duration / it.duration_milliseconds * 100) )+' <span class="profiler-unit">% in '+( value.toLowerCase() )+'</span> </td> ';} } out+=' </tr> </tfoot> </table> ';if(it.client_timings){out+=' <table class="profiler-timings profiler-client-timings"> <thead> <tr> <th>client event</th> <th>duration (ms)</th> <th>from start (ms)</th> </tr> </thead> <tbody> ';var arr3=MiniProfiler.getClientTimings(it.client_timings);if(arr3){var value,i3=-1,l3=arr3.length-1;while(i3<l3){value=arr3[i3+=1];out+=' <tr class="';if(value.isTrivial){out+='profiler-trivial';}out+='"> <td class="profiler-label">'+( value.name )+'</td> <td class="profiler-duration"> ';if(value.duration >= 0){out+=' <span class="profiler-unit"></span>'+( MiniProfiler.formatDuration(value.duration) )+' ';}out+=' </td> <td class="profiler-duration time-from-start"> <span class="profiler-unit">+</span>'+( MiniProfiler.formatDuration(value.start) )+' </td> </tr> ';} } out+=' </tbody> <tfoot> <td colspan="3"> '+( MiniProfiler.templates.linksTemplate({timing: it.root, page: it}) )+' </td> </tfoot> </table> ';}out+=' ';if(it.custom_fields && Object.keys(it.custom_fields).length > 0){out+=' <p class="custom-fields-title">Snapshot custom fields</p> <table class="profiler-timings"> <tbody> ';var arr4=Object.keys(it.custom_fields);if(arr4){var key,i4=-1,l4=arr4.length-1;while(i4<l4){key=arr4[i4+=1];out+=' <tr> <td class="profiler-label">'+( key )+'</td> <td class="profiler-label">'+( it.custom_fields[key] )+'</td> </tr> ';} } out+=' </tbody> </table> ';}out+=' </div> </div> ';if(it.has_sql_timings){out+=' <div class="profiler-queries"> <table> <thead> <tr> <th
|
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
|
) {
|
@@ -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,7 +83,7 @@ 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
|
|
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}")
|
@@ -38,7 +40,8 @@ module Rack
|
|
38
40
|
@skip_sql_param_names = /password/ # skips parameters with the name password by default
|
39
41
|
@enable_advanced_debugging_tools = false
|
40
42
|
@snapshot_every_n_requests = -1
|
41
|
-
@
|
43
|
+
@max_snapshot_groups = 50
|
44
|
+
@max_snapshots_per_group = 15
|
42
45
|
|
43
46
|
# ui parameters
|
44
47
|
@autorized = true
|
@@ -57,6 +60,7 @@ module Rack
|
|
57
60
|
@snapshots_transport_auth_key = nil
|
58
61
|
@snapshots_redact_sql_queries = true
|
59
62
|
@snapshots_transport_gzip_requests = false
|
63
|
+
@enable_hotwire_turbo_drive_support = false
|
60
64
|
|
61
65
|
self
|
62
66
|
}
|
@@ -64,11 +68,13 @@ module Rack
|
|
64
68
|
|
65
69
|
attr_accessor :authorization_mode, :auto_inject, :backtrace_ignores,
|
66
70
|
:backtrace_includes, :backtrace_remove, :backtrace_threshold_ms,
|
67
|
-
:base_url_path, :disable_caching, :enabled,
|
71
|
+
:base_url_path, :cookie_path, :disable_caching, :enabled,
|
68
72
|
:flamegraph_sample_rate, :logger, :pre_authorize_cb, :skip_paths,
|
69
73
|
:skip_schema_queries, :storage, :storage_failure, :storage_instance,
|
70
74
|
:storage_options, :user_provider, :enable_advanced_debugging_tools,
|
71
|
-
:skip_sql_param_names, :suppress_encoding, :max_sql_param_length
|
75
|
+
:skip_sql_param_names, :suppress_encoding, :max_sql_param_length,
|
76
|
+
:content_security_policy_nonce, :enable_hotwire_turbo_drive_support,
|
77
|
+
:flamegraph_mode
|
72
78
|
|
73
79
|
# ui accessors
|
74
80
|
attr_accessor :collapse_results, :max_traces_to_show, :position,
|
@@ -76,10 +82,10 @@ module Rack
|
|
76
82
|
:start_hidden, :toggle_shortcut, :html_container
|
77
83
|
|
78
84
|
# snapshot related config
|
79
|
-
attr_accessor :snapshot_every_n_requests, :
|
85
|
+
attr_accessor :snapshot_every_n_requests, :max_snapshots_per_group,
|
80
86
|
:snapshot_hidden_custom_fields, :snapshots_transport_destination_url,
|
81
87
|
:snapshots_transport_auth_key, :snapshots_redact_sql_queries,
|
82
|
-
:snapshots_transport_gzip_requests
|
88
|
+
:snapshots_transport_gzip_requests, :max_snapshot_groups
|
83
89
|
|
84
90
|
# Deprecated options
|
85
91
|
attr_accessor :use_existing_jquery
|
@@ -130,10 +130,10 @@ module Rack
|
|
130
130
|
def serve_results(env)
|
131
131
|
request = Rack::Request.new(env)
|
132
132
|
id = request.params['id']
|
133
|
-
|
134
|
-
is_snapshot =
|
133
|
+
group_name = request.params['group']
|
134
|
+
is_snapshot = group_name && group_name.size > 0
|
135
135
|
if is_snapshot
|
136
|
-
page_struct = @storage.load_snapshot(id)
|
136
|
+
page_struct = @storage.load_snapshot(id, group_name)
|
137
137
|
else
|
138
138
|
page_struct = @storage.load(id)
|
139
139
|
end
|
@@ -362,8 +362,17 @@ module Rack
|
|
362
362
|
else
|
363
363
|
sample_rate = config.flamegraph_sample_rate
|
364
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
|
+
|
365
374
|
flamegraph = StackProf.run(
|
366
|
-
mode:
|
375
|
+
mode: mode,
|
367
376
|
raw: true,
|
368
377
|
aggregate: false,
|
369
378
|
interval: (sample_rate * 1000).to_i
|
@@ -630,7 +639,7 @@ module Rack
|
|
630
639
|
end
|
631
640
|
|
632
641
|
def text_result(body)
|
633
|
-
headers = { 'Content-Type' => 'text/plain' }
|
642
|
+
headers = { 'Content-Type' => 'text/plain; charset=utf-8' }
|
634
643
|
[200, headers, [body]]
|
635
644
|
end
|
636
645
|
|
@@ -643,7 +652,7 @@ module Rack
|
|
643
652
|
headers = { 'Content-Type' => 'text/html' }
|
644
653
|
body = "<html><body>
|
645
654
|
<pre style='line-height: 30px; font-size: 16px;'>
|
646
|
-
|
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:
|
647
656
|
|
648
657
|
#{make_link "help", env} : display this screen
|
649
658
|
#{make_link "env", env} : display the rack environment
|
@@ -658,6 +667,7 @@ Append the following to your query string:
|
|
658
667
|
#{make_link "flamegraph", env} : a graph representing sampled activity (requires the stackprof gem).
|
659
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).
|
660
669
|
#{make_link "flamegraph&flamegraph_sample_rate=1", env}: creates a flamegraph with the specified sample rate (in ms). Overrides value set in config
|
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
|
661
671
|
#{make_link "flamegraph_embed", env} : a graph representing sampled activity (requires the stackprof gem), embedded resources for use on an intranet.
|
662
672
|
#{make_link "trace-exceptions", env} : will return all the spots where your application raises exceptions
|
663
673
|
#{make_link "analyze-memory", env} : will perform basic memory analysis of heap
|
@@ -728,6 +738,10 @@ Append the following to your query string:
|
|
728
738
|
url = "#{path}includes.js?v=#{version}" if !url
|
729
739
|
css_url = "#{path}includes.css?v=#{version}" if !css_url
|
730
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
|
+
|
731
745
|
settings = {
|
732
746
|
path: path,
|
733
747
|
url: url,
|
@@ -745,7 +759,9 @@ Append the following to your query string:
|
|
745
759
|
startHidden: @config.start_hidden,
|
746
760
|
collapseResults: @config.collapse_results,
|
747
761
|
htmlContainer: @config.html_container,
|
748
|
-
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,
|
749
765
|
}
|
750
766
|
|
751
767
|
if current && current.page_struct
|
@@ -757,7 +773,7 @@ Append the following to your query string:
|
|
757
773
|
end
|
758
774
|
|
759
775
|
# TODO : cache this snippet
|
760
|
-
script =
|
776
|
+
script = ::File.read(::File.expand_path('../html/profile_handler.js', ::File.dirname(__FILE__)))
|
761
777
|
# replace the variables
|
762
778
|
settings.each do |k, v|
|
763
779
|
regex = Regexp.new("\\{#{k.to_s}\\}")
|
@@ -786,16 +802,16 @@ Append the following to your query string:
|
|
786
802
|
headers = { 'Content-Type' => 'text/html' }
|
787
803
|
qp = Rack::Utils.parse_nested_query(env['QUERY_STRING'])
|
788
804
|
if group_name = qp["group_name"]
|
789
|
-
list = @storage.
|
805
|
+
list = @storage.snapshots_group(group_name)
|
790
806
|
list.each do |snapshot|
|
791
|
-
snapshot[:url] = url_for_snapshot(snapshot[:id])
|
807
|
+
snapshot[:url] = url_for_snapshot(snapshot[:id], group_name)
|
792
808
|
end
|
793
809
|
data = {
|
794
810
|
group_name: group_name,
|
795
811
|
list: list
|
796
812
|
}
|
797
813
|
else
|
798
|
-
list = @storage.
|
814
|
+
list = @storage.snapshots_overview
|
799
815
|
list.each do |group|
|
800
816
|
group[:url] = url_for_snapshots_group(group[:name])
|
801
817
|
end
|
@@ -848,7 +864,7 @@ Append the following to your query string:
|
|
848
864
|
if defined?(Rails) && defined?(ActionController::RoutingError)
|
849
865
|
hash = Rails.application.routes.recognize_path(path, method: method)
|
850
866
|
if hash && hash[:controller] && hash[:action]
|
851
|
-
"#{
|
867
|
+
"#{hash[:controller]}##{hash[:action]}"
|
852
868
|
end
|
853
869
|
end
|
854
870
|
rescue ActionController::RoutingError
|
@@ -860,8 +876,8 @@ Append the following to your query string:
|
|
860
876
|
"/#{@config.base_url_path.gsub('/', '')}/snapshots?#{qs}"
|
861
877
|
end
|
862
878
|
|
863
|
-
def url_for_snapshot(id)
|
864
|
-
qs = Rack::Utils.build_query({ id: id,
|
879
|
+
def url_for_snapshot(id, group_name)
|
880
|
+
qs = Rack::Utils.build_query({ id: id, group: group_name })
|
865
881
|
"/#{@config.base_url_path.gsub('/', '')}/results?#{qs}"
|
866
882
|
end
|
867
883
|
|
@@ -886,8 +902,12 @@ Append the following to your query string:
|
|
886
902
|
if Rack::MiniProfiler.snapshots_transporter?
|
887
903
|
Rack::MiniProfiler::SnapshotsTransporter.transport(page_struct)
|
888
904
|
else
|
905
|
+
group_name = rails_route_from_path(page_struct[:request_path], page_struct[:request_method])
|
906
|
+
group_name ||= page_struct[:request_path]
|
907
|
+
group_name = "#{page_struct[:request_method]} #{group_name}"
|
889
908
|
@storage.push_snapshot(
|
890
909
|
page_struct,
|
910
|
+
group_name,
|
891
911
|
@config
|
892
912
|
)
|
893
913
|
end
|
@@ -45,80 +45,53 @@ module Rack
|
|
45
45
|
raise NotImplementedError.new("should_take_snapshot? is not implemented")
|
46
46
|
end
|
47
47
|
|
48
|
-
def push_snapshot(page_struct, config)
|
48
|
+
def push_snapshot(page_struct, group_name, config)
|
49
49
|
raise NotImplementedError.new("push_snapshot is not implemented")
|
50
50
|
end
|
51
51
|
|
52
|
-
|
53
|
-
|
52
|
+
# returns a hash where the keys are group names and the values
|
53
|
+
# are hashes that contain 3 keys:
|
54
|
+
# 1. `:worst_score` => the duration of the worst/slowest snapshot in the group (float)
|
55
|
+
# 2. `:best_score` => the duration of the best/fastest snapshot in the group (float)
|
56
|
+
# 3. `:snapshots_count` => the number of snapshots in the group (integer)
|
57
|
+
def fetch_snapshots_overview
|
58
|
+
raise NotImplementedError.new("fetch_snapshots_overview is not implemented")
|
54
59
|
end
|
55
60
|
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
groups[group_name][:best_score] = snapshot.duration_ms
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
groups = groups.to_a
|
61
|
+
# @param group_name [String]
|
62
|
+
# @return [Array<Rack::MiniProfiler::TimerStruct::Page>] list of snapshots of the group. Blank array if the group doesn't exist.
|
63
|
+
def fetch_snapshots_group(group_name)
|
64
|
+
raise NotImplementedError.new("fetch_snapshots_group is not implemented")
|
65
|
+
end
|
66
|
+
|
67
|
+
def load_snapshot(id, group_name)
|
68
|
+
raise NotImplementedError.new("load_snapshot is not implemented")
|
69
|
+
end
|
70
|
+
|
71
|
+
def snapshots_overview
|
72
|
+
groups = fetch_snapshots_overview.to_a
|
73
73
|
groups.sort_by! { |name, hash| hash[:worst_score] }
|
74
74
|
groups.reverse!
|
75
75
|
groups.map! { |name, hash| hash.merge(name: name) }
|
76
76
|
groups
|
77
77
|
end
|
78
78
|
|
79
|
-
def
|
79
|
+
def snapshots_group(group_name)
|
80
|
+
snapshots = fetch_snapshots_group(group_name)
|
80
81
|
data = []
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
timestamp: snapshot[:started_at],
|
90
|
-
custom_fields: snapshot[:custom_fields]
|
91
|
-
}
|
92
|
-
end
|
93
|
-
end
|
82
|
+
snapshots.each do |snapshot|
|
83
|
+
data << {
|
84
|
+
id: snapshot[:id],
|
85
|
+
duration: snapshot.duration_ms,
|
86
|
+
sql_count: snapshot[:sql_count],
|
87
|
+
timestamp: snapshot[:started_at],
|
88
|
+
custom_fields: snapshot[:custom_fields]
|
89
|
+
}
|
94
90
|
end
|
95
91
|
data.sort_by! { |s| s[:duration] }
|
96
92
|
data.reverse!
|
97
93
|
data
|
98
94
|
end
|
99
|
-
|
100
|
-
def load_snapshot(id)
|
101
|
-
raise NotImplementedError.new("load_snapshot is not implemented")
|
102
|
-
end
|
103
|
-
|
104
|
-
private
|
105
|
-
|
106
|
-
def default_snapshot_grouping(snapshot)
|
107
|
-
group_name = rails_route_from_path(snapshot[:request_path], snapshot[:request_method])
|
108
|
-
group_name ||= snapshot[:request_path]
|
109
|
-
"#{snapshot[:request_method]} #{group_name}"
|
110
|
-
end
|
111
|
-
|
112
|
-
def rails_route_from_path(path, method)
|
113
|
-
if defined?(Rails) && defined?(ActionController::RoutingError)
|
114
|
-
hash = Rails.application.routes.recognize_path(path, method: method)
|
115
|
-
if hash && hash[:controller] && hash[:action]
|
116
|
-
"#{hash[:controller]}##{hash[:action]}"
|
117
|
-
end
|
118
|
-
end
|
119
|
-
rescue ActionController::RoutingError
|
120
|
-
nil
|
121
|
-
end
|
122
95
|
end
|
123
96
|
end
|
124
97
|
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
|
@@ -53,6 +53,7 @@ module Rack
|
|
53
53
|
|
54
54
|
@token1, @token2, @cycle_at = nil
|
55
55
|
@snapshots_cycle = 0
|
56
|
+
@snapshot_groups = {}
|
56
57
|
@snapshots = []
|
57
58
|
|
58
59
|
initialize_locks
|
@@ -152,28 +153,69 @@ module Rack
|
|
152
153
|
end
|
153
154
|
end
|
154
155
|
|
155
|
-
def push_snapshot(page_struct, config)
|
156
|
+
def push_snapshot(page_struct, group_name, config)
|
156
157
|
@snapshots_lock.synchronize do
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
158
|
+
group = @snapshot_groups[group_name]
|
159
|
+
if !group
|
160
|
+
@snapshot_groups[group_name] = {
|
161
|
+
worst_score: page_struct.duration_ms,
|
162
|
+
best_score: page_struct.duration_ms,
|
163
|
+
snapshots: [page_struct]
|
164
|
+
}
|
165
|
+
if @snapshot_groups.size > config.max_snapshot_groups
|
166
|
+
group_keys = @snapshot_groups.keys
|
167
|
+
group_keys.sort_by! do |key|
|
168
|
+
@snapshot_groups[key][:worst_score]
|
169
|
+
end
|
170
|
+
group_keys.reverse!
|
171
|
+
group_keys.pop(group_keys.size - config.max_snapshot_groups)
|
172
|
+
@snapshot_groups = @snapshot_groups.slice(*group_keys)
|
173
|
+
end
|
174
|
+
else
|
175
|
+
snapshots = group[:snapshots]
|
176
|
+
snapshots << page_struct
|
177
|
+
snapshots.sort_by!(&:duration_ms)
|
178
|
+
snapshots.reverse!
|
179
|
+
if snapshots.size > config.max_snapshots_per_group
|
180
|
+
snapshots.pop(snapshots.size - config.max_snapshots_per_group)
|
181
|
+
end
|
182
|
+
group[:worst_score] = snapshots[0].duration_ms
|
183
|
+
group[:best_score] = snapshots[-1].duration_ms
|
162
184
|
end
|
163
185
|
end
|
164
186
|
end
|
165
187
|
|
166
|
-
def
|
188
|
+
def fetch_snapshots_overview
|
167
189
|
@snapshots_lock.synchronize do
|
168
|
-
|
169
|
-
|
190
|
+
groups = {}
|
191
|
+
@snapshot_groups.each do |name, group|
|
192
|
+
groups[name] = {
|
193
|
+
worst_score: group[:worst_score],
|
194
|
+
best_score: group[:best_score],
|
195
|
+
snapshots_count: group[:snapshots].size
|
196
|
+
}
|
170
197
|
end
|
198
|
+
groups
|
171
199
|
end
|
172
200
|
end
|
173
201
|
|
174
|
-
def
|
202
|
+
def fetch_snapshots_group(group_name)
|
175
203
|
@snapshots_lock.synchronize do
|
176
|
-
|
204
|
+
group = @snapshot_groups[group_name]
|
205
|
+
if group
|
206
|
+
group[:snapshots].dup
|
207
|
+
else
|
208
|
+
[]
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
def load_snapshot(id, group_name)
|
214
|
+
@snapshots_lock.synchronize do
|
215
|
+
group = @snapshot_groups[group_name]
|
216
|
+
if group
|
217
|
+
group[:snapshots].find { |s| s[:id] == id }
|
218
|
+
end
|
177
219
|
end
|
178
220
|
end
|
179
221
|
|
@@ -182,7 +224,7 @@ module Rack
|
|
182
224
|
# used in tests only
|
183
225
|
def wipe_snapshots_data
|
184
226
|
@snapshots_cycle = 0
|
185
|
-
@
|
227
|
+
@snapshot_groups = {}
|
186
228
|
end
|
187
229
|
end
|
188
230
|
end
|
@@ -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
|
@@ -131,81 +133,127 @@ unviewed_ids: #{get_unviewed_ids(user)}
|
|
131
133
|
)
|
132
134
|
end
|
133
135
|
|
134
|
-
def push_snapshot(page_struct, config)
|
135
|
-
|
136
|
-
|
136
|
+
def push_snapshot(page_struct, group_name, config)
|
137
|
+
group_zset_key = group_snapshot_zset_key(group_name)
|
138
|
+
group_hash_key = group_snapshot_hash_key(group_name)
|
139
|
+
overview_zset_key = snapshot_overview_zset_key
|
137
140
|
|
138
141
|
id = page_struct[:id]
|
139
|
-
score = page_struct.duration_ms
|
140
|
-
|
142
|
+
score = page_struct.duration_ms.to_s
|
143
|
+
|
144
|
+
per_group_limit = config.max_snapshots_per_group.to_s
|
145
|
+
groups_limit = config.max_snapshot_groups.to_s
|
141
146
|
bytes = Marshal.dump(page_struct)
|
142
147
|
|
143
148
|
lua = <<~LUA
|
144
|
-
local
|
145
|
-
local
|
149
|
+
local group_zset_key = KEYS[1]
|
150
|
+
local group_hash_key = KEYS[2]
|
151
|
+
local overview_zset_key = KEYS[3]
|
152
|
+
|
146
153
|
local id = ARGV[1]
|
147
154
|
local score = tonumber(ARGV[2])
|
148
|
-
local
|
149
|
-
local
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
155
|
+
local group_name = ARGV[3]
|
156
|
+
local per_group_limit = tonumber(ARGV[4])
|
157
|
+
local groups_limit = tonumber(ARGV[5])
|
158
|
+
local prefix = ARGV[6]
|
159
|
+
local bytes = ARGV[7]
|
160
|
+
|
161
|
+
local current_group_score = redis.call("ZSCORE", overview_zset_key, group_name)
|
162
|
+
if current_group_score == false or score > tonumber(current_group_score) then
|
163
|
+
redis.call("ZADD", overview_zset_key, score, group_name)
|
164
|
+
end
|
165
|
+
|
166
|
+
local do_save = true
|
167
|
+
local overview_size = redis.call("ZCARD", overview_zset_key)
|
168
|
+
while (overview_size > groups_limit) do
|
169
|
+
local lowest_group = redis.call("ZRANGE", overview_zset_key, 0, 0)[1]
|
170
|
+
redis.call("ZREM", overview_zset_key, lowest_group)
|
171
|
+
if lowest_group == group_name then
|
172
|
+
do_save = false
|
173
|
+
else
|
174
|
+
local lowest_group_zset_key = prefix .. "-mp-group-snapshot-zset-key-" .. lowest_group
|
175
|
+
local lowest_group_hash_key = prefix .. "-mp-group-snapshot-hash-key-" .. lowest_group
|
176
|
+
redis.call("DEL", lowest_group_zset_key, lowest_group_hash_key)
|
177
|
+
end
|
178
|
+
overview_size = overview_size - 1
|
179
|
+
end
|
180
|
+
|
181
|
+
if do_save then
|
182
|
+
redis.call("ZADD", group_zset_key, score, id)
|
183
|
+
local group_size = redis.call("ZCARD", group_zset_key)
|
184
|
+
while (group_size > per_group_limit) do
|
185
|
+
local lowest_snapshot_id = redis.call("ZRANGE", group_zset_key, 0, 0)[1]
|
186
|
+
redis.call("ZREM", group_zset_key, lowest_snapshot_id)
|
187
|
+
if lowest_snapshot_id == id then
|
188
|
+
do_save = false
|
189
|
+
else
|
190
|
+
redis.call("HDEL", group_hash_key, lowest_snapshot_id)
|
191
|
+
end
|
192
|
+
group_size = group_size - 1
|
193
|
+
end
|
194
|
+
if do_save then
|
195
|
+
redis.call("HSET", group_hash_key, id, bytes)
|
196
|
+
end
|
156
197
|
end
|
157
198
|
LUA
|
158
199
|
redis.eval(
|
159
200
|
lua,
|
160
|
-
keys: [
|
161
|
-
argv: [id, score,
|
201
|
+
keys: [group_zset_key, group_hash_key, overview_zset_key],
|
202
|
+
argv: [id, score, group_name, per_group_limit, groups_limit, @prefix, bytes]
|
162
203
|
)
|
163
204
|
end
|
164
205
|
|
165
|
-
def
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
batch.map! do |id, bytes|
|
179
|
-
begin
|
180
|
-
Marshal.load(bytes)
|
181
|
-
rescue
|
182
|
-
corrupt_snapshots << id
|
183
|
-
nil
|
184
|
-
end
|
206
|
+
def fetch_snapshots_overview
|
207
|
+
overview_zset_key = snapshot_overview_zset_key
|
208
|
+
groups = redis
|
209
|
+
.zrange(overview_zset_key, 0, -1, withscores: true)
|
210
|
+
.map { |(name, worst_score)| [name, { worst_score: worst_score }] }
|
211
|
+
|
212
|
+
prefixed_group_names = groups.map { |(group_name, _)| group_snapshot_zset_key(group_name) }
|
213
|
+
metadata = redis.eval(<<~LUA, keys: prefixed_group_names)
|
214
|
+
local metadata = {}
|
215
|
+
for i, k in ipairs(KEYS) do
|
216
|
+
local best = redis.call("ZRANGE", k, 0, 0, "WITHSCORES")[2]
|
217
|
+
local count = redis.call("ZCARD", k)
|
218
|
+
metadata[i] = {best, count}
|
185
219
|
end
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
220
|
+
return metadata
|
221
|
+
LUA
|
222
|
+
groups.each.with_index do |(_, hash), index|
|
223
|
+
best, count = metadata[index]
|
224
|
+
hash[:best_score] = best.to_f
|
225
|
+
hash[:snapshots_count] = count.to_i
|
226
|
+
end
|
227
|
+
groups.to_h
|
228
|
+
end
|
229
|
+
|
230
|
+
def fetch_snapshots_group(group_name)
|
231
|
+
group_hash_key = group_snapshot_hash_key(group_name)
|
232
|
+
snapshots = []
|
233
|
+
corrupt_snapshots = []
|
234
|
+
redis.hgetall(group_hash_key).each do |id, bytes|
|
235
|
+
# rubocop:disable Security/MarshalLoad
|
236
|
+
snapshots << Marshal.load(bytes)
|
237
|
+
# rubocop:enable Security/MarshalLoad
|
238
|
+
rescue
|
239
|
+
corrupt_snapshots << id
|
190
240
|
end
|
191
241
|
if corrupt_snapshots.size > 0
|
192
|
-
|
193
|
-
redis.zrem(zset_key, corrupt_snapshots)
|
194
|
-
redis.hdel(hash_key, corrupt_snapshots)
|
195
|
-
end
|
242
|
+
cleanup_corrupt_snapshots(corrupt_snapshots, group_name)
|
196
243
|
end
|
244
|
+
snapshots
|
197
245
|
end
|
198
246
|
|
199
|
-
def load_snapshot(id)
|
200
|
-
|
201
|
-
bytes = redis.hget(
|
247
|
+
def load_snapshot(id, group_name)
|
248
|
+
group_hash_key = group_snapshot_hash_key(group_name)
|
249
|
+
bytes = redis.hget(group_hash_key, id)
|
250
|
+
return if !bytes
|
202
251
|
begin
|
252
|
+
# rubocop:disable Security/MarshalLoad
|
203
253
|
Marshal.load(bytes)
|
254
|
+
# rubocop:enable Security/MarshalLoad
|
204
255
|
rescue
|
205
|
-
|
206
|
-
redis.zrem(snapshot_zset_key(), id)
|
207
|
-
redis.hdel(hash_key, id)
|
208
|
-
end
|
256
|
+
cleanup_corrupt_snapshots([id], group_name)
|
209
257
|
nil
|
210
258
|
end
|
211
259
|
end
|
@@ -231,12 +279,20 @@ unviewed_ids: #{get_unviewed_ids(user)}
|
|
231
279
|
@snapshot_counter_key ||= "#{@prefix}-mini-profiler-snapshots-counter"
|
232
280
|
end
|
233
281
|
|
234
|
-
def
|
235
|
-
|
282
|
+
def group_snapshot_zset_key(group_name)
|
283
|
+
# if you change this key, remember to change it in the LUA script in
|
284
|
+
# the push_snapshot method as well
|
285
|
+
"#{@prefix}-mp-group-snapshot-zset-key-#{group_name}"
|
236
286
|
end
|
237
287
|
|
238
|
-
def
|
239
|
-
|
288
|
+
def group_snapshot_hash_key(group_name)
|
289
|
+
# if you change this key, remember to change it in the LUA script in
|
290
|
+
# the push_snapshot method as well
|
291
|
+
"#{@prefix}-mp-group-snapshot-hash-key-#{group_name}"
|
292
|
+
end
|
293
|
+
|
294
|
+
def snapshot_overview_zset_key
|
295
|
+
"#{@prefix}-mp-overviewgroup-snapshot-zset-key"
|
240
296
|
end
|
241
297
|
|
242
298
|
def cached_redis_eval(script, script_sha, reraise: true, argv: [], keys: [])
|
@@ -251,13 +307,45 @@ unviewed_ids: #{get_unviewed_ids(user)}
|
|
251
307
|
end
|
252
308
|
end
|
253
309
|
|
310
|
+
def cleanup_corrupt_snapshots(corrupt_snapshots_ids, group_name)
|
311
|
+
group_hash_key = group_snapshot_hash_key(group_name)
|
312
|
+
group_zset_key = group_snapshot_zset_key(group_name)
|
313
|
+
overview_zset_key = snapshot_overview_zset_key
|
314
|
+
lua = <<~LUA
|
315
|
+
local group_hash_key = KEYS[1]
|
316
|
+
local group_zset_key = KEYS[2]
|
317
|
+
local overview_zset_key = KEYS[3]
|
318
|
+
local group_name = ARGV[1]
|
319
|
+
for i, k in ipairs(ARGV) do
|
320
|
+
if k ~= group_name then
|
321
|
+
redis.call("HDEL", group_hash_key, k)
|
322
|
+
redis.call("ZREM", group_zset_key, k)
|
323
|
+
end
|
324
|
+
end
|
325
|
+
if redis.call("ZCARD", group_zset_key) == 0 then
|
326
|
+
redis.call("ZREM", overview_zset_key, group_name)
|
327
|
+
redis.call("DEL", group_hash_key, group_zset_key)
|
328
|
+
else
|
329
|
+
local worst_score = tonumber(redis.call("ZRANGE", group_zset_key, -1, -1, "WITHSCORES")[2])
|
330
|
+
redis.call("ZADD", overview_zset_key, worst_score, group_name)
|
331
|
+
end
|
332
|
+
LUA
|
333
|
+
redis.eval(
|
334
|
+
lua,
|
335
|
+
keys: [group_hash_key, group_zset_key, overview_zset_key],
|
336
|
+
argv: [group_name, *corrupt_snapshots_ids]
|
337
|
+
)
|
338
|
+
end
|
339
|
+
|
254
340
|
# only used in tests
|
255
341
|
def wipe_snapshots_data
|
256
|
-
redis.
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
342
|
+
keys = redis.keys(group_snapshot_hash_key('*'))
|
343
|
+
keys += redis.keys(group_snapshot_zset_key('*'))
|
344
|
+
redis.del(
|
345
|
+
keys,
|
346
|
+
snapshot_overview_zset_key,
|
347
|
+
snapshot_counter_key
|
348
|
+
)
|
261
349
|
end
|
262
350
|
end
|
263
351
|
end
|
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:
|
4
|
+
version: 3.0.0
|
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-24 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.
|