4f998b64718cab82a209123cb3bcb8294241b620
[Packages/TYPO3.CMS.git] / typo3 / sysext / core / Classes / TimeTracker / TimeTracker.php
1 <?php
2 namespace TYPO3\CMS\Core\TimeTracker;
3
4 /*
5 * This file is part of the TYPO3 CMS project.
6 *
7 * It is free software; you can redistribute it and/or modify it under
8 * the terms of the GNU General Public License, either version 2
9 * of the License, or any later version.
10 *
11 * For the full copyright and license information, please read the
12 * LICENSE.txt file that was distributed with this source code.
13 *
14 * The TYPO3 project - inspiring people to share!
15 */
16 use TYPO3\CMS\Backend\Utility\IconUtility;
17
18 /**
19 * Frontend Timetracking functions
20 *
21 * Is used to register how much time is used with operations in TypoScript
22 */
23 class TimeTracker {
24
25 /**
26 * Is loaded with the millisecond time when this object is created
27 *
28 * @var int
29 */
30 public $starttime = 0;
31
32 /**
33 * Log Rendering flag. If set, ->push() and ->pull() is called from the cObj->cObjGetSingle().
34 * This determines whether or not the TypoScript parsing activity is logged. But it also slows down the rendering
35 *
36 * @var bool
37 */
38 public $LR = 1;
39
40 /**
41 * @var array
42 */
43 public $printConf = array(
44 'showParentKeys' => 1,
45 'contentLength' => 10000,
46 // Determines max length of displayed content before it gets cropped.
47 'contentLength_FILE' => 400,
48 // Determines max length of displayed content FROM FILE cObjects before it gets cropped. Reason is that most FILE cObjects are huge and often used as template-code.
49 'flag_tree' => 1,
50 'flag_messages' => 1,
51 'flag_queries' => 0,
52 'flag_content' => 0,
53 'allTime' => 0,
54 'keyLgd' => 40
55 );
56
57 /**
58 * @var array
59 */
60 public $wrapError = array();
61
62 /**
63 * @var array
64 */
65 public $wrapIcon = array();
66
67 /**
68 * @var int
69 */
70 public $uniqueCounter = 0;
71
72 /**
73 * @var array
74 */
75 public $tsStack = array(array());
76
77 /**
78 * @var int
79 */
80 public $tsStackLevel = 0;
81
82 /**
83 * @var array
84 */
85 public $tsStackLevelMax = array();
86
87 /**
88 * @var array
89 */
90 public $tsStackLog = array();
91
92 /**
93 * @var int
94 */
95 public $tsStackPointer = 0;
96
97 /**
98 * @var array
99 */
100 public $currentHashPointer = array();
101
102 /**
103 * Log entries that take than this number of milliseconds (own time) will be highlighted during log display. Set 0 to disable highlighting.
104 *
105 * @var int
106 */
107 public $highlightLongerThan = 0;
108
109 /*******************************************
110 *
111 * Logging parsing times in the scripts
112 *
113 *******************************************/
114 /**
115 * Constructor
116 * Sets the starting time
117 *
118 * @return void
119 */
120 public function start() {
121 $this->wrapError = array(
122 0 => array('', ''),
123 1 => array('<strong>', '</strong>'),
124 2 => array('<strong style="color:#ff6600;">', '</strong>'),
125 3 => array('<strong style="color:#ff0000;">', '</strong>')
126 );
127 $this->wrapIcon = array(
128 0 => '',
129 1 => 'actions-document-info',
130 2 => 'status-dialog-warning',
131 3 => 'status-dialog-error'
132 );
133 $this->starttime = $this->getMilliseconds();
134 }
135
136 /**
137 * Pushes an element to the TypoScript tracking array
138 *
139 * @param string $tslabel Label string for the entry, eg. TypoScript property name
140 * @param string $value Additional value(?)
141 * @return void
142 * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::cObjGetSingle(), pull()
143 */
144 public function push($tslabel, $value = '') {
145 array_push($this->tsStack[$this->tsStackPointer], $tslabel);
146 array_push($this->currentHashPointer, 'timetracker_' . $this->uniqueCounter++);
147 $this->tsStackLevel++;
148 $this->tsStackLevelMax[] = $this->tsStackLevel;
149 // setTSlog
150 $k = end($this->currentHashPointer);
151 $this->tsStackLog[$k] = array(
152 'level' => $this->tsStackLevel,
153 'tsStack' => $this->tsStack,
154 'value' => $value,
155 'starttime' => microtime(TRUE),
156 'stackPointer' => $this->tsStackPointer
157 );
158 }
159
160 /**
161 * Pulls an element from the TypoScript tracking array
162 *
163 * @param string $content The content string generated within the push/pull part.
164 * @return void
165 * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::cObjGetSingle(), push()
166 */
167 public function pull($content = '') {
168 $k = end($this->currentHashPointer);
169 $this->tsStackLog[$k]['endtime'] = microtime(TRUE);
170 $this->tsStackLog[$k]['content'] = $content;
171 $this->tsStackLevel--;
172 array_pop($this->tsStack[$this->tsStackPointer]);
173 array_pop($this->currentHashPointer);
174 }
175
176 /**
177 * Logs the TypoScript entry
178 *
179 * @param string $content The message string
180 * @param int $num Message type: 0: information, 1: message, 2: warning, 3: error
181 * @return void
182 * @see \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::CONTENT()
183 */
184 public function setTSlogMessage($content, $num = 0) {
185 end($this->currentHashPointer);
186 $k = current($this->currentHashPointer);
187 // Enlarge the "details" column by adding a span
188 if (strlen($content) > 30) {
189 $placeholder = '<br /><span style="width: 300px; height: 1px; display: inline-block;"></span>';
190 }
191 $this->tsStackLog[$k]['message'][] = IconUtility::getSpriteIcon($this->wrapIcon[$num]) . $this->wrapError[$num][0] . htmlspecialchars($content) . $this->wrapError[$num][1] . $placeholder;
192 }
193
194 /**
195 * Set TSselectQuery - for messages in TypoScript debugger.
196 *
197 * @param array $data Query array
198 * @param string $msg Message/Label to attach
199 * @return void
200 */
201 public function setTSselectQuery(array $data, $msg = '') {
202 end($this->currentHashPointer);
203 $k = current($this->currentHashPointer);
204 if ($msg !== '') {
205 $data['msg'] = $msg;
206 }
207 $this->tsStackLog[$k]['selectQuery'][] = $data;
208 }
209
210 /**
211 * Increases the stack pointer
212 *
213 * @return void
214 * @see decStackPointer(), \TYPO3\CMS\Frontend\Page\PageGenerator::renderContent(), \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::cObjGetSingle()
215 */
216 public function incStackPointer() {
217 $this->tsStackPointer++;
218 $this->tsStack[$this->tsStackPointer] = array();
219 }
220
221 /**
222 * Decreases the stack pointer
223 *
224 * @return void
225 * @see incStackPointer(), \TYPO3\CMS\Frontend\Page\PageGenerator::renderContent(), \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::cObjGetSingle()
226 */
227 public function decStackPointer() {
228 unset($this->tsStack[$this->tsStackPointer]);
229 $this->tsStackPointer--;
230 }
231
232 /**
233 * Gets a microtime value as milliseconds value.
234 *
235 * @param float $microtime The microtime value - if not set the current time is used
236 * @return int The microtime value as milliseconds value
237 */
238 public function getMilliseconds($microtime = NULL) {
239 if (!isset($microtime)) {
240 $microtime = microtime(TRUE);
241 }
242 return round($microtime * 1000);
243 }
244
245 /**
246 * Gets the difference between a given microtime value and the starting time as milliseconds.
247 *
248 * @param float $microtime The microtime value - if not set the current time is used
249 * @return int The difference between a given microtime value and starting time as milliseconds
250 */
251 public function getDifferenceToStarttime($microtime = NULL) {
252 return $this->getMilliseconds($microtime) - $this->starttime;
253 }
254
255 /*******************************************
256 *
257 * Printing the parsing time information (for Admin Panel)
258 *
259 *******************************************/
260 /**
261 * Print TypoScript parsing log
262 *
263 * @return string HTML table with the information about parsing times.
264 */
265 public function printTSlog() {
266 // Calculate times and keys for the tsStackLog
267 foreach ($this->tsStackLog as $uniqueId => &$data) {
268 $data['endtime'] = $this->getDifferenceToStarttime($data['endtime']);
269 $data['starttime'] = $this->getDifferenceToStarttime($data['starttime']);
270 $data['deltatime'] = $data['endtime'] - $data['starttime'];
271 if (is_array($data['tsStack'])) {
272 $data['key'] = implode($data['stackPointer'] ? '.' : '/', end($data['tsStack']));
273 }
274 }
275 unset($data);
276 // Create hierarchical array of keys pointing to the stack
277 $arr = array();
278 foreach ($this->tsStackLog as $uniqueId => $data) {
279 $this->createHierarchyArray($arr, $data['level'], $uniqueId);
280 }
281 // Parsing the registeret content and create icon-html for the tree
282 $this->tsStackLog[$arr['0.'][0]]['content'] = $this->fixContent($arr['0.'], $this->tsStackLog[$arr['0.'][0]]['content'], '', 0, $arr['0.'][0]);
283 // Displaying the tree:
284 $outputArr = array();
285 $outputArr[] = $this->fw('TypoScript Key');
286 $outputArr[] = $this->fw('Value');
287 if ($this->printConf['allTime']) {
288 $outputArr[] = $this->fw('Time');
289 $outputArr[] = $this->fw('Own');
290 $outputArr[] = $this->fw('Sub');
291 $outputArr[] = $this->fw('Total');
292 } else {
293 $outputArr[] = $this->fw('Own');
294 }
295 $outputArr[] = $this->fw('Details');
296 $out = '';
297 foreach ($outputArr as $row) {
298 $out .= '
299 <th><strong>' . $row . '</strong></th>';
300 }
301 $out = '<tr>' . $out . '</tr>';
302 $flag_tree = $this->printConf['flag_tree'];
303 $flag_messages = $this->printConf['flag_messages'];
304 $flag_content = $this->printConf['flag_content'];
305 $flag_queries = $this->printConf['flag_queries'];
306 $keyLgd = $this->printConf['keyLgd'];
307 $factor = $this->printConf['factor'];
308 $col = $this->printConf['col'];
309 $highlight_col = $this->printConf['highlight_col'];
310 $c = 0;
311 foreach ($this->tsStackLog as $uniqueId => $data) {
312 if ($this->highlightLongerThan && (int)$data['owntime'] > (int)$this->highlightLongerThan) {
313 $logRowClass = 'typo3-adminPanel-logRow-highlight';
314 } else {
315 $logRowClass = $c % 2 ? 'line-odd' : 'line-even';
316 }
317 $item = '';
318 // If first...
319 if (!$c) {
320 $data['icons'] = '';
321 $data['key'] = 'Script Start';
322 $data['value'] = '';
323 }
324 // Key label:
325 $keyLabel = '';
326 if (!$flag_tree && $data['stackPointer']) {
327 $temp = array();
328 foreach ($data['tsStack'] as $k => $v) {
329 $temp[] = \TYPO3\CMS\Core\Utility\GeneralUtility::fixed_lgd_cs(implode($v, $k ? '.' : '/'), -$keyLgd);
330 }
331 array_pop($temp);
332 $temp = array_reverse($temp);
333 array_pop($temp);
334 if (!empty($temp)) {
335 $keyLabel = '<br /><span style="color:#999999;">' . implode($temp, '<br />') . '</span>';
336 }
337 }
338 if ($flag_tree) {
339 $tmp = \TYPO3\CMS\Core\Utility\GeneralUtility::trimExplode('.', $data['key'], TRUE);
340 $theLabel = end($tmp);
341 } else {
342 $theLabel = $data['key'];
343 }
344 $theLabel = \TYPO3\CMS\Core\Utility\GeneralUtility::fixed_lgd_cs($theLabel, -$keyLgd);
345 $theLabel = $data['stackPointer'] ? '<span class="stackPointer">' . $theLabel . '</span>' : $theLabel;
346 $keyLabel = $theLabel . $keyLabel;
347 $item .= '<td class="' . $logRowClass . '">' . ($flag_tree ? $data['icons'] : '') . $this->fw($keyLabel) . '</td>';
348 // Key value:
349 $keyValue = $data['value'];
350 $item .= '<td class="' . $logRowClass . ' typo3-adminPanel-tsLogTime">' . $this->fw(htmlspecialchars($keyValue)) . '</td>';
351 if ($this->printConf['allTime']) {
352 $item .= '<td class="' . $logRowClass . ' typo3-adminPanel-tsLogTime"> ' . $this->fw($data['starttime']) . '</td>';
353 $item .= '<td class="' . $logRowClass . ' typo3-adminPanel-tsLogTime"> ' . $this->fw($data['owntime']) . '</td>';
354 $item .= '<td class="' . $logRowClass . ' typo3-adminPanel-tsLogTime"> ' . $this->fw(($data['subtime'] ? '+' . $data['subtime'] : '')) . '</td>';
355 $item .= '<td class="' . $logRowClass . ' typo3-adminPanel-tsLogTime"> ' . $this->fw(($data['subtime'] ? '=' . $data['deltatime'] : '')) . '</td>';
356 } else {
357 $item .= '<td class="' . $logRowClass . ' typo3-adminPanel-tsLogTime"> ' . $this->fw($data['owntime']) . '</td>';
358 }
359 // Messages:
360 $msgArr = array();
361 $msg = '';
362 if ($flag_messages && is_array($data['message'])) {
363 foreach ($data['message'] as $v) {
364 $msgArr[] = nl2br($v);
365 }
366 }
367 if ($flag_queries && is_array($data['selectQuery'])) {
368 $msgArr[] = \TYPO3\CMS\Core\Utility\DebugUtility::viewArray($data['selectQuery']);
369 }
370 if ($flag_content && (string)$data['content'] !== '') {
371 $maxlen = 120;
372 // Break lines which are too longer than $maxlen chars (can happen if content contains long paths...)
373 if (preg_match_all('/(\\S{' . $maxlen . ',})/', $data['content'], $reg)) {
374 foreach ($reg[1] as $key => $match) {
375 $match = preg_replace('/(.{' . $maxlen . '})/', '$1 ', $match);
376 $data['content'] = str_replace($reg[0][$key], $match, $data['content']);
377 }
378 }
379 $msgArr[] = '<span style="color:#000066;">' . nl2br($data['content']) . '</span>';
380 }
381 if (!empty($msgArr)) {
382 $msg = implode($msgArr, '<hr />');
383 }
384 $item .= '<td valign="top" class="' . $logRowClass . '" style="text-align:left;">' . $this->fw($msg) . '</td>';
385 $out .= '<tr>' . $item . '</tr>';
386 $c++;
387 }
388 $out = '<table class="admin-panel-table typo3-adminPanel-tsLog">' . $out . '</table>';
389 return $out;
390 }
391
392 /**
393 * Recursively generates the content to display
394 *
395 * @param array $arr Array which is modified with content. Reference
396 * @param string $content Current content string for the level
397 * @param string $depthData Prefixed icons for new PM icons
398 * @param bool $first Set this for the first call from outside.
399 * @param string $vKey Seems to be the previous tsStackLog key
400 * @return string Returns the $content string generated/modified. Also the $arr array is modified!
401 */
402 protected function fixContent(&$arr, $content, $depthData = '', $first = 0, $vKey = '') {
403 $ac = 0;
404 $c = 0;
405 // First, find number of entries
406 foreach ($arr as $k => $v) {
407 if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($k)) {
408 $ac++;
409 }
410 }
411 // Traverse through entries
412 $subtime = 0;
413 foreach ($arr as $k => $v) {
414 if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($k)) {
415 $c++;
416 $deeper = is_array($arr[$k . '.']) ? 1 : 0;
417 $PM = 'join';
418 $LN = $ac == $c ? 'blank' : 'line';
419 $BTM = $ac == $c ? 'bottom' : '';
420 $PM = is_array($arr[$k . '.']) ? ($deeper ? 'minus' : 'plus') : 'join';
421 $this->tsStackLog[$v]['icons'] = $depthData . ($first ? '' : '<img src="' . TYPO3_mainDir . 'gfx/ol/' . $PM . $BTM . '.gif" width="18" height="16" align="top" border="0" alt="" />');
422 if ($this->tsStackLog[$v]['content'] !== '') {
423 $content = str_replace($this->tsStackLog[$v]['content'], $v, $content);
424 }
425 if (is_array($arr[$k . '.'])) {
426 $this->tsStackLog[$v]['content'] = $this->fixContent($arr[$k . '.'], $this->tsStackLog[$v]['content'], $depthData . ($first ? '' : '<img src="' . TYPO3_mainDir . 'gfx/ol/' . $LN . '.gif" width="18" height="16" align="top" border="0" alt="" />'), 0, $v);
427 } else {
428 $this->tsStackLog[$v]['content'] = $this->fixCLen($this->tsStackLog[$v]['content'], $this->tsStackLog[$v]['value']);
429 $this->tsStackLog[$v]['subtime'] = '';
430 $this->tsStackLog[$v]['owntime'] = $this->tsStackLog[$v]['deltatime'];
431 }
432 $subtime += $this->tsStackLog[$v]['deltatime'];
433 }
434 }
435 // Set content with special chars
436 if (isset($this->tsStackLog[$vKey])) {
437 $this->tsStackLog[$vKey]['subtime'] = $subtime;
438 $this->tsStackLog[$vKey]['owntime'] = $this->tsStackLog[$vKey]['deltatime'] - $subtime;
439 }
440 $content = $this->fixCLen($content, $this->tsStackLog[$vKey]['value']);
441 // Traverse array again, this time substitute the unique hash with the red key
442 foreach ($arr as $k => $v) {
443 if (\TYPO3\CMS\Core\Utility\MathUtility::canBeInterpretedAsInteger($k)) {
444 if ($this->tsStackLog[$v]['content'] !== '') {
445 $content = str_replace($v, '<strong style="color:red;">[' . $this->tsStackLog[$v]['key'] . ']</strong>', $content);
446 }
447 }
448 }
449 // Return the content
450 return $content;
451 }
452
453 /**
454 * Wraps the input content string in green colored span-tags IF the length o fthe input string exceeds $this->printConf['contentLength'] (or $this->printConf['contentLength_FILE'] if $v == "FILE"
455 *
456 * @param string $c The content string
457 * @param string $v Command: If "FILE" then $this->printConf['contentLength_FILE'] is used for content length comparison, otherwise $this->printConf['contentLength']
458 * @return string
459 */
460 protected function fixCLen($c, $v) {
461 $len = $v == 'FILE' ? $this->printConf['contentLength_FILE'] : $this->printConf['contentLength'];
462 if (strlen($c) > $len) {
463 $c = '<span style="color:green;">' . htmlspecialchars(\TYPO3\CMS\Core\Utility\GeneralUtility::fixed_lgd_cs($c, $len)) . '</span>';
464 } else {
465 $c = htmlspecialchars($c);
466 }
467 return $c;
468 }
469
470 /**
471 * Wraps input string in a <span> tag
472 *
473 * @param string $str The string to be wrapped
474 * @return string
475 */
476 protected function fw($str) {
477 return '<span>' . $str . '</span>';
478 }
479
480 /**
481 * Helper function for internal data manipulation
482 *
483 * @param array $arr Array (passed by reference) and modified
484 * @param int $pointer Pointer value
485 * @param string $uniqueId Unique ID string
486 * @return void
487 * @access private
488 * @see printTSlog()
489 */
490 protected function createHierarchyArray(&$arr, $pointer, $uniqueId) {
491 if (!is_array($arr)) {
492 $arr = array();
493 }
494 if ($pointer > 0) {
495 end($arr);
496 $k = key($arr);
497 $this->createHierarchyArray($arr[(int)$k . '.'], $pointer - 1, $uniqueId);
498 } else {
499 $arr[] = $uniqueId;
500 }
501 }
502
503 }