rack-mini-profiler 2.0.0 → 2.1.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,22 @@ $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;
641
+ box-sizing: border-box;
642
+ }
643
+ th {
644
+ border-right: 1px solid #ffffff;
645
+ }
646
+ }
647
+ }
@@ -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
  {{?}}
@@ -216,3 +233,58 @@
216
233
  </td>
217
234
  </tr>
218
235
  </script>
236
+
237
+ <script id="snapshotsGroupsList" type="text/x-dot-tmpl">
238
+ {{? it.list && it.list.length }}
239
+ <table class="snapshots-table">
240
+ <thead>
241
+ <tr>
242
+ <th>Requests Group</th>
243
+ <th>Worst Time (ms)</th>
244
+ </tr>
245
+ </thead>
246
+ <tbody>
247
+ {{~ it.list :row}}
248
+ <tr>
249
+ <td><a href="{{= row.url }}">{{= row.name }}</a></td>
250
+ <td>{{= MiniProfiler.formatDuration(row.worst_score) }}</td>
251
+ </tr>
252
+ {{~}}
253
+ </tbody>
254
+ </table>
255
+ {{??}}
256
+ <h2>No snapshots exist</h2>
257
+ {{?}}
258
+ </script>
259
+
260
+ <script id="snapshotsList" type="text/x-dot-tmpl">
261
+ {{? it.list && it.list.length }}
262
+ <h2>Snapshots for {{= it.group_name }}</h2>
263
+ <table class="snapshots-table">
264
+ <thead>
265
+ <tr>
266
+ <th>ID</th>
267
+ <th>Duration (ms)</th>
268
+ <th>Age</th>
269
+ </tr>
270
+ </thead>
271
+ <tbody>
272
+ {{~ it.list :row}}
273
+ <tr>
274
+ <td><a href="{{= row.url }}">
275
+ {{= row.id }}
276
+ </a></td>
277
+ <td>{{= MiniProfiler.formatDuration(row.duration) }}</td>
278
+ <td>
279
+ {{? row.timestamp }}
280
+ {{= MiniProfiler.timestampToRelative(row.timestamp) }}
281
+ {{?}}
282
+ </td>
283
+ </tr>
284
+ {{~}}
285
+ </tbody>
286
+ </table>
287
+ {{??}}
288
+ <h2>No snapshots for {{= it.group_name }}</h2>
289
+ {{?}}
290
+ </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}"></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))+'</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
  ) {
@@ -25,6 +25,14 @@ 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> </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><a href="'+( row.url )+'">'+( row.name )+'</a></td> <td>'+( MiniProfiler.formatDuration(row.worst_score) )+'</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=' ';if(it.list && it.list.length){out+=' <h2>Snapshots for '+( it.group_name )+'</h2> <table class="snapshots-table"> <thead> <tr> <th>ID</th> <th>Duration (ms)</th> <th>Age</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><a href="'+( row.url )+'"> '+( row.id )+' </a></td> <td>'+( MiniProfiler.formatDuration(row.duration) )+'</td> <td> ';if(row.timestamp){out+=' '+( MiniProfiler.timestampToRelative(row.timestamp) )+' ';}out+=' </td> </tr> ';} } out+=' </tbody> </table> ';}else{out+=' <h2>No snapshots for '+( it.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 = 'b5b2bc8cce501b6f38c294cea2f0d2c2'
5
5
  end
6
6
  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
@@ -60,7 +62,8 @@ module Rack
60
62
  :base_url_path, :disable_caching, :enabled,
61
63
  :flamegraph_sample_rate, :logger, :pre_authorize_cb, :skip_paths,
62
64
  :skip_schema_queries, :storage, :storage_failure, :storage_instance,
63
- :storage_options, :user_provider, :enable_advanced_debugging_tools
65
+ :storage_options, :user_provider, :enable_advanced_debugging_tools,
66
+ :snapshot_every_n_requests, :snapshots_limit
64
67
  attr_accessor :skip_sql_param_names, :suppress_encoding, :max_sql_param_length
65
68
 
66
69
  # ui accessors
@@ -71,6 +74,15 @@ module Rack
71
74
  # Deprecated options
72
75
  attr_accessor :use_existing_jquery
73
76
 
77
+ attr_reader :assets_url
78
+
79
+ def assets_url=(lmbda)
80
+ if defined?(Rack::MiniProfilerRails)
81
+ Rack::MiniProfilerRails.create_engine
82
+ end
83
+ @assets_url = lmbda
84
+ end
85
+
74
86
  def vertical_position
75
87
  position.include?('bottom') ? 'bottom' : 'top'
76
88
  end
@@ -8,7 +8,7 @@ module Rack
8
8
  attr_accessor :subscribe_sql_active_record
9
9
 
10
10
  def patch_rails?
11
- !!defined?(::Rack::MiniProfiler::ENABLE_RAILS_PATCHES)
11
+ !!defined?(Rack::MINI_PROFILER_ENABLE_RAILS_PATCHES)
12
12
  end
13
13
 
14
14
  def generate_id
@@ -38,9 +38,20 @@ 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
41
42
  Thread.current[:mini_profiler_private] = c
42
43
  end
43
44
 
45
+ def add_snapshot_custom_field(key, value)
46
+ thread_var_key = :mini_profiler_snapshot_custom_fields
47
+ Thread.current[thread_var_key] ||= {}
48
+ Thread.current[thread_var_key][key] = value
49
+ end
50
+
51
+ def get_snapshot_custom_fields
52
+ Thread.current[:mini_profiler_snapshot_custom_fields]
53
+ end
54
+
44
55
  # discard existing results, don't track this request
45
56
  def discard_results
46
57
  self.current.discard = true if current
@@ -106,14 +117,23 @@ module Rack
106
117
  def serve_results(env)
107
118
  request = Rack::Request.new(env)
108
119
  id = request.params['id']
109
- page_struct = @storage.load(id)
110
- unless page_struct
120
+ is_snapshot = request.params['snapshot']
121
+ is_snapshot = [true, "true"].include?(is_snapshot)
122
+ if is_snapshot
123
+ page_struct = @storage.load_snapshot(id)
124
+ else
125
+ page_struct = @storage.load(id)
126
+ end
127
+ if !page_struct && is_snapshot
128
+ id = ERB::Util.html_escape(id)
129
+ return [404, {}, ["Snapshot with id '#{id}' not found"]]
130
+ elsif !page_struct
111
131
  @storage.set_viewed(user(env), id)
112
- id = ERB::Util.html_escape(request.params['id'])
132
+ id = ERB::Util.html_escape(id)
113
133
  user_info = ERB::Util.html_escape(user(env))
114
134
  return [404, {}, ["Request not found: #{id} - user #{user_info}"]]
115
135
  end
116
- unless page_struct[:has_user_viewed]
136
+ if !page_struct[:has_user_viewed] && !is_snapshot
117
137
  page_struct[:client_timings] = TimerStruct::Client.init_from_form_data(env, page_struct)
118
138
  page_struct[:has_user_viewed] = true
119
139
  @storage.save(page_struct)
@@ -148,11 +168,12 @@ module Rack
148
168
  file_name = path.sub(@config.base_url_path, '')
149
169
 
150
170
  return serve_results(env) if file_name.eql?('results')
171
+ return handle_snapshots_request(env) if file_name.eql?('snapshots')
151
172
 
152
173
  resources_env = env.dup
153
174
  resources_env['PATH_INFO'] = file_name
154
175
 
155
- rack_file = Rack::File.new(MiniProfiler.resources_root, 'Cache-Control' => 'max-age:86400')
176
+ rack_file = Rack::File.new(MiniProfiler.resources_root, 'Cache-Control' => "max-age=#{cache_control_value}")
156
177
  rack_file.call(resources_env)
157
178
  end
158
179
 
@@ -177,7 +198,6 @@ module Rack
177
198
  end
178
199
 
179
200
  def call(env)
180
-
181
201
  start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
182
202
  client_settings = ClientSettings.new(env, @storage, start)
183
203
  MiniProfiler.deauthorize_request if @config.authorization_mode == :whitelist
@@ -189,15 +209,31 @@ module Rack
189
209
  # Someone (e.g. Rails engine) could change the SCRIPT_NAME so we save it
190
210
  env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME'] = ENV['PASSENGER_BASE_URI'] || env['SCRIPT_NAME']
191
211
 
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/
212
+ skip_it = /pp=skip/.match?(query_string) || (
213
+ @config.skip_paths &&
214
+ @config.skip_paths.any? do |p|
215
+ if p.instance_of?(String)
216
+ path.start_with?(p)
217
+ elsif p.instance_of?(Regexp)
218
+ p.match?(path)
219
+ end
220
+ end
221
+ )
222
+ if skip_it
223
+ return client_settings.handle_cookie(@app.call(env))
224
+ end
225
+
226
+ skip_it = (@config.pre_authorize_cb && !@config.pre_authorize_cb.call(env))
195
227
 
196
228
  if skip_it || (
197
229
  @config.authorization_mode == :whitelist &&
198
230
  !client_settings.has_valid_cookie?
199
231
  )
200
- return client_settings.handle_cookie(@app.call(env))
232
+ if take_snapshot?(path)
233
+ return client_settings.handle_cookie(take_snapshot(env, start))
234
+ else
235
+ return client_settings.handle_cookie(@app.call(env))
236
+ end
201
237
  end
202
238
 
203
239
  # handle all /mini-profiler requests here
@@ -308,6 +344,15 @@ module Rack
308
344
  status, headers, body = @app.call(env)
309
345
  end
310
346
  end
347
+ elsif path == '/rack-mini-profiler/requests'
348
+ blank_page_html = <<~HTML
349
+ <html>
350
+ <head></head>
351
+ <body></body>
352
+ </html>
353
+ HTML
354
+
355
+ status, headers, body = [200, { 'Content-Type' => 'text/html' }, [blank_page_html.dup]]
311
356
  else
312
357
  status, headers, body = @app.call(env)
313
358
  end
@@ -364,17 +409,6 @@ module Rack
364
409
  return client_settings.handle_cookie(self.flamegraph(flamegraph))
365
410
  end
366
411
 
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
412
  begin
379
413
  @storage.save(page_struct)
380
414
  # no matter what it is, it should be unviewed, otherwise we will miss POST
@@ -630,10 +664,20 @@ Append the following to your query string:
630
664
  # * you do not want script to be automatically appended for the current page. You can also call cancel_auto_inject
631
665
  def get_profile_script(env)
632
666
  path = "#{env['RACK_MINI_PROFILER_ORIGINAL_SCRIPT_NAME']}#{@config.base_url_path}"
667
+ version = MiniProfiler::ASSET_VERSION
668
+ if @config.assets_url
669
+ url = @config.assets_url.call('rack-mini-profiler.js', version, env)
670
+ css_url = @config.assets_url.call('rack-mini-profiler.css', version, env)
671
+ end
672
+
673
+ url = "#{path}includes.js?v=#{version}" if !url
674
+ css_url = "#{path}includes.css?v=#{version}" if !css_url
633
675
 
634
676
  settings = {
635
677
  path: path,
636
- version: MiniProfiler::ASSET_VERSION,
678
+ url: url,
679
+ cssUrl: css_url,
680
+ version: version,
637
681
  verticalPosition: @config.vertical_position,
638
682
  horizontalPosition: @config.horizontal_position,
639
683
  showTrivial: @config.show_trivial,
@@ -673,5 +717,104 @@ Append the following to your query string:
673
717
  current.inject_js = false
674
718
  end
675
719
 
720
+ def cache_control_value
721
+ 86400
722
+ end
723
+
724
+ private
725
+
726
+ def handle_snapshots_request(env)
727
+ self.current = nil
728
+ MiniProfiler.authorize_request
729
+ status = 200
730
+ headers = { 'Content-Type' => 'text/html' }
731
+ qp = Rack::Utils.parse_nested_query(env['QUERY_STRING'])
732
+ if group_name = qp["group_name"]
733
+ list = @storage.find_snapshots_group(group_name)
734
+ list.each do |snapshot|
735
+ snapshot[:url] = url_for_snapshot(snapshot[:id])
736
+ end
737
+ data = {
738
+ group_name: group_name,
739
+ list: list
740
+ }
741
+ else
742
+ list = @storage.snapshot_groups_overview
743
+ list.each do |group|
744
+ group[:url] = url_for_snapshots_group(group[:name])
745
+ end
746
+ data = {
747
+ page: "overview",
748
+ list: list
749
+ }
750
+ end
751
+ data_html = <<~HTML
752
+ <div style="display: none;" id="snapshots-data">
753
+ #{data.to_json}
754
+ </div>
755
+ HTML
756
+ response = Rack::Response.new([], status, headers)
757
+
758
+ response.write <<~HTML
759
+ <html>
760
+ <head></head>
761
+ <body class="mp-snapshots">
762
+ HTML
763
+ response.write(data_html)
764
+ script = self.get_profile_script(env)
765
+ response.write(script)
766
+ response.write <<~HTML
767
+ </body>
768
+ </html>
769
+ HTML
770
+ response.finish
771
+ end
772
+
773
+ def rails_route_from_path(path, method)
774
+ if defined?(Rails) && defined?(ActionController::RoutingError)
775
+ hash = Rails.application.routes.recognize_path(path, method: method)
776
+ if hash && hash[:controller] && hash[:action]
777
+ "#{method} #{hash[:controller]}##{hash[:action]}"
778
+ end
779
+ end
780
+ rescue ActionController::RoutingError
781
+ nil
782
+ end
783
+
784
+ def url_for_snapshots_group(group_name)
785
+ qs = Rack::Utils.build_query({ group_name: group_name })
786
+ "/#{@config.base_url_path.gsub('/', '')}/snapshots?#{qs}"
787
+ end
788
+
789
+ def url_for_snapshot(id)
790
+ qs = Rack::Utils.build_query({ id: id, snapshot: true })
791
+ "/#{@config.base_url_path.gsub('/', '')}/results?#{qs}"
792
+ end
793
+
794
+ def take_snapshot?(path)
795
+ @config.snapshot_every_n_requests > 0 &&
796
+ !path.start_with?(@config.base_url_path) &&
797
+ @storage.should_take_snapshot?(@config.snapshot_every_n_requests)
798
+ end
799
+
800
+ def take_snapshot(env, start)
801
+ MiniProfiler.create_current(env, @config)
802
+ results = @app.call(env)
803
+ status = results[0].to_i
804
+ if status >= 200 && status < 300
805
+ page_struct = current.page_struct
806
+ page_struct[:root].record_time(
807
+ (Process.clock_gettime(Process::CLOCK_MONOTONIC) - start) * 1000
808
+ )
809
+ custom_fields = MiniProfiler.get_snapshot_custom_fields
810
+ page_struct[:custom_fields] = custom_fields if custom_fields
811
+ @storage.push_snapshot(
812
+ page_struct,
813
+ @config
814
+ )
815
+ end
816
+ self.current = nil
817
+ results
818
+ end
676
819
  end
677
820
  end