rack-mini-profiler 2.0.1 → 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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