rack-mini-profiler 2.1.0 → 2.2.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 +7 -0
- data/README.md +14 -0
- data/lib/html/includes.css +3 -1
- data/lib/html/includes.js +17 -2
- data/lib/html/includes.scss +4 -1
- data/lib/html/includes.tmpl +25 -7
- data/lib/html/profile_handler.js +1 -1
- data/lib/html/vendor.js +4 -4
- data/lib/mini_profiler/asset_version.rb +1 -1
- data/lib/mini_profiler/client_settings.rb +1 -0
- data/lib/mini_profiler/config.rb +10 -2
- data/lib/mini_profiler/profiler.rb +22 -5
- data/lib/mini_profiler/profiling_methods.rb +8 -1
- data/lib/mini_profiler/snapshots_transporter.rb +84 -0
- data/lib/mini_profiler/storage/abstract_store.rb +13 -7
- data/lib/mini_profiler/timer_struct/page.rb +48 -1
- data/lib/mini_profiler/timer_struct/sql.rb +2 -2
- data/lib/mini_profiler/version.rb +1 -1
- data/lib/rack-mini-profiler.rb +1 -0
- data/rack-mini-profiler.gemspec +1 -0
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 42690a86bcd4e4cfd8bf17289dc7b2f583bdd4ec286b4bbcdd50454f0f73551b
|
4
|
+
data.tar.gz: 723291f0ae196b09f585a331fc4e33b54bfd4be7fba527b623d851dc77524cce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ca4bd7e3f35d42171bf2e39775a4a8fb565b907ba1eab40002886f7933bb4e9d30bfb8d9a87a7cb408e68b3f7e1a5dbbd360fa633f8e5c94793218abb40b32d5
|
7
|
+
data.tar.gz: 877164b6caf03efc6fa4183bed8f6dfce09c691e98c9ac3588fae8307d6b1632be8e666140c4a581280b6f0bfb69c2f5d4cb100ecca0f4afe1d3a9cd770b8e7f
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
# CHANGELOG
|
2
2
|
|
3
|
+
## 2.2.0 - 2020-10-19
|
4
|
+
|
5
|
+
- [UX] Enhancements to snapshots UI
|
6
|
+
- [FEATURE] Mini Profiler cookie is now sameSite=lax
|
7
|
+
- [FEATURE] Snapshots transporter
|
8
|
+
- [FEATURE] Redact SQL queries in snapshots by default
|
9
|
+
|
3
10
|
## 2.1.0 - 2020-09-17
|
4
11
|
|
5
12
|
- [FEATURE] Allow assets to be precompiled with Sprockets
|
data/README.md
CHANGED
@@ -198,6 +198,16 @@ Access to the snapshots page is restricted to only those who can see the speed b
|
|
198
198
|
|
199
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
200
|
|
201
|
+
#### Snapshots Transporter
|
202
|
+
|
203
|
+
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.
|
204
|
+
|
205
|
+
The transporter uses a buffer to temporarily hold snapshots in memory with a limit of 100 snapshots. Every 10 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.
|
206
|
+
|
207
|
+
The body of the requests made by the transporter 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).
|
208
|
+
|
209
|
+
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.
|
210
|
+
|
201
211
|
## Access control in non-development environments
|
202
212
|
|
203
213
|
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.
|
@@ -384,6 +394,10 @@ enable_advanced_debugging_tools|`false`|Enables sensitive debugging tools that c
|
|
384
394
|
assets_url|`nil`|See the "Register MiniProfiler's assets in the Rails assets pipeline" section above.
|
385
395
|
snapshot_every_n_requests|`-1`|Determines how frequently snapshots are taken. See the "Snapshots Sampling" above for more details.
|
386
396
|
snapshots_limit|`1000`|Determines how many snapshots Mini Profiler is allowed to keep.
|
397
|
+
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.
|
398
|
+
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.
|
399
|
+
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.
|
400
|
+
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.
|
387
401
|
|
388
402
|
### Using MiniProfiler with `Rack::Deflate` middleware
|
389
403
|
|
data/lib/html/includes.css
CHANGED
@@ -434,7 +434,9 @@
|
|
434
434
|
background: #6a737c;
|
435
435
|
color: #ffffff; }
|
436
436
|
.mp-snapshots .snapshots-table th, .mp-snapshots .snapshots-table td {
|
437
|
-
padding: 5px;
|
437
|
+
padding: 5px 10px;
|
438
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; }
|
439
441
|
.mp-snapshots .snapshots-table th {
|
440
442
|
border-right: 1px solid #ffffff; }
|
data/lib/html/includes.js
CHANGED
@@ -679,7 +679,20 @@ var MiniProfiler = (function() {
|
|
679
679
|
if (data.page === "overview") {
|
680
680
|
temp.innerHTML = MiniProfiler.templates.snapshotsGroupsList(data);
|
681
681
|
} else if (data.group_name) {
|
682
|
-
|
682
|
+
var allCustomFieldsNames = [];
|
683
|
+
data.list.forEach(function (snapshot) {
|
684
|
+
Object.keys(snapshot.custom_fields).forEach(function (k) {
|
685
|
+
if (allCustomFieldsNames.indexOf(k) === -1 &&
|
686
|
+
options.hiddenCustomFields.indexOf(k.toLowerCase()) === -1) {
|
687
|
+
allCustomFieldsNames.push(k);
|
688
|
+
}
|
689
|
+
});
|
690
|
+
});
|
691
|
+
allCustomFieldsNames.sort();
|
692
|
+
temp.innerHTML = MiniProfiler.templates.snapshotsList({
|
693
|
+
data: data,
|
694
|
+
allCustomFieldsNames: allCustomFieldsNames
|
695
|
+
});
|
683
696
|
}
|
684
697
|
Array.from(temp.children).forEach(function (child) {
|
685
698
|
document.body.appendChild(child);
|
@@ -994,6 +1007,7 @@ var MiniProfiler = (function() {
|
|
994
1007
|
sessionStorage["rack-mini-profiler-start-hidden"] === "true";
|
995
1008
|
var htmlContainer = script.getAttribute("data-html-container");
|
996
1009
|
var cssUrl = script.getAttribute("data-css-url");
|
1010
|
+
var hiddenCustomFields = script.getAttribute("data-hidden-custom-fields").toLowerCase().split(",");
|
997
1011
|
return {
|
998
1012
|
ids: ids,
|
999
1013
|
path: path,
|
@@ -1011,7 +1025,8 @@ var MiniProfiler = (function() {
|
|
1011
1025
|
startHidden: startHidden,
|
1012
1026
|
collapseResults: collapseResults,
|
1013
1027
|
htmlContainer: htmlContainer,
|
1014
|
-
cssUrl: cssUrl
|
1028
|
+
cssUrl: cssUrl,
|
1029
|
+
hiddenCustomFields: hiddenCustomFields
|
1015
1030
|
};
|
1016
1031
|
})();
|
1017
1032
|
|
data/lib/html/includes.scss
CHANGED
@@ -637,8 +637,11 @@ $zindex: 2147483640; // near 32bit max 2147483647
|
|
637
637
|
color: #ffffff;
|
638
638
|
}
|
639
639
|
th, td {
|
640
|
-
padding: 5px;
|
640
|
+
padding: 5px 10px;
|
641
641
|
box-sizing: border-box;
|
642
|
+
&:not(.request-group) {
|
643
|
+
text-align: center;
|
644
|
+
}
|
642
645
|
}
|
643
646
|
th {
|
644
647
|
border-right: 1px solid #ffffff;
|
data/lib/html/includes.tmpl
CHANGED
@@ -17,7 +17,7 @@
|
|
17
17
|
<span class="profiler-name">
|
18
18
|
{{= it.name}} <span class="profiler-overall-duration">({{= MiniProfiler.formatDuration(it.duration_milliseconds)}} ms)</span>
|
19
19
|
</span>
|
20
|
-
<span class="profiler-server-time">{{= it.machine_name}} on {{= MiniProfiler.renderDate(it.
|
20
|
+
<span class="profiler-server-time">{{= it.machine_name}} on {{= MiniProfiler.renderDate(it.started_formatted)}}</span>
|
21
21
|
</div>
|
22
22
|
<div class="profiler-output">
|
23
23
|
<table class="profiler-timings">
|
@@ -217,7 +217,11 @@
|
|
217
217
|
<td>
|
218
218
|
<div class="query">
|
219
219
|
<pre class="profiler-stack-trace">{{= it.s.stack_trace_snippet }}</pre>
|
220
|
-
|
220
|
+
{{? it.s.formatted_command_string}}
|
221
|
+
<pre class="prettyprint lang-sql"><code>{{= it.s.formatted_command_string }}; {{= MiniProfiler.formatParameters(it.s.parameters) }}</code></pre>
|
222
|
+
{{??}}
|
223
|
+
<i>Query redacted</i>
|
224
|
+
{{?}}
|
221
225
|
</div>
|
222
226
|
</td>
|
223
227
|
</tr>
|
@@ -241,13 +245,17 @@
|
|
241
245
|
<tr>
|
242
246
|
<th>Requests Group</th>
|
243
247
|
<th>Worst Time (ms)</th>
|
248
|
+
<th>Best Time (ms)</th>
|
249
|
+
<th>No. of Snapshots</th>
|
244
250
|
</tr>
|
245
251
|
</thead>
|
246
252
|
<tbody>
|
247
253
|
{{~ it.list :row}}
|
248
254
|
<tr>
|
249
|
-
<td><a href="{{= row.url }}">{{= row.name }}</a></td>
|
255
|
+
<td class="request-group"><a href="{{= row.url }}">{{= row.name }}</a></td>
|
250
256
|
<td>{{= MiniProfiler.formatDuration(row.worst_score) }}</td>
|
257
|
+
<td>{{= MiniProfiler.formatDuration(row.best_score) }}</td>
|
258
|
+
<td>{{= row.snapshots_count }}</td>
|
251
259
|
</tr>
|
252
260
|
{{~}}
|
253
261
|
</tbody>
|
@@ -258,23 +266,33 @@
|
|
258
266
|
</script>
|
259
267
|
|
260
268
|
<script id="snapshotsList" type="text/x-dot-tmpl">
|
261
|
-
{{
|
262
|
-
|
269
|
+
{{ var data = it.data; }}
|
270
|
+
{{ var customFieldsNames = it.allCustomFieldsNames; }}
|
271
|
+
{{? data.list && data.list.length }}
|
272
|
+
<h2>Snapshots for {{= data.group_name }}</h2>
|
263
273
|
<table class="snapshots-table">
|
264
274
|
<thead>
|
265
275
|
<tr>
|
266
276
|
<th>ID</th>
|
267
277
|
<th>Duration (ms)</th>
|
278
|
+
<th>SQL Count</th>
|
279
|
+
{{~ customFieldsNames :name }}
|
280
|
+
<th>{{= name }}</th>
|
281
|
+
{{~}}
|
268
282
|
<th>Age</th>
|
269
283
|
</tr>
|
270
284
|
</thead>
|
271
285
|
<tbody>
|
272
|
-
{{~
|
286
|
+
{{~ data.list :row}}
|
273
287
|
<tr>
|
274
288
|
<td><a href="{{= row.url }}">
|
275
289
|
{{= row.id }}
|
276
290
|
</a></td>
|
277
291
|
<td>{{= MiniProfiler.formatDuration(row.duration) }}</td>
|
292
|
+
<td>{{= row.sql_count }}</td>
|
293
|
+
{{~ customFieldsNames :name }}
|
294
|
+
<td>{{= row.custom_fields[name] || "" }}</td>
|
295
|
+
{{~}}
|
278
296
|
<td>
|
279
297
|
{{? row.timestamp }}
|
280
298
|
{{= MiniProfiler.timestampToRelative(row.timestamp) }}
|
@@ -285,6 +303,6 @@
|
|
285
303
|
</tbody>
|
286
304
|
</table>
|
287
305
|
{{??}}
|
288
|
-
<h2>No snapshots for {{=
|
306
|
+
<h2>No snapshots for {{= data.group_name }}</h2>
|
289
307
|
{{?}}
|
290
308
|
</script>
|
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}"></script>
|
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>
|
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.
|
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></th> <th>duration (ms)</th> <th class="profiler-duration-with-children">with children (ms)</th> <th class="time-from-start">from start (ms)</th> ';if(it.has_sql_timings){out+=' <th colspan="2">query time (ms)</th> ';}out+=' ';var arr1=it.custom_timing_names;if(arr1){var value,i1=-1,l1=arr1.length-1;while(i1<l1){value=arr1[i1+=1];out+=' <th colspan="2">'+( value.toLowerCase() )+' (ms)</th> ';} } out+=' </tr> </thead> <tbody> '+( MiniProfiler.templates.timingTemplate({timing: it.root, page: it}) )+' </tbody> <tfoot> <tr> <td colspan="3"> ';if(!it.client_timings){out+=' '+( MiniProfiler.templates.linksTemplate({timing: it.root, page: it}) )+' ';}out+=' <a class="profiler-toggle-duration-with-children" title="toggles column with aggregate child durations">show time with children</a> <a class="profiler-snapshots-page-link" title="Go to snapshots page" href="'+( MiniProfiler.options.path )+'snapshots">snapshots</a> </td> ';if(it.has_sql_timings){out+=' <td colspan="2" class="profiler-number profiler-percent-in-sql" title="'+( MiniProfiler.getSqlTimingsCount(it.root) )+' queries spent '+( MiniProfiler.formatDuration(it.duration_milliseconds_in_sql) )+' ms of total request time"> '+( MiniProfiler.formatDuration(it.duration_milliseconds_in_sql / it.duration_milliseconds * 100) )+' <span class="profiler-unit">% in sql</span> </td> ';}out+=' ';var arr2=it.custom_timing_names;if(arr2){var value,i2=-1,l2=arr2.length-1;while(i2<l2){value=arr2[i2+=1];out+=' <td colspan="2" class="profiler-number profiler-percentage-in-sql" title="'+( it.custom_timing_stats[value].count )+' '+( value.toLowerCase() )+' invocations spent '+( MiniProfiler.formatDuration(it.custom_timing_stats[value].duration) )+' ms of total request time"> '+( MiniProfiler.formatDuration(it.custom_timing_stats[value].duration / it.duration_milliseconds * 100) )+' <span class="profiler-unit">% in '+( value.toLowerCase() )+'</span> </td> ';} } out+=' </tr> </tfoot> </table> ';if(it.client_timings){out+=' <table class="profiler-timings profiler-client-timings"> <thead> <tr> <th>client event</th> <th>duration (ms)</th> <th>from start (ms)</th> </tr> </thead> <tbody> ';var arr3=MiniProfiler.getClientTimings(it.client_timings);if(arr3){var value,i3=-1,l3=arr3.length-1;while(i3<l3){value=arr3[i3+=1];out+=' <tr class="';if(value.isTrivial){out+='profiler-trivial';}out+='"> <td class="profiler-label">'+( value.name )+'</td> <td class="profiler-duration"> ';if(value.duration >= 0){out+=' <span class="profiler-unit"></span>'+( MiniProfiler.formatDuration(value.duration) )+' ';}out+=' </td> <td class="profiler-duration time-from-start"> <span class="profiler-unit">+</span>'+( MiniProfiler.formatDuration(value.start) )+' </td> </tr> ';} } out+=' </tbody> <tfoot> <td colspan="3"> '+( MiniProfiler.templates.linksTemplate({timing: it.root, page: it}) )+' </td> </tfoot> </table> ';}out+=' ';if(it.custom_fields && Object.keys(it.custom_fields).length > 0){out+=' <p class="custom-fields-title">Snapshot custom fields</p> <table class="profiler-timings"> <tbody> ';var arr4=Object.keys(it.custom_fields);if(arr4){var key,i4=-1,l4=arr4.length-1;while(i4<l4){key=arr4[i4+=1];out+=' <tr> <td class="profiler-label">'+( key )+'</td> <td class="profiler-label">'+( it.custom_fields[key] )+'</td> </tr> ';} } out+=' </tbody> </table> ';}out+=' </div> </div> ';if(it.has_sql_timings){out+=' <div class="profiler-queries"> <table> <thead> <tr> <th style="text-align:right">step<br />time from start<br />query type<br />duration</th> <th style="text-align:left">call stack<br />query</th> </tr> </thead> <tbody> ';var arr5=MiniProfiler.getSqlTimings(it.root);if(arr5){var value,index=-1,l5=arr5.length-1;while(index<l5){value=arr5[index+=1];out+=' '+( MiniProfiler.templates.sqlGapTemplate({g: value.prevGap}) )+' '+( MiniProfiler.templates.sqlTimingTemplate({i: index, s: value}) )+' ';if(value.nextGap){out+=' '+( MiniProfiler.templates.sqlGapTemplate({g: value.nextGap}) )+' ';}out+=' ';} } out+=' </tbody> </table> <p class="profiler-trivial-gap-container"> <a class="profiler-toggle-trivial-gaps">show trivial gaps</a> </p> </div> ';}out+=' </div>';return out;
|
11
11
|
}
|
12
12
|
MiniProfiler.templates["linksTemplate"] = function anonymous(it
|
13
13
|
) {
|
@@ -19,7 +19,7 @@ var out=' <tr class="';if(it.timing.is_trivial){out+='profiler-trivial';}out+='"
|
|
19
19
|
}
|
20
20
|
MiniProfiler.templates["sqlTimingTemplate"] = function anonymous(it
|
21
21
|
) {
|
22
|
-
var out=' <tr class="'+( it.s.row_class || '' )+'" data-timing-id="'+( it.s.parent_timing_id )+'"> <td class="profiler-info"> <div>'+( it.s.parent_timing_name )+'</div> <div class="profiler-number"><span class="profiler-unit">T+</span>'+( MiniProfiler.formatDuration(it.s.start_milliseconds) )+' <span class="profiler-unit">ms</span></div> <div> ';if(it.s.is_duplicate){out+='<span class="profiler-warning">DUPLICATE</span>';}out+=' '+( MiniProfiler.renderExecuteType(it.s.execute_type) )+' </div> <div title="';if(it.s.execute_type == 3){out+='first result fetched: '+( it.s.first_fetch_duration_milliseconds )+'ms';}out+='">'+( MiniProfiler.formatDuration(it.s.duration_milliseconds) )+' <span class="profiler-unit">ms</span></div> </td> <td> <div class="query"> <pre class="profiler-stack-trace">'+( it.s.stack_trace_snippet )+'</pre> <pre class="prettyprint lang-sql"><code>'+( it.s.formatted_command_string )+'; '+( MiniProfiler.formatParameters(it.s.parameters) )+'</code></pre> </div> </td> </tr>';return out;
|
22
|
+
var out=' <tr class="'+( it.s.row_class || '' )+'" data-timing-id="'+( it.s.parent_timing_id )+'"> <td class="profiler-info"> <div>'+( it.s.parent_timing_name )+'</div> <div class="profiler-number"><span class="profiler-unit">T+</span>'+( MiniProfiler.formatDuration(it.s.start_milliseconds) )+' <span class="profiler-unit">ms</span></div> <div> ';if(it.s.is_duplicate){out+='<span class="profiler-warning">DUPLICATE</span>';}out+=' '+( MiniProfiler.renderExecuteType(it.s.execute_type) )+' </div> <div title="';if(it.s.execute_type == 3){out+='first result fetched: '+( it.s.first_fetch_duration_milliseconds )+'ms';}out+='">'+( MiniProfiler.formatDuration(it.s.duration_milliseconds) )+' <span class="profiler-unit">ms</span></div> </td> <td> <div class="query"> <pre class="profiler-stack-trace">'+( it.s.stack_trace_snippet )+'</pre> ';if(it.s.formatted_command_string){out+=' <pre class="prettyprint lang-sql"><code>'+( it.s.formatted_command_string )+'; '+( MiniProfiler.formatParameters(it.s.parameters) )+'</code></pre> ';}else{out+=' <i>Query redacted</i> ';}out+=' </div> </td> </tr>';return out;
|
23
23
|
}
|
24
24
|
MiniProfiler.templates["sqlGapTemplate"] = function anonymous(it
|
25
25
|
) {
|
@@ -27,11 +27,11 @@ var out=' <tr class="profiler-gap-info';if(it.g.duration < 4){out+=' profiler-tr
|
|
27
27
|
}
|
28
28
|
MiniProfiler.templates["snapshotsGroupsList"] = function anonymous(it
|
29
29
|
) {
|
30
|
-
var out=' ';if(it.list && it.list.length){out+=' <table class="snapshots-table"> <thead> <tr> <th>Requests Group</th> <th>Worst Time (ms)</th> </tr> </thead> <tbody> ';var arr1=it.list;if(arr1){var row,i1=-1,l1=arr1.length-1;while(i1<l1){row=arr1[i1+=1];out+=' <tr> <td><a href="'+( row.url )+'">'+( row.name )+'</a></td> <td>'+( MiniProfiler.formatDuration(row.worst_score) )+'</td> </tr> ';} } out+=' </tbody> </table> ';}else{out+=' <h2>No snapshots exist</h2> ';}return out;
|
30
|
+
var out=' ';if(it.list && it.list.length){out+=' <table class="snapshots-table"> <thead> <tr> <th>Requests Group</th> <th>Worst Time (ms)</th> <th>Best Time (ms)</th> <th>No. of Snapshots</th> </tr> </thead> <tbody> ';var arr1=it.list;if(arr1){var row,i1=-1,l1=arr1.length-1;while(i1<l1){row=arr1[i1+=1];out+=' <tr> <td class="request-group"><a href="'+( row.url )+'">'+( row.name )+'</a></td> <td>'+( MiniProfiler.formatDuration(row.worst_score) )+'</td> <td>'+( MiniProfiler.formatDuration(row.best_score) )+'</td> <td>'+( row.snapshots_count )+'</td> </tr> ';} } out+=' </tbody> </table> ';}else{out+=' <h2>No snapshots exist</h2> ';}return out;
|
31
31
|
}
|
32
32
|
MiniProfiler.templates["snapshotsList"] = function anonymous(it
|
33
33
|
) {
|
34
|
-
var out=' ';if(
|
34
|
+
var out=' '; var data = it.data; out+=' '; var customFieldsNames = it.allCustomFieldsNames; out+=' ';if(data.list && data.list.length){out+=' <h2>Snapshots for '+( data.group_name )+'</h2> <table class="snapshots-table"> <thead> <tr> <th>ID</th> <th>Duration (ms)</th> <th>SQL Count</th> ';var arr1=customFieldsNames;if(arr1){var name,i1=-1,l1=arr1.length-1;while(i1<l1){name=arr1[i1+=1];out+=' <th>'+( name )+'</th> ';} } out+=' <th>Age</th> </tr> </thead> <tbody> ';var arr2=data.list;if(arr2){var row,i2=-1,l2=arr2.length-1;while(i2<l2){row=arr2[i2+=1];out+=' <tr> <td><a href="'+( row.url )+'"> '+( row.id )+' </a></td> <td>'+( MiniProfiler.formatDuration(row.duration) )+'</td> <td>'+( row.sql_count )+'</td> ';var arr3=customFieldsNames;if(arr3){var name,i3=-1,l3=arr3.length-1;while(i3<l3){name=arr3[i3+=1];out+=' <td>'+( row.custom_fields[name] || "" )+'</td> ';} } out+=' <td> ';if(row.timestamp){out+=' '+( MiniProfiler.timestampToRelative(row.timestamp) )+' ';}out+=' </td> </tr> ';} } out+=' </tbody> </table> ';}else{out+=' <h2>No snapshots for '+( data.group_name )+'</h2> ';}return out;
|
35
35
|
}
|
36
36
|
|
37
37
|
if (typeof prettyPrint === "undefined") {
|
@@ -76,6 +76,7 @@ module Rack
|
|
76
76
|
settings_string = settings.map { |k, v| "#{k}=#{v}" }.join(",")
|
77
77
|
cookie = { value: settings_string, path: '/', httponly: true }
|
78
78
|
cookie[:secure] = true if @request.ssl?
|
79
|
+
cookie[:same_site] = 'Lax'
|
79
80
|
Rack::Utils.set_cookie_header!(headers, COOKIE_NAME, cookie)
|
80
81
|
end
|
81
82
|
end
|
data/lib/mini_profiler/config.rb
CHANGED
@@ -52,6 +52,10 @@ module Rack
|
|
52
52
|
@toggle_shortcut = 'alt+p'
|
53
53
|
@html_container = 'body'
|
54
54
|
@position = "top-left"
|
55
|
+
@snapshot_hidden_custom_fields = []
|
56
|
+
@snapshots_transport_destination_url = nil
|
57
|
+
@snapshots_transport_auth_key = nil
|
58
|
+
@snapshots_redact_sql_queries = true
|
55
59
|
|
56
60
|
self
|
57
61
|
}
|
@@ -63,14 +67,18 @@ module Rack
|
|
63
67
|
:flamegraph_sample_rate, :logger, :pre_authorize_cb, :skip_paths,
|
64
68
|
:skip_schema_queries, :storage, :storage_failure, :storage_instance,
|
65
69
|
:storage_options, :user_provider, :enable_advanced_debugging_tools,
|
66
|
-
:
|
67
|
-
attr_accessor :skip_sql_param_names, :suppress_encoding, :max_sql_param_length
|
70
|
+
:skip_sql_param_names, :suppress_encoding, :max_sql_param_length
|
68
71
|
|
69
72
|
# ui accessors
|
70
73
|
attr_accessor :collapse_results, :max_traces_to_show, :position,
|
71
74
|
:show_children, :show_controls, :show_trivial, :show_total_sql_count,
|
72
75
|
:start_hidden, :toggle_shortcut, :html_container
|
73
76
|
|
77
|
+
# snapshot related config
|
78
|
+
attr_accessor :snapshot_every_n_requests, :snapshots_limit,
|
79
|
+
:snapshot_hidden_custom_fields, :snapshots_transport_destination_url,
|
80
|
+
:snapshots_transport_auth_key, :snapshots_redact_sql_queries
|
81
|
+
|
74
82
|
# Deprecated options
|
75
83
|
attr_accessor :use_existing_jquery
|
76
84
|
|
@@ -39,6 +39,7 @@ module Rack
|
|
39
39
|
def current=(c)
|
40
40
|
# we use TLS cause we need access to this from sql blocks and code blocks that have no access to env
|
41
41
|
Thread.current[:mini_profiler_snapshot_custom_fields] = nil
|
42
|
+
Thread.current[:mp_ongoing_snapshot] = nil
|
42
43
|
Thread.current[:mini_profiler_private] = c
|
43
44
|
end
|
44
45
|
|
@@ -94,6 +95,16 @@ module Rack
|
|
94
95
|
params
|
95
96
|
end
|
96
97
|
end
|
98
|
+
|
99
|
+
def snapshots_transporter?
|
100
|
+
!!config.snapshots_transport_destination_url &&
|
101
|
+
!!config.snapshots_transport_auth_key
|
102
|
+
end
|
103
|
+
|
104
|
+
def redact_sql_queries?
|
105
|
+
Thread.current[:mp_ongoing_snapshot] == true &&
|
106
|
+
Rack::MiniProfiler.config.snapshots_redact_sql_queries
|
107
|
+
end
|
97
108
|
end
|
98
109
|
|
99
110
|
#
|
@@ -689,7 +700,8 @@ Append the following to your query string:
|
|
689
700
|
toggleShortcut: @config.toggle_shortcut,
|
690
701
|
startHidden: @config.start_hidden,
|
691
702
|
collapseResults: @config.collapse_results,
|
692
|
-
htmlContainer: @config.html_container
|
703
|
+
htmlContainer: @config.html_container,
|
704
|
+
hiddenCustomFields: @config.snapshot_hidden_custom_fields.join(',')
|
693
705
|
}
|
694
706
|
|
695
707
|
if current && current.page_struct
|
@@ -799,6 +811,7 @@ Append the following to your query string:
|
|
799
811
|
|
800
812
|
def take_snapshot(env, start)
|
801
813
|
MiniProfiler.create_current(env, @config)
|
814
|
+
Thread.current[:mp_ongoing_snapshot] = true
|
802
815
|
results = @app.call(env)
|
803
816
|
status = results[0].to_i
|
804
817
|
if status >= 200 && status < 300
|
@@ -808,10 +821,14 @@ Append the following to your query string:
|
|
808
821
|
)
|
809
822
|
custom_fields = MiniProfiler.get_snapshot_custom_fields
|
810
823
|
page_struct[:custom_fields] = custom_fields if custom_fields
|
811
|
-
|
812
|
-
page_struct
|
813
|
-
|
814
|
-
|
824
|
+
if Rack::MiniProfiler.snapshots_transporter?
|
825
|
+
Rack::MiniProfiler::SnapshotsTransporter.transport(page_struct)
|
826
|
+
else
|
827
|
+
@storage.push_snapshot(
|
828
|
+
page_struct,
|
829
|
+
@config
|
830
|
+
)
|
831
|
+
end
|
815
832
|
end
|
816
833
|
self.current = nil
|
817
834
|
results
|
@@ -7,7 +7,14 @@ module Rack
|
|
7
7
|
def record_sql(query, elapsed_ms, params = nil)
|
8
8
|
return unless current && current.current_timer
|
9
9
|
c = current
|
10
|
-
c.current_timer.add_sql(
|
10
|
+
c.current_timer.add_sql(
|
11
|
+
redact_sql_queries? ? nil : query,
|
12
|
+
elapsed_ms,
|
13
|
+
c.page_struct,
|
14
|
+
redact_sql_queries? ? nil : params,
|
15
|
+
c.skip_backtrace,
|
16
|
+
c.full_backtrace
|
17
|
+
)
|
11
18
|
end
|
12
19
|
|
13
20
|
def start_step(name)
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class ::Rack::MiniProfiler::SnapshotsTransporter
|
4
|
+
@@transported_snapshots_count = 0
|
5
|
+
@@successful_http_requests_count = 0
|
6
|
+
@@failed_http_requests_count = 0
|
7
|
+
|
8
|
+
class << self
|
9
|
+
def transported_snapshots_count
|
10
|
+
@@transported_snapshots_count
|
11
|
+
end
|
12
|
+
def successful_http_requests_count
|
13
|
+
@@successful_http_requests_count
|
14
|
+
end
|
15
|
+
def failed_http_requests_count
|
16
|
+
@@failed_http_requests_count
|
17
|
+
end
|
18
|
+
|
19
|
+
def transport(snapshot)
|
20
|
+
@transporter ||= self.new(Rack::MiniProfiler.config)
|
21
|
+
@transporter.ship(snapshot)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :buffer
|
26
|
+
attr_accessor :max_buffer_size
|
27
|
+
|
28
|
+
def initialize(config)
|
29
|
+
@uri = URI(config.snapshots_transport_destination_url)
|
30
|
+
@auth_key = config.snapshots_transport_auth_key
|
31
|
+
@thread = nil
|
32
|
+
@thread_mutex = Mutex.new
|
33
|
+
@buffer = []
|
34
|
+
@buffer_mutex = Mutex.new
|
35
|
+
@max_buffer_size = 100
|
36
|
+
@testing = false
|
37
|
+
end
|
38
|
+
|
39
|
+
def ship(snapshot)
|
40
|
+
@buffer_mutex.synchronize do
|
41
|
+
@buffer << snapshot
|
42
|
+
@buffer.shift if @buffer.size > @max_buffer_size
|
43
|
+
end
|
44
|
+
@thread_mutex.synchronize { start_thread }
|
45
|
+
end
|
46
|
+
|
47
|
+
def flush_buffer
|
48
|
+
buffer_content = @buffer_mutex.synchronize do
|
49
|
+
@buffer.dup if @buffer.size > 0
|
50
|
+
end
|
51
|
+
if buffer_content
|
52
|
+
request = Net::HTTP::Post.new(
|
53
|
+
@uri,
|
54
|
+
'Content-Type' => 'application/json',
|
55
|
+
'Mini-Profiler-Transport-Auth' => @auth_key
|
56
|
+
)
|
57
|
+
request.body = { snapshots: buffer_content }.to_json
|
58
|
+
http = Net::HTTP.new(@uri.hostname, @uri.port)
|
59
|
+
http.use_ssl = @uri.scheme == 'https'
|
60
|
+
res = http.request(request)
|
61
|
+
if res.code.to_i == 200
|
62
|
+
@@successful_http_requests_count += 1
|
63
|
+
@@transported_snapshots_count += buffer_content.size
|
64
|
+
@buffer_mutex.synchronize do
|
65
|
+
@buffer -= buffer_content
|
66
|
+
end
|
67
|
+
else
|
68
|
+
@@failed_http_requests_count += 1
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
|
75
|
+
def start_thread
|
76
|
+
return if @thread&.alive? || @testing
|
77
|
+
@thread = Thread.new do
|
78
|
+
while true
|
79
|
+
sleep 10
|
80
|
+
flush_buffer
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -58,17 +58,21 @@ module Rack
|
|
58
58
|
fetch_snapshots do |batch|
|
59
59
|
batch.each do |snapshot|
|
60
60
|
group_name = default_snapshot_grouping(snapshot)
|
61
|
-
|
62
|
-
|
61
|
+
hash = groups[group_name] ||= {}
|
62
|
+
hash[:snapshots_count] ||= 0
|
63
|
+
hash[:snapshots_count] += 1
|
64
|
+
if !hash[:worst_score] || hash[:worst_score] < snapshot.duration_ms
|
65
|
+
groups[group_name][:worst_score] = snapshot.duration_ms
|
66
|
+
end
|
67
|
+
if !hash[:best_score] || hash[:best_score] > snapshot.duration_ms
|
68
|
+
groups[group_name][:best_score] = snapshot.duration_ms
|
63
69
|
end
|
64
70
|
end
|
65
71
|
end
|
66
72
|
groups = groups.to_a
|
67
|
-
groups.sort_by! { |name,
|
73
|
+
groups.sort_by! { |name, hash| hash[:worst_score] }
|
68
74
|
groups.reverse!
|
69
|
-
groups.map!
|
70
|
-
{ name: name, worst_score: score }
|
71
|
-
end
|
75
|
+
groups.map! { |name, hash| hash.merge(name: name) }
|
72
76
|
groups
|
73
77
|
end
|
74
78
|
|
@@ -81,7 +85,9 @@ module Rack
|
|
81
85
|
data << {
|
82
86
|
id: snapshot[:id],
|
83
87
|
duration: snapshot.duration_ms,
|
84
|
-
|
88
|
+
sql_count: snapshot[:sql_count],
|
89
|
+
timestamp: snapshot[:started_at],
|
90
|
+
custom_fields: snapshot[:custom_fields]
|
85
91
|
}
|
86
92
|
end
|
87
93
|
end
|
@@ -10,6 +10,53 @@ module Rack
|
|
10
10
|
# :has_many TimerStruct::Sql children
|
11
11
|
# :has_many TimerStruct::Custom children
|
12
12
|
class Page < TimerStruct::Base
|
13
|
+
class << self
|
14
|
+
def from_hash(hash)
|
15
|
+
hash = symbolize_hash(hash)
|
16
|
+
if hash.key?(:custom_timing_names)
|
17
|
+
hash[:custom_timing_names] = []
|
18
|
+
end
|
19
|
+
hash.delete(:started_formatted)
|
20
|
+
if hash.key?(:duration_milliseconds)
|
21
|
+
hash[:duration_milliseconds] = 0
|
22
|
+
end
|
23
|
+
page = self.allocate
|
24
|
+
page.instance_variable_set(:@attributes, hash)
|
25
|
+
page
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def symbolize_hash(hash)
|
31
|
+
new_hash = {}
|
32
|
+
hash.each do |k, v|
|
33
|
+
sym_k = String === k ? k.to_sym : k
|
34
|
+
if Hash === v
|
35
|
+
new_hash[sym_k] = symbolize_hash(v)
|
36
|
+
elsif Array === v
|
37
|
+
new_hash[sym_k] = symbolize_array(v)
|
38
|
+
else
|
39
|
+
new_hash[sym_k] = v
|
40
|
+
end
|
41
|
+
end
|
42
|
+
new_hash
|
43
|
+
end
|
44
|
+
|
45
|
+
def symbolize_array(array)
|
46
|
+
array.map do |item|
|
47
|
+
if Array === item
|
48
|
+
symbolize_array(item)
|
49
|
+
elsif Hash === item
|
50
|
+
symbolize_hash(item)
|
51
|
+
else
|
52
|
+
item
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
attr_reader :attributes
|
59
|
+
|
13
60
|
def initialize(env)
|
14
61
|
timer_id = MiniProfiler.generate_id
|
15
62
|
page_name = env['PATH_INFO']
|
@@ -74,7 +121,7 @@ module Rack
|
|
74
121
|
|
75
122
|
def extra_json
|
76
123
|
{
|
77
|
-
|
124
|
+
started_formatted: '/Date(%d)/' % @attributes[:started_at],
|
78
125
|
duration_milliseconds: @attributes[:root][:duration_milliseconds],
|
79
126
|
custom_timing_names: @attributes[:custom_timing_stats].keys.sort
|
80
127
|
}
|
@@ -38,12 +38,12 @@ module Rack
|
|
38
38
|
start_millis = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1000).to_i - page[:started]) - duration_ms
|
39
39
|
super(
|
40
40
|
execute_type: 3, # TODO
|
41
|
-
formatted_command_string: ERB::Util.html_escape(query),
|
41
|
+
formatted_command_string: query ? ERB::Util.html_escape(query) : nil,
|
42
42
|
stack_trace_snippet: stack_trace,
|
43
43
|
start_milliseconds: start_millis,
|
44
44
|
duration_milliseconds: duration_ms,
|
45
45
|
first_fetch_duration_milliseconds: duration_ms,
|
46
|
-
parameters: trim_binds(params),
|
46
|
+
parameters: query ? trim_binds(params) : nil,
|
47
47
|
parent_timing_id: nil,
|
48
48
|
is_duplicate: false
|
49
49
|
)
|
data/lib/rack-mini-profiler.rb
CHANGED
@@ -26,6 +26,7 @@ require 'mini_profiler/profiling_methods'
|
|
26
26
|
require 'mini_profiler/context'
|
27
27
|
require 'mini_profiler/client_settings'
|
28
28
|
require 'mini_profiler/gc_profiler'
|
29
|
+
require 'mini_profiler/snapshots_transporter'
|
29
30
|
require 'mini_profiler/profiler'
|
30
31
|
require 'patches/sql_patches'
|
31
32
|
require 'patches/net_patches'
|
data/rack-mini-profiler.gemspec
CHANGED
@@ -42,6 +42,7 @@ Gem::Specification.new do |s|
|
|
42
42
|
s.add_development_dependency 'listen'
|
43
43
|
s.add_development_dependency 'webpacker', '~> 5.1'
|
44
44
|
s.add_development_dependency 'rails', '~> 5.1'
|
45
|
+
s.add_development_dependency 'webmock', '3.9.1'
|
45
46
|
|
46
47
|
s.require_paths = ["lib"]
|
47
48
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rack-mini-profiler
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.2.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: 2020-
|
13
|
+
date: 2020-10-20 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rack
|
@@ -222,6 +222,20 @@ dependencies:
|
|
222
222
|
- - "~>"
|
223
223
|
- !ruby/object:Gem::Version
|
224
224
|
version: '5.1'
|
225
|
+
- !ruby/object:Gem::Dependency
|
226
|
+
name: webmock
|
227
|
+
requirement: !ruby/object:Gem::Requirement
|
228
|
+
requirements:
|
229
|
+
- - '='
|
230
|
+
- !ruby/object:Gem::Version
|
231
|
+
version: 3.9.1
|
232
|
+
type: :development
|
233
|
+
prerelease: false
|
234
|
+
version_requirements: !ruby/object:Gem::Requirement
|
235
|
+
requirements:
|
236
|
+
- - '='
|
237
|
+
- !ruby/object:Gem::Version
|
238
|
+
version: 3.9.1
|
225
239
|
description: Profiling toolkit for Rack applications with Rails integration. Client
|
226
240
|
Side profiling, DB profiling and Server profiling.
|
227
241
|
email: sam.saffron@gmail.com
|
@@ -255,6 +269,7 @@ files:
|
|
255
269
|
- lib/mini_profiler/gc_profiler.rb
|
256
270
|
- lib/mini_profiler/profiler.rb
|
257
271
|
- lib/mini_profiler/profiling_methods.rb
|
272
|
+
- lib/mini_profiler/snapshots_transporter.rb
|
258
273
|
- lib/mini_profiler/storage/abstract_store.rb
|
259
274
|
- lib/mini_profiler/storage/file_store.rb
|
260
275
|
- lib/mini_profiler/storage/memcache_store.rb
|