Add ProfilerExcimer
authorTim Starling <tstarling@wikimedia.org>
Wed, 28 Nov 2018 03:36:22 +0000 (14:36 +1100)
committerTim Starling <tstarling@wikimedia.org>
Fri, 7 Dec 2018 00:42:59 +0000 (11:42 +1100)
Add a Profiler subclass for the new excimer extension. Since it does not
provide function counts, it's a little bit awkward to return it in the
format required by getFunctionStats(), but getOutput() works quite well.

Fix totally broken ProfilerOutputDb, the first parameter to the
onTransactionCommitOrIdle callback is not a Database. It is in the
second parameter, but most callers do not use it.

Change-Id: Icb20f3a5b0b09ff2905f1711f3681c398aa026e2
Depends-On: I6a9ccf5a12ef998e029033adf08af95c42fb7f8e

autoload.php
includes/profiler/ProfilerExcimer.php [new file with mode: 0644]
includes/profiler/output/ProfilerOutputDb.php
tests/phan/config.php
tests/phan/stubs/excimer.php [new file with mode: 0644]

index 02e35a8..c79ca23 100644 (file)
@@ -1127,6 +1127,7 @@ $wgAutoloadLocalClasses = [
        'ProcessCacheLRU' => __DIR__ . '/includes/libs/ProcessCacheLRU.php',
        'Processor' => __DIR__ . '/includes/registration/Processor.php',
        'Profiler' => __DIR__ . '/includes/profiler/Profiler.php',
+       'ProfilerExcimer' => __DIR__ . '/includes/profiler/ProfilerExcimer.php',
        'ProfilerOutput' => __DIR__ . '/includes/profiler/output/ProfilerOutput.php',
        'ProfilerOutputDb' => __DIR__ . '/includes/profiler/output/ProfilerOutputDb.php',
        'ProfilerOutputDump' => __DIR__ . '/includes/profiler/output/ProfilerOutputDump.php',
diff --git a/includes/profiler/ProfilerExcimer.php b/includes/profiler/ProfilerExcimer.php
new file mode 100644 (file)
index 0000000..776136f
--- /dev/null
@@ -0,0 +1,131 @@
+<?php
+
+class ProfilerExcimer extends Profiler {
+       private $cpuProf;
+       private $realProf;
+       private $period;
+
+       public function __construct( array $params = [] ) {
+               parent::__construct( $params );
+
+               $this->period = $params['period'] ?? 0.01;
+               $maxDepth = $params['maxDepth'] ?? 100;
+
+               $this->cpuProf = new ExcimerProfiler;
+               $this->cpuProf->setEventType( EXCIMER_CPU );
+               $this->cpuProf->setPeriod( $this->period );
+               $this->cpuProf->setMaxDepth( $maxDepth );
+
+               $this->realProf = new ExcimerProfiler;
+               $this->realProf->setEventType( EXCIMER_REAL );
+               $this->realProf->setPeriod( $this->period );
+               $this->realProf->setMaxDepth( $maxDepth );
+
+               $this->cpuProf->start();
+               $this->realProf->start();
+       }
+
+       public function scopedProfileIn( $section ) {
+       }
+
+       public function close() {
+               $this->cpuProf->stop();
+               $this->realProf->stop();
+       }
+
+       public function getFunctionStats() {
+               $this->close();
+               $cpuStats = $this->cpuProf->getLog()->aggregateByFunction();
+               $realStats = $this->realProf->getLog()->aggregateByFunction();
+               $allNames = array_keys( $realStats + $cpuStats );
+               $cpuSamples = $this->cpuProf->getLog()->getEventCount();
+               $realSamples = $this->realProf->getLog()->getEventCount();
+
+               $resultStats = [ [
+                       'name' => '-total',
+                       'calls' => 1,
+                       'memory' => 0,
+                       '%memory' => 0,
+                       'min_real' => 0,
+                       'max_real' => 0,
+                       'cpu' => $cpuSamples * $this->period * 1000,
+                       '%cpu' => 100,
+                       'real' => $realSamples * $this->period * 1000,
+                       '%real' => 100,
+               ] ];
+
+               foreach ( $allNames as $funcName ) {
+                       $cpuEntry = $cpuStats[$funcName] ?? false;
+                       $realEntry = $realStats[$funcName] ?? false;
+                       $resultEntry = [
+                               'name' => $funcName,
+                               'calls' => 0,
+                               'memory' => 0,
+                               '%memory' => 0,
+                               'min_real' => 0,
+                               'max_real' => 0,
+                       ];
+
+                       if ( $cpuEntry ) {
+                               $resultEntry['cpu'] = $cpuEntry['inclusive'] * $this->period * 1000;
+                               $resultEntry['%cpu'] = $cpuEntry['inclusive'] / $cpuSamples * 100;
+                       } else {
+                               $resultEntry['cpu'] = 0;
+                               $resultEntry['%cpu'] = 0;
+                       }
+                       if ( $realEntry ) {
+                               $resultEntry['real'] = $realEntry['inclusive'] * $this->period * 1000;
+                               $resultEntry['%real'] = $realEntry['inclusive'] / $realSamples * 100;
+                       } else {
+                               $resultEntry['real'] = 0;
+                               $resultEntry['%real'] = 0;
+                       }
+
+                       $resultStats[] = $resultEntry;
+               }
+               return $resultStats;
+       }
+
+       public function getOutput() {
+               $this->close();
+               $cpuLog = $this->cpuProf->getLog();
+               $realLog = $this->realProf->getLog();
+               $cpuStats = $cpuLog->aggregateByFunction();
+               $realStats = $realLog->aggregateByFunction();
+               $allNames = array_keys( $cpuStats + $realStats );
+               $cpuSamples = $cpuLog->getEventCount();
+               $realSamples = $realLog->getEventCount();
+
+               $result = '';
+
+               $titleFormat = "%-70s %10s %11s %10s %11s %10s %11s %10s %11s\n";
+               $statsFormat = "%-70s %10d %10.1f%% %10d %10.1f%% %10d %10.1f%% %10d %10.1f%%\n";
+               $result .= sprintf( $titleFormat,
+                       'Name',
+                       'CPU incl', 'CPU incl%', 'CPU self', 'CPU self%',
+                       'Real incl', 'Real incl%', 'Real self', 'Real self%'
+               );
+
+               foreach ( $allNames as $funcName ) {
+                       $realEntry = $realStats[$funcName] ?? false;
+                       $cpuEntry = $cpuStats[$funcName] ?? false;
+                       $realIncl = $realEntry ? $realEntry['inclusive'] : 0;
+                       $realSelf = $realEntry ? $realEntry['self'] : 0;
+                       $cpuIncl = $cpuEntry ? $cpuEntry['inclusive'] : 0;
+                       $cpuSelf = $cpuEntry ? $cpuEntry['self'] : 0;
+                       $result .= sprintf( $statsFormat,
+                               $funcName,
+                               $cpuIncl * $this->period * 1000,
+                               $cpuIncl == 0 ? 0 : $cpuIncl / $cpuSamples * 100,
+                               $cpuSelf * $this->period * 1000,
+                               $cpuSelf == 0 ? 0 : $cpuSelf / $cpuSamples * 100,
+                               $realIncl * $this->period * 1000,
+                               $realIncl == 0 ? 0 : $realIncl / $realSamples * 100,
+                               $realSelf * $this->period * 1000,
+                               $realSelf == 0 ? 0 : $realSelf / $realSamples * 100
+                       );
+               }
+
+               return $result;
+       }
+}
index 6e0085d..ea5f7ad 100644 (file)
@@ -21,7 +21,6 @@
  * @ingroup Profiler
  */
 
-use Wikimedia\Rdbms\Database;
 use Wikimedia\Rdbms\DBError;
 
 /**
@@ -56,7 +55,7 @@ class ProfilerOutputDb extends ProfilerOutput {
                }
 
                $fname = __METHOD__;
-               $dbw->onTransactionCommitOrIdle( function ( Database $dbw ) use ( $stats, $fname ) {
+               $dbw->onTransactionCommitOrIdle( function () use ( $stats, $fname, $dbw ) {
                        $pfhost = $this->perHost ? wfHostname() : '';
                        // Sqlite: avoid excess b-tree rebuilds (mostly for non-WAL mode)
                        // non-Sqlite: lower contention with small transactions
index 585ebb9..fa351ea 100644 (file)
@@ -42,6 +42,7 @@ return [
                // Load the interface for the version of PHPUnit that isn't installed.
                // Phan only supports PHP 7.0+ (and not HHVM), so we only need to stub PHPUnit 4.
                class_exists( PHPUnit_TextUI_Command::class ) ? [] : [ 'tests/phan/stubs/phpunit4.php' ],
+               class_exists( ProfilerExcimer::class ) ? [] : [ 'tests/phan/stubs/excimer.php' ],
                [
                        'maintenance/7zip.inc',
                        'maintenance/cleanupTable.inc',
diff --git a/tests/phan/stubs/excimer.php b/tests/phan/stubs/excimer.php
new file mode 100644 (file)
index 0000000..af3a673
--- /dev/null
@@ -0,0 +1,86 @@
+<?php
+
+// phpcs:ignoreFile
+
+class ExcimerProfiler {
+       public function __construct() {
+       }
+       public function setPeriod( $period ) {
+       }
+       public function setEventType( $event_type ) {
+       }
+       public function setMaxDepth( $maxDepth ) {
+       }
+       public function setFlushCallback( $callback, $max_samples ) {
+       }
+       public function clearFlushCallback() {
+       }
+       public function start() {
+       }
+       public function stop() {
+       }
+       public function getLog() {
+       }
+       public function flush() {
+       }
+}
+
+class ExcimerLog {
+       private final function __construct() {
+       }
+       function formatCollapsed() {
+       }
+       function aggregateByFunction() {
+       }
+       function getEventCount() {
+       }
+       function current() {
+       }
+       function key() {
+       }
+       function next() {
+       }
+       function rewind() {
+       }
+       function valid() {
+       }
+       function count() {
+       }
+       function offsetExists( $offset ) {
+       }
+       function offsetGet( $offset ) {
+       }
+       function offsetSet( $offset, $value ) {
+       }
+       function offsetUnset( $offset ) {
+       }
+
+}
+
+class ExcimerLogEntry {
+       private final function __construct() {
+       }
+       function getTimestamp() {
+       }
+       function getEventCount() {
+       }
+       function getTrace() {
+       }
+}
+
+class ExcimerTimer {
+       function setEventType( $event_type ) {
+       }
+       function setInterval( $interval ) {
+       }
+       function setPeriod( $period ) {
+       }
+       function setCallback( $callback ) {
+       }
+       function start() {
+       }
+       function stop() {
+       }
+       function getTime() {
+       }
+}