rack-mini-profiler 2.0.4 → 2.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +28 -0
  3. data/README.md +53 -5
  4. data/lib/html/includes.css +38 -0
  5. data/lib/html/includes.js +265 -175
  6. data/lib/html/includes.scss +35 -4
  7. data/lib/html/includes.tmpl +93 -3
  8. data/lib/html/profile_handler.js +1 -1
  9. data/lib/html/rack-mini-profiler.css +3 -0
  10. data/lib/html/rack-mini-profiler.js +2 -0
  11. data/lib/html/speedscope/LICENSE +21 -0
  12. data/lib/html/speedscope/README.md +3 -0
  13. data/lib/html/speedscope/demangle-cpp.1768f4cc.js +4 -0
  14. data/lib/html/speedscope/favicon-16x16.f74b3187.png +0 -0
  15. data/lib/html/speedscope/favicon-32x32.bc503437.png +0 -0
  16. data/lib/html/speedscope/file-format-schema.json +324 -0
  17. data/lib/html/speedscope/fonts/source-code-pro-regular.css +8 -0
  18. data/lib/html/speedscope/fonts/source-code-pro-v13-regular.woff +0 -0
  19. data/lib/html/speedscope/fonts/source-code-pro-v13-regular.woff2 +0 -0
  20. data/lib/html/speedscope/import.cf0fa83f.js +115 -0
  21. data/lib/html/speedscope/index.html +2 -0
  22. data/lib/html/speedscope/release.txt +3 -0
  23. data/lib/html/speedscope/reset.8c46b7a1.css +2 -0
  24. data/lib/html/speedscope/source-map.438fa06b.js +24 -0
  25. data/lib/html/speedscope/speedscope.44364064.js +200 -0
  26. data/lib/html/vendor.js +10 -2
  27. data/lib/mini_profiler/asset_version.rb +1 -1
  28. data/lib/mini_profiler/client_settings.rb +3 -2
  29. data/lib/mini_profiler/config.rb +24 -2
  30. data/lib/mini_profiler/profiler.rb +214 -22
  31. data/lib/mini_profiler/profiling_methods.rb +11 -2
  32. data/lib/mini_profiler/snapshots_transporter.rb +109 -0
  33. data/lib/mini_profiler/storage/abstract_store.rb +78 -0
  34. data/lib/mini_profiler/storage/memory_store.rb +54 -5
  35. data/lib/mini_profiler/storage/redis_store.rb +134 -0
  36. data/lib/mini_profiler/timer_struct/page.rb +52 -2
  37. data/lib/mini_profiler/timer_struct/sql.rb +2 -2
  38. data/lib/mini_profiler/version.rb +1 -1
  39. data/lib/mini_profiler_rails/railtie.rb +11 -0
  40. data/lib/patches/db/mysql2.rb +4 -27
  41. data/lib/patches/db/mysql2/alias_method.rb +30 -0
  42. data/lib/patches/db/mysql2/prepend.rb +34 -0
  43. data/lib/prepend_mysql2_patch.rb +5 -0
  44. data/lib/rack-mini-profiler.rb +1 -0
  45. data/rack-mini-profiler.gemspec +6 -4
  46. metadata +63 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2e54608bc4885796e11d6e4fac9cc56fe478f60c5cd43d53637451ef3524f086
4
- data.tar.gz: 6a87a58c7821a279ce08b66432de9a199a6f775f897610e03e12d497eb7ccc46
3
+ metadata.gz: 287f0fe8da1845abde81f60250c960808168f0d278cf905786293c584fe78c23
4
+ data.tar.gz: c43613852efe89a7e59c642220b4609b086190473a0c2c5a83ead3b1abbdce9c
5
5
  SHA512:
6
- metadata.gz: 3730c9e4610fd90cc0fac86c1cddcf6181e15cef5932ba86f8f8cbca1df77c02ba7847c2e1868b48e51299b6c51224fe533818c3cc245dd02a2da11b2298569c
7
- data.tar.gz: 98f54eed006f7f13840481b777dbe4add5f9983c5ed367f8046a942a584a5ad3eb4ea5ae6e3aef7e4aa0760b37c78ac5dd6ee5bcd45e42091a943dfabdb343b1
6
+ metadata.gz: c4944a4bbf9021b4639dc18d5e13d6f901eef47785b1e66ac37b261307cf82f70dd5ebc242fb099fecada4c450594155e600150baedfd7cb2b1d7f7b7527ef69
7
+ data.tar.gz: ba0ee401a63f3fe3a27acc1812d790a1e11de26f6d6ffd38d34730bfec3ecf26c96f20f9912fe071917a31a346115c10cd959bff35516624d3f816c5aa84426c
@@ -1,5 +1,33 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 2.3.1 - 2021-01-29
4
+
5
+ - [FIX] compatability with Ruby 3.0
6
+ - [FIX] compatability with peek-mysql2
7
+
8
+ ## 2.3.0 - 2020-12-29
9
+
10
+ - [FEATURE] flamegraphs are now based off speedscope
11
+
12
+ ## 2.2.1 - 2020-12-23
13
+
14
+ - [FIX] Turbolinks integration causing increasing number of GET requests
15
+ - [FEATURE] enahanced log transporter with compression and exponential backoff
16
+ - [FEATURE] sameSite=Lax added to MiniProfiler cookie
17
+
18
+ ## 2.2.0 - 2020-10-19
19
+
20
+ - [UX] Enhancements to snapshots UI
21
+ - [FEATURE] Mini Profiler cookie is now sameSite=lax
22
+ - [FEATURE] Snapshots transporter
23
+ - [FEATURE] Redact SQL queries in snapshots by default
24
+
25
+ ## 2.1.0 - 2020-09-17
26
+
27
+ - [FEATURE] Allow assets to be precompiled with Sprockets
28
+ - [FEATURE] Snapshots sampling (see README in repo)
29
+ - [FEATURE] Allow `skip_paths` config to contain regular expressions
30
+
3
31
  ## 2.0.4 - 2020-08-04
4
32
 
5
33
  - [FIX] webpacker may exist with no config, allow for that
data/README.md CHANGED
@@ -1,7 +1,5 @@
1
1
  # rack-mini-profiler
2
2
 
3
- [![Code Climate](https://codeclimate.com/github/MiniProfiler/rack-mini-profiler/badges/gpa.svg)](https://codeclimate.com/github/MiniProfiler/rack-mini-profiler) [![Build Status](https://travis-ci.org/MiniProfiler/rack-mini-profiler.svg)](https://travis-ci.org/MiniProfiler/rack-mini-profiler)
4
-
5
3
  Middleware that displays speed badge for every html page. Designed to work both in production and in development.
6
4
 
7
5
  #### Features
@@ -43,7 +41,6 @@ You can also include optional libraries to enable additional features.
43
41
  gem 'memory_profiler'
44
42
 
45
43
  # For call-stack profiling flamegraphs
46
- gem 'flamegraph'
47
44
  gem 'stackprof'
48
45
  ```
49
46
 
@@ -81,6 +78,16 @@ gem 'rack-mini-profiler', require: ['prepend_net_http_patch']
81
78
 
82
79
  This conflict happens when a ruby method is patched twice, once using module prepend, and once using method aliasing. See this [ruby issue](https://bugs.ruby-lang.org/issues/11120) for details. The fix is to apply all patches the same way. Mini Profiler by default will apply its patch using method aliasing, but you can change that to module prepend by adding `require: ['prepend_net_http_patch']` to the gem line as shown above.
83
80
 
81
+ #### `peek-mysql2` stack level too deep errors
82
+
83
+ If you use peek-mysql2 with Rails >= 5, you'll need to use this gem spec in your Gemfile:
84
+
85
+ ```ruby
86
+ gem 'rack-mini-profiler', require: ['prepend_mysql2_patch', 'rack-mini-profiler']
87
+ ```
88
+
89
+ This should not be necessary with Rails < 5 because peek-mysql2 hooks into mysql2 gem in different ways depending on your Rails version.
90
+
84
91
  #### Rails and manual initialization
85
92
 
86
93
  In case you need to make sure rack_mini_profiler is initialized after all other gems, or you want to execute some code before rack_mini_profiler required:
@@ -161,7 +168,7 @@ export RACK_MINI_PROFILER_PATCH="false"
161
168
 
162
169
  To generate [flamegraphs](http://samsaffron.com/archive/2013/03/19/flame-graphs-in-ruby-miniprofiler):
163
170
 
164
- * add the [**flamegraph**](https://github.com/SamSaffron/flamegraph) gem to your Gemfile
171
+ * add the [**stackprof**](https://rubygems.org/gems/stackprof) gem to your Gemfile
165
172
  * visit a page in your app with `?pp=flamegraph`
166
173
 
167
174
  ### Memory Profiling
@@ -186,6 +193,30 @@ There are two additional `pp` options that can be used to analyze memory which d
186
193
  * Use `?pp=profile-gc` to report on Garbage Collection statistics
187
194
  * Use `?pp=analyze-memory` to report on ObjectSpace statistics
188
195
 
196
+ ### Snapshots Sampling
197
+
198
+ 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
+
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).
201
+
202
+ 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
+
204
+ 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.
205
+
206
+ Mini Profiler will keep a maximum of 1000 snapshots by default, and you can change that via the `snapshots_limit` config. When snapshots reach the configured limit, Mini Profiler will save a new snapshot only if it's worse than at least one of the existing snapshots and delete the best one (i.e. the snapshot whose request took the least time compared to other snapshots).
207
+
208
+ #### Snapshots Transporter
209
+
210
+ Mini Profiler can be configured so that it sends snapshots over HTTP using the snapshots transporter. The main use-case of the transporter is to allow the aggregation of snapshots from multiple applications/sources in a single place. To enable the snapshots transporter, you need to provide a destination URL to the `snapshots_transport_destination_url` config, and a secure key to the `snapshots_transport_auth_key` config (will be used for authorization). Both of these configs are required for the transporter to be enabled.
211
+
212
+ The transporter uses a buffer to temporarily hold snapshots in memory with a limit of 100 snapshots. Every 30 seconds, *if* the buffer is not empty, the transporter will make a `POST` request with the buffer content to the destination URL. Requests made by the transporter will have a `Mini-Profiler-Transport-Auth` header with the value of the `snapshots_transport_auth_key` config. The destination should only accept requests that include this header AND the header's value matches the key you set to the `snapshots_transport_auth_key` config.
213
+
214
+ If the specified destination responds with a non-200 status code, the transporter will increase the interval between requests by `2^n` seconds where `n` is the number of failed requests since the last successful request. The base interval between requests is 30 seconds. So if a request fails, the next request will be `30 + 2^1 = 32` seconds later. If the next request fails too, the next one will be `30 + 2^2 = 34` seconds later and so on until a request succeeds at which point the interval will return to 30 seconds. The interval will not go beyond 1 hour.
215
+
216
+ Requests made by the transporter can be optionally gzip-compressed by setting the `snapshots_transport_gzip_requests` config to true. The body of the requests (after decompression, if you opt for compression) is a JSON string with a single top-level key called `snapshots` and it has an array of snapshots. The structure of a snapshot is too complex to be explained here, but it has the same structure that Mini Profiler client expects. So if your use-case is to simply be able to view snapshots from multiple sources in one place, you should simply store the snapshots as-is, and then serve them to Mini Profiler client to consume. If the destination application also has Mini Profiler, you can simply use the API of the storage backends to store the incoming snapshots and Mini Profiler will treat them the same as local snapshots (e.g. they'll be grouped and displayed in the same manner described in the previous section).
217
+
218
+ Mini Profiler offers an API to add extra fields (a.k.a custom fields) to snapshots. For example, you may want to add whether the request was made by a logged-in or anonymous user, the version of your application or any other things that are specific to your application. To add custom fields to a snapshot, call the `Rack::MiniProfiler.add_snapshot_custom_field(<key>, <value>)` method anywhere during the lifetime of a request, and the snapshot of that request will include the fields you added. If you have a Rails app, you can call that method in an `after_action` callback. Custom fields are cleared between requests.
219
+
189
220
  ## Access control in non-development environments
190
221
 
191
222
  rack-mini-profiler is designed with production profiling in mind. To enable that run `Rack::MiniProfiler.authorize_request` once you know a request is allowed to profile.
@@ -329,6 +360,15 @@ _Note:_ The GUID (`data-version` and the `?v=` parameter on the `src`) will chan
329
360
  #### Using MiniProfiler's built in route for apps without HTML responses
330
361
  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.
331
362
 
363
+ #### Register MiniProfiler's assets in the Rails assets pipeline
364
+ MiniProfiler can be configured so it registers its assets in the assets pipeline. To do that, you'll need to provide a lambda (or proc) to the `assets_url` config (see the below section). The callback will receive 3 arguments which are: `name` represents asset name (currently it's either `rack-mini-profiling.js` or `rack-mini-profiling.css`), `assets_version` is a 32 characters long hash of MiniProfiler's assets, and `env` which is the `env` object of the request. MiniProfiler expects the `assets_url` callback to return a URL from which the asset can be loaded (the return value will be used as a `href`/`src` attribute in the DOM). If the `assets_url` callback is not set (the default) or it returns a non-truthy value, MiniProfiler will fallback to loading assets from its own middleware (`/mini-profiler-resources/*`). The following callback should work for most applications:
365
+
366
+ ```ruby
367
+ Rack::MiniProfiler.config.assets_url = ->(name, version, env) {
368
+ ActionController::Base.helpers.asset_path(name)
369
+ }
370
+ ```
371
+
332
372
  ### Configuration Options
333
373
 
334
374
  You can set configuration options using the configuration accessor on `Rack::MiniProfiler`.
@@ -344,7 +384,7 @@ Option|Default|Description
344
384
  -------|---|--------
345
385
  pre_authorize_cb|Rails: dev only<br>Rack: always on|A lambda callback that returns true to make mini_profiler visible on a given request.
346
386
  position|`'top-left'`|Display mini_profiler on `'top-right'`, `'top-left'`, `'bottom-right'` or `'bottom-left'`.
347
- skip_paths|`[]`|Paths that skip profiling.
387
+ skip_paths|`[]`|An array of paths that skip profiling. Both `String` and `Regexp` are acceptable in the array.
348
388
  skip_schema_queries|Rails dev: `true`<br>Othwerwise: `false`|`true` to skip schema queries.
349
389
  auto_inject|`true`|`true` to inject the miniprofiler script in the page.
350
390
  backtrace_ignores|`[]`|Regexes of lines to be removed from backtraces.
@@ -360,6 +400,14 @@ max_traces_to_show|20|Maximum number of mini profiler timing blocks to show on o
360
400
  html_container|`body`|The HTML container (as a jQuery selector) to inject the mini_profiler UI into
361
401
  show_total_sql_count|`false`|Displays the total number of SQL executions.
362
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.
403
+ assets_url|`nil`|See the "Register MiniProfiler's assets in the Rails assets pipeline" section above.
404
+ snapshot_every_n_requests|`-1`|Determines how frequently snapshots are taken. See the "Snapshots Sampling" above for more details.
405
+ snapshots_limit|`1000`|Determines how many snapshots Mini Profiler is allowed to keep.
406
+ 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.
407
+ 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.
408
+ 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
+ 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
+ snapshots_transport_gzip_requests|`false`|Make the snapshots transporter gzip the requests it makes to `snapshots_transport_destination_url`.
363
411
 
364
412
  ### Using MiniProfiler with `Rack::Deflate` middleware
365
413
 
@@ -1,9 +1,20 @@
1
1
  @charset "UTF-8";
2
+ .mp-snapshots,
2
3
  .profiler-result,
3
4
  .profiler-queries {
4
5
  color: #555;
5
6
  line-height: 1;
6
7
  font-size: 12px; }
8
+ .mp-snapshots pre,
9
+ .mp-snapshots code,
10
+ .mp-snapshots label,
11
+ .mp-snapshots table,
12
+ .mp-snapshots tbody,
13
+ .mp-snapshots thead,
14
+ .mp-snapshots tfoot,
15
+ .mp-snapshots tr,
16
+ .mp-snapshots th,
17
+ .mp-snapshots td,
7
18
  .profiler-result pre,
8
19
  .profiler-result code,
9
20
  .profiler-result label,
@@ -33,27 +44,40 @@
33
44
  background-color: transparent;
34
45
  overflow: visible;
35
46
  max-height: none; }
47
+ .mp-snapshots table,
36
48
  .profiler-result table,
37
49
  .profiler-queries table {
38
50
  border-collapse: collapse;
39
51
  border-spacing: 0; }
52
+ .mp-snapshots a,
53
+ .mp-snapshots a:hover,
40
54
  .profiler-result a,
41
55
  .profiler-result a:hover,
42
56
  .profiler-queries a,
43
57
  .profiler-queries a:hover {
44
58
  cursor: pointer;
45
59
  color: #0077cc; }
60
+ .mp-snapshots a,
46
61
  .profiler-result a,
47
62
  .profiler-queries a {
48
63
  text-decoration: none; }
64
+ .mp-snapshots a:hover,
49
65
  .profiler-result a:hover,
50
66
  .profiler-queries a:hover {
51
67
  text-decoration: underline; }
68
+ .mp-snapshots .custom-fields-title,
69
+ .profiler-result .custom-fields-title,
70
+ .profiler-queries .custom-fields-title {
71
+ color: #555;
72
+ font: Helvetica, Arial, sans-serif;
73
+ font-size: 14px; }
52
74
 
53
75
  .profiler-result {
54
76
  font-family: Helvetica, Arial, sans-serif; }
55
77
  .profiler-result .profiler-toggle-duration-with-children {
56
78
  float: right; }
79
+ .profiler-result .profiler-snapshots-page-link {
80
+ float: left; }
57
81
  .profiler-result table.profiler-client-timings {
58
82
  margin-top: 10px; }
59
83
  .profiler-result .profiler-label {
@@ -402,3 +426,17 @@
402
426
  background: #ffffbb; }
403
427
  100% {
404
428
  background: #fff; } }
429
+
430
+ .mp-snapshots {
431
+ font-family: Helvetica, Arial, sans-serif;
432
+ font-size: 16px; }
433
+ .mp-snapshots .snapshots-table thead {
434
+ background: #6a737c;
435
+ color: #ffffff; }
436
+ .mp-snapshots .snapshots-table th, .mp-snapshots .snapshots-table td {
437
+ padding: 5px 10px;
438
+ box-sizing: border-box; }
439
+ .mp-snapshots .snapshots-table th:not(.request-group), .mp-snapshots .snapshots-table td:not(.request-group) {
440
+ text-align: center; }
441
+ .mp-snapshots .snapshots-table th {
442
+ border-right: 1px solid #ffffff; }
@@ -1,12 +1,13 @@
1
1
  "use strict";
2
2
 
3
- var MiniProfiler = (function() {
3
+ var _MiniProfiler = (function() {
4
4
  var _arguments = arguments;
5
5
  var options,
6
6
  container,
7
7
  controls,
8
- fetchedIds = [],
9
- fetchingIds = [],
8
+ fetchedIds = (window.MiniProfiler && window.MiniProfiler.fetchedIds) || [],
9
+ fetchingIds =
10
+ (window.MiniProfiler && window.MiniProfiler.fetchingIds) || [],
10
11
  // so we never pull down a profiler twice
11
12
  ajaxStartTime,
12
13
  totalsControl,
@@ -634,6 +635,10 @@ var MiniProfiler = (function() {
634
635
  }
635
636
  };
636
637
 
638
+ var turbolinksSkipResultsFetch = function turbolinksSkipResultsFetch(event) {
639
+ event.data.xhr.__miniProfilerSkipResultsFetch = true;
640
+ };
641
+
637
642
  var bindDocumentEvents = function bindDocumentEvents() {
638
643
  document.addEventListener("click", onClickEvents);
639
644
  document.addEventListener("keyup", onClickEvents);
@@ -642,6 +647,10 @@ var MiniProfiler = (function() {
642
647
  if (typeof Turbolinks !== "undefined" && Turbolinks.supported) {
643
648
  document.addEventListener("page:change", unbindDocumentEvents);
644
649
  document.addEventListener("turbolinks:load", unbindDocumentEvents);
650
+ document.addEventListener(
651
+ "turbolinks:request-start",
652
+ turbolinksSkipResultsFetch
653
+ );
645
654
  }
646
655
  };
647
656
 
@@ -651,6 +660,10 @@ var MiniProfiler = (function() {
651
660
  document.removeEventListener("keyup", toggleShortcutEvent);
652
661
  document.removeEventListener("page:change", unbindDocumentEvents);
653
662
  document.removeEventListener("turbolinks:load", unbindDocumentEvents);
663
+ document.removeEventListener(
664
+ "turbolinks:request-start",
665
+ turbolinksSkipResultsFetch
666
+ );
654
667
  };
655
668
 
656
669
  var initFullView = function initFullView() {
@@ -673,6 +686,34 @@ var MiniProfiler = (function() {
673
686
  });
674
687
  };
675
688
 
689
+ var initSnapshots = function initSnapshots(dataElement) {
690
+ var data = JSON.parse(dataElement.textContent);
691
+ var temp = document.createElement("DIV");
692
+ if (data.page === "overview") {
693
+ temp.innerHTML = MiniProfiler.templates.snapshotsGroupsList(data);
694
+ } else if (data.group_name) {
695
+ var allCustomFieldsNames = [];
696
+ data.list.forEach(function(snapshot) {
697
+ Object.keys(snapshot.custom_fields).forEach(function(k) {
698
+ if (
699
+ allCustomFieldsNames.indexOf(k) === -1 &&
700
+ options.hiddenCustomFields.indexOf(k.toLowerCase()) === -1
701
+ ) {
702
+ allCustomFieldsNames.push(k);
703
+ }
704
+ });
705
+ });
706
+ allCustomFieldsNames.sort();
707
+ temp.innerHTML = MiniProfiler.templates.snapshotsList({
708
+ data: data,
709
+ allCustomFieldsNames: allCustomFieldsNames
710
+ });
711
+ }
712
+ Array.from(temp.children).forEach(function(child) {
713
+ document.body.appendChild(child);
714
+ });
715
+ };
716
+
676
717
  var initControls = function initControls(container) {
677
718
  if (options.showControls) {
678
719
  var _controls = document.createElement("div");
@@ -741,218 +782,225 @@ var MiniProfiler = (function() {
741
782
  fetchResults(options.ids);
742
783
  }
743
784
 
744
- var send = XMLHttpRequest.prototype.send;
745
-
746
- XMLHttpRequest.prototype.send = function(data) {
747
- ajaxStartTime = new Date();
748
- this.addEventListener("load", function() {
749
- // responseURL isn't available in IE11
750
- if (
751
- this.responseURL &&
752
- this.responseURL.indexOf(window.location.origin) !== 0
753
- ) {
754
- return;
755
- }
756
- // getAllResponseHeaders isn't available in Edge.
757
- var allHeaders = this.getAllResponseHeaders
758
- ? this.getAllResponseHeaders()
759
- : null;
760
- if (
761
- allHeaders &&
762
- allHeaders.toLowerCase().indexOf("x-miniprofiler-ids") === -1
763
- ) {
764
- return;
765
- }
766
- // should be a string of comma-separated ids
767
- var stringIds = this.getResponseHeader("X-MiniProfiler-Ids");
768
-
769
- if (stringIds) {
770
- var ids = stringIds.split(",");
771
- fetchResults(ids);
772
- }
773
- });
774
- send.call(this, data);
775
- }; // fetch results after ASP Ajax calls
776
-
777
- if (
778
- typeof Sys != "undefined" &&
779
- typeof Sys.WebForms != "undefined" &&
780
- typeof Sys.WebForms.PageRequestManager != "undefined"
781
- ) {
782
- // Get the instance of PageRequestManager.
783
- var PageRequestManager = Sys.WebForms.PageRequestManager.getInstance();
784
- PageRequestManager.add_endRequest(function(sender, args) {
785
- if (args) {
786
- var response = args.get_response();
785
+ if (!window.MiniProfiler || !window.MiniProfiler.patchesApplied) {
786
+ var send = XMLHttpRequest.prototype.send;
787
787
 
788
+ XMLHttpRequest.prototype.send = function(data) {
789
+ ajaxStartTime = new Date();
790
+ this.addEventListener("load", function() {
791
+ // responseURL isn't available in IE11
788
792
  if (
789
- response.get_responseAvailable() &&
790
- response._xmlHttpRequest !== null
793
+ this.responseURL &&
794
+ this.responseURL.indexOf(window.location.origin) !== 0
791
795
  ) {
792
- var stringIds = args
793
- .get_response()
794
- .getResponseHeader("X-MiniProfiler-Ids");
795
-
796
- if (stringIds) {
797
- var ids = stringIds.split(",");
798
- fetchResults(ids);
799
- }
796
+ return;
800
797
  }
801
- }
802
- });
803
- } // more Asp.Net callbacks
804
-
805
- if (typeof WebForm_ExecuteCallback == "function") {
806
- WebForm_ExecuteCallback = (function(callbackObject) {
807
- // Store original function
808
- var original = WebForm_ExecuteCallback;
809
- return function(callbackObject) {
810
- original(callbackObject);
811
- var stringIds = callbackObject.xmlRequest.getResponseHeader(
812
- "X-MiniProfiler-Ids"
813
- );
798
+ if (this.__miniProfilerSkipResultsFetch) {
799
+ return;
800
+ }
801
+ // getAllResponseHeaders isn't available in Edge.
802
+ var allHeaders = this.getAllResponseHeaders
803
+ ? this.getAllResponseHeaders()
804
+ : null;
805
+ if (
806
+ allHeaders &&
807
+ allHeaders.toLowerCase().indexOf("x-miniprofiler-ids") === -1
808
+ ) {
809
+ return;
810
+ }
811
+ // should be a string of comma-separated ids
812
+ var stringIds = this.getResponseHeader("X-MiniProfiler-Ids");
814
813
 
815
814
  if (stringIds) {
816
815
  var ids = stringIds.split(",");
817
816
  fetchResults(ids);
818
817
  }
819
- };
820
- })();
821
- } // also fetch results after ExtJS requests, in case it is being used
818
+ });
819
+ send.call(this, data);
820
+ }; // fetch results after ASP Ajax calls
822
821
 
823
- if (
824
- typeof Ext != "undefined" &&
825
- typeof Ext.Ajax != "undefined" &&
826
- typeof Ext.Ajax.on != "undefined"
827
- ) {
828
- // Ext.Ajax is a singleton, so we just have to attach to its 'requestcomplete' event
829
- Ext.Ajax.on("requestcomplete", function(e, xhr, settings) {
830
- //iframed file uploads don't have headers
831
- if (!xhr || !xhr.getResponseHeader) {
832
- return;
833
- }
822
+ if (
823
+ typeof Sys != "undefined" &&
824
+ typeof Sys.WebForms != "undefined" &&
825
+ typeof Sys.WebForms.PageRequestManager != "undefined"
826
+ ) {
827
+ // Get the instance of PageRequestManager.
828
+ var PageRequestManager = Sys.WebForms.PageRequestManager.getInstance();
829
+ PageRequestManager.add_endRequest(function(sender, args) {
830
+ if (args) {
831
+ var response = args.get_response();
832
+
833
+ if (
834
+ response.get_responseAvailable() &&
835
+ response._xmlHttpRequest !== null
836
+ ) {
837
+ var stringIds = args
838
+ .get_response()
839
+ .getResponseHeader("X-MiniProfiler-Ids");
840
+
841
+ if (stringIds) {
842
+ var ids = stringIds.split(",");
843
+ fetchResults(ids);
844
+ }
845
+ }
846
+ }
847
+ });
848
+ } // more Asp.Net callbacks
849
+
850
+ if (typeof WebForm_ExecuteCallback == "function") {
851
+ WebForm_ExecuteCallback = (function(callbackObject) {
852
+ // Store original function
853
+ var original = WebForm_ExecuteCallback;
854
+ return function(callbackObject) {
855
+ original(callbackObject);
856
+ var stringIds = callbackObject.xmlRequest.getResponseHeader(
857
+ "X-MiniProfiler-Ids"
858
+ );
834
859
 
835
- var stringIds = xhr.getResponseHeader("X-MiniProfiler-Ids");
860
+ if (stringIds) {
861
+ var ids = stringIds.split(",");
862
+ fetchResults(ids);
863
+ }
864
+ };
865
+ })();
866
+ } // also fetch results after ExtJS requests, in case it is being used
836
867
 
837
- if (stringIds) {
838
- var ids = stringIds.split(",");
839
- fetchResults(ids);
840
- }
841
- });
842
- }
868
+ if (
869
+ typeof Ext != "undefined" &&
870
+ typeof Ext.Ajax != "undefined" &&
871
+ typeof Ext.Ajax.on != "undefined"
872
+ ) {
873
+ // Ext.Ajax is a singleton, so we just have to attach to its 'requestcomplete' event
874
+ Ext.Ajax.on("requestcomplete", function(e, xhr, settings) {
875
+ //iframed file uploads don't have headers
876
+ if (!xhr || !xhr.getResponseHeader) {
877
+ return;
878
+ }
843
879
 
844
- if (typeof MooTools != "undefined" && typeof Request != "undefined") {
845
- Request.prototype.addEvents({
846
- onComplete: function onComplete() {
847
- var stringIds = this.xhr.getResponseHeader("X-MiniProfiler-Ids");
880
+ var stringIds = xhr.getResponseHeader("X-MiniProfiler-Ids");
848
881
 
849
882
  if (stringIds) {
850
883
  var ids = stringIds.split(",");
851
884
  fetchResults(ids);
852
885
  }
853
- }
854
- });
855
- } // add support for AngularJS, which use the basic XMLHttpRequest object.
856
-
857
- if (window.angular && typeof XMLHttpRequest != "undefined") {
858
- var _send = XMLHttpRequest.prototype.send;
859
-
860
- XMLHttpRequest.prototype.send = function sendReplacement(data) {
861
- if (this.onreadystatechange) {
862
- if (
863
- typeof this.miniprofiler == "undefined" ||
864
- typeof this.miniprofiler.prev_onreadystatechange == "undefined"
865
- ) {
866
- this.miniprofiler = {
867
- prev_onreadystatechange: this.onreadystatechange
868
- };
886
+ });
887
+ }
869
888
 
870
- this.onreadystatechange = function onReadyStateChangeReplacement() {
871
- if (this.readyState == 4) {
872
- var stringIds = this.getResponseHeader("X-MiniProfiler-Ids");
889
+ if (typeof MooTools != "undefined" && typeof Request != "undefined") {
890
+ Request.prototype.addEvents({
891
+ onComplete: function onComplete() {
892
+ var stringIds = this.xhr.getResponseHeader("X-MiniProfiler-Ids");
873
893
 
874
- if (stringIds) {
875
- var ids = stringIds.split(",");
876
- fetchResults(ids);
894
+ if (stringIds) {
895
+ var ids = stringIds.split(",");
896
+ fetchResults(ids);
897
+ }
898
+ }
899
+ });
900
+ } // add support for AngularJS, which use the basic XMLHttpRequest object.
901
+
902
+ if (window.angular && typeof XMLHttpRequest != "undefined") {
903
+ var _send = XMLHttpRequest.prototype.send;
904
+
905
+ XMLHttpRequest.prototype.send = function sendReplacement(data) {
906
+ if (this.onreadystatechange) {
907
+ if (
908
+ typeof this.miniprofiler == "undefined" ||
909
+ typeof this.miniprofiler.prev_onreadystatechange == "undefined"
910
+ ) {
911
+ this.miniprofiler = {
912
+ prev_onreadystatechange: this.onreadystatechange
913
+ };
914
+
915
+ this.onreadystatechange = function onReadyStateChangeReplacement() {
916
+ if (this.readyState == 4) {
917
+ var stringIds = this.getResponseHeader("X-MiniProfiler-Ids");
918
+
919
+ if (stringIds) {
920
+ var ids = stringIds.split(",");
921
+ fetchResults(ids);
922
+ }
877
923
  }
878
- }
879
924
 
880
- if (this.miniprofiler.prev_onreadystatechange !== null)
881
- return this.miniprofiler.prev_onreadystatechange.apply(
882
- this,
883
- arguments
884
- );
885
- };
925
+ if (this.miniprofiler.prev_onreadystatechange !== null)
926
+ return this.miniprofiler.prev_onreadystatechange.apply(
927
+ this,
928
+ arguments
929
+ );
930
+ };
931
+ }
886
932
  }
887
- }
888
-
889
- return _send.apply(this, arguments);
890
- };
891
- } // https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
892
933
 
893
- if (
894
- typeof window.fetch === "function" &&
895
- window.fetch.__patchedByMiniProfiler === undefined
896
- ) {
897
- var __originalFetch = window.fetch;
934
+ return _send.apply(this, arguments);
935
+ };
936
+ } // https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
898
937
 
899
- window.fetch = function(input, init) {
900
- var originalFetchRun = __originalFetch(input, init);
938
+ if (typeof window.fetch === "function") {
939
+ var __originalFetch = window.fetch;
901
940
 
902
- originalFetchRun.then(function(response) {
903
- try {
904
- // look for x-mini-profile-ids
905
- var entries = response.headers.entries();
906
- var _iteratorNormalCompletion = true;
907
- var _didIteratorError = false;
908
- var _iteratorError = undefined;
941
+ window.fetch = function(input, init) {
942
+ var originalFetchRun = __originalFetch(input, init);
909
943
 
944
+ originalFetchRun.then(function(response) {
910
945
  try {
911
- for (
912
- var _iterator = entries[Symbol.iterator](), _step;
913
- !(_iteratorNormalCompletion = (_step = _iterator.next()).done);
914
- _iteratorNormalCompletion = true
915
- ) {
916
- var pair = _step.value;
917
-
918
- if (pair[0] && pair[0].toLowerCase() == "x-miniprofiler-ids") {
919
- var ids = pair[1].split(",");
920
- fetchResults(ids);
921
- }
922
- }
923
- } catch (err) {
924
- _didIteratorError = true;
925
- _iteratorError = err;
926
- } finally {
946
+ // look for x-mini-profile-ids
947
+ var entries = response.headers.entries();
948
+ var _iteratorNormalCompletion = true;
949
+ var _didIteratorError = false;
950
+ var _iteratorError = undefined;
951
+
927
952
  try {
928
- if (!_iteratorNormalCompletion && _iterator.return != null) {
929
- _iterator.return();
953
+ for (
954
+ var _iterator = entries[Symbol.iterator](), _step;
955
+ !(_iteratorNormalCompletion = (_step = _iterator.next())
956
+ .done);
957
+ _iteratorNormalCompletion = true
958
+ ) {
959
+ var pair = _step.value;
960
+
961
+ if (
962
+ pair[0] &&
963
+ pair[0].toLowerCase() == "x-miniprofiler-ids"
964
+ ) {
965
+ var ids = pair[1].split(",");
966
+ fetchResults(ids);
967
+ }
930
968
  }
969
+ } catch (err) {
970
+ _didIteratorError = true;
971
+ _iteratorError = err;
931
972
  } finally {
932
- if (_didIteratorError) {
933
- throw _iteratorError;
973
+ try {
974
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
975
+ _iterator.return();
976
+ }
977
+ } finally {
978
+ if (_didIteratorError) {
979
+ throw _iteratorError;
980
+ }
934
981
  }
935
982
  }
983
+ } catch (e) {
984
+ console.error(e);
936
985
  }
937
- } catch (e) {
938
- console.error(e);
939
- }
940
- });
941
- return originalFetchRun;
942
- };
943
-
944
- window.fetch.__patchedByMiniProfiler = true;
945
- } // some elements want to be hidden on certain doc events
986
+ });
987
+ return originalFetchRun;
988
+ };
989
+ }
990
+ window.MiniProfiler.patchesApplied = true;
991
+ }
946
992
 
947
993
  bindDocumentEvents();
948
994
  };
949
995
 
950
996
  return {
997
+ fetchedIds: fetchedIds,
998
+ fetchingIds: fetchingIds,
951
999
  init: function init() {
952
1000
  var script = document.getElementById("mini-profiler");
953
1001
  if (!script || !script.getAttribute) return;
954
1002
 
955
- options = (function() {
1003
+ this.options = options = (function() {
956
1004
  var version = script.getAttribute("data-version");
957
1005
  var path = script.getAttribute("data-path");
958
1006
  var currentId = script.getAttribute("data-current-id");
@@ -980,6 +1028,11 @@ var MiniProfiler = (function() {
980
1028
  script.getAttribute("data-start-hidden") === "true" ||
981
1029
  sessionStorage["rack-mini-profiler-start-hidden"] === "true";
982
1030
  var htmlContainer = script.getAttribute("data-html-container");
1031
+ var cssUrl = script.getAttribute("data-css-url");
1032
+ var hiddenCustomFields = script
1033
+ .getAttribute("data-hidden-custom-fields")
1034
+ .toLowerCase()
1035
+ .split(",");
983
1036
  return {
984
1037
  ids: ids,
985
1038
  path: path,
@@ -996,11 +1049,19 @@ var MiniProfiler = (function() {
996
1049
  toggleShortcut: toggleShortcut,
997
1050
  startHidden: startHidden,
998
1051
  collapseResults: collapseResults,
999
- htmlContainer: htmlContainer
1052
+ htmlContainer: htmlContainer,
1053
+ cssUrl: cssUrl,
1054
+ hiddenCustomFields: hiddenCustomFields
1000
1055
  };
1001
1056
  })();
1002
1057
 
1003
1058
  var doInit = function doInit() {
1059
+ var snapshotsElement = document.getElementById("snapshots-data");
1060
+ if (snapshotsElement != null) {
1061
+ initSnapshots(snapshotsElement);
1062
+ return;
1063
+ }
1064
+
1004
1065
  // when rendering a shared, full page, this div will exist
1005
1066
  container = document.querySelectorAll(".profiler-result-full");
1006
1067
 
@@ -1056,7 +1117,7 @@ var MiniProfiler = (function() {
1056
1117
 
1057
1118
  var init = function init() {
1058
1119
  if (options.authorized) {
1059
- var url = options.path + "includes.css?v=" + options.version;
1120
+ var url = options.cssUrl;
1060
1121
 
1061
1122
  if (document.createStyleSheet) {
1062
1123
  document.createStyleSheet(url);
@@ -1394,8 +1455,37 @@ var MiniProfiler = (function() {
1394
1455
  },
1395
1456
  showTotalSqlCount: function showTotalSqlCount() {
1396
1457
  return options.showTotalSqlCount;
1458
+ },
1459
+ timestampToRelative: function timestampToRelative(timestamp) {
1460
+ var now = Math.round(new Date().getTime() / 1000);
1461
+ timestamp = Math.round(timestamp / 1000);
1462
+ var diff = now - timestamp;
1463
+ if (diff < 60) {
1464
+ return "< 1 minute";
1465
+ }
1466
+ var buildDisplayTime = function buildDisplayTime(num, unit) {
1467
+ var res = num + " " + unit;
1468
+ if (num !== 1) {
1469
+ res += "s";
1470
+ }
1471
+ return res;
1472
+ };
1473
+ diff = Math.round(diff / 60);
1474
+ if (diff <= 60) {
1475
+ return buildDisplayTime(diff, "minute");
1476
+ }
1477
+ diff = Math.round(diff / 60);
1478
+ if (diff <= 24) {
1479
+ return buildDisplayTime(diff, "hour");
1480
+ }
1481
+ diff = Math.round(diff / 24);
1482
+ return buildDisplayTime(diff, "day");
1397
1483
  }
1398
1484
  };
1399
1485
  })();
1400
1486
 
1401
- MiniProfiler.init();
1487
+ if (window.MiniProfiler) {
1488
+ _MiniProfiler.patchesApplied = window.MiniProfiler.patchesApplied;
1489
+ }
1490
+ window.MiniProfiler = _MiniProfiler;
1491
+ _MiniProfiler.init();