rack-mini-profiler 2.0.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +27 -0
- data/README.md +75 -30
- data/lib/html/includes.css +42 -9
- data/lib/html/includes.js +105 -29
- data/lib/html/includes.scss +35 -4
- data/lib/html/includes.tmpl +94 -4
- data/lib/html/profile_handler.js +1 -1
- data/lib/html/rack-mini-profiler.css +3 -0
- data/lib/html/rack-mini-profiler.js +2 -0
- data/lib/html/vendor.js +11 -3
- data/lib/mini_profiler/asset_version.rb +1 -1
- data/lib/mini_profiler/client_settings.rb +1 -0
- data/lib/mini_profiler/config.rb +22 -2
- data/lib/mini_profiler/profiler.rb +183 -23
- 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 +78 -0
- data/lib/mini_profiler/storage/memory_store.rb +54 -5
- data/lib/mini_profiler/storage/redis_store.rb +136 -2
- data/lib/mini_profiler/timer_struct/page.rb +52 -2
- data/lib/mini_profiler/timer_struct/sql.rb +2 -2
- data/lib/mini_profiler/version.rb +1 -1
- data/lib/mini_profiler_rails/railtie.rb +13 -0
- data/lib/mini_profiler_rails/railtie_methods.rb +6 -0
- data/lib/patches/net_patches.rb +18 -8
- data/lib/prepend_net_http_patch.rb +5 -0
- data/lib/rack-mini-profiler.rb +1 -0
- data/rack-mini-profiler.gemspec +5 -2
- metadata +66 -20
data/lib/html/includes.scss
CHANGED
@@ -14,9 +14,10 @@ $codeFonts: Consolas, monospace, serif;
|
|
14
14
|
$zindex: 2147483640; // near 32bit max 2147483647
|
15
15
|
|
16
16
|
// do some resets
|
17
|
+
.mp-snapshots,
|
17
18
|
.profiler-result,
|
18
19
|
.profiler-queries {
|
19
|
-
color:
|
20
|
+
color: $textColor;
|
20
21
|
line-height: 1;
|
21
22
|
font-size: 12px;
|
22
23
|
|
@@ -55,6 +56,11 @@ $zindex: 2147483640; // near 32bit max 2147483647
|
|
55
56
|
text-decoration: underline;
|
56
57
|
}
|
57
58
|
}
|
59
|
+
.custom-fields-title {
|
60
|
+
color: $textColor;
|
61
|
+
font: $normalFonts;
|
62
|
+
font-size: 14px;
|
63
|
+
}
|
58
64
|
}
|
59
65
|
|
60
66
|
// styles shared between popup view and full view
|
@@ -62,6 +68,9 @@ $zindex: 2147483640; // near 32bit max 2147483647
|
|
62
68
|
.profiler-toggle-duration-with-children {
|
63
69
|
float: right;
|
64
70
|
}
|
71
|
+
.profiler-snapshots-page-link {
|
72
|
+
float: left;
|
73
|
+
}
|
65
74
|
table.profiler-client-timings {
|
66
75
|
margin-top: 10px;
|
67
76
|
}
|
@@ -199,7 +208,7 @@ $zindex: 2147483640; // near 32bit max 2147483647
|
|
199
208
|
|
200
209
|
th {
|
201
210
|
background-color: #fff;
|
202
|
-
border-bottom: 1px solid
|
211
|
+
border-bottom: 1px solid $textColor;
|
203
212
|
font-weight: bold;
|
204
213
|
padding: 15px;
|
205
214
|
white-space: nowrap;
|
@@ -452,7 +461,7 @@ $zindex: 2147483640; // near 32bit max 2147483647
|
|
452
461
|
line-height: 18px;
|
453
462
|
overflow: auto;
|
454
463
|
|
455
|
-
@include box-shadow(0px, 1px, 15px,
|
464
|
+
@include box-shadow(0px, 1px, 15px, $textColor);
|
456
465
|
|
457
466
|
.profiler-info {
|
458
467
|
margin-bottom: 3px;
|
@@ -592,7 +601,7 @@ $zindex: 2147483640; // near 32bit max 2147483647
|
|
592
601
|
}
|
593
602
|
th {
|
594
603
|
font-size: 16px;
|
595
|
-
color:
|
604
|
+
color: $textColor;
|
596
605
|
line-height: 20px;
|
597
606
|
}
|
598
607
|
|
@@ -617,3 +626,25 @@ $zindex: 2147483640; // near 32bit max 2147483647
|
|
617
626
|
background: #fff;
|
618
627
|
}
|
619
628
|
}
|
629
|
+
|
630
|
+
.mp-snapshots {
|
631
|
+
font-family: $normalFonts;
|
632
|
+
font-size: 16px;
|
633
|
+
|
634
|
+
.snapshots-table {
|
635
|
+
thead {
|
636
|
+
background: #6a737c;
|
637
|
+
color: #ffffff;
|
638
|
+
}
|
639
|
+
th, td {
|
640
|
+
padding: 5px 10px;
|
641
|
+
box-sizing: border-box;
|
642
|
+
&:not(.request-group) {
|
643
|
+
text-align: center;
|
644
|
+
}
|
645
|
+
}
|
646
|
+
th {
|
647
|
+
border-right: 1px solid #ffffff;
|
648
|
+
}
|
649
|
+
}
|
650
|
+
}
|
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">
|
@@ -45,6 +45,10 @@
|
|
45
45
|
{{= MiniProfiler.templates.linksTemplate({timing: it.root, page: it}) }}
|
46
46
|
{{?}}
|
47
47
|
<a class="profiler-toggle-duration-with-children" title="toggles column with aggregate child durations">show time with children</a>
|
48
|
+
<a
|
49
|
+
class="profiler-snapshots-page-link"
|
50
|
+
title="Go to snapshots page"
|
51
|
+
href="{{= MiniProfiler.options.path }}snapshots">snapshots</a>
|
48
52
|
</td>
|
49
53
|
{{? it.has_sql_timings}}
|
50
54
|
<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">
|
@@ -92,6 +96,19 @@
|
|
92
96
|
</tfoot>
|
93
97
|
</table>
|
94
98
|
{{?}}
|
99
|
+
{{? it.custom_fields && Object.keys(it.custom_fields).length > 0 }}
|
100
|
+
<p class="custom-fields-title">Snapshot custom fields</p>
|
101
|
+
<table class="profiler-timings">
|
102
|
+
<tbody>
|
103
|
+
{{~ Object.keys(it.custom_fields) :key }}
|
104
|
+
<tr>
|
105
|
+
<td class="profiler-label">{{= key }}</td>
|
106
|
+
<td class="profiler-label">{{= it.custom_fields[key] }}</td>
|
107
|
+
</tr>
|
108
|
+
{{~}}
|
109
|
+
</tbody>
|
110
|
+
</table>
|
111
|
+
{{?}}
|
95
112
|
</div>
|
96
113
|
</div>
|
97
114
|
|
@@ -128,8 +145,8 @@
|
|
128
145
|
{{? it.custom_link}}
|
129
146
|
<a href="{{= it.custom_link }}" class="profiler-custom-link" target="_blank">{{= it.custom_link_name }}</a>
|
130
147
|
{{?}}
|
131
|
-
{{? it.has_trivial_timings}}
|
132
|
-
<a class="profiler-toggle-trivial" data-show-on-load="{{= it.has_all_trivial_timings }}" title="toggles any rows with < {{= it.trivial_duration_threshold_milliseconds }} ms">
|
148
|
+
{{? it.page.has_trivial_timings}}
|
149
|
+
<a class="profiler-toggle-trivial" data-show-on-load="{{= it.page.has_all_trivial_timings }}" title="toggles any rows with < {{= it.page.trivial_duration_threshold_milliseconds }} ms">
|
133
150
|
show trivial
|
134
151
|
</a>
|
135
152
|
{{?}}
|
@@ -200,7 +217,11 @@
|
|
200
217
|
<td>
|
201
218
|
<div class="query">
|
202
219
|
<pre class="profiler-stack-trace">{{= it.s.stack_trace_snippet }}</pre>
|
203
|
-
|
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
|
+
{{?}}
|
204
225
|
</div>
|
205
226
|
</td>
|
206
227
|
</tr>
|
@@ -216,3 +237,72 @@
|
|
216
237
|
</td>
|
217
238
|
</tr>
|
218
239
|
</script>
|
240
|
+
|
241
|
+
<script id="snapshotsGroupsList" type="text/x-dot-tmpl">
|
242
|
+
{{? it.list && it.list.length }}
|
243
|
+
<table class="snapshots-table">
|
244
|
+
<thead>
|
245
|
+
<tr>
|
246
|
+
<th>Requests Group</th>
|
247
|
+
<th>Worst Time (ms)</th>
|
248
|
+
<th>Best Time (ms)</th>
|
249
|
+
<th>No. of Snapshots</th>
|
250
|
+
</tr>
|
251
|
+
</thead>
|
252
|
+
<tbody>
|
253
|
+
{{~ it.list :row}}
|
254
|
+
<tr>
|
255
|
+
<td class="request-group"><a href="{{= row.url }}">{{= row.name }}</a></td>
|
256
|
+
<td>{{= MiniProfiler.formatDuration(row.worst_score) }}</td>
|
257
|
+
<td>{{= MiniProfiler.formatDuration(row.best_score) }}</td>
|
258
|
+
<td>{{= row.snapshots_count }}</td>
|
259
|
+
</tr>
|
260
|
+
{{~}}
|
261
|
+
</tbody>
|
262
|
+
</table>
|
263
|
+
{{??}}
|
264
|
+
<h2>No snapshots exist</h2>
|
265
|
+
{{?}}
|
266
|
+
</script>
|
267
|
+
|
268
|
+
<script id="snapshotsList" type="text/x-dot-tmpl">
|
269
|
+
{{ var data = it.data; }}
|
270
|
+
{{ var customFieldsNames = it.allCustomFieldsNames; }}
|
271
|
+
{{? data.list && data.list.length }}
|
272
|
+
<h2>Snapshots for {{= data.group_name }}</h2>
|
273
|
+
<table class="snapshots-table">
|
274
|
+
<thead>
|
275
|
+
<tr>
|
276
|
+
<th>ID</th>
|
277
|
+
<th>Duration (ms)</th>
|
278
|
+
<th>SQL Count</th>
|
279
|
+
{{~ customFieldsNames :name }}
|
280
|
+
<th>{{= name }}</th>
|
281
|
+
{{~}}
|
282
|
+
<th>Age</th>
|
283
|
+
</tr>
|
284
|
+
</thead>
|
285
|
+
<tbody>
|
286
|
+
{{~ data.list :row}}
|
287
|
+
<tr>
|
288
|
+
<td><a href="{{= row.url }}">
|
289
|
+
{{= row.id }}
|
290
|
+
</a></td>
|
291
|
+
<td>{{= MiniProfiler.formatDuration(row.duration) }}</td>
|
292
|
+
<td>{{= row.sql_count }}</td>
|
293
|
+
{{~ customFieldsNames :name }}
|
294
|
+
<td>{{= row.custom_fields[name] || "" }}</td>
|
295
|
+
{{~}}
|
296
|
+
<td>
|
297
|
+
{{? row.timestamp }}
|
298
|
+
{{= MiniProfiler.timestampToRelative(row.timestamp) }}
|
299
|
+
{{?}}
|
300
|
+
</td>
|
301
|
+
</tr>
|
302
|
+
{{~}}
|
303
|
+
</tbody>
|
304
|
+
</table>
|
305
|
+
{{??}}
|
306
|
+
<h2>No snapshots for {{= data.group_name }}</h2>
|
307
|
+
{{?}}
|
308
|
+
</script>
|
data/lib/html/profile_handler.js
CHANGED
@@ -1 +1 @@
|
|
1
|
-
<script async type="text/javascript" id="mini-profiler" src="{
|
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,11 +7,11 @@
|
|
7
7
|
MiniProfiler.templates = {};
|
8
8
|
MiniProfiler.templates["profilerTemplate"] = function anonymous(it
|
9
9
|
) {
|
10
|
-
var out=' <div class="profiler-result"> <div class="profiler-button ';if(it.has_duplicate_sql_timings){out+='profiler-warning';}out+='"> ';if(it.has_duplicate_sql_timings){out+='<span class="profiler-nuclear">!</span>';}out+=' <span class="profiler-number"> '+( MiniProfiler.formatDuration(it.duration_milliseconds))+' <span class="profiler-unit">ms</span> </span> ';if(MiniProfiler.showTotalSqlCount()){out+=' <span class="profiler-number"> '+( it.sql_count)+' <span class="profiler-unit">sql</span> </span> ';}out+=' </div> <div class="profiler-popup"> <div class="profiler-info"> <span class="profiler-name"> '+( it.name)+' <span class="profiler-overall-duration">('+( MiniProfiler.formatDuration(it.duration_milliseconds))+' ms)</span> </span> <span class="profiler-server-time">'+( it.machine_name)+' on '+( MiniProfiler.renderDate(it.
|
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
|
) {
|
14
|
-
var out=' <a href="'+( MiniProfiler.shareUrl(it.page.id) )+'" class="profiler-share-profiler-results" target="_blank">share</a> <a href="'+( MiniProfiler.moreUrl(it.timing.name) )+'" class="profiler-more-actions">more</a> ';if(it.custom_link){out+=' <a href="'+( it.custom_link )+'" class="profiler-custom-link" target="_blank">'+( it.custom_link_name )+'</a> ';}out+=' ';if(it.has_trivial_timings){out+=' <a class="profiler-toggle-trivial" data-show-on-load="'+( it.has_all_trivial_timings )+'" title="toggles any rows with < '+( it.trivial_duration_threshold_milliseconds )+' ms"> show trivial </a> ';}return out;
|
14
|
+
var out=' <a href="'+( MiniProfiler.shareUrl(it.page.id) )+'" class="profiler-share-profiler-results" target="_blank">share</a> <a href="'+( MiniProfiler.moreUrl(it.timing.name) )+'" class="profiler-more-actions">more</a> ';if(it.custom_link){out+=' <a href="'+( it.custom_link )+'" class="profiler-custom-link" target="_blank">'+( it.custom_link_name )+'</a> ';}out+=' ';if(it.page.has_trivial_timings){out+=' <a class="profiler-toggle-trivial" data-show-on-load="'+( it.page.has_all_trivial_timings )+'" title="toggles any rows with < '+( it.page.trivial_duration_threshold_milliseconds )+' ms"> show trivial </a> ';}return out;
|
15
15
|
}
|
16
16
|
MiniProfiler.templates["timingTemplate"] = function anonymous(it
|
17
17
|
) {
|
@@ -19,12 +19,20 @@ 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
|
) {
|
26
26
|
var out=' <tr class="profiler-gap-info';if(it.g.duration < 4){out+=' profiler-trivial-gaps';}out+='"> <td class="profiler-info"> '+( it.g.duration )+' <span class="profiler-unit">ms</span> </td> <td class="query"> <div>'+( it.g.topReason.name )+' — '+( it.g.topReason.duration.toFixed(2) )+' <span class="profiler-unit">ms</span></div> </td> </tr>';return out;
|
27
27
|
}
|
28
|
+
MiniProfiler.templates["snapshotsGroupsList"] = function anonymous(it
|
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> <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
|
+
}
|
32
|
+
MiniProfiler.templates["snapshotsList"] = function anonymous(it
|
33
|
+
) {
|
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
|
+
}
|
28
36
|
|
29
37
|
if (typeof prettyPrint === "undefined") {
|
30
38
|
// prettify.js
|
@@ -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
@@ -37,6 +37,8 @@ module Rack
|
|
37
37
|
@max_sql_param_length = 0 # disable sql parameter collection by default
|
38
38
|
@skip_sql_param_names = /password/ # skips parameters with the name password by default
|
39
39
|
@enable_advanced_debugging_tools = false
|
40
|
+
@snapshot_every_n_requests = -1
|
41
|
+
@snapshots_limit = 1000
|
40
42
|
|
41
43
|
# ui parameters
|
42
44
|
@autorized = true
|
@@ -50,6 +52,10 @@ module Rack
|
|
50
52
|
@toggle_shortcut = 'alt+p'
|
51
53
|
@html_container = 'body'
|
52
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
|
53
59
|
|
54
60
|
self
|
55
61
|
}
|
@@ -60,17 +66,31 @@ module Rack
|
|
60
66
|
:base_url_path, :disable_caching, :enabled,
|
61
67
|
:flamegraph_sample_rate, :logger, :pre_authorize_cb, :skip_paths,
|
62
68
|
:skip_schema_queries, :storage, :storage_failure, :storage_instance,
|
63
|
-
:storage_options, :user_provider, :enable_advanced_debugging_tools
|
64
|
-
|
69
|
+
:storage_options, :user_provider, :enable_advanced_debugging_tools,
|
70
|
+
:skip_sql_param_names, :suppress_encoding, :max_sql_param_length
|
65
71
|
|
66
72
|
# ui accessors
|
67
73
|
attr_accessor :collapse_results, :max_traces_to_show, :position,
|
68
74
|
:show_children, :show_controls, :show_trivial, :show_total_sql_count,
|
69
75
|
:start_hidden, :toggle_shortcut, :html_container
|
70
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
|
+
|
71
82
|
# Deprecated options
|
72
83
|
attr_accessor :use_existing_jquery
|
73
84
|
|
85
|
+
attr_reader :assets_url
|
86
|
+
|
87
|
+
def assets_url=(lmbda)
|
88
|
+
if defined?(Rack::MiniProfilerRails)
|
89
|
+
Rack::MiniProfilerRails.create_engine
|
90
|
+
end
|
91
|
+
@assets_url = lmbda
|
92
|
+
end
|
93
|
+
|
74
94
|
def vertical_position
|
75
95
|
position.include?('bottom') ? 'bottom' : 'top'
|
76
96
|
end
|
@@ -38,9 +38,21 @@ module Rack
|
|
38
38
|
|
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
|
+
Thread.current[:mini_profiler_snapshot_custom_fields] = nil
|
42
|
+
Thread.current[:mp_ongoing_snapshot] = nil
|
41
43
|
Thread.current[:mini_profiler_private] = c
|
42
44
|
end
|
43
45
|
|
46
|
+
def add_snapshot_custom_field(key, value)
|
47
|
+
thread_var_key = :mini_profiler_snapshot_custom_fields
|
48
|
+
Thread.current[thread_var_key] ||= {}
|
49
|
+
Thread.current[thread_var_key][key] = value
|
50
|
+
end
|
51
|
+
|
52
|
+
def get_snapshot_custom_fields
|
53
|
+
Thread.current[:mini_profiler_snapshot_custom_fields]
|
54
|
+
end
|
55
|
+
|
44
56
|
# discard existing results, don't track this request
|
45
57
|
def discard_results
|
46
58
|
self.current.discard = true if current
|
@@ -83,6 +95,16 @@ module Rack
|
|
83
95
|
params
|
84
96
|
end
|
85
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
|
86
108
|
end
|
87
109
|
|
88
110
|
#
|
@@ -106,14 +128,23 @@ module Rack
|
|
106
128
|
def serve_results(env)
|
107
129
|
request = Rack::Request.new(env)
|
108
130
|
id = request.params['id']
|
109
|
-
|
110
|
-
|
131
|
+
is_snapshot = request.params['snapshot']
|
132
|
+
is_snapshot = [true, "true"].include?(is_snapshot)
|
133
|
+
if is_snapshot
|
134
|
+
page_struct = @storage.load_snapshot(id)
|
135
|
+
else
|
136
|
+
page_struct = @storage.load(id)
|
137
|
+
end
|
138
|
+
if !page_struct && is_snapshot
|
139
|
+
id = ERB::Util.html_escape(id)
|
140
|
+
return [404, {}, ["Snapshot with id '#{id}' not found"]]
|
141
|
+
elsif !page_struct
|
111
142
|
@storage.set_viewed(user(env), id)
|
112
|
-
id = ERB::Util.html_escape(
|
143
|
+
id = ERB::Util.html_escape(id)
|
113
144
|
user_info = ERB::Util.html_escape(user(env))
|
114
145
|
return [404, {}, ["Request not found: #{id} - user #{user_info}"]]
|
115
146
|
end
|
116
|
-
|
147
|
+
if !page_struct[:has_user_viewed] && !is_snapshot
|
117
148
|
page_struct[:client_timings] = TimerStruct::Client.init_from_form_data(env, page_struct)
|
118
149
|
page_struct[:has_user_viewed] = true
|
119
150
|
@storage.save(page_struct)
|
@@ -148,11 +179,12 @@ module Rack
|
|
148
179
|
file_name = path.sub(@config.base_url_path, '')
|
149
180
|
|
150
181
|
return serve_results(env) if file_name.eql?('results')
|
182
|
+
return handle_snapshots_request(env) if file_name.eql?('snapshots')
|
151
183
|
|
152
184
|
resources_env = env.dup
|
153
185
|
resources_env['PATH_INFO'] = file_name
|
154
186
|
|
155
|
-
rack_file = Rack::File.new(MiniProfiler.resources_root, 'Cache-Control' =>
|
187
|
+
rack_file = Rack::File.new(MiniProfiler.resources_root, 'Cache-Control' => "max-age=#{cache_control_value}")
|
156
188
|
rack_file.call(resources_env)
|
157
189
|
end
|
158
190
|
|
@@ -177,7 +209,6 @@ module Rack
|
|
177
209
|
end
|
178
210
|
|
179
211
|
def call(env)
|
180
|
-
|
181
212
|
start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
182
213
|
client_settings = ClientSettings.new(env, @storage, start)
|
183
214
|
MiniProfiler.deauthorize_request if @config.authorization_mode == :whitelist
|
@@ -189,15 +220,31 @@ module Rack
|
|
189
220
|
# Someone (e.g. Rails engine) could change the SCRIPT_NAME so we save it
|
190
221
|
env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME'] = ENV['PASSENGER_BASE_URI'] || env['SCRIPT_NAME']
|
191
222
|
|
192
|
-
skip_it = (
|
193
|
-
|
194
|
-
|
223
|
+
skip_it = /pp=skip/.match?(query_string) || (
|
224
|
+
@config.skip_paths &&
|
225
|
+
@config.skip_paths.any? do |p|
|
226
|
+
if p.instance_of?(String)
|
227
|
+
path.start_with?(p)
|
228
|
+
elsif p.instance_of?(Regexp)
|
229
|
+
p.match?(path)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
)
|
233
|
+
if skip_it
|
234
|
+
return client_settings.handle_cookie(@app.call(env))
|
235
|
+
end
|
236
|
+
|
237
|
+
skip_it = (@config.pre_authorize_cb && !@config.pre_authorize_cb.call(env))
|
195
238
|
|
196
239
|
if skip_it || (
|
197
240
|
@config.authorization_mode == :whitelist &&
|
198
241
|
!client_settings.has_valid_cookie?
|
199
242
|
)
|
200
|
-
|
243
|
+
if take_snapshot?(path)
|
244
|
+
return client_settings.handle_cookie(take_snapshot(env, start))
|
245
|
+
else
|
246
|
+
return client_settings.handle_cookie(@app.call(env))
|
247
|
+
end
|
201
248
|
end
|
202
249
|
|
203
250
|
# handle all /mini-profiler requests here
|
@@ -308,6 +355,15 @@ module Rack
|
|
308
355
|
status, headers, body = @app.call(env)
|
309
356
|
end
|
310
357
|
end
|
358
|
+
elsif path == '/rack-mini-profiler/requests'
|
359
|
+
blank_page_html = <<~HTML
|
360
|
+
<html>
|
361
|
+
<head></head>
|
362
|
+
<body></body>
|
363
|
+
</html>
|
364
|
+
HTML
|
365
|
+
|
366
|
+
status, headers, body = [200, { 'Content-Type' => 'text/html' }, [blank_page_html.dup]]
|
311
367
|
else
|
312
368
|
status, headers, body = @app.call(env)
|
313
369
|
end
|
@@ -364,17 +420,6 @@ module Rack
|
|
364
420
|
return client_settings.handle_cookie(self.flamegraph(flamegraph))
|
365
421
|
end
|
366
422
|
|
367
|
-
if path == '/rack-mini-profiler/requests'
|
368
|
-
blank_page_html = <<~HTML
|
369
|
-
<html>
|
370
|
-
<head></head>
|
371
|
-
<body></body>
|
372
|
-
</html>
|
373
|
-
HTML
|
374
|
-
|
375
|
-
status, headers, body = [200, { 'Content-Type' => 'text/html' }, [blank_page_html.dup]]
|
376
|
-
end
|
377
|
-
|
378
423
|
begin
|
379
424
|
@storage.save(page_struct)
|
380
425
|
# no matter what it is, it should be unviewed, otherwise we will miss POST
|
@@ -630,10 +675,20 @@ Append the following to your query string:
|
|
630
675
|
# * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
|
631
676
|
def get_profile_script(env)
|
632
677
|
path = "#{env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME']}#{@config.base_url_path}"
|
678
|
+
version = MiniProfiler::ASSET_VERSION
|
679
|
+
if @config.assets_url
|
680
|
+
url = @config.assets_url.call('rack-mini-profiler.js', version, env)
|
681
|
+
css_url = @config.assets_url.call('rack-mini-profiler.css', version, env)
|
682
|
+
end
|
683
|
+
|
684
|
+
url = "#{path}includes.js?v=#{version}" if !url
|
685
|
+
css_url = "#{path}includes.css?v=#{version}" if !css_url
|
633
686
|
|
634
687
|
settings = {
|
635
688
|
path: path,
|
636
|
-
|
689
|
+
url: url,
|
690
|
+
cssUrl: css_url,
|
691
|
+
version: version,
|
637
692
|
verticalPosition: @config.vertical_position,
|
638
693
|
horizontalPosition: @config.horizontal_position,
|
639
694
|
showTrivial: @config.show_trivial,
|
@@ -645,7 +700,8 @@ Append the following to your query string:
|
|
645
700
|
toggleShortcut: @config.toggle_shortcut,
|
646
701
|
startHidden: @config.start_hidden,
|
647
702
|
collapseResults: @config.collapse_results,
|
648
|
-
htmlContainer: @config.html_container
|
703
|
+
htmlContainer: @config.html_container,
|
704
|
+
hiddenCustomFields: @config.snapshot_hidden_custom_fields.join(',')
|
649
705
|
}
|
650
706
|
|
651
707
|
if current && current.page_struct
|
@@ -673,5 +729,109 @@ Append the following to your query string:
|
|
673
729
|
current.inject_js = false
|
674
730
|
end
|
675
731
|
|
732
|
+
def cache_control_value
|
733
|
+
86400
|
734
|
+
end
|
735
|
+
|
736
|
+
private
|
737
|
+
|
738
|
+
def handle_snapshots_request(env)
|
739
|
+
self.current = nil
|
740
|
+
MiniProfiler.authorize_request
|
741
|
+
status = 200
|
742
|
+
headers = { 'Content-Type' => 'text/html' }
|
743
|
+
qp = Rack::Utils.parse_nested_query(env['QUERY_STRING'])
|
744
|
+
if group_name = qp["group_name"]
|
745
|
+
list = @storage.find_snapshots_group(group_name)
|
746
|
+
list.each do |snapshot|
|
747
|
+
snapshot[:url] = url_for_snapshot(snapshot[:id])
|
748
|
+
end
|
749
|
+
data = {
|
750
|
+
group_name: group_name,
|
751
|
+
list: list
|
752
|
+
}
|
753
|
+
else
|
754
|
+
list = @storage.snapshot_groups_overview
|
755
|
+
list.each do |group|
|
756
|
+
group[:url] = url_for_snapshots_group(group[:name])
|
757
|
+
end
|
758
|
+
data = {
|
759
|
+
page: "overview",
|
760
|
+
list: list
|
761
|
+
}
|
762
|
+
end
|
763
|
+
data_html = <<~HTML
|
764
|
+
<div style="display: none;" id="snapshots-data">
|
765
|
+
#{data.to_json}
|
766
|
+
</div>
|
767
|
+
HTML
|
768
|
+
response = Rack::Response.new([], status, headers)
|
769
|
+
|
770
|
+
response.write <<~HTML
|
771
|
+
<html>
|
772
|
+
<head></head>
|
773
|
+
<body class="mp-snapshots">
|
774
|
+
HTML
|
775
|
+
response.write(data_html)
|
776
|
+
script = self.get_profile_script(env)
|
777
|
+
response.write(script)
|
778
|
+
response.write <<~HTML
|
779
|
+
</body>
|
780
|
+
</html>
|
781
|
+
HTML
|
782
|
+
response.finish
|
783
|
+
end
|
784
|
+
|
785
|
+
def rails_route_from_path(path, method)
|
786
|
+
if defined?(Rails) && defined?(ActionController::RoutingError)
|
787
|
+
hash = Rails.application.routes.recognize_path(path, method: method)
|
788
|
+
if hash && hash[:controller] && hash[:action]
|
789
|
+
"#{method} #{hash[:controller]}##{hash[:action]}"
|
790
|
+
end
|
791
|
+
end
|
792
|
+
rescue ActionController::RoutingError
|
793
|
+
nil
|
794
|
+
end
|
795
|
+
|
796
|
+
def url_for_snapshots_group(group_name)
|
797
|
+
qs = Rack::Utils.build_query({ group_name: group_name })
|
798
|
+
"/#{@config.base_url_path.gsub('/', '')}/snapshots?#{qs}"
|
799
|
+
end
|
800
|
+
|
801
|
+
def url_for_snapshot(id)
|
802
|
+
qs = Rack::Utils.build_query({ id: id, snapshot: true })
|
803
|
+
"/#{@config.base_url_path.gsub('/', '')}/results?#{qs}"
|
804
|
+
end
|
805
|
+
|
806
|
+
def take_snapshot?(path)
|
807
|
+
@config.snapshot_every_n_requests > 0 &&
|
808
|
+
!path.start_with?(@config.base_url_path) &&
|
809
|
+
@storage.should_take_snapshot?(@config.snapshot_every_n_requests)
|
810
|
+
end
|
811
|
+
|
812
|
+
def take_snapshot(env, start)
|
813
|
+
MiniProfiler.create_current(env, @config)
|
814
|
+
Thread.current[:mp_ongoing_snapshot] = true
|
815
|
+
results = @app.call(env)
|
816
|
+
status = results[0].to_i
|
817
|
+
if status >= 200 && status < 300
|
818
|
+
page_struct = current.page_struct
|
819
|
+
page_struct[:root].record_time(
|
820
|
+
(Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000
|
821
|
+
)
|
822
|
+
custom_fields = MiniProfiler.get_snapshot_custom_fields
|
823
|
+
page_struct[:custom_fields] = custom_fields if custom_fields
|
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
|
832
|
+
end
|
833
|
+
self.current = nil
|
834
|
+
results
|
835
|
+
end
|
676
836
|
end
|
677
837
|
end
|