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