rack-mini-profiler 2.0.2 → 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 +23 -0
- data/README.md +65 -30
- data/lib/html/includes.css +42 -9
- data/lib/html/includes.js +65 -3
- data/lib/html/includes.scss +35 -4
- data/lib/html/includes.tmpl +92 -2
- 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 +10 -2
- 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 +180 -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 +4 -2
- metadata +52 -20
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
|
|
@@ -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,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,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' => "max-age
|
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
|
@@ -676,5 +732,106 @@ Append the following to your query string:
|
|
676
732
|
def cache_control_value
|
677
733
|
86400
|
678
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
|
679
836
|
end
|
680
837
|
end
|