Code Coverage |
||||||||||
Classes and Traits |
Functions and Methods |
Lines |
||||||||
Total | |
0.00% |
0 / 1 |
|
0.00% |
0 / 16 |
CRAP | |
0.00% |
0 / 128 |
Profile | |
0.00% |
0 / 1 |
|
0.00% |
0 / 16 |
1122 | |
0.00% |
0 / 128 |
__construct | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 7 |
|||
start | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
end | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
pushNestedProfile | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
bumpProfileTimeUse | |
0.00% |
0 / 1 |
56 | |
0.00% |
0 / 17 |
|||
bumpTimeUse | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
bumpMWTime | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
bumpCount | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 5 |
|||
formatLine | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 8 |
|||
cmpProfile | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
|||
formatProfile | |
0.00% |
0 / 1 |
30 | |
0.00% |
0 / 29 |
|||
printProfile | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 27 |
|||
swallowArray | |
0.00% |
0 / 1 |
12 | |
0.00% |
0 / 7 |
|||
reduce | |
0.00% |
0 / 1 |
6 | |
0.00% |
0 / 8 |
|||
reduceProfileTree | |
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 6 |
|||
|
0.00% |
0 / 1 |
2 | |
0.00% |
0 / 2 |
<?php | |
declare( strict_types = 1 ); | |
namespace Wikimedia\Parsoid\Config; | |
/** | |
* Records time profiling information | |
*/ | |
class Profile { | |
/** @var float */ | |
public $startTime; | |
/** @var float */ | |
public $endTime; | |
/** @var array */ | |
private $timeProfile; | |
/** @var array */ | |
private $mwProfile; | |
/** @var array */ | |
private $timeCategories; | |
/** @var array */ | |
private $counts; | |
/** | |
* Array of profiles for nested pipelines. So, we effectively end up with | |
* a profile tree with the top-level-doc profile as the root profile. | |
* @var array<Profile> | |
*/ | |
private $nestedProfiles; | |
/** | |
* This is the most recently pushed nested profile from a nested pipeline. | |
* @var Profile | |
*/ | |
private $recentNestedProfile; | |
public function __construct() { | |
$this->timeCategories = []; | |
$this->timeProfile = []; | |
$this->mwProfile = []; | |
$this->counts = []; | |
$this->nestedProfiles = []; | |
$this->recentNestedProfile = null; | |
} | |
public function start(): void { | |
$this->startTime = microtime( true ); | |
} | |
public function end(): void { | |
$this->endTime = microtime( true ); | |
} | |
/** | |
* @param Profile $p | |
*/ | |
public function pushNestedProfile( Profile $p ): void { | |
$this->nestedProfiles[] = $this->recentNestedProfile = $p; | |
} | |
/** | |
* @param array &$profile | |
* @param string $resource | |
* @param float $time Time in milliseconds | |
* @param ?string $cat | |
*/ | |
private function bumpProfileTimeUse( | |
array &$profile, string $resource, float $time, ?string $cat | |
): void { | |
if ( $profile === $this->timeProfile && $this->recentNestedProfile ) { | |
// Eliminate double-counting | |
$time -= ( $this->recentNestedProfile->endTime - $this->recentNestedProfile->startTime ) * 1000; | |
$this->recentNestedProfile = null; | |
} | |
if ( !isset( $profile[$resource] ) ) { | |
$profile[$resource] = 0; | |
} | |
$profile[$resource] += $time; | |
if ( $cat ) { | |
if ( !isset( $this->timeCategories[$cat] ) ) { | |
$this->timeCategories[$cat] = 0; | |
} | |
$this->timeCategories[$cat] += $time; | |
} | |
} | |
/** | |
* Update a profile timer. | |
* | |
* @param string $resource | |
* @param float $time Time in milliseconds | |
* @param string|null $cat | |
*/ | |
public function bumpTimeUse( string $resource, float $time, string $cat = null ): void { | |
$this->bumpProfileTimeUse( $this->timeProfile, $resource, $time, $cat ); | |
} | |
/** | |
* Update profile usage for "MW API" requests | |
* | |
* @param string $resource | |
* @param float $time Time in milliseconds | |
* @param string|null $cat | |
*/ | |
public function bumpMWTime( string $resource, float $time, string $cat = null ): void { | |
// FIXME: For now, skip the category since this leads to double counting | |
// when reportind time by categories since this time is part of other | |
// '$this->timeProfile' categories already. | |
$this->bumpProfileTimeUse( $this->mwProfile, $resource, $time, null ); | |
} | |
/** | |
* Update a profile counter. | |
* | |
* @param string $resource | |
* @param int $n The amount to increment the counter; defaults to 1. | |
*/ | |
public function bumpCount( string $resource, int $n = 1 ): void { | |
if ( !isset( $this->counts[$resource] ) ) { | |
$this->counts[$resource] = 0; | |
} | |
$this->counts[$resource] += $n; | |
} | |
/** | |
* @param string $k | |
* @param mixed $v | |
* @param string $comment | |
* @return string | |
*/ | |
private function formatLine( string $k, $v, string $comment = '' ): string { | |
$buf = str_pad( $k, 60, " ", STR_PAD_LEFT ) . ':'; | |
if ( $v === round( $v ) ) { | |
$v = (string)$v; | |
} else { | |
$v = str_pad( (string)( floor( $v * 10000 ) / 10000 ), 5, ' ', STR_PAD_LEFT ); | |
} | |
return $buf . str_pad( $v, 10, " ", STR_PAD_LEFT ) . ( $comment ? ' (' . $comment . ')' : '' ); | |
} | |
/** | |
* Sort comparison function | |
* @param array $a | |
* @param array $b | |
* @return float | |
*/ | |
private static function cmpProfile( array $a, array $b ): float { | |
return $b[1] - $a[1]; | |
} | |
/** | |
* @param array $profile | |
* @param array $options | |
* @return array | |
*/ | |
private function formatProfile( array $profile, array $options = [] ): array { | |
// Sort time profile in descending order | |
$total = 0; | |
$outLines = []; | |
foreach ( $profile as $k => $v ) { | |
$total += $v; | |
$outLines[] = [ $k, $v ]; | |
} | |
usort( $outLines, [ self::class, 'cmpProfile' ] ); | |
$lines = []; | |
foreach ( $outLines as $line ) { | |
$k = $line[0]; | |
$v = $line[1]; | |
$lineComment = ''; | |
if ( isset( $options['printPercentage'] ) ) { | |
$lineComment = (string)( round( $v * 1000 / $total ) / 10 ) . '%'; | |
} | |
$buf = $this->formatLine( $k, $v, $lineComment ); | |
if ( isset( $this->counts[$k] ) ) { | |
$buf .= str_pad( '; count: ' . | |
str_pad( (string)( $this->counts[$k] ), 6, ' ', STR_PAD_LEFT ), | |
6, " ", STR_PAD_LEFT ); | |
$buf .= str_pad( '; per-instance: ' . | |
str_pad( | |
(string)( floor( $v * 10000 / $this->counts[$k] ) / 10000 ), 5, ' ', STR_PAD_LEFT | |
), 10 ); | |
} | |
$lines[] = $buf; | |
} | |
return [ 'buf' => implode( "\n", $lines ), 'total' => $total ]; | |
} | |
/** | |
* @return string | |
*/ | |
private function printProfile(): string { | |
$outLines = []; | |
$mwOut = $this->formatProfile( $this->mwProfile ); | |
$cpuOut = $this->formatProfile( $this->timeProfile ); | |
$outLines[] = str_repeat( "-", 85 ); | |
$outLines[] = "Recorded times (in ms) for various parse components"; | |
$outLines[] = ""; | |
$outLines[] = $cpuOut['buf']; | |
$outLines[] = str_repeat( "-", 85 ); | |
$outLines[] = 'Recorded times (in ms) for various "MW API" requests'; | |
$outLines[] = ""; | |
$outLines[] = $mwOut['buf']; | |
$outLines[] = str_repeat( "-", 85 ); | |
$parseTime = ( $this->endTime - $this->startTime ) * 1000; | |
$outLines[] = $this->formatLine( 'TOTAL PARSE TIME (1)', $parseTime ); | |
$outLines[] = $this->formatLine( 'TOTAL PARSOID CPU TIME (2)', $cpuOut['total'] ); | |
if ( $mwOut['total'] > 0 ) { | |
$outLines[] = $this->formatLine( 'TOTAL "MW API" TIME', $mwOut['total'] ); | |
} | |
$outLines[] = $this->formatLine( 'Un/over-accounted parse time: (1) - (2)', | |
$parseTime - $cpuOut['total'] ); | |
$outLines[] = ""; | |
$catOut = $this->formatProfile( $this->timeCategories, [ 'printPercentage' => true ] ); | |
$outLines[] = $catOut['buf']; | |
$outLines[] = ""; | |
$outLines[] = str_repeat( "-", 85 ); | |
// dump to stderr via error_log | |
return implode( "\n", $outLines ); | |
} | |
/** | |
* @param array $a | |
* @param array &$res | |
*/ | |
private static function swallowArray( array $a, array &$res ): void { | |
foreach ( $a as $k => $v ) { | |
if ( !isset( $res[$k] ) ) { | |
$res[$k] = 0; | |
} | |
$res[$k] += $v; | |
} | |
} | |
/** | |
* @param Profile $reducedProfile | |
*/ | |
private function reduce( Profile $reducedProfile ): void { | |
self::swallowArray( $this->counts, $reducedProfile->counts ); | |
self::swallowArray( $this->timeCategories, $reducedProfile->timeCategories ); | |
self::swallowArray( $this->timeProfile, $reducedProfile->timeProfile ); | |
self::swallowArray( $this->mwProfile, $reducedProfile->mwProfile ); | |
foreach ( $this->nestedProfiles as $p ) { | |
$p->reduce( $reducedProfile ); | |
} | |
} | |
/** | |
* @return Profile | |
*/ | |
private function reduceProfileTree(): Profile { | |
$reducedProfile = new Profile(); | |
$reducedProfile->startTime = $this->startTime; | |
$reducedProfile->endTime = $this->endTime; | |
$this->reduce( $reducedProfile ); | |
return $reducedProfile; | |
} | |
/** | |
* @return string | |
*/ | |
public function print(): string { | |
return $this->reduceProfileTree()->printProfile(); | |
} | |
} |