|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/** |
|
4
|
|
|
* This file contains a (static) class that will track some debug information |
|
5
|
|
|
* if debug is on. |
|
6
|
|
|
* |
|
7
|
|
|
* @name ElkArte Forum |
|
8
|
|
|
* @copyright ElkArte Forum contributors |
|
9
|
|
|
* @license BSD http://opensource.org/licenses/BSD-3-Clause |
|
10
|
|
|
* |
|
11
|
|
|
* This file contains code covered by: |
|
12
|
|
|
* copyright: 2011 Simple Machines (http://www.simplemachines.org) |
|
13
|
|
|
* license: BSD, See included LICENSE.TXT for terms and conditions. |
|
14
|
|
|
* |
|
15
|
|
|
* @version 1.1 |
|
16
|
|
|
* |
|
17
|
|
|
*/ |
|
18
|
|
|
|
|
19
|
|
|
/** |
|
20
|
|
|
* Stored debugging information. |
|
21
|
|
|
*/ |
|
22
|
|
|
class Debug |
|
23
|
|
|
{ |
|
24
|
|
|
/** |
|
25
|
|
|
* This is used to remember if the debug is on or off |
|
26
|
|
|
* @var bool |
|
27
|
|
|
*/ |
|
28
|
|
|
private $_track = true; |
|
29
|
|
|
|
|
30
|
|
|
/** |
|
31
|
|
|
* A list of known debug entities (here to preserve a kind of order) |
|
32
|
|
|
* @var mixed[] |
|
33
|
|
|
*/ |
|
34
|
|
|
private $_debugs = array( |
|
35
|
|
|
'templates' => array(), |
|
36
|
|
|
'sub_templates' => array(), |
|
37
|
|
|
'language_files' => array(), |
|
38
|
|
|
'sheets' => array(), |
|
39
|
|
|
'javascript' => array(), |
|
40
|
|
|
'hooks' => array(), |
|
41
|
|
|
'files_included' => array(), |
|
42
|
|
|
'tokens' => array(), |
|
43
|
|
|
); |
|
44
|
|
|
|
|
45
|
|
|
/** |
|
46
|
|
|
* Holds the output ot the getrusage php function |
|
47
|
|
|
* @var mixed[] |
|
48
|
|
|
*/ |
|
49
|
|
|
private $_rusage = array(); |
|
50
|
|
|
|
|
51
|
|
|
/** |
|
52
|
|
|
* Holds All the cache hits for a page load |
|
53
|
|
|
* @var string[] |
|
54
|
|
|
*/ |
|
55
|
|
|
private $_cache_hits = array(); |
|
56
|
|
|
|
|
57
|
|
|
/** |
|
58
|
|
|
* Number of times the cache has been used |
|
59
|
|
|
* @var int |
|
60
|
|
|
*/ |
|
61
|
|
|
private $_cache_count = 0; |
|
62
|
|
|
|
|
63
|
|
|
/** |
|
64
|
|
|
* All the queries executed |
|
65
|
|
|
* @var mixed[] |
|
66
|
|
|
*/ |
|
67
|
|
|
private $_db_cache = array(); |
|
68
|
|
|
|
|
69
|
|
|
/** |
|
70
|
|
|
* Number of queries |
|
71
|
|
|
* @var int |
|
72
|
|
|
*/ |
|
73
|
|
|
private $_db_count = 0; |
|
74
|
|
|
|
|
75
|
|
|
/** |
|
76
|
|
|
* Some generic "system" debug info |
|
77
|
|
|
* @var string[] |
|
78
|
|
|
*/ |
|
79
|
|
|
private $_system = array(); |
|
80
|
|
|
|
|
81
|
|
|
/** |
|
82
|
|
|
* The instance of the class |
|
83
|
|
|
* @var object |
|
84
|
|
|
*/ |
|
85
|
|
|
private static $_instance = null; |
|
86
|
|
|
|
|
87
|
|
|
|
|
88
|
|
|
/** |
|
89
|
|
|
* Adds a new generic debug entry |
|
90
|
|
|
* |
|
91
|
|
|
* @param string $type the kind of debug entry |
|
92
|
|
|
* @param string|string[] string or array of the entry to show |
|
93
|
|
|
*/ |
|
94
|
|
|
public function add($type, $value) |
|
95
|
|
|
{ |
|
96
|
|
|
if (!$this->_track) |
|
97
|
|
|
return; |
|
98
|
|
|
|
|
99
|
|
|
if (is_array($value)) |
|
100
|
|
|
$this->_debugs[$type] = array_merge($this->_debugs[$type], $value); |
|
101
|
|
|
else |
|
102
|
|
|
$this->_debugs[$type][] = $value; |
|
103
|
|
|
} |
|
104
|
|
|
|
|
105
|
|
|
/** |
|
106
|
|
|
* Adds a new cache hits |
|
107
|
|
|
* |
|
108
|
|
|
* @param mixed[] $value contains the relevant cache info, in the form: |
|
109
|
|
|
* d => method: put or get |
|
110
|
|
|
* k => cache key |
|
111
|
|
|
* t => time taken to get/put the entry |
|
112
|
|
|
* s => length of the serialized value |
|
113
|
|
|
*/ |
|
114
|
|
|
public function cache($value) |
|
115
|
|
|
{ |
|
116
|
|
|
if (!$this->_track) |
|
117
|
|
|
return; |
|
118
|
|
|
|
|
119
|
|
|
$this->_cache_hits[] = $value; |
|
120
|
|
|
$this->_cache_count++; |
|
121
|
|
|
} |
|
122
|
|
|
|
|
123
|
|
|
/** |
|
124
|
|
|
* Return the number of cache hits |
|
125
|
|
|
* |
|
126
|
|
|
* @return int |
|
127
|
|
|
*/ |
|
128
|
|
|
public function cache_count() |
|
129
|
|
|
{ |
|
130
|
|
|
return $this->_cache_count; |
|
131
|
|
|
} |
|
132
|
|
|
|
|
133
|
|
|
/** |
|
134
|
|
|
* Adds a new database query |
|
135
|
|
|
* |
|
136
|
|
|
* @param mixed[] $value contains the relevant queries info, in the form: |
|
137
|
|
|
* q => the query string (only for the first 50 queries, after that only a "...") |
|
138
|
|
|
* f => the file in which the query has been executed |
|
139
|
|
|
* l => the line at which the query has been executed |
|
140
|
|
|
* s => seconds at which the query has been executed into the request |
|
141
|
|
|
* t => time taken by the query |
|
142
|
|
|
*/ |
|
143
|
|
|
public function db_query($value) |
|
144
|
|
|
{ |
|
145
|
|
|
if (!$this->_track) |
|
146
|
|
|
return; |
|
147
|
|
|
|
|
148
|
|
|
$this->_db_cache[] = $value; |
|
149
|
|
|
$this->_db_count++; |
|
150
|
|
|
} |
|
151
|
|
|
|
|
152
|
|
|
/** |
|
153
|
|
|
* Merges the values passed with the current database entries |
|
154
|
|
|
* |
|
155
|
|
|
* @param mixed[] $value An array of queries info, see the db method for details |
|
156
|
|
|
*/ |
|
157
|
|
|
public function merge_db($value) |
|
158
|
|
|
{ |
|
159
|
|
|
if (!$this->_track) |
|
160
|
|
|
return; |
|
161
|
|
|
|
|
162
|
|
|
$this->_db_cache = array_merge($value, $this->_db_cache); |
|
163
|
|
|
$this->_db_count = count($this->_db_cache) + 1; |
|
164
|
|
|
} |
|
165
|
|
|
|
|
166
|
|
|
/** |
|
167
|
|
|
* Return the current database entries |
|
168
|
|
|
* |
|
169
|
|
|
* @return array |
|
170
|
|
|
*/ |
|
171
|
|
|
public function get_db() |
|
172
|
|
|
{ |
|
173
|
|
|
if (!$this->_track) |
|
174
|
|
|
return array(); |
|
175
|
|
|
|
|
176
|
|
|
return $this->_db_cache; |
|
177
|
|
|
} |
|
178
|
|
|
|
|
179
|
|
|
/** |
|
180
|
|
|
* Adds a new getrusage value (by default two are added: one at the beginning |
|
181
|
|
|
* of the script execution and one at the end) |
|
182
|
|
|
* |
|
183
|
|
|
* @param string $point can be end or start depending on when the function |
|
184
|
|
|
* is called |
|
185
|
|
|
* @param string[]|null $rusage value of getrusage or null to let the method call it |
|
186
|
|
|
*/ |
|
187
|
|
|
public function rusage($point, $rusage = null) |
|
188
|
|
|
{ |
|
189
|
|
|
// getrusage is missing in php < 7 on Windows |
|
190
|
|
|
if ($this->_track === false || function_exists('getrusage') === false) |
|
191
|
|
|
{ |
|
192
|
|
|
return; |
|
193
|
|
|
} |
|
194
|
|
|
|
|
195
|
|
|
if ($rusage === null) |
|
196
|
|
|
$this->_rusage[$point] = getrusage(); |
|
197
|
|
|
else |
|
198
|
|
|
$this->_rusage[$point] = $rusage; |
|
199
|
|
|
} |
|
200
|
|
|
|
|
201
|
|
|
/** |
|
202
|
|
|
* Enables tracking of debug entries |
|
203
|
|
|
*/ |
|
204
|
|
|
public function on() |
|
205
|
|
|
{ |
|
206
|
|
|
$this->_track = true; |
|
207
|
|
|
} |
|
208
|
|
|
|
|
209
|
|
|
/** |
|
210
|
|
|
* Disables tracking of debug entries |
|
211
|
|
|
*/ |
|
212
|
|
|
public function off() |
|
213
|
|
|
{ |
|
214
|
|
|
$this->_track = false; |
|
215
|
|
|
} |
|
216
|
|
|
|
|
217
|
|
|
/** |
|
218
|
|
|
* Toggles the visibility of the queries |
|
219
|
|
|
*/ |
|
220
|
|
|
public function toggleViewQueries() |
|
221
|
|
|
{ |
|
222
|
|
|
$_SESSION['view_queries'] = $_SESSION['view_queries'] == 1 ? 0 : 1; |
|
223
|
|
|
} |
|
224
|
|
|
|
|
225
|
|
|
/** |
|
226
|
|
|
* Collects some other generic system information necessary for the |
|
227
|
|
|
* debug screen |
|
228
|
|
|
*/ |
|
229
|
|
|
private function _prepare_last_bits() |
|
230
|
|
|
{ |
|
231
|
|
|
global $context; |
|
232
|
|
|
|
|
233
|
|
|
if (empty($_SESSION['view_queries'])) |
|
234
|
|
|
$_SESSION['view_queries'] = 0; |
|
235
|
|
|
|
|
236
|
|
|
$files = get_included_files(); |
|
237
|
|
|
$total_size = 0; |
|
238
|
|
|
for ($i = 0, $n = count($files); $i < $n; $i++) |
|
239
|
|
|
{ |
|
240
|
|
|
if (file_exists($files[$i])) |
|
241
|
|
|
$total_size += filesize($files[$i]); |
|
242
|
|
|
$this->add('files_included', strtr($files[$i], array(BOARDDIR => '.'))); |
|
243
|
|
|
} |
|
244
|
|
|
|
|
245
|
|
|
if (!empty($this->_db_cache)) |
|
246
|
|
|
$_SESSION['debug'] = $this->_db_cache; |
|
247
|
|
|
|
|
248
|
|
|
// Compute some system info, if we can |
|
249
|
|
|
$this->_system['system_type'] = php_uname(); |
|
250
|
|
|
require_once(SUBSDIR . '/Server.subs.php'); |
|
251
|
|
|
$this->_system['server_load'] = detectServerLoad(); |
|
252
|
|
|
$this->_system['script_mem_load'] = byte_format(memory_get_peak_usage()); |
|
253
|
|
|
|
|
254
|
|
|
// getrusage() information is CPU time, not wall clock time like microtime, *nix only |
|
255
|
|
|
$this->rusage('end'); |
|
256
|
|
|
|
|
257
|
|
|
if (!empty($this->_rusage)) |
|
258
|
|
|
{ |
|
259
|
|
|
$this->_system['script_cpu_load'] = ($this->_rusage['end']['ru_utime.tv_sec'] - $this->_rusage['start']['ru_utime.tv_sec'] + ($this->_rusage['end']['ru_utime.tv_usec'] / 1000000)) . ' / ' . ($this->_rusage['end']['ru_stime.tv_sec'] - $this->_rusage['start']['ru_stime.tv_sec'] + ($this->_rusage['end']['ru_stime.tv_usec'] / 1000000)); |
|
260
|
|
|
} |
|
261
|
|
|
|
|
262
|
|
|
$this->_system['browser'] = $context['browser_body_id'] . ' <em>(' . implode('</em>, <em>', array_reverse(array_keys($context['browser'], true))) . ')</em>'; |
|
263
|
|
|
|
|
264
|
|
|
// What tokens are active? |
|
265
|
|
|
if (isset($_SESSION['token'])) |
|
266
|
|
|
$this->add('tokens', array_keys($_SESSION['token'])); |
|
267
|
|
|
} |
|
268
|
|
|
|
|
269
|
|
|
/** |
|
270
|
|
|
* This function shows the debug information tracked |
|
271
|
|
|
*/ |
|
272
|
|
|
public function display() |
|
273
|
|
|
{ |
|
274
|
|
|
global $scripturl, $txt; |
|
275
|
|
|
|
|
276
|
|
|
$this->_prepare_last_bits(); |
|
277
|
|
|
$expand_id = array(); |
|
278
|
|
|
|
|
279
|
|
|
// Gotta have valid HTML ;). |
|
280
|
|
|
$temp = ob_get_contents(); |
|
281
|
|
|
ob_clean(); |
|
282
|
|
|
|
|
283
|
|
|
echo preg_replace('~</body>\s*</html>~', '', $temp), ' |
|
284
|
|
|
<div id="debug_logging_wrapper"> |
|
285
|
|
|
<div id="debug_logging" class="smalltext">'; |
|
286
|
|
|
|
|
287
|
|
|
foreach ($this->_system as $key => $value) |
|
288
|
|
|
if (!empty($value)) |
|
289
|
|
|
echo ' |
|
290
|
|
|
', $txt['debug_' . $key], $value, '<br />'; |
|
291
|
|
|
|
|
292
|
|
|
$expandable = array('hooks', 'files_included'); |
|
293
|
|
|
|
|
294
|
|
|
foreach ($this->_debugs as $key => $value) |
|
295
|
|
|
{ |
|
296
|
|
|
$value = array_map('htmlentities', $value); |
|
297
|
|
|
if (in_array($key, $expandable)) |
|
298
|
|
|
{ |
|
299
|
|
|
$key = htmlentities($key, ENT_QUOTES); |
|
300
|
|
|
$expand_id[] = 'debug_' . $key; |
|
301
|
|
|
$pre = ' (<a id="debug_' . $key . '" href="#">' . $txt['debug_show'] . '</a><span class="hide">'; |
|
302
|
|
|
$post = '</span>)'; |
|
303
|
|
|
} |
|
304
|
|
|
else |
|
305
|
|
|
{ |
|
306
|
|
|
$pre = ''; |
|
307
|
|
|
$post = ''; |
|
308
|
|
|
} |
|
309
|
|
|
|
|
310
|
|
|
echo ' |
|
311
|
|
|
', $txt['debug_' . $key], count($value), ' - ' . $pre . '<em>', implode('</em>, <em>', $value), '</em>.' . $post . '<br />'; |
|
312
|
|
|
} |
|
313
|
|
|
|
|
314
|
|
|
// If the cache is on, how successful was it? |
|
315
|
|
|
if (Cache::instance()->isEnabled() && !empty($this->_cache_hits)) |
|
316
|
|
|
{ |
|
317
|
|
|
$entries = array(); |
|
318
|
|
|
$total_t = 0; |
|
319
|
|
|
$total_s = 0; |
|
320
|
|
|
foreach ($this->_cache_hits as $cache_hit) |
|
321
|
|
|
{ |
|
322
|
|
|
$entries[] = $cache_hit['d'] . ' ' . $cache_hit['k'] . ': ' . sprintf($txt['debug_cache_seconds_bytes'], comma_format($cache_hit['t'], 5), $cache_hit['s']); |
|
323
|
|
|
$total_t += $cache_hit['t']; |
|
324
|
|
|
$total_s += $cache_hit['s']; |
|
325
|
|
|
} |
|
326
|
|
|
$expand_id[] = 'debug_cache_info'; |
|
327
|
|
|
|
|
328
|
|
|
echo ' |
|
329
|
|
|
', $txt['debug_cache_hits'], $this->cache_count(), ': ', sprintf($txt['debug_cache_seconds_bytes_total'], comma_format($total_t, 5), comma_format($total_s)), ' (<a id="debug_cache_info" href="#">', $txt['debug_show'], '</a><span class="hide"><em>', implode('</em>, <em>', $entries), '</em></span>)<br />'; |
|
330
|
|
|
} |
|
331
|
|
|
|
|
332
|
|
|
// Want to see the querys in a new windows? |
|
333
|
|
|
echo ' |
|
334
|
|
|
<a href="', $scripturl, '?action=viewquery" target="_blank" class="new_win">', sprintf($txt['debug_queries_used'], $this->_db_count), '</a><br />'; |
|
335
|
|
|
|
|
336
|
|
|
if ($_SESSION['view_queries'] == 1 && !empty($this->_db_cache)) |
|
337
|
|
|
$this->_show_queries(); |
|
338
|
|
|
|
|
339
|
|
|
// Or show/hide the querys in line with all of this data |
|
340
|
|
|
echo ' |
|
341
|
|
|
<a href="' . $scripturl . '?action=viewquery;sa=hide">', $txt['debug_' . (empty($_SESSION['view_queries']) ? 'show' : 'hide') . '_queries'], '</a> |
|
342
|
|
|
</div> |
|
343
|
|
|
</div>'; |
|
344
|
|
|
|
|
345
|
|
|
if (!empty($expand_id)) |
|
346
|
|
|
{ |
|
347
|
|
|
echo ' |
|
348
|
|
|
<script> |
|
349
|
|
|
$(function() { |
|
350
|
|
|
$(\'#', implode(', #', $expand_id), '\').click(function(ev) { |
|
351
|
|
|
ev.preventDefault(); |
|
352
|
|
|
$(this).next().toggle(); |
|
353
|
|
|
$(this).remove(); |
|
354
|
|
|
}); |
|
355
|
|
|
}); |
|
356
|
|
|
</script>'; |
|
357
|
|
|
} |
|
358
|
|
|
|
|
359
|
|
|
echo ' |
|
360
|
|
|
</body></html>'; |
|
361
|
|
|
} |
|
362
|
|
|
|
|
363
|
|
|
/** |
|
364
|
|
|
* Displays a page with all the queries executed during the "current" |
|
365
|
|
|
* page load and allows to EXPLAIN them |
|
366
|
|
|
* |
|
367
|
|
|
* @param integer $query_id the id of the query to EXPLAIN, if -1 no queries are explained |
|
368
|
|
|
*/ |
|
369
|
|
|
public function viewQueries($query_id) |
|
370
|
|
|
{ |
|
371
|
|
|
$queries_data = array(); |
|
372
|
|
|
|
|
373
|
|
|
$query_analysis = new Query_Analysis(); |
|
374
|
|
|
|
|
375
|
|
|
foreach ($_SESSION['debug'] as $q => $query_data) |
|
376
|
|
|
{ |
|
377
|
|
|
$queries_data[$q] = $query_analysis->extractInfo($query_data); |
|
378
|
|
|
|
|
379
|
|
|
// Explain the query. |
|
380
|
|
|
if ($query_id == $q && $queries_data[$q]['is_select']) |
|
381
|
|
|
{ |
|
382
|
|
|
$queries_data[$q]['explain'] = $query_analysis->doExplain(); |
|
383
|
|
|
} |
|
384
|
|
|
} |
|
385
|
|
|
|
|
386
|
|
|
return $queries_data; |
|
387
|
|
|
} |
|
388
|
|
|
|
|
389
|
|
|
/** |
|
390
|
|
|
* Displays a list of queries executed during the current |
|
391
|
|
|
* page load |
|
392
|
|
|
*/ |
|
393
|
|
|
private function _show_queries() |
|
394
|
|
|
{ |
|
395
|
|
|
global $scripturl, $txt; |
|
396
|
|
|
|
|
397
|
|
|
foreach ($this->_db_cache as $q => $qq) |
|
398
|
|
|
{ |
|
399
|
|
|
$is_select = strpos(trim($qq['q']), 'SELECT') === 0 || preg_match('~^INSERT(?: IGNORE)? INTO \w+(?:\s+\([^)]+\))?\s+SELECT .+$~s', trim($qq['q'])) != 0 || strpos(trim($qq['q']), 'WITH') === 0; |
|
400
|
|
|
|
|
401
|
|
|
// Temporary tables created in earlier queries are not explainable. |
|
402
|
|
|
if ($is_select) |
|
403
|
|
|
{ |
|
404
|
|
|
foreach (array('log_topics_unread', 'topics_posted_in', 'tmp_log_search_topics', 'tmp_log_search_messages') as $tmp) |
|
405
|
|
|
if (strpos(trim($qq['q']), $tmp) !== false) |
|
406
|
|
|
{ |
|
407
|
|
|
$is_select = false; |
|
408
|
|
|
break; |
|
409
|
|
|
} |
|
410
|
|
|
} |
|
411
|
|
|
// But actual creation of the temporary tables are. |
|
412
|
|
|
elseif (preg_match('~^CREATE TEMPORARY TABLE .+?SELECT .+$~s', trim($qq['q'])) != 0) |
|
413
|
|
|
$is_select = true; |
|
414
|
|
|
|
|
415
|
|
|
// Make the filenames look a bit better. |
|
416
|
|
|
if (isset($qq['f'])) |
|
417
|
|
|
$qq['f'] = preg_replace('~^' . preg_quote(BOARDDIR, '~') . '~', '...', $qq['f']); |
|
418
|
|
|
|
|
419
|
|
|
echo ' |
|
420
|
|
|
<strong>', $is_select ? '<a href="' . $scripturl . '?action=viewquery;qq=' . ($q + 1) . '#qq' . $q . '" target="_blank" class="new_win">' : '', nl2br(str_replace("\t", ' ', htmlspecialchars(ltrim($qq['q'], "\n\r"), ENT_COMPAT, 'UTF-8'))) . ($is_select ? '</a></strong>' : '</strong>') . '<br /> |
|
421
|
|
|
'; |
|
422
|
|
|
if (!empty($qq['f']) && !empty($qq['l'])) |
|
423
|
|
|
echo sprintf($txt['debug_query_in_line'], $qq['f'], $qq['l']); |
|
424
|
|
|
|
|
425
|
|
|
if (isset($qq['s'], $qq['t']) && isset($txt['debug_query_which_took_at'])) |
|
426
|
|
|
echo sprintf($txt['debug_query_which_took_at'], round($qq['t'], 8), round($qq['s'], 8)) . '<br />'; |
|
427
|
|
|
elseif (isset($qq['t'])) |
|
428
|
|
|
echo sprintf($txt['debug_query_which_took'], round($qq['t'], 8)) . '<br />'; |
|
429
|
|
|
echo ' |
|
430
|
|
|
<br />'; |
|
431
|
|
|
} |
|
432
|
|
|
} |
|
433
|
|
|
|
|
434
|
|
|
/** |
|
435
|
|
|
* Return the single instance of this class |
|
436
|
|
|
* @return Debug |
|
437
|
|
|
*/ |
|
438
|
|
|
public static function instance() |
|
439
|
|
|
{ |
|
440
|
|
|
if (self::$_instance === null) |
|
441
|
|
|
self::$_instance = new Debug(); |
|
442
|
|
|
|
|
443
|
|
|
return self::$_instance; |
|
444
|
|
|
} |
|
445
|
|
|
} |