rack-mini-profiler 2.0.1 → 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 +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
|