diff --git a/XDebugTrace.php b/XDebugTrace.php index 20d8e31..12f7811 100644 --- a/XDebugTrace.php +++ b/XDebugTrace.php @@ -3,6 +3,7 @@ namespace Panel; use + Nette, Nette\Templating\FileTemplate, Nette\Latte\Engine; @@ -139,9 +140,15 @@ class XDebugTrace extends Nette\Object implements Nette\Diagnostics\IBarPanel /** - * @var bool skip PHP internals functions when parsing + * @var array of bool default filtering callback setting */ - protected $skipInternals = TRUE; + protected $skipOver = array( + 'phpInternals' => TRUE, + 'XDebugTrace' => TRUE, + 'Nette' => TRUE, + 'callbacks' => TRUE, + 'includes' => TRUE, + ); /** @@ -162,13 +169,17 @@ class XDebugTrace extends Nette\Object implements Nette\Diagnostics\IBarPanel * @param bool skip PHP internal functions when parsing trace file * @throws \Nette\InvalidStateException */ - public function __construct($traceFile, $skipInternals = TRUE) + public function __construct($traceFile) { if (self::$instance !== NULL) { throw new \Nette\InvalidStateException('Class ' . get_class($this) . ' can be instantized only once, xdebug_start_trace() can run only once.'); } self::$instance = $this; + if (substr_compare($traceFile, '.xt', -3, 3, TRUE) === 0) { + $traceFile = substr($traceFile, 0, -3); + } + if (!extension_loaded('xdebug')) { $this->setError('XDebug extension is not loaded'); @@ -179,7 +190,6 @@ public function __construct($traceFile, $skipInternals = TRUE) $this->traceFile = $traceFile; } - $this->skipInternals($skipInternals); $this->addFilterCallback(array($this, 'defaultFilterCb')); } @@ -670,50 +680,79 @@ protected function addRecord(\stdClass $record) /* ~~~ Trace records filtering ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ /** - * Setting of default filter callback. - * @param bool skip PHP internal functions + * Setting of default filtering callback. + * + * @param bool skip or not + * @param string + * @return Panel\XDebugTrace + */ + public function skip($type, $skip) + { + if (!array_key_exists($type, $this->skipOver)) { + throw new Nette\InvalidArgumentException("Unknown skip type '$type'. Use one of [" . implode(', ', array_keys($this->skipOver)) . ']'); + } + + $this->skipOver[$type] = (bool) $skip; + return $this; + } + + + + /** + * Shortcut to self::skip('phpInternals', bool) + * + * @param bool skip PHP internal functions? + * @return Panel\XDebugTrace */ public function skipInternals($skip) { - $this->skipInternals = $skip; + return $this->skip('phpInternals', $skip); } /** - * Default filter + * Default filtering callback. * * @param stdClass trace file record * @return int bitmask of self::SKIP, self::STOP */ protected function defaultFilterCb(\stdClass $record) { - if ($this->skipInternals && $record->isInternal) { + if ($this->skipOver['phpInternals'] && $record->isInternal) { return self::SKIP; } - if ($record->filename === __FILE__) { - return self::SKIP; - } + if ($this->skipOver['XDebugTrace']) { + if ($record->filename === __FILE__) { + return self::SKIP; + } - if (strncmp($record->function, 'Nette\\', 6) === 0) { - return self::SKIP; - } + if (strncmp($record->function, 'Panel\\XDebugTrace::', 19) === 0) { + return self::SKIP; + } - if (strncmp($record->function, 'Panel\\XDebugTrace::', 19) === 0) { - return self::SKIP; + if (strncmp($record->function, 'Panel\\XDebugTrace->', 19) === 0) { + return self::SKIP; + } } - if (strncmp($record->function, 'Panel\\XDebugTrace->', 19) === 0) { - return self::SKIP; + if ($this->skipOver['Nette']) { + if (strncmp($record->function, 'Nette\\', 6) === 0) { + return self::SKIP; + } } - if (strcmp($record->function, 'callback') === 0) { - return self::SKIP; + if ($this->skipOver['callbacks']) { + if ($record->function === 'callback' || $record->function === '{closure}') { + return self::SKIP; + } } - if ($record->includeFile !== NULL) { - return self::SKIP; + if ($this->skipOver['includes']) { + if ($record->includeFile !== NULL) { + return self::SKIP; + } } } @@ -769,8 +808,163 @@ public function addFilterCallback($callback, $flags = NULL) public function setFilterCallback($callback, $flags = NULL) { $flags = ((int) $flags) | self::FILTER_REPLACE; - return $this->addFilterCallback($callback, $flags); } + + + /* ~~~ Filtering callback shortcuts ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ + /** + * Trace all. + */ + public function traceAll() + { + $cb = function () { + return NULL; + }; + + $this->setFilterCallback($cb, self::FILTER_BOTH); + } + + + + /** + * Trace function by name. + * + * @param string|array name of function or pair array(class, method) + * @param bool show inside function trace too + * @param bool show internals in inside function trace + */ + public function traceFunction($name, $inDetail = FALSE, $showInternals = FALSE) + { + if (is_array($name)) { + $name1 = implode('::', $name); + $name2 = implode('->', $name); + } else { + $name1 = $name2 = (string) $name; + } + + $cb = function(\stdClass $record, $onEntry) use ($name1, $name2, $inDetail, $showInternals) { + static $cnt = 0; + + if ($record->function === $name1 || $record->function === $name2) { + $cnt += $onEntry ? 1 : -1; + return NULL; + } + + return ($inDetail && $cnt && ($showInternals || !$record->isInternal)) ? NULL : XDebugTrace::SKIP; + }; + + $this->setFilterCallback($cb, self::FILTER_BOTH); + } + + + + /** + * Trace function which name is expressed by PCRE reqular expression. + * + * @param string regular expression + * @param bool show inside function trace too + * @param bool show internals in inside function trace + */ + public function traceFunctionRe($re, $inDetail = FALSE, $showInternals = FALSE) + { + $cb = function(\stdClass $record, $onEntry) use ($re, $inDetail, $showInternals) { + static $cnt = 0; + + if (preg_match($re, $record->function)) { + $cnt += $onEntry ? 1 : -1; + return NULL; + } + + return ($inDetail && $cnt && ($showInternals || !$record->isInternal)) ? NULL : XDebugTrace::SKIP; + }; + + $this->setFilterCallback($cb, self::FILTER_BOTH); + } + + + + /** + * Trace functions running over/under the time. + * + * @param float delta time + * @param bool TRUE = over the delta time, FALSE = under the delta time + */ + public function traceDeltaTime($delta, $over = TRUE) + { + if (is_string($delta)) { + static $multipliers = array( + 's' => 1, + 'ms' => 0.001, + 'us' => 0.000001, + 'ns' => 0.000000001, + ); + + foreach ($multipliers as $suffix => $multipler) { + if (substr_compare($delta, $suffix, -2, 2, TRUE) === 0) { + $delta = substr($delta, 0, -2) * $multipler; + break; + } + } + } + $delta = (float) $delta; + + $cb = function(\stdClass $record) use ($delta, $over) { + if ($over) { + if ($record->deltaTime < $delta) { + return XDebugTrace::SKIP; + } + } else { + if ($record->deltaTime > $delta) { + return XDebugTrace::SKIP; + } + } + }; + + $this->setFilterCallback($cb, self::FILTER_EXIT); + } + + + + /** + * Trace functions which consumes over/under the memory. + * + * @param float delta memory + * @param bool TRUE = over the delta memory, FALSE = under the delta memory + */ + public function traceDeltaMemory($delta, $over = TRUE) + { + if (is_string($delta)) { + static $multipliers = array( + 'b' => 1, + 'kb' => 1000, + 'mb' => 1000000, + 'gb' => 1000000000, + ); + + foreach ($multipliers as $suffix => $multipler) { + if (substr_compare($delta, $suffix, -2, 2, TRUE) === 0) { + $delta = substr($delta, 0, -2) * $multipler; + break; + } + } + } + $delta = (float) $delta; + + $cb = function(\stdClass $record) use ($delta, $over) { + if ($over) { + if ($record->deltaMemory < $delta) { + return XDebugTrace::SKIP; + } + } else { + if ($record->deltaMemory > $delta) { + return XDebugTrace::SKIP; + } + } + }; + + $this->setFilterCallback($cb, self::FILTER_EXIT); + } + } diff --git a/content.latte b/content.latte index b403f40..5e87fc8 100644 --- a/content.latte +++ b/content.latte @@ -27,6 +27,10 @@ #nette-debug .xdbgpanel .nette-inner .timeSlow { color: red; } + + #nette-debug .xdbgpanel .nette-inner { + overflow: scroll !important; + }
diff --git a/examples.php b/examples.php index 74bb14a..dfb261e 100644 --- a/examples.php +++ b/examples.php @@ -15,14 +15,15 @@ 2. REGISTER PANEL In next step, register panel in bootstrap.php. You need a web server writable directory for temporary trace file. In XDebugTrace constructor - provide only file name without extension. xdebug_trace_start() always - add .xt extension. + provide path to temporary trace file. */ define('TMP_DIR', __DIR__ . '/../temp'); $xdebugTrace = new \Panel\XDebugTrace(TMP_DIR . '/xdebug_trace'); \Nette\Diagnostics\Debugger::addPanel($xdebugTrace); + + /* 3. START-PAUSE-STOP TRACING Now, when panel is registered, you can start tracing. @@ -46,6 +47,8 @@ \Panel\XDebugTrace::callPause(); \Panel\XDebugTrace::callStop(); + + /* 4. OWN TEMPLATES You can use all of \Nette\Templating\FileTemplate advantages and set own @@ -54,14 +57,50 @@ $template = $xdebugTrace->getTemplate(); $template->setFile('templates/my-template.latte'); + + /* 5. TRACE RECORDS FILTERING Filtering is a most ambitious work with this panel. Without this, HTML output can be huge (megabytes). XDebugTrace panel provide simple mechanism - for trace records filtering. You can register own filtering callbacks. + for trace records filtering. You can use prepared filters (methods starts + by 'trace' prefix). +*/ +// Trace everything. Be careful, HTML output can be huge! +$xdebugTrace->traceAll(); + + +// Trace single function... +$xdebugTrace->traceFunction('weirdFunction'); +// ... and all the inside calls too... +$xdebugTrace->traceFunction('weirdFunction', TRUE); +// ... and PHP internals too. +$xdebugTrace->traceFunction('weirdFunction', TRUE, TRUE); + + +// Trace static method... +$xdebugTrace->traceFunction('MyClass::weirdFunction'); +// ... or dynamic... +$xdebugTrace->traceFunction('MyClass->weirdFunction'); +// ... or both. +$xdebugTrace->traceFunction(array('MyClass', 'weirdFunction')); + + +// Trace functions by PCRE regular expression +$xdebugTrace->traceFunctionRe('/^weird/i'); + + +// Trace only functions running over the 15 miliseconds... +$xdebugTrace->traceDeltaTime('15ms'); +// ... or function which consumes more then 20kB. +$xdebugTrace->traceDeltaMemory('20kB'); - At first, take a look on XDebugTrace::defaultFilterCb() source code. This is - a default filtering callback. Is used when you dont register own one. + +/* + If you want use own filters, at first, take a look on + XDebugTrace::defaultFilterCb() source code. This is a default filtering + callback. Is used when you don't register own one. And take a look on + XDebugTrace::trace.....() methods source code. At second, is good to know xdebug trace file structure: @@ -71,7 +110,7 @@ [Exit record] level id 1 time memory - [Example] + [An example] 3 121 0 0.012 401442 myFunction - myFunction() enter 4 122 0 0.014 401442 strpos - strpos() enter 4 122 1 0.015 401454 - strpos() exit @@ -88,59 +127,26 @@ $flags is a bitmask of \Panel\XDebugTrace::FILTER_* constants. FILTER_ENTRY - call filter on entry records (default) FILTER_EXIT - call filter on exit records - FILTER_BOTH - same as FILTER_ENTRY | FILTER_EXIT + FILTER_BOTH = FILTER_ENTRY | FILTER_EXIT FILTER_APPEND_ENTRY - append filter behind others (default is prepend) FILTER_APPEND_EXIT - append filter behind others (default is prepend) - FILTER_APPEND - same as FILTER_APPEND_ENTRY | FILTER_APPEND_EXIT + FILTER_APPEND = FILTER_APPEND_ENTRY | FILTER_APPEND_EXIT FILTER_REPLACE_ENTRY - remove all entry filters FILTER_REPLACE_EXIT - remove all exit filters - FILTER_REPLACE - same as FILTER_REPLACE_ENTRY | FILTER_REPLACE_EXIT + FILTER_REPLACE = FILTER_REPLACE_ENTRY | FILTER_REPLACE_EXIT Your callback should return bitmask of flags: \Panel\XDebugTrace::SKIP - skip this record \Panel\XDebugTrace::STOP - don't call followed filters - Examples follow. -*/ - -// Display only count() function traces -$xdebugTrace->setFilterCallback(function($record) { - if ($record->function !== 'count') { - return \Panel\XDebugTrace::SKIP; - } -}); - - - -// We want display every Nette\Utils\LimitedScope::load() calling in detail. -// Note the second $onEntry parametr. Is TRUE for entry recods, FALSE for exit. -// Note the FILTER_BOTH flag. It menans: "Call filter on entry and exit records" -$xdebugTrace->setFilterCallback(function($record, $onEntry){ - static $cnt = 0; - - if ($record->function === 'Nette\Utils\LimitedScope::load') { - $cnt += $onEntry ? 1 : -1; - return NULL; - } - - return $cnt ? NULL : \Panel\XDebugTrace::SKIP; -}, \Panel\XDebugTrace::FILTER_BOTH); - + or return NULL. NULL means record passed and will be printed in bar. + Simple example follows. +*/ // Display everything except for internal functions $xdebugTrace->setFilterCallback(function($record){ return $record->isInternal ? \Panel\XDebugTrace::SKIP : NULL; }); - - - -// Display only functions which run longer then 1 ms. -// Must be registered with FILTER_EXIT. ENTRY records have deltaTime = NULL -$xdebugTrace->setFilterCallback(function($record) { - if ($record->deltaTime < 0.001) { - return \Panel\XDebugTrace::SKIP; - } -}, \Panel\XDebugTrace::FILTER_EXIT); diff --git a/screenshot.png b/screenshot.png index aa992fd..56c8593 100644 Binary files a/screenshot.png and b/screenshot.png differ