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