rack-mini-profiler 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 93c2989b331d19e51611a97941ea8259812285211b00ad25bb56e55a7fe17b37
4
- data.tar.gz: '07594d8609bad0a73f86cf56cfe1bf1aaeb8e279edeba50e2a5c4a3bf86be0fb'
3
+ metadata.gz: 1603a0c0246f9b132f81e74331425a1217156d62f71f20a06ecd3edca91aef51
4
+ data.tar.gz: a341e35cf639c11cc61c9ef5fd1ecd25e89ff12da299d3a6bfe8cb479c5dd7d8
5
5
  SHA512:
6
- metadata.gz: f651aeb06ef7bab9e853aacc4ed3ab22b8470b3b8723b7d7f727092c6e7e35db75003cdb8bbeba2ef94637c9ff85ea5bf269cbefa748f46aaeb72208d900b387
7
- data.tar.gz: ffe60bc20d0132e5e5c34e55debb739da0f3a62fce0d9a391f515fe9ba52f9c6c8f2cd3f327e433a8f2a9222bb60b1bbede8f1f28a9b9f39cb442b4097b982e1
6
+ metadata.gz: 4244e2738847e0e34b33cebda96f45d79ad82201ce969310bf45f1ad7431561af0d41610c2e8c60a0c64d428032a1e4bea6cc5b02aa71b5462a918474228844a
7
+ data.tar.gz: b1c0d88134e780bbbb91f711366400585d51d98107d1f35af85ca7c38498f0e23de019f2eb05259e833d8deabf485dc70f1dd581c28e132aba68bfa785967774
@@ -1,5 +1,29 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 2.1.0 - 2020-09-17
4
+
5
+ - [FEATURE] Allow assets to be precompiled with Sprockets
6
+ - [FEATURE] Snapshots sampling (see README in repo)
7
+ - [FEATURE] Allow `skip_paths` config to contain regular expressions
8
+
9
+ ## 2.0.4 - 2020-08-04
10
+
11
+ - [FIX] webpacker may exist with no config, allow for that
12
+
13
+ ## 2.0.3 - 2020-07-29
14
+
15
+ - [FIX] support for deprecation free Redis 4.2
16
+ - [FEATURE] skip /packs when serving static assets
17
+ - [FEATURE] allow Net::HTTP patch to be applied with either prerpend or alias
18
+
19
+ ## 2.0.2 - 2020-05-25
20
+
21
+ - [FIX] client timings were not showing up when you clicked show trivial
22
+
23
+ ## 2.0.1 - 2020-03-17
24
+
25
+ - [REVERT] Prepend Net::HTTP patch instead of class_eval and aliasing (#429) (technique clashes with New Relic and Skylight agents)
26
+
3
27
  ## 2.0.0 - 2020-03-11
4
28
 
5
29
  - [FEATURE] Prepend Net::HTTP patch instead of class_eval and aliasing (#429)
data/README.md CHANGED
@@ -29,7 +29,7 @@ If you feel like taking on any of this start an issue and update us on your prog
29
29
 
30
30
  ## Installation
31
31
 
32
- Install/add to Gemfile in Ruby 2.3+
32
+ Install/add to Gemfile in Ruby 2.4+
33
33
 
34
34
  ```ruby
35
35
  gem 'rack-mini-profiler'
@@ -65,6 +65,22 @@ If you don't want to manually require Mini Profiler:
65
65
  gem 'rack-mini-profiler', require: ['enable_rails_patches', 'rack-mini-profiler']
66
66
  ```
67
67
 
68
+ #### `Net::HTTP` stack level too deep errors
69
+
70
+ If you start seeing `SystemStackError: stack level too deep` errors from `Net::HTTP` after installing Mini Profiler, this means there is another patch for `Net::HTTP#request` that conflicts with Mini Profiler's patch in your application. To fix this, change `rack-mini-profiler` gem line in your `Gemfile` to the following:
71
+
72
+ ```ruby
73
+ gem 'rack-mini-profiler', require: ['prepend_net_http_patch', 'rack-mini-profiler']
74
+ ```
75
+
76
+ If you currently have `require: false`, remove the `'rack-mini-profiler'` string from the `require` array above so the gem line becomes like this:
77
+
78
+ ```ruby
79
+ gem 'rack-mini-profiler', require: ['prepend_net_http_patch']
80
+ ```
81
+
82
+ 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
+
68
84
  #### Rails and manual initialization
69
85
 
70
86
  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:
@@ -170,6 +186,18 @@ There are two additional `pp` options that can be used to analyze memory which d
170
186
  * Use `?pp=profile-gc` to report on Garbage Collection statistics
171
187
  * Use `?pp=analyze-memory` to report on ObjectSpace statistics
172
188
 
189
+ ### Snapshots Sampling
190
+
191
+ 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.
192
+
193
+ 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).
194
+
195
+ 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.
196
+
197
+ 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.
198
+
199
+ 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).
200
+
173
201
  ## Access control in non-development environments
174
202
 
175
203
  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.
@@ -313,6 +341,15 @@ _Note:_ The GUID (`data-version` and the `?v=` parameter on the `src`) will chan
313
341
  #### Using MiniProfiler's built in route for apps without HTML responses
314
342
  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.
315
343
 
344
+ #### Register MiniProfiler's assets in the Rails assets pipeline
345
+ 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:
346
+
347
+ ```ruby
348
+ Rack::MiniProfiler.config.assets_url = ->(name, version, env) {
349
+ ActionController::Base.helpers.asset_path(name)
350
+ }
351
+ ```
352
+
316
353
  ### Configuration Options
317
354
 
318
355
  You can set configuration options using the configuration accessor on `Rack::MiniProfiler`.
@@ -328,8 +365,8 @@ Option|Default|Description
328
365
  -------|---|--------
329
366
  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.
330
367
  position|`'top-left'`|Display mini_profiler on `'top-right'`, `'top-left'`, `'bottom-right'` or `'bottom-left'`.
331
- skip_paths|`[]`|Paths that skip profiling.
332
- skip_schema_queries|Rails dev: `'true'`<br>Othwerwise: `'false'`|`'true'` to log schema queries.
368
+ skip_paths|`[]`|An array of paths that skip profiling. Both `String` and `Regexp` are acceptable in the array.
369
+ skip_schema_queries|Rails dev: `true`<br>Othwerwise: `false`|`true` to skip schema queries.
333
370
  auto_inject|`true`|`true` to inject the miniprofiler script in the page.
334
371
  backtrace_ignores|`[]`|Regexes of lines to be removed from backtraces.
335
372
  backtrace_includes|Rails: `[/^\/?(app\|config\|lib\|test)/]`<br>Rack: `[]`|Regexes of lines to keep in backtraces.
@@ -344,33 +381,17 @@ max_traces_to_show|20|Maximum number of mini profiler timing blocks to show on o
344
381
  html_container|`body`|The HTML container (as a jQuery selector) to inject the mini_profiler UI into
345
382
  show_total_sql_count|`false`|Displays the total number of SQL executions.
346
383
  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.
384
+ assets_url|`nil`|See the "Register MiniProfiler's assets in the Rails assets pipeline" section above.
385
+ snapshot_every_n_requests|`-1`|Determines how frequently snapshots are taken. See the "Snapshots Sampling" above for more details.
386
+ snapshots_limit|`1000`|Determines how many snapshots Mini Profiler is allowed to keep.
347
387
 
348
- ### Custom middleware ordering (required if using `Rack::Deflate` with Rails)
349
-
350
- If you are using `Rack::Deflate` with rails and rack-mini-profiler in its default configuration,
351
- `Rack::MiniProfiler` will be injected (as always) at position 0 in the middleware stack. This
352
- will result in it attempting to inject html into the already-compressed response body. To fix this,
353
- the middleware ordering must be overriden.
354
-
355
- To do this, first add `, require: false` to the gemfile entry for rack-mini-profiler.
356
- This will prevent the railtie from running. Then, customize the initialization
357
- in the initializer like so:
358
-
359
- ```ruby
360
- require 'rack-mini-profiler'
361
-
362
- Rack::MiniProfilerRails.initialize!(Rails.application)
363
-
364
- Rails.application.middleware.delete(Rack::MiniProfiler)
365
- Rails.application.middleware.insert_after(Rack::Deflater, Rack::MiniProfiler)
366
- ```
367
-
368
- Deleting the middleware and then reinserting it is a bit inelegant, but
369
- a sufficient and costless solution. It is possible that rack-mini-profiler might
370
- support this scenario more directly if it is found that
371
- there is significant need for this confriguration or that
372
- the above recipe causes problems.
388
+ ### Using MiniProfiler with `Rack::Deflate` middleware
373
389
 
390
+ If you are using `Rack::Deflate` with Rails and `rack-mini-profiler` in its default configuration,
391
+ `Rack::MiniProfiler` will be injected (as always) at position 0 in the middleware stack,
392
+ which means it will run after `Rack::Deflate` on response processing. To prevent attempting to inject
393
+ HTML in already compressed response body MiniProfiler will suppress compression by setting
394
+ `identity` encoding in `Accept-Encoding` request header.
374
395
 
375
396
  ## Special query strings
376
397
 
@@ -407,6 +428,16 @@ if JSON.const_defined?(:Pure)
407
428
  end
408
429
  ```
409
430
 
431
+ ## Development
432
+
433
+ If you want to contribute to this project, that's great, thank you! You can run the following rake task:
434
+
435
+ ```
436
+ $ bundle exec rake client_dev
437
+ ```
438
+
439
+ which will start a local Sinatra server at `http://localhost:9292` where you'll be able to preview your changes. Refreshing the page should be enough to see any changes you make to files in the `lib/html` directory.
440
+
410
441
  ## Running the Specs
411
442
 
412
443
  ```
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Rack
4
- class MiniProfiler
5
- ENABLE_RAILS_PATCHES = true
6
- end
4
+ MINI_PROFILER_ENABLE_RAILS_PATCHES = true
7
5
  end
@@ -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 {
@@ -207,8 +231,7 @@
207
231
  top: 0px; }
208
232
  .profiler-results.profiler-top.profiler-left {
209
233
  left: 0px; }
210
- .profiler-results.profiler-top.profiler-left.profiler-no-controls .profiler-totals,
211
- .profiler-results.profiler-top.profiler-left.profiler-no-controls .profiler-result:last-child .profiler-button,
234
+ .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,
212
235
  .profiler-results.profiler-top.profiler-left .profiler-controls {
213
236
  -webkit-border-bottom-right-radius: 10px;
214
237
  -moz-border-radius-bottomright: 10px;
@@ -218,8 +241,7 @@
218
241
  border-right: 1px solid #888; }
219
242
  .profiler-results.profiler-top.profiler-right {
220
243
  right: 0px; }
221
- .profiler-results.profiler-top.profiler-right.profiler-no-controls .profiler-totals,
222
- .profiler-results.profiler-top.profiler-right.profiler-no-controls .profiler-result:last-child .profiler-button,
244
+ .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,
223
245
  .profiler-results.profiler-top.profiler-right .profiler-controls {
224
246
  -webkit-border-bottom-left-radius: 10px;
225
247
  -moz-border-radius-bottomleft: 10px;
@@ -231,8 +253,7 @@
231
253
  bottom: 0px; }
232
254
  .profiler-results.profiler-bottom.profiler-left {
233
255
  left: 0px; }
234
- .profiler-results.profiler-bottom.profiler-left.profiler-no-controls .profiler-totals,
235
- .profiler-results.profiler-bottom.profiler-left.profiler-no-controls .profiler-result:first-child .profiler-button,
256
+ .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,
236
257
  .profiler-results.profiler-bottom.profiler-left .profiler-controls {
237
258
  -webkit-border-top-right-radius: 10px;
238
259
  -moz-border-radius-topright: 10px;
@@ -242,8 +263,7 @@
242
263
  border-right: 1px solid #888; }
243
264
  .profiler-results.profiler-bottom.profiler-right {
244
265
  right: 0px; }
245
- .profiler-results.profiler-bottom.profiler-right.profiler-no-controls .profiler-totals,
246
- .profiler-results.profiler-bottom.profiler-right.profiler-no-controls .profiler-result:first-child .profiler-button,
266
+ .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,
247
267
  .profiler-results.profiler-bottom.profiler-right .profiler-controls {
248
268
  -webkit-border-bottom-top-radius: 10px;
249
269
  -moz-border-radius-topleft: 10px;
@@ -349,7 +369,6 @@
349
369
  @media print {
350
370
  .profiler-results {
351
371
  display: none; } }
352
-
353
372
  .profiler-queries-bg {
354
373
  z-index: 2147483642;
355
374
  display: none;
@@ -407,3 +426,15 @@
407
426
  background: #ffffbb; }
408
427
  100% {
409
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;
438
+ box-sizing: border-box; }
439
+ .mp-snapshots .snapshots-table th {
440
+ border-right: 1px solid #ffffff; }
@@ -108,18 +108,8 @@ var MiniProfiler = (function() {
108
108
  // ie is buggy strip out functions
109
109
  var copy = {
110
110
  navigation: {},
111
- timing: {}
111
+ timing: clientPerformance.timing.toJSON()
112
112
  };
113
- var timing = extend({}, clientPerformance.timing);
114
-
115
- for (p in timing) {
116
- if (
117
- timing.hasOwnProperty(p) &&
118
- !(typeof timing[p] === "function")
119
- ) {
120
- copy.timing[p] = timing[p];
121
- }
122
- }
123
113
 
124
114
  if (clientPerformance.navigation) {
125
115
  copy.navigation.redirectCount =
@@ -147,10 +137,13 @@ var MiniProfiler = (function() {
147
137
  (function() {
148
138
  var request = new XMLHttpRequest();
149
139
  var url = options.path + "results";
150
- var params = "id="
151
- .concat(id, "&clientPerformance=")
152
- .concat(clientPerformance, "&clientProbes=")
153
- .concat(clientProbes, "&popup=1");
140
+ var params = {
141
+ id: id,
142
+ clientPerformance: clientPerformance,
143
+ clientProbes: clientProbes,
144
+ popup: 1
145
+ };
146
+ var queryParam = toQueryString(params);
154
147
  request.open("POST", url, true);
155
148
 
156
149
  request.onload = function() {
@@ -172,24 +165,45 @@ var MiniProfiler = (function() {
172
165
  "Content-Type",
173
166
  "application/x-www-form-urlencoded"
174
167
  );
175
- request.send(params);
168
+ request.send(queryParam);
176
169
  })();
177
170
  }
178
171
  }
179
172
  };
180
173
 
181
- var extend = function extend(out) {
182
- out = out || {};
183
-
184
- for (var i = 1; i < _arguments.length; i++) {
185
- if (!_arguments[i]) continue;
186
-
187
- for (var key in _arguments[i]) {
188
- if (_arguments[i].hasOwnProperty(key)) out[key] = _arguments[i][key];
174
+ var toQueryString = function toQueryString(data, parentKey) {
175
+ var result = [];
176
+ for (var key in data) {
177
+ var val = data[key];
178
+ var newKey = !parentKey ? key : parentKey + "[" + key + "]";
179
+ if (
180
+ typeof val === "object" &&
181
+ !Array.isArray(val) &&
182
+ val !== null &&
183
+ val !== undefined
184
+ ) {
185
+ result[result.length] = toQueryString(val, newKey);
186
+ } else {
187
+ if (Array.isArray(val)) {
188
+ val.forEach(function(v) {
189
+ result[result.length] =
190
+ encodeURIComponent(newKey + "[]") + "=" + encodeURIComponent(v);
191
+ });
192
+ } else if (val === null || val === undefined) {
193
+ result[result.length] = encodeURIComponent(newKey) + "=";
194
+ } else {
195
+ result[result.length] =
196
+ encodeURIComponent(newKey) +
197
+ "=" +
198
+ encodeURIComponent(val.toString());
199
+ }
189
200
  }
190
201
  }
191
-
192
- return out;
202
+ return result
203
+ .filter(function(element) {
204
+ return element && element.length > 0;
205
+ })
206
+ .join("&");
193
207
  };
194
208
 
195
209
  var renderTemplate = function renderTemplate(json) {
@@ -659,6 +673,19 @@ var MiniProfiler = (function() {
659
673
  });
660
674
  };
661
675
 
676
+ var initSnapshots = function initSnapshots(dataElement) {
677
+ var data = JSON.parse(dataElement.textContent);
678
+ var temp = document.createElement("DIV");
679
+ if (data.page === "overview") {
680
+ temp.innerHTML = MiniProfiler.templates.snapshotsGroupsList(data);
681
+ } else if (data.group_name) {
682
+ temp.innerHTML = MiniProfiler.templates.snapshotsList(data);
683
+ }
684
+ Array.from(temp.children).forEach(function (child) {
685
+ document.body.appendChild(child);
686
+ });
687
+ };
688
+
662
689
  var initControls = function initControls(container) {
663
690
  if (options.showControls) {
664
691
  var _controls = document.createElement("div");
@@ -938,7 +965,7 @@ var MiniProfiler = (function() {
938
965
  var script = document.getElementById("mini-profiler");
939
966
  if (!script || !script.getAttribute) return;
940
967
 
941
- options = (function() {
968
+ this.options = options = (function() {
942
969
  var version = script.getAttribute("data-version");
943
970
  var path = script.getAttribute("data-path");
944
971
  var currentId = script.getAttribute("data-current-id");
@@ -966,6 +993,7 @@ var MiniProfiler = (function() {
966
993
  script.getAttribute("data-start-hidden") === "true" ||
967
994
  sessionStorage["rack-mini-profiler-start-hidden"] === "true";
968
995
  var htmlContainer = script.getAttribute("data-html-container");
996
+ var cssUrl = script.getAttribute("data-css-url");
969
997
  return {
970
998
  ids: ids,
971
999
  path: path,
@@ -982,11 +1010,18 @@ var MiniProfiler = (function() {
982
1010
  toggleShortcut: toggleShortcut,
983
1011
  startHidden: startHidden,
984
1012
  collapseResults: collapseResults,
985
- htmlContainer: htmlContainer
1013
+ htmlContainer: htmlContainer,
1014
+ cssUrl: cssUrl
986
1015
  };
987
1016
  })();
988
1017
 
989
1018
  var doInit = function doInit() {
1019
+ var snapshotsElement = document.getElementById("snapshots-data");
1020
+ if (snapshotsElement != null) {
1021
+ initSnapshots(snapshotsElement);
1022
+ return;
1023
+ }
1024
+
990
1025
  // when rendering a shared, full page, this div will exist
991
1026
  container = document.querySelectorAll(".profiler-result-full");
992
1027
 
@@ -1042,7 +1077,7 @@ var MiniProfiler = (function() {
1042
1077
 
1043
1078
  var init = function init() {
1044
1079
  if (options.authorized) {
1045
- var url = options.path + "includes.css?v=" + options.version;
1080
+ var url = options.cssUrl;
1046
1081
 
1047
1082
  if (document.createStyleSheet) {
1048
1083
  document.createStyleSheet(url);
@@ -1380,8 +1415,34 @@ var MiniProfiler = (function() {
1380
1415
  },
1381
1416
  showTotalSqlCount: function showTotalSqlCount() {
1382
1417
  return options.showTotalSqlCount;
1418
+ },
1419
+ timestampToRelative: function timestampToRelative(timestamp) {
1420
+ var now = Math.round((new Date()).getTime() / 1000);
1421
+ timestamp = Math.round(timestamp / 1000);
1422
+ var diff = now - timestamp;
1423
+ if (diff < 60) {
1424
+ return "< 1 minute";
1425
+ }
1426
+ var buildDisplayTime = function buildDisplayTime(num, unit) {
1427
+ var res = num + " " + unit;
1428
+ if (num !== 1) {
1429
+ res += "s";
1430
+ }
1431
+ return res;
1432
+ }
1433
+ diff = Math.round(diff / 60);
1434
+ if (diff <= 60) {
1435
+ return buildDisplayTime(diff, "minute");
1436
+ }
1437
+ diff = Math.round(diff / 60);
1438
+ if (diff <= 24) {
1439
+ return buildDisplayTime(diff, "hour");
1440
+ }
1441
+ diff = Math.round(diff / 24);
1442
+ return buildDisplayTime(diff, "day");
1383
1443
  }
1384
1444
  };
1385
1445
  })();
1386
1446
 
1447
+ window.MiniProfiler = MiniProfiler;
1387
1448
  MiniProfiler.init();