rack-mini-profiler 3.3.1 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc0d1c8641a30fc59950236e1761ac6a0f1c07cdbc73c5192e256c7bfd84a6af
4
- data.tar.gz: 20d8e78c618c96da12676078fff49bb2bf71014e6c8ace339d1fcdc4cd73e8a4
3
+ metadata.gz: fb35c9ff911dd9dcc3fc5cccf15d62f21f0c04ed79ff5dec3e88746fcb90a43f
4
+ data.tar.gz: a8cd39cdb11b4bb2ed154e9c3269de66b6f9c351c8cdcdb775e73e0c25e5d5a2
5
5
  SHA512:
6
- metadata.gz: 534f1a6ea263d6e9aaea85e14d0aa09ac893081ad350530c8122c241f1cb0f3397cd7fdf1ca5a65e96bca92384e0b38a6ebbcec8b9e94a4cafb4565198cbd743
7
- data.tar.gz: 9114e796825cbaa2b88b71c5bfb0a2b5aa0ea6453a4bf4364d6666d37b42fbcdf12991b0df95ceffaefca47907b9d5c36932ada2c77b4d4120889b4f093b2c23
6
+ metadata.gz: 1c1123a2fc22b5f20ea63fa089b4914f214a6da3d88dbba2fe1b3f597c92c653a166dc1c97d8a68e51a71893b9787ca11b99db1a68aa19da04cad494d8f33af9
7
+ data.tar.gz: b21baa7055d5252cf3f57031be17e67810d898e4b40428d12409027454bfa15b414e2346f07e49d09b139945eb177f8951a34f41c765773ac543c49c2f0efd81
data/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # CHANGELOG
2
2
 
3
+ ## 4.0 - 2025-06-11
4
+
5
+ - [BREAKING CHANGE] Ruby version 3.1.0 or later is required. [#632](https://github.com/MiniProfiler/rack-mini-profiler/pull/632)
6
+ - [FEATURE] Implement prepend patch for postgres [#625](https://github.com/MiniProfiler/rack-mini-profiler/pull/625)
7
+ - [FEATURE] Show Active Record QueryCache hits in UI. [#640](https://github.com/MiniProfiler/rack-mini-profiler/pull/640)
8
+ - [FEATURE] Show record type and count in SQL query UI. [#638](https://github.com/MiniProfiler/rack-mini-profiler/pull/638)
9
+ - [FIX] Requests page fails due to trying to modify a frozen string. [#630](https://github.com/MiniProfiler/rack-mini-profiler/pull/630)
10
+ - [FIX] alignment of SQL query start times. [#627](https://github.com/MiniProfiler/rack-mini-profiler/pull/627)
11
+ - [FIX] Lower case HTTP response headers to be compatible with Rack 3 [#628](https://github.com/MiniProfiler/rack-mini-profiler/pull/628)
12
+ - [FIX] Truncate long profiler name in profiler popup. [#634](https://github.com/MiniProfiler/rack-mini-profiler/pull/634)
13
+ - [FIX] `flamegraph_mode` query param having no effect. [#635](https://github.com/MiniProfiler/rack-mini-profiler/pull/635)
14
+ - [FIX] max_traces_to_show had chance to break the profiler frontend [#297](https://github.com/MiniProfiler/rack-mini-profiler/issues/297)
15
+
3
16
  ## 3.3.1 - 2024-02-15
4
17
  - [FEATURE] Support dynamic `config.content_security_policy_nonce` [#609](https://github.com/MiniProfiler/rack-mini-profiler/pull/609)
5
18
  - [FEATURE] Add flamgraph path to response header: [#601](https://github.com/MiniProfiler/rack-mini-profiler/pull/601)
data/README.md CHANGED
@@ -90,6 +90,20 @@ gem 'rack-mini-profiler', require: ['prepend_mysql2_patch', 'rack-mini-profiler'
90
90
 
91
91
  This should not be necessary with Rails < 5 because peek-mysql2 hooks into mysql2 gem in different ways depending on your Rails version.
92
92
 
93
+ #### `pg` stack level too deep errors
94
+
95
+ If you encounter `SystemStackError (stack level too deep)` from PG, you'll need to use this gem spec in your Gemfile:
96
+
97
+ ```ruby
98
+ gem 'rack-mini-profiler', require: ['prepend_pg_patch', 'rack-mini-profiler']
99
+ ```
100
+
101
+ Or if you initially have `require: false`, then use
102
+
103
+ ```ruby
104
+ gem 'rack-mini-profiler', require: ['prepend_pg_patch']
105
+ ```
106
+
93
107
  #### Rails and manual initialization
94
108
 
95
109
  In case you need to make sure rack_mini_profiler is initialized after all other gems, or you want to execute some code before rack_mini_profiler required:
@@ -245,6 +259,7 @@ rack-mini-profiler is designed with production profiling in mind. To enable that
245
259
  end
246
260
  ```
247
261
 
262
+ > [!WARNING]
248
263
  > If your production application is running on more than one server (or more than one dyno) you will need to configure rack mini profiler's storage to use Redis or Memcache. See [storage](#storage) for information on changing the storage backend.
249
264
 
250
265
  Note:
@@ -113,6 +113,8 @@
113
113
  float: left; }
114
114
  .profiler-result .profiler-info .profiler-server-time {
115
115
  white-space: nowrap; }
116
+ .profiler-result .profiler-info .profiler-number {
117
+ display: block; }
116
118
  .profiler-result .profiler-timings th {
117
119
  background-color: #fff;
118
120
  color: #767676;
@@ -133,19 +135,21 @@
133
135
  .profiler-result .profiler-timings .profiler-queries-duration {
134
136
  padding-left: 6px; }
135
137
  .profiler-result .profiler-timings .profiler-percent-in-sql {
136
- white-space: nowrap;
137
- text-align: right; }
138
- .profiler-result .profiler-timings tfoot td {
139
- padding-top: 10px;
138
+ white-space: nowrap; }
139
+ .profiler-result .profiler-timings tfoot tr td:last-child {
140
140
  text-align: right; }
141
- .profiler-result .profiler-timings tfoot td a {
141
+ .profiler-result .profiler-timings-summary {
142
+ display: flex;
143
+ justify-content: space-between;
144
+ padding-top: 10px; }
145
+ .profiler-result .profiler-timings-summary a {
142
146
  font-size: 95%;
143
147
  display: inline-block;
144
148
  margin-left: 12px; }
145
- .profiler-result .profiler-timings tfoot td a:first-child {
149
+ .profiler-result .profiler-timings-summary a:first-child {
146
150
  float: left;
147
151
  margin-left: 0px; }
148
- .profiler-result .profiler-timings tfoot td a.profiler-custom-link {
152
+ .profiler-result .profiler-timings-summary a.profiler-custom-link {
149
153
  float: left; }
150
154
  .profiler-result .profiler-queries {
151
155
  font-family: Helvetica, Arial, sans-serif; }
@@ -163,6 +167,12 @@
163
167
  background-color: #fdd; }
164
168
  .profiler-result .profiler-queries tr.very-very-slow {
165
169
  background-color: #fcc; }
170
+ .profiler-result .profiler-queries tr.cached {
171
+ background-color: #f2f0ef; }
172
+ .profiler-result .profiler-queries span.cached {
173
+ color: #818589; }
174
+ .profiler-result .profiler-queries span.cached + pre {
175
+ display: inline; }
166
176
  .profiler-result .profiler-queries pre {
167
177
  font-family: Consolas, monospace, serif;
168
178
  white-space: pre-wrap; }
@@ -340,14 +350,22 @@
340
350
  text-align: left;
341
351
  line-height: 18px;
342
352
  overflow: auto;
353
+ max-width: 800px;
343
354
  box-shadow: 0px 1px 15px #555; }
344
355
  .profiler-results .profiler-popup .profiler-info {
345
356
  margin-bottom: 3px;
346
357
  padding-bottom: 2px;
347
- border-bottom: 1px solid #ddd; }
358
+ border-bottom: 1px solid #ddd;
359
+ display: flex;
360
+ width: inherit; }
348
361
  .profiler-results .profiler-popup .profiler-info .profiler-name {
362
+ overflow: hidden;
363
+ text-overflow: ellipsis;
364
+ text-align: left;
365
+ white-space: nowrap;
349
366
  font-size: 110%;
350
- font-weight: bold; }
367
+ font-weight: bold;
368
+ padding-right: 10px; }
351
369
  .profiler-results .profiler-popup .profiler-info .profiler-name .profiler-overall-duration {
352
370
  display: none; }
353
371
  .profiler-results .profiler-popup .profiler-info .profiler-server-time {
data/lib/html/includes.js CHANGED
@@ -285,10 +285,10 @@ var _MiniProfiler = (function() {
285
285
  }); // limit count
286
286
 
287
287
  if (
288
- container.querySelectorAll(".profiler-result").length >
288
+ container.querySelectorAll(".profiler-result:not(:has(.profiler-totals))").length >
289
289
  options.maxTracesToShow
290
290
  ) {
291
- var elem = container.querySelector(".profiler-result");
291
+ var elem = container.querySelector('.profiler-result:not(:has(.profiler-totals))');
292
292
 
293
293
  if (elem) {
294
294
  elem.parentElement.removeChild(elem);
@@ -1179,6 +1179,7 @@ var _MiniProfiler = (function() {
1179
1179
 
1180
1180
  reqs = 0;
1181
1181
  totalTime = 0;
1182
+ totalSqlCount = 0;
1182
1183
  expandedResults = false;
1183
1184
  toArray(
1184
1185
  document.querySelectorAll(".profiler-results .profiler-result")
@@ -1288,6 +1289,10 @@ var _MiniProfiler = (function() {
1288
1289
 
1289
1290
  sqlTiming.parent_timing_name = timing.name;
1290
1291
 
1292
+ if (sqlTiming.cached) {
1293
+ sqlTiming.row_class = "cached";
1294
+ }
1295
+
1291
1296
  if (sqlTiming.duration_milliseconds > 50) {
1292
1297
  sqlTiming.row_class = "slow";
1293
1298
  }
@@ -114,6 +114,9 @@ $zindex: 2147483640; // near 32bit max 2147483647
114
114
  .profiler-server-time {
115
115
  white-space: nowrap;
116
116
  }
117
+ .profiler-number {
118
+ display: block;
119
+ }
117
120
  }
118
121
 
119
122
  .profiler-timings {
@@ -147,27 +150,28 @@ $zindex: 2147483640; // near 32bit max 2147483647
147
150
  }
148
151
  .profiler-percent-in-sql {
149
152
  white-space: nowrap;
153
+ }
154
+ tfoot tr td:last-child {
150
155
  text-align: right;
151
156
  }
157
+ }
152
158
 
153
- tfoot {
154
- td {
155
- padding-top: 10px;
156
- text-align: right;
159
+ .profiler-timings-summary {
160
+ display: flex;
161
+ justify-content: space-between;
162
+ padding-top: 10px;
157
163
 
158
- a {
159
- font-size: 95%;
160
- display: inline-block;
161
- margin-left: 12px;
164
+ a {
165
+ font-size: 95%;
166
+ display: inline-block;
167
+ margin-left: 12px;
162
168
 
163
- &:first-child {
164
- float: left;
165
- margin-left: 0px;
166
- }
167
- &.profiler-custom-link {
168
- float: left;
169
- }
170
- }
169
+ &:first-child {
170
+ float: left;
171
+ margin-left: 0px;
172
+ }
173
+ &.profiler-custom-link {
174
+ float: left;
171
175
  }
172
176
  }
173
177
  }
@@ -202,6 +206,18 @@ $zindex: 2147483640; // near 32bit max 2147483647
202
206
  background-color: #fcc;
203
207
  }
204
208
 
209
+ tr.cached {
210
+ background-color: #f2f0ef;
211
+ }
212
+
213
+ span.cached {
214
+ color: #818589;
215
+ }
216
+
217
+ span.cached + pre {
218
+ display: inline;
219
+ }
220
+
205
221
  pre {
206
222
  font-family: $codeFonts;
207
223
  white-space: pre-wrap;
@@ -482,6 +498,7 @@ $zindex: 2147483640; // near 32bit max 2147483647
482
498
  text-align: left;
483
499
  line-height: 18px;
484
500
  overflow: auto;
501
+ max-width: 800px;
485
502
 
486
503
  @include box-shadow(0px, 1px, 15px, $textColor);
487
504
 
@@ -489,14 +506,23 @@ $zindex: 2147483640; // near 32bit max 2147483647
489
506
  margin-bottom: 3px;
490
507
  padding-bottom: 2px;
491
508
  border-bottom: 1px solid #ddd;
509
+ display: flex;
510
+ width: inherit;
492
511
 
493
512
  .profiler-name {
513
+ overflow: hidden;
514
+ text-overflow: ellipsis;
515
+ text-align: left;
516
+ white-space: nowrap;
494
517
  font-size: 110%;
495
518
  font-weight: bold;
519
+ padding-right: 10px;
520
+
496
521
  .profiler-overall-duration {
497
522
  display: none;
498
523
  }
499
524
  }
525
+
500
526
  .profiler-server-time {
501
527
  font-size: 95%;
502
528
  }
@@ -21,8 +21,8 @@
21
21
 
22
22
  <div class="profiler-popup">
23
23
  <div class="profiler-info">
24
- <span class="profiler-name">
25
- {{= it.name}} <span class="profiler-overall-duration">({{= MiniProfiler.formatDuration(it.duration_milliseconds)}} ms)</span>
24
+ <span class="profiler-name" title="{{= it.name}}">
25
+ {{= it.name }} <span class="profiler-overall-duration">({{= MiniProfiler.formatDuration(it.duration_milliseconds)}} ms)</span>
26
26
  </span>
27
27
  <span class="profiler-server-time">{{= it.machine_name}} on {{= MiniProfiler.renderDate(it.started_formatted)}}</span>
28
28
  </div>
@@ -45,33 +45,42 @@
45
45
  <tbody>
46
46
  {{= MiniProfiler.templates.timingTemplate({timing: it.root, page: it}) }}
47
47
  </tbody>
48
- <tfoot>
49
- <tr>
50
- <td colspan="3">
51
- {{? !it.client_timings}}
52
- {{= MiniProfiler.templates.linksTemplate({timing: it.root, page: it}) }}
53
- {{?}}
54
- <a class="profiler-toggle-duration-with-children" title="toggles column with aggregate child durations">show time with children</a>
55
- <a
56
- class="profiler-snapshots-page-link"
57
- title="Go to snapshots page"
58
- href="{{= MiniProfiler.options.path }}snapshots">snapshots</a>
59
- </td>
60
- {{? it.has_sql_timings}}
61
- <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">
62
- {{= MiniProfiler.formatDuration(it.duration_milliseconds_in_sql / it.duration_milliseconds * 100) }}
63
- <span class="profiler-unit">% in sql</span>
48
+ {{? it.has_sql_timings}}
49
+ <tfoot>
50
+ <tr>
51
+ <td colspan="1">
52
+ SQL Summary:
64
53
  </td>
65
- {{?}}
66
- {{~ it.custom_timing_names :value}}
67
- <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">
68
- {{= MiniProfiler.formatDuration(it.custom_timing_stats[value].duration / it.duration_milliseconds * 100) }}
69
- <span class="profiler-unit">% in {{= value.toLowerCase() }}</span>
54
+ <td colspan="5" title="percent of total request time spent in SQL">
55
+ {{=it.sql_count}} {{? it.cached_sql_count > 0 }} ({{=it.cached_sql_count}} cached) {{?}}
56
+ <span class="profiler-unit" title="percent of total request time spent in SQL"> -
57
+ {{= MiniProfiler.formatDuration(it.duration_milliseconds_in_sql / it.duration_milliseconds * 100) }}% in sql
58
+ </span>
70
59
  </td>
71
- {{~}}
72
- </tr>
73
- </tfoot>
60
+ </tr>
61
+ </tfoot>
62
+ {{?}}
74
63
  </table>
64
+
65
+ <div class="profiler-timings-summary">
66
+ <div>
67
+ {{? !it.client_timings}}
68
+ {{= MiniProfiler.templates.linksTemplate({timing: it.root, page: it}) }}
69
+ {{?}}
70
+ <a class="profiler-toggle-duration-with-children" title="toggles column with aggregate child durations">show time with children</a>
71
+ <a
72
+ class="profiler-snapshots-page-link"
73
+ title="Go to snapshots page"
74
+ href="{{= MiniProfiler.options.path }}snapshots">snapshots</a>
75
+ </div>
76
+ {{~ it.custom_timing_names :value}}
77
+ <div 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">
78
+ {{= MiniProfiler.formatDuration(it.custom_timing_stats[value].duration / it.duration_milliseconds * 100) }}
79
+ <span class="profiler-unit">% in {{= value.toLowerCase() }}</span>
80
+ </div>
81
+ {{~}}
82
+ </div>
83
+
75
84
  {{? it.client_timings}}
76
85
  <table class="profiler-timings profiler-client-timings">
77
86
  <thead>
@@ -124,7 +133,7 @@
124
133
  <table>
125
134
  <thead>
126
135
  <tr>
127
- <th class="ta-right">step<br />time from start<br />query type<br />duration</th>
136
+ <th class="ta-right">step<br />time from start<br />query type<br />duration<br />records</th>
128
137
  <th class="ta-left">call stack<br />query</th>
129
138
  </tr>
130
139
  </thead>
@@ -223,10 +232,16 @@
223
232
  {{= MiniProfiler.renderExecuteType(it.s.execute_type) }}
224
233
  </div>
225
234
  <div title="{{? it.s.execute_type == 3}}first result fetched: {{= it.s.first_fetch_duration_milliseconds }}ms{{?}}">{{= MiniProfiler.formatDuration(it.s.duration_milliseconds) }} <span class="profiler-unit">ms</span></div>
235
+ {{? it.s.row_count > 0 }}
236
+ <div title="number and type of records instantiated by query">{{= it.s.class_name }}: {{= it.s.row_count }}</div>
237
+ {{?}}
226
238
  </td>
227
239
  <td>
228
240
  <div class="query">
229
241
  <pre class="profiler-stack-trace">{{= it.s.stack_trace_snippet }}</pre>
242
+ {{? it.s.cached }}
243
+ <span class="cached"> [CACHE] </span>
244
+ {{?}}
230
245
  {{? it.s.formatted_command_string}}
231
246
  <pre class="prettyprint lang-sql"><code>{{= it.s.formatted_command_string }}; {{= MiniProfiler.formatParameters(it.s.parameters) }}</code></pre>
232
247
  {{??}}
data/lib/html/vendor.js CHANGED
@@ -7,7 +7,7 @@
7
7
  MiniProfiler.templates = {};
8
8
  MiniProfiler.templates["profilerTemplate"] = function anonymous(it
9
9
  ) {
10
- var out=' <div class="profiler-result"> <div class="profiler-button ';if(it.has_duplicate_sql_timings){out+='profiler-warning';}out+='"> ';if(it.has_duplicate_sql_timings){out+='<span class="profiler-nuclear">!</span>';}out+=' <span class="profiler-number profiler-duration-milliseconds"> '+( MiniProfiler.formatDuration(it.duration_milliseconds))+' <span class="profiler-unit">ms</span> </span> ';if(MiniProfiler.showTotalSqlCount()){out+=' <span class="profiler-number profiler-sql-count"> '+( it.sql_count)+' <span class="profiler-unit">sql</span> </span> ';}out+=' <span class="profiler-name"> ';if(it.name.length >= 30){out+=' '+( it.name.substring(0,15) + "..." + it.name.slice(-15) )+' ';}else{out+=' '+( it.name)+' ';}out+=' </span> </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>event</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 class="ta-right">step<br />time from start<br />query type<br />duration</th> <th class="ta-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;
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 profiler-duration-milliseconds"> '+( MiniProfiler.formatDuration(it.duration_milliseconds))+' <span class="profiler-unit">ms</span> </span> ';if(MiniProfiler.showTotalSqlCount()){out+=' <span class="profiler-number profiler-sql-count"> '+( it.sql_count)+' <span class="profiler-unit">sql</span> </span> ';}out+=' <span class="profiler-name"> ';if(it.name.length >= 30){out+=' '+( it.name.substring(0,15) + "..." + it.name.slice(-15) )+' ';}else{out+=' '+( it.name)+' ';}out+=' </span> </div> <div class="profiler-popup"> <div class="profiler-info"> <span class="profiler-name" title="'+( it.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>event</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> ';if(it.has_sql_timings){out+=' <tfoot> <tr> <td colspan="1"> SQL Summary: </td> <td colspan="5" title="percent of total request time spent in SQL"> '+(it.sql_count)+' ';if(it.cached_sql_count > 0){out+=' ('+(it.cached_sql_count)+' cached) ';}out+=' <span class="profiler-unit" title="percent of total request time spent in SQL"> - '+( MiniProfiler.formatDuration(it.duration_milliseconds_in_sql / it.duration_milliseconds * 100) )+'% in sql </span> </td> </tr> </tfoot> ';}out+=' </table> <div class="profiler-timings-summary"> <div> ';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> </div> ';var arr2=it.custom_timing_names;if(arr2){var value,i2=-1,l2=arr2.length-1;while(i2<l2){value=arr2[i2+=1];out+=' <div 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> </div> ';} } out+=' </div> ';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 class="ta-right">step<br />time from start<br />query type<br />duration<br />records</th> <th class="ta-left">call stack<br />query</th> </tr> </thead> <tbody> ';var arr5=MiniProfiler.getSqlTimings(it.root);if(arr5){var value,index=-1,l5=arr5.length-1;while(index<l5){value=arr5[index+=1];out+=' '+( MiniProfiler.templates.sqlGapTemplate({g: value.prevGap}) )+' '+( MiniProfiler.templates.sqlTimingTemplate({i: index, s: value}) )+' ';if(value.nextGap){out+=' '+( MiniProfiler.templates.sqlGapTemplate({g: value.nextGap}) )+' ';}out+=' ';} } out+=' </tbody> </table> <p class="profiler-trivial-gap-container"> <a class="profiler-toggle-trivial-gaps">show trivial gaps</a> </p> </div> ';}out+=' </div>';return out;
11
11
  }
12
12
  MiniProfiler.templates["linksTemplate"] = function anonymous(it
13
13
  ) {
@@ -19,7 +19,7 @@ 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> ';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;
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> ';if(it.s.row_count > 0){out+=' <div title="number and type of records instantiated by query">'+( it.s.class_name )+': '+( it.s.row_count )+'</div> ';}out+=' </td> <td> <div class="query"> <pre class="profiler-stack-trace">'+( it.s.stack_trace_snippet )+'</pre> ';if(it.s.cached){out+=' <span class="cached"> [CACHE] </span> ';}out+=' ';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
  ) {
@@ -5,7 +5,7 @@ module Rack
5
5
  def serve_snapshot(env)
6
6
  MiniProfiler.authorize_request
7
7
  status = 200
8
- headers = { 'Content-Type' => 'text/html' }
8
+ headers = { 'content-type' => 'text/html' }
9
9
  qp = Rack::Utils.parse_nested_query(env['QUERY_STRING'])
10
10
  if group_name = qp["group_name"]
11
11
  list = @storage.snapshots_group(group_name)
@@ -56,9 +56,9 @@ module Rack
56
56
  resources_env['PATH_INFO'] = file_name
57
57
 
58
58
  if Gem::Version.new(Rack.release) >= Gem::Version.new("2.1.0")
59
- rack_file = Rack::Files.new(resources_root, 'Cache-Control' => "max-age=#{cache_control_value}")
59
+ rack_file = Rack::Files.new(resources_root, 'cache-control' => "max-age=#{cache_control_value}")
60
60
  else
61
- rack_file = Rack::File.new(resources_root, 'Cache-Control' => "max-age=#{cache_control_value}")
61
+ rack_file = Rack::File.new(resources_root, 'cache-control' => "max-age=#{cache_control_value}")
62
62
  end
63
63
 
64
64
  rack_file.call(resources_env)
@@ -93,11 +93,11 @@ module Rack
93
93
  # If we're an XMLHttpRequest, serve up the contents as JSON
94
94
  if request.xhr?
95
95
  result_json = page_struct.to_json
96
- [200, { 'Content-Type' => 'application/json' }, [result_json]]
96
+ [200, { 'content-type' => 'application/json' }, [result_json]]
97
97
  else
98
98
  # Otherwise give the HTML back
99
99
  html = generate_html(page_struct, env)
100
- [200, { 'Content-Type' => 'text/html' }, [html]]
100
+ [200, { 'content-type' => 'text/html' }, [html]]
101
101
  end
102
102
  end
103
103
 
@@ -130,7 +130,7 @@ module Rack
130
130
 
131
131
  unless defined?(MemoryProfiler) && MemoryProfiler.respond_to?(:report)
132
132
  message = "Please install the memory_profiler gem and require it: add gem 'memory_profiler' to your Gemfile"
133
- status, headers, body = @app.call(env)
133
+ _status, headers, body = @app.call(env)
134
134
  body.close if body.respond_to? :close
135
135
 
136
136
  return client_settings.handle_cookie(
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
  module Rack
3
3
  class MiniProfiler
4
- ASSET_VERSION = '116e2a6fd81c286e004e2a0afb03baa1'
4
+ ASSET_VERSION = 'e0bcc9ce0ae3bb5d6b736b6f282f601f'
5
5
  end
6
6
  end
@@ -4,7 +4,7 @@ module Rack
4
4
  class MiniProfiler
5
5
  module ProfilingMethods
6
6
 
7
- def record_sql(query, elapsed_ms, params = nil)
7
+ def record_sql(query, elapsed_ms, params = nil, cached = nil)
8
8
  return unless current && current.current_timer
9
9
  c = current
10
10
  c.current_timer.add_sql(
@@ -13,7 +13,8 @@ module Rack
13
13
  c.page_struct,
14
14
  redact_sql_queries? ? nil : params,
15
15
  c.skip_backtrace,
16
- c.full_backtrace
16
+ c.full_backtrace,
17
+ cached
17
18
  )
18
19
  end
19
20
 
@@ -42,7 +42,7 @@ module Rack
42
42
  @client.add("#{@prefix}-#{user}-v", [], @expires_in_seconds)
43
43
  MAX_RETRIES.times do
44
44
  break if @client.cas("#{@prefix}-#{user}-v", @expires_in_seconds) do |ids|
45
- ids << id unless ids.include?(id)
45
+ ids << id if !ids.include?(id)
46
46
  ids
47
47
  end
48
48
  end
@@ -78,6 +78,7 @@ module Rack
78
78
  trivial_duration_threshold_milliseconds: 2,
79
79
  head: nil,
80
80
  sql_count: 0,
81
+ cached_sql_count: 0,
81
82
  duration_milliseconds_in_sql: 0,
82
83
  has_sql_timings: true,
83
84
  has_duplicate_sql_timings: false,
@@ -101,14 +101,15 @@ module Rack
101
101
  end
102
102
  end
103
103
 
104
- def add_sql(query, elapsed_ms, page, params = nil, skip_backtrace = false, full_backtrace = false)
105
- TimerStruct::Sql.new(query, elapsed_ms, page, self, params, skip_backtrace, full_backtrace).tap do |timer|
104
+ def add_sql(query, elapsed_ms, page, params = nil, skip_backtrace = false, full_backtrace = false, cached = false)
105
+ TimerStruct::Sql.new(query, elapsed_ms, page, self, params, skip_backtrace, full_backtrace, cached).tap do |timer|
106
106
  self[:sql_timings].push(timer)
107
107
  timer[:parent_timing_id] = self[:id]
108
108
  self[:has_sql_timings] = true
109
109
  self[:sql_timings_duration_milliseconds] += elapsed_ms
110
110
  page[:duration_milliseconds_in_sql] += elapsed_ms
111
111
  page[:sql_count] += 1
112
+ page[:cached_sql_count] += 1 if cached
112
113
  end
113
114
  end
114
115
 
@@ -10,7 +10,7 @@ module Rack
10
10
  class Sql < TimerStruct::Base
11
11
  attr_accessor :parent
12
12
 
13
- def initialize(query, duration_ms, page, parent, params = nil, skip_backtrace = false, full_backtrace = false)
13
+ def initialize(query, duration_ms, page, parent, params = nil, skip_backtrace = false, full_backtrace = false, cached = false)
14
14
 
15
15
  stack_trace = nil
16
16
  unless skip_backtrace || duration_ms < Rack::MiniProfiler.config.backtrace_threshold_ms
@@ -46,7 +46,10 @@ module Rack
46
46
  duration_milliseconds: duration_ms,
47
47
  first_fetch_duration_milliseconds: duration_ms,
48
48
  parameters: query ? trim_binds(params) : nil,
49
+ row_count: 0,
50
+ class_name: nil,
49
51
  parent_timing_id: nil,
52
+ cached: cached,
50
53
  is_duplicate: false
51
54
  )
52
55
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Rack
4
4
  class MiniProfiler
5
- VERSION = "3.3.1"
5
+ VERSION = "4.0.0"
6
6
  SOURCE_CODE_URI = "https://github.com/MiniProfiler/rack-mini-profiler"
7
7
  end
8
8
  end
@@ -110,7 +110,7 @@ module Rack
110
110
  end
111
111
 
112
112
  def flamegraph(graph, path, env)
113
- headers = { 'Content-Type' => 'text/html' }
113
+ headers = { 'content-type' => 'text/html' }
114
114
  iframe_src = "#{public_base_path(env)}speedscope/index.html"
115
115
  html = <<~HTML
116
116
  <!DOCTYPE html>
@@ -141,7 +141,7 @@ module Rack
141
141
  end
142
142
 
143
143
  def help(client_settings, env)
144
- headers = { 'Content-Type' => 'text/html' }
144
+ headers = { 'content-type' => 'text/html' }
145
145
  html = <<~HTML
146
146
  <!DOCTYPE html>
147
147
  <html>
data/lib/mini_profiler.rb CHANGED
@@ -296,8 +296,8 @@ module Rack
296
296
 
297
297
  mode_match_data = action_parameters(env)['flamegraph_mode']
298
298
 
299
- if mode_match_data && [:cpu, :wall, :object, :custom].include?(mode_match_data[1].to_sym)
300
- mode = mode_match_data[1].to_sym
299
+ if mode_match_data && [:cpu, :wall, :object, :custom].include?(mode_match_data.to_sym)
300
+ mode = mode_match_data.to_sym
301
301
  else
302
302
  mode = config.flamegraph_mode
303
303
  end
@@ -329,7 +329,7 @@ module Rack
329
329
  )
330
330
  end
331
331
  elsif path == '/rack-mini-profiler/requests'
332
- status, headers, body = [200, { 'Content-Type' => 'text/html' }, [blank_page_html]]
332
+ status, headers, body = [200, { 'Content-Type' => 'text/html' }, [blank_page_html.dup]] # important to dup here!
333
333
  else
334
334
  status, headers, body = @app.call(env)
335
335
  end
@@ -417,7 +417,7 @@ module Rack
417
417
  end
418
418
 
419
419
  def action_parameters(env)
420
- query_params = Rack::Utils.parse_nested_query(env['QUERY_STRING'])
420
+ Rack::Utils.parse_nested_query(env['QUERY_STRING'])
421
421
  end
422
422
 
423
423
  def inject_profiler(env, status, headers, body)
@@ -111,7 +111,8 @@ module Rack::MiniProfilerRails
111
111
  Rack::MiniProfiler.record_sql(
112
112
  payload[:sql],
113
113
  (finish - start) * 1000,
114
- Rack::MiniProfiler.binds_to_params(payload[:binds])
114
+ Rack::MiniProfiler.binds_to_params(payload[:binds]),
115
+ payload[:cached]
115
116
  )
116
117
  end
117
118
 
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PG::Result
4
+ alias_method :each_without_profiling, :each
5
+ alias_method :values_without_profiling, :values
6
+
7
+ def values(*args, &blk)
8
+ return values_without_profiling(*args, &blk) unless defined?(@miniprofiler_sql_id)
9
+ mp_report_sql do
10
+ values_without_profiling(*args , &blk)
11
+ end
12
+ end
13
+
14
+ def each(*args, &blk)
15
+ return each_without_profiling(*args, &blk) unless defined?(@miniprofiler_sql_id)
16
+ mp_report_sql do
17
+ each_without_profiling(*args, &blk)
18
+ end
19
+ end
20
+
21
+ def mp_report_sql(&block)
22
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
23
+ result = yield
24
+ elapsed_time = SqlPatches.elapsed_time(start)
25
+ @miniprofiler_sql_id.report_reader_duration(elapsed_time)
26
+ result
27
+ end
28
+ end
29
+
30
+ class PG::Connection
31
+ alias_method :exec_without_profiling, :exec
32
+ alias_method :async_exec_without_profiling, :async_exec
33
+ alias_method :exec_prepared_without_profiling, :exec_prepared
34
+ alias_method :send_query_prepared_without_profiling, :send_query_prepared
35
+ alias_method :prepare_without_profiling, :prepare
36
+
37
+ if Gem::Version.new(PG::VERSION) >= Gem::Version.new("1.1.0")
38
+ alias_method :exec_params_without_profiling, :exec_params
39
+ end
40
+
41
+ def prepare(*args, &blk)
42
+ # we have no choice but to do this here,
43
+ # if we do the check for profiling first, our cache may miss critical stuff
44
+
45
+ @prepare_map ||= {}
46
+ @prepare_map[args[0]] = args[1]
47
+ # dont leak more than 10k ever
48
+ @prepare_map = {} if @prepare_map.length > 1000
49
+
50
+ return prepare_without_profiling(*args, &blk) unless SqlPatches.should_measure?
51
+ prepare_without_profiling(*args, &blk)
52
+ end
53
+
54
+ def exec(*args, &blk)
55
+ return exec_without_profiling(*args, &blk) unless SqlPatches.should_measure?
56
+
57
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
58
+ result = exec_without_profiling(*args, &blk)
59
+ elapsed_time = SqlPatches.elapsed_time(start)
60
+ record = ::Rack::MiniProfiler.record_sql(args[0], elapsed_time)
61
+ result.instance_variable_set("@miniprofiler_sql_id", record) if result
62
+
63
+ result
64
+ end
65
+
66
+ if Gem::Version.new(PG::VERSION) >= Gem::Version.new("1.1.0")
67
+ def exec_params(*args, &blk)
68
+ return exec_params_without_profiling(*args, &blk) unless SqlPatches.should_measure?
69
+
70
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
71
+ result = exec_params_without_profiling(*args, &blk)
72
+ elapsed_time = SqlPatches.elapsed_time(start)
73
+ record = ::Rack::MiniProfiler.record_sql(args[0], elapsed_time)
74
+ result.instance_variable_set("@miniprofiler_sql_id", record) if result
75
+
76
+ result
77
+ end
78
+ end
79
+
80
+ def exec_prepared(*args, &blk)
81
+ return exec_prepared_without_profiling(*args, &blk) unless SqlPatches.should_measure?
82
+
83
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
84
+ result = exec_prepared_without_profiling(*args, &blk)
85
+ elapsed_time = SqlPatches.elapsed_time(start)
86
+ mapped = args[0]
87
+ mapped = @prepare_map[mapped] || args[0] if @prepare_map
88
+ record = ::Rack::MiniProfiler.record_sql(mapped, elapsed_time)
89
+ result.instance_variable_set("@miniprofiler_sql_id", record) if result
90
+
91
+ result
92
+ end
93
+
94
+ def send_query_prepared(*args, &blk)
95
+ return send_query_prepared_without_profiling(*args, &blk) unless SqlPatches.should_measure?
96
+
97
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
98
+ result = send_query_prepared_without_profiling(*args, &blk)
99
+ elapsed_time = SqlPatches.elapsed_time(start)
100
+ mapped = args[0]
101
+ mapped = @prepare_map[mapped] || args[0] if @prepare_map
102
+ record = ::Rack::MiniProfiler.record_sql(mapped, elapsed_time)
103
+ result.instance_variable_set("@miniprofiler_sql_id", record) if result
104
+
105
+ result
106
+ end
107
+
108
+ def async_exec(*args, &blk)
109
+ return async_exec_without_profiling(*args, &blk) unless SqlPatches.should_measure?
110
+
111
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
112
+ result = exec_without_profiling(*args, &blk)
113
+ elapsed_time = SqlPatches.elapsed_time(start)
114
+ record = ::Rack::MiniProfiler.record_sql(args[0], elapsed_time)
115
+ result.instance_variable_set("@miniprofiler_sql_id", record) if result
116
+
117
+ result
118
+ end
119
+
120
+ alias_method :query, :exec
121
+ end
@@ -0,0 +1,115 @@
1
+ # frozen_string_literal: true
2
+
3
+ class PG::Result
4
+ module MiniProfiler
5
+ def values(*args, &blk)
6
+ return super unless defined?(@miniprofiler_sql_id)
7
+ mp_report_sql do
8
+ super
9
+ end
10
+ end
11
+
12
+ def each(*args, &blk)
13
+ return super unless defined?(@miniprofiler_sql_id)
14
+ mp_report_sql do
15
+ super
16
+ end
17
+ end
18
+
19
+ def mp_report_sql(&block)
20
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
21
+ result = yield
22
+ elapsed_time = SqlPatches.elapsed_time(start)
23
+ @miniprofiler_sql_id.report_reader_duration(elapsed_time)
24
+ result
25
+ end
26
+ end
27
+
28
+ prepend MiniProfiler
29
+ end
30
+
31
+ class PG::Connection
32
+ module MiniProfiler
33
+ def prepare(*args, &blk)
34
+ # we have no choice but to do this here,
35
+ # if we do the check for profiling first, our cache may miss critical stuff
36
+
37
+ @prepare_map ||= {}
38
+ @prepare_map[args[0]] = args[1]
39
+ # dont leak more than 10k ever
40
+ @prepare_map = {} if @prepare_map.length > 1000
41
+
42
+ return super unless SqlPatches.should_measure?
43
+ super
44
+ end
45
+
46
+ def exec(*args, &blk)
47
+ return super unless SqlPatches.should_measure?
48
+
49
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
50
+ result = super
51
+ elapsed_time = SqlPatches.elapsed_time(start)
52
+ record = ::Rack::MiniProfiler.record_sql(args[0], elapsed_time)
53
+ result.instance_variable_set("@miniprofiler_sql_id", record) if result
54
+
55
+ result
56
+ end
57
+
58
+ if Gem::Version.new(PG::VERSION) >= Gem::Version.new("1.1.0")
59
+ def exec_params(*args, &blk)
60
+ return super unless SqlPatches.should_measure?
61
+
62
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
63
+ result = super
64
+ elapsed_time = SqlPatches.elapsed_time(start)
65
+ record = ::Rack::MiniProfiler.record_sql(args[0], elapsed_time)
66
+ result.instance_variable_set("@miniprofiler_sql_id", record) if result
67
+
68
+ result
69
+ end
70
+ end
71
+
72
+ def exec_prepared(*args, &blk)
73
+ return super unless SqlPatches.should_measure?
74
+
75
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
76
+ result = super
77
+ elapsed_time = SqlPatches.elapsed_time(start)
78
+ mapped = args[0]
79
+ mapped = @prepare_map[mapped] || args[0] if @prepare_map
80
+ record = ::Rack::MiniProfiler.record_sql(mapped, elapsed_time)
81
+ result.instance_variable_set("@miniprofiler_sql_id", record) if result
82
+
83
+ result
84
+ end
85
+
86
+ def send_query_prepared(*args, &blk)
87
+ return super unless SqlPatches.should_measure?
88
+
89
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
90
+ result = super
91
+ elapsed_time = SqlPatches.elapsed_time(start)
92
+ mapped = args[0]
93
+ mapped = @prepare_map[mapped] || args[0] if @prepare_map
94
+ record = ::Rack::MiniProfiler.record_sql(mapped, elapsed_time)
95
+ result.instance_variable_set("@miniprofiler_sql_id", record) if result
96
+
97
+ result
98
+ end
99
+
100
+ def async_exec(*args, &blk)
101
+ return super unless SqlPatches.should_measure?
102
+
103
+ start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
104
+ result = super
105
+ elapsed_time = SqlPatches.elapsed_time(start)
106
+ record = ::Rack::MiniProfiler.record_sql(args[0], elapsed_time)
107
+ result.instance_variable_set("@miniprofiler_sql_id", record) if result
108
+
109
+ result
110
+ end
111
+ end
112
+
113
+ prepend MiniProfiler
114
+ alias_method :query, :exec
115
+ end
data/lib/patches/db/pg.rb CHANGED
@@ -1,122 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # PG patches, keep in mind exec and async_exec have a exec{|r| } semantics that is yet to be implemented
4
- class PG::Result
5
- alias_method :each_without_profiling, :each
6
- alias_method :values_without_profiling, :values
7
-
8
- def values(*args, &blk)
9
- return values_without_profiling(*args, &blk) unless defined?(@miniprofiler_sql_id)
10
- mp_report_sql do
11
- values_without_profiling(*args , &blk)
12
- end
13
- end
14
-
15
- def each(*args, &blk)
16
- return each_without_profiling(*args, &blk) unless defined?(@miniprofiler_sql_id)
17
- mp_report_sql do
18
- each_without_profiling(*args, &blk)
19
- end
20
- end
21
-
22
- def mp_report_sql(&block)
23
- start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
24
- result = yield
25
- elapsed_time = SqlPatches.elapsed_time(start)
26
- @miniprofiler_sql_id.report_reader_duration(elapsed_time)
27
- result
28
- end
29
- end
30
-
31
- class PG::Connection
32
- alias_method :exec_without_profiling, :exec
33
- alias_method :async_exec_without_profiling, :async_exec
34
- alias_method :exec_prepared_without_profiling, :exec_prepared
35
- alias_method :send_query_prepared_without_profiling, :send_query_prepared
36
- alias_method :prepare_without_profiling, :prepare
37
-
38
- if Gem::Version.new(PG::VERSION) >= Gem::Version.new("1.1.0")
39
- alias_method :exec_params_without_profiling, :exec_params
40
- end
41
-
42
- def prepare(*args, &blk)
43
- # we have no choice but to do this here,
44
- # if we do the check for profiling first, our cache may miss critical stuff
45
-
46
- @prepare_map ||= {}
47
- @prepare_map[args[0]] = args[1]
48
- # dont leak more than 10k ever
49
- @prepare_map = {} if @prepare_map.length > 1000
50
-
51
- return prepare_without_profiling(*args, &blk) unless SqlPatches.should_measure?
52
- prepare_without_profiling(*args, &blk)
53
- end
54
-
55
- def exec(*args, &blk)
56
- return exec_without_profiling(*args, &blk) unless SqlPatches.should_measure?
57
-
58
- start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
59
- result = exec_without_profiling(*args, &blk)
60
- elapsed_time = SqlPatches.elapsed_time(start)
61
- record = ::Rack::MiniProfiler.record_sql(args[0], elapsed_time)
62
- result.instance_variable_set("@miniprofiler_sql_id", record) if result
63
-
64
- result
65
- end
66
-
67
- if Gem::Version.new(PG::VERSION) >= Gem::Version.new("1.1.0")
68
- def exec_params(*args, &blk)
69
- return exec_params_without_profiling(*args, &blk) unless SqlPatches.should_measure?
70
-
71
- start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
72
- result = exec_params_without_profiling(*args, &blk)
73
- elapsed_time = SqlPatches.elapsed_time(start)
74
- record = ::Rack::MiniProfiler.record_sql(args[0], elapsed_time)
75
- result.instance_variable_set("@miniprofiler_sql_id", record) if result
76
-
77
- result
78
- end
79
- end
80
-
81
- def exec_prepared(*args, &blk)
82
- return exec_prepared_without_profiling(*args, &blk) unless SqlPatches.should_measure?
83
-
84
- start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
85
- result = exec_prepared_without_profiling(*args, &blk)
86
- elapsed_time = SqlPatches.elapsed_time(start)
87
- mapped = args[0]
88
- mapped = @prepare_map[mapped] || args[0] if @prepare_map
89
- record = ::Rack::MiniProfiler.record_sql(mapped, elapsed_time)
90
- result.instance_variable_set("@miniprofiler_sql_id", record) if result
91
-
92
- result
93
- end
94
-
95
- def send_query_prepared(*args, &blk)
96
- return send_query_prepared_without_profiling(*args, &blk) unless SqlPatches.should_measure?
97
-
98
- start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
99
- result = send_query_prepared_without_profiling(*args, &blk)
100
- elapsed_time = SqlPatches.elapsed_time(start)
101
- mapped = args[0]
102
- mapped = @prepare_map[mapped] || args[0] if @prepare_map
103
- record = ::Rack::MiniProfiler.record_sql(mapped, elapsed_time)
104
- result.instance_variable_set("@miniprofiler_sql_id", record) if result
105
-
106
- result
107
- end
108
-
109
- def async_exec(*args, &blk)
110
- return async_exec_without_profiling(*args, &blk) unless SqlPatches.should_measure?
111
-
112
- start = Process.clock_gettime(Process::CLOCK_MONOTONIC)
113
- result = exec_without_profiling(*args, &blk)
114
- elapsed_time = SqlPatches.elapsed_time(start)
115
- record = ::Rack::MiniProfiler.record_sql(args[0], elapsed_time)
116
- result.instance_variable_set("@miniprofiler_sql_id", record) if result
117
-
118
- result
119
- end
120
-
121
- alias_method :query, :exec
3
+ if defined?(Rack::MINI_PROFILER_PREPEND_PG_PATCH)
4
+ require "patches/db/pg/prepend"
5
+ else
6
+ require "patches/db/pg/alias_method"
122
7
  end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Rack
4
+ MINI_PROFILER_PREPEND_PG_PATCH = true
5
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  lib = File.expand_path('../lib', __FILE__)
4
- $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ $LOAD_PATH.unshift(lib) if !$LOAD_PATH.include?(lib)
5
5
  require 'mini_profiler/version'
6
6
 
7
7
  Gem::Specification.new do |s|
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
21
21
  "CHANGELOG.md"
22
22
  ]
23
23
  s.add_runtime_dependency 'rack', '>= 1.2.0'
24
- s.required_ruby_version = '>= 2.7.0'
24
+ s.required_ruby_version = '>= 3.1.0'
25
25
 
26
26
  s.metadata = {
27
27
  'source_code_uri' => Rack::MiniProfiler::SOURCE_CODE_URI,
@@ -41,7 +41,7 @@ Gem::Specification.new do |s|
41
41
  s.add_development_dependency 'rubocop-discourse'
42
42
  s.add_development_dependency 'listen'
43
43
  s.add_development_dependency 'webpacker'
44
- s.add_development_dependency 'rails', '~> 6.0'
44
+ s.add_development_dependency 'rails', '>= 7.1'
45
45
  s.add_development_dependency 'webmock', '3.9.1'
46
46
  s.add_development_dependency 'rubyzip'
47
47
 
metadata CHANGED
@@ -1,16 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rack-mini-profiler
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.1
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sam Saffron
8
8
  - Robin Ward
9
9
  - Aleks Totic
10
- autorequire:
11
10
  bindir: bin
12
11
  cert_chain: []
13
- date: 2024-02-14 00:00:00.000000000 Z
12
+ date: 2025-06-10 00:00:00.000000000 Z
14
13
  dependencies:
15
14
  - !ruby/object:Gem::Dependency
16
15
  name: rack
@@ -212,16 +211,16 @@ dependencies:
212
211
  name: rails
213
212
  requirement: !ruby/object:Gem::Requirement
214
213
  requirements:
215
- - - "~>"
214
+ - - ">="
216
215
  - !ruby/object:Gem::Version
217
- version: '6.0'
216
+ version: '7.1'
218
217
  type: :development
219
218
  prerelease: false
220
219
  version_requirements: !ruby/object:Gem::Requirement
221
220
  requirements:
222
- - - "~>"
221
+ - - ">="
223
222
  - !ruby/object:Gem::Version
224
- version: '6.0'
223
+ version: '7.1'
225
224
  - !ruby/object:Gem::Dependency
226
225
  name: webmock
227
226
  requirement: !ruby/object:Gem::Requirement
@@ -329,6 +328,8 @@ files:
329
328
  - lib/patches/db/nobrainer.rb
330
329
  - lib/patches/db/oracle_enhanced.rb
331
330
  - lib/patches/db/pg.rb
331
+ - lib/patches/db/pg/alias_method.rb
332
+ - lib/patches/db/pg/prepend.rb
332
333
  - lib/patches/db/plucky.rb
333
334
  - lib/patches/db/riak.rb
334
335
  - lib/patches/db/rsolr.rb
@@ -337,6 +338,7 @@ files:
337
338
  - lib/patches/sql_patches.rb
338
339
  - lib/prepend_mysql2_patch.rb
339
340
  - lib/prepend_net_http_patch.rb
341
+ - lib/prepend_pg_patch.rb
340
342
  - lib/rack-mini-profiler.rb
341
343
  - rack-mini-profiler.gemspec
342
344
  homepage: https://miniprofiler.com
@@ -345,7 +347,6 @@ licenses:
345
347
  metadata:
346
348
  source_code_uri: https://github.com/MiniProfiler/rack-mini-profiler
347
349
  changelog_uri: https://github.com/MiniProfiler/rack-mini-profiler/blob/master/CHANGELOG.md
348
- post_install_message:
349
350
  rdoc_options: []
350
351
  require_paths:
351
352
  - lib
@@ -353,15 +354,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
353
354
  requirements:
354
355
  - - ">="
355
356
  - !ruby/object:Gem::Version
356
- version: 2.7.0
357
+ version: 3.1.0
357
358
  required_rubygems_version: !ruby/object:Gem::Requirement
358
359
  requirements:
359
360
  - - ">="
360
361
  - !ruby/object:Gem::Version
361
362
  version: '0'
362
363
  requirements: []
363
- rubygems_version: 3.5.4
364
- signing_key:
364
+ rubygems_version: 3.6.3
365
365
  specification_version: 4
366
366
  summary: Profiles loading speed for rack applications.
367
367
  test_files: []