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.
@@ -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: #555;
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 #555;
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, #555);
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: #555;
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
+ }
@@ -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.started)}}</span>
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 &lt; {{= 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 &lt; {{= 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
- <pre class="prettyprint lang-sql"><code>{{= it.s.formatted_command_string }}; {{= MiniProfiler.formatParameters(it.s.parameters) }}</code></pre>
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>
@@ -1 +1 @@
1
- <script async type="text/javascript" id="mini-profiler" src="{path}includes.js?v={version}" data-version="{version}" data-path="{path}" data-current-id="{currentId}" data-ids="{ids}" data-horizontal-position="{horizontalPosition}" data-vertical-position="{verticalPosition}" data-trivial="{showTrivial}" data-children="{showChildren}" data-max-traces="{maxTracesToShow}" data-controls="{showControls}" data-total-sql-count="{showTotalSqlCount}" data-authorized="{authorized}" data-toggle-shortcut="{toggleShortcut}" data-start-hidden="{startHidden}" data-collapse-results="{collapseResults}" data-html-container="{htmlContainer}"></script>
1
+ <script async type="text/javascript" id="mini-profiler" src="{url}" data-css-url="{cssUrl}" data-version="{version}" data-path="{path}" data-current-id="{currentId}" data-ids="{ids}" data-horizontal-position="{horizontalPosition}" data-vertical-position="{verticalPosition}" data-trivial="{showTrivial}" data-children="{showChildren}" data-max-traces="{maxTracesToShow}" data-controls="{showControls}" data-total-sql-count="{showTotalSqlCount}" data-authorized="{authorized}" data-toggle-shortcut="{toggleShortcut}" data-start-hidden="{startHidden}" data-collapse-results="{collapseResults}" data-html-container="{htmlContainer}" data-hidden-custom-fields="{hiddenCustomFields}"></script>
@@ -0,0 +1,3 @@
1
+ /*
2
+ *= require ./includes
3
+ */
@@ -0,0 +1,2 @@
1
+ //= require ./includes
2
+ //= require ./vendor
@@ -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.started))+'</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> </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+=' </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 arr4=MiniProfiler.getSqlTimings(it.root);if(arr4){var value,index=-1,l4=arr4.length-1;while(index<l4){value=arr4[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;
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 &lt; '+( 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 &lt; '+( 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 )+' &mdash; '+( 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
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Rack
3
3
  class MiniProfiler
4
- ASSET_VERSION = '67dd1c2571ced7fc74ae7f1813e47bdf'
4
+ ASSET_VERSION = 'f8924c1fd3fbe1787f0bed5c8998b636'
5
5
  end
6
6
  end
@@ -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
@@ -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
- attr_accessor :skip_sql_param_names, :suppress_encoding, :max_sql_param_length
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
- page_struct = @storage.load(id)
110
- unless page_struct
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(request.params['id'])
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
- unless page_struct[:has_user_viewed]
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:86400')
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 = (@config.pre_authorize_cb && !@config.pre_authorize_cb.call(env)) ||
193
- (@config.skip_paths && @config.skip_paths.any? { |p| path.start_with?(p) }) ||
194
- query_string =~ /pp=skip/
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
- return client_settings.handle_cookie(@app.call(env))
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
- version: MiniProfiler::ASSET_VERSION,
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