Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
0.00% |
0 / 69 |
|
0.00% |
0 / 11 |
CRAP | |
0.00% |
0 / 1 |
| Profiler | |
0.00% |
0 / 68 |
|
0.00% |
0 / 11 |
1482 | |
0.00% |
0 / 1 |
| __construct | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
| init | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
56 | |||
| instance | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
| getProfileID | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
6 | |||
| scopedProfileIn | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| scopedProfileOut | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getTransactionProfiler | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| close | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| getOutputs | |
0.00% |
0 / 11 |
|
0.00% |
0 / 1 |
30 | |||
| logData | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
56 | |||
| logDataPageOutputOnly | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
42 | |||
| getContentType | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
20 | |||
| setAllowOutput | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
| getFunctionStats | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| getOutput | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
| 1 | <?php |
| 2 | /** |
| 3 | * @license GPL-2.0-or-later |
| 4 | * @file |
| 5 | */ |
| 6 | |
| 7 | namespace MediaWiki\Profiler; |
| 8 | |
| 9 | use MediaWiki\Logger\LoggerFactory; |
| 10 | use MediaWiki\Profiler\Output\ProfilerOutput; |
| 11 | use MediaWiki\WikiMap\WikiMap; |
| 12 | use Psr\Log\LoggerInterface; |
| 13 | use UnexpectedValueException; |
| 14 | use Wikimedia\Rdbms\TransactionProfiler; |
| 15 | |
| 16 | /** |
| 17 | * @defgroup Profiler Profiler |
| 18 | */ |
| 19 | |
| 20 | /** |
| 21 | * Profiler base class that defines the interface and some shared |
| 22 | * functionality. |
| 23 | * |
| 24 | * @ingroup Profiler |
| 25 | */ |
| 26 | abstract class Profiler { |
| 27 | /** @var string|false Profiler ID for bucketing data */ |
| 28 | protected $profileID = false; |
| 29 | /** @var array All of the params passed from $wgProfiler */ |
| 30 | protected $params = []; |
| 31 | /** @var TransactionProfiler */ |
| 32 | protected $trxProfiler; |
| 33 | /** @var LoggerInterface */ |
| 34 | protected $logger; |
| 35 | /** @var bool */ |
| 36 | private $allowOutput = false; |
| 37 | |
| 38 | /** @var Profiler */ |
| 39 | private static $instance = null; |
| 40 | |
| 41 | /** |
| 42 | * @param array $params See $wgProfiler. |
| 43 | */ |
| 44 | public function __construct( array $params ) { |
| 45 | if ( isset( $params['profileID'] ) ) { |
| 46 | $this->profileID = $params['profileID']; |
| 47 | } |
| 48 | $this->params = $params; |
| 49 | $this->trxProfiler = new TransactionProfiler(); |
| 50 | $this->logger = LoggerFactory::getInstance( 'profiler' ); |
| 51 | } |
| 52 | |
| 53 | /** |
| 54 | * @internal For use by Setup.php |
| 55 | * @param array $profilerConf Value from $wgProfiler |
| 56 | */ |
| 57 | final public static function init( array $profilerConf ): void { |
| 58 | $params = $profilerConf + [ |
| 59 | 'class' => ProfilerStub::class, |
| 60 | 'sampling' => 1, |
| 61 | 'threshold' => 0.0, |
| 62 | 'output' => [], |
| 63 | 'cliEnable' => false, |
| 64 | ]; |
| 65 | |
| 66 | // Avoid global func wfIsCLI() during setup |
| 67 | $isCLI = ( PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg' ); |
| 68 | $inSample = $params['sampling'] === 1 || mt_rand( 1, $params['sampling'] ) === 1; |
| 69 | if ( |
| 70 | !$inSample || |
| 71 | // On CLI, profiling is disabled by default, and can be explicitly enabled |
| 72 | // via the `--profiler` option, which MediaWiki\Maintenance\MaintenanceRunner::setup |
| 73 | // translates into 'cliEnable'. |
| 74 | // See also $wgProfiler docs. |
| 75 | // |
| 76 | // For this to work, Setup.php must call Profiler::init() after handling of |
| 77 | // MW_FINAL_SETUP_CALLBACK, which is what doMaintenance.php uses to call |
| 78 | // MaintenanceRunner::setup. |
| 79 | ( $isCLI && !$params['cliEnable'] ) |
| 80 | ) { |
| 81 | $params['class'] = ProfilerStub::class; |
| 82 | } |
| 83 | |
| 84 | if ( !is_array( $params['output'] ) ) { |
| 85 | $params['output'] = [ $params['output'] ]; |
| 86 | } |
| 87 | |
| 88 | self::$instance = new $params['class']( $params ); |
| 89 | } |
| 90 | |
| 91 | /** |
| 92 | * @return Profiler |
| 93 | */ |
| 94 | final public static function instance() { |
| 95 | if ( !self::$instance ) { |
| 96 | trigger_error( 'Called Profiler::instance before settings are loaded', E_USER_WARNING ); |
| 97 | self::init( [] ); |
| 98 | } |
| 99 | |
| 100 | return self::$instance; |
| 101 | } |
| 102 | |
| 103 | /** |
| 104 | * @return string |
| 105 | */ |
| 106 | public function getProfileID() { |
| 107 | if ( $this->profileID === false ) { |
| 108 | return WikiMap::getCurrentWikiDbDomain()->getId(); |
| 109 | } else { |
| 110 | return $this->profileID; |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | /** |
| 115 | * Mark the start of a custom profiling frame (e.g. DB queries). |
| 116 | * The frame ends when the result of this method falls out of scope. |
| 117 | * |
| 118 | * @param string $section |
| 119 | * @since 1.25 |
| 120 | */ |
| 121 | #[\NoDiscard] |
| 122 | abstract public function scopedProfileIn( $section ): ?SectionProfileCallback; |
| 123 | |
| 124 | /** |
| 125 | * @param SectionProfileCallback|null &$section |
| 126 | */ |
| 127 | public function scopedProfileOut( ?SectionProfileCallback &$section = null ) { |
| 128 | $section = null; |
| 129 | } |
| 130 | |
| 131 | /** |
| 132 | * @return TransactionProfiler |
| 133 | * @since 1.25 |
| 134 | */ |
| 135 | public function getTransactionProfiler() { |
| 136 | return $this->trxProfiler; |
| 137 | } |
| 138 | |
| 139 | /** |
| 140 | * Close opened profiling sections |
| 141 | */ |
| 142 | abstract public function close(); |
| 143 | |
| 144 | /** |
| 145 | * Get all usable outputs. |
| 146 | * |
| 147 | * @return ProfilerOutput[] |
| 148 | * @since 1.25 |
| 149 | */ |
| 150 | private function getOutputs() { |
| 151 | $outputs = []; |
| 152 | foreach ( $this->params['output'] as $outputType ) { |
| 153 | // The class may be specified as either the full class name (for |
| 154 | // example, 'ProfilerOutputStats') or (for backward compatibility) |
| 155 | // the trailing portion of the class name (for example, 'stats'). |
| 156 | $outputClass = !str_contains( $outputType, 'ProfilerOutput' ) |
| 157 | ? 'ProfilerOutput' . ucfirst( $outputType ) |
| 158 | : $outputType; |
| 159 | if ( !class_exists( $outputClass ) ) { |
| 160 | throw new UnexpectedValueException( "'$outputType' is an invalid output type" ); |
| 161 | } |
| 162 | $outputInstance = new $outputClass( $this, $this->params ); |
| 163 | if ( $outputInstance->canUse() ) { |
| 164 | $outputs[] = $outputInstance; |
| 165 | } |
| 166 | } |
| 167 | return $outputs; |
| 168 | } |
| 169 | |
| 170 | /** |
| 171 | * Log data to all the applicable backing stores |
| 172 | * |
| 173 | * This logs the profiling data to the backing store for each configured ProfilerOutput |
| 174 | * instance. It also logs any request data for the TransactionProfiler instance. |
| 175 | * |
| 176 | * @since 1.25 |
| 177 | */ |
| 178 | public function logData() { |
| 179 | if ( $this->params['threshold'] > 0.0 ) { |
| 180 | // Note, this is also valid for CLI processes. |
| 181 | $timeElapsed = microtime( true ) - $_SERVER['REQUEST_TIME_FLOAT']; |
| 182 | if ( $timeElapsed <= $this->params['threshold'] ) { |
| 183 | return; |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | $outputs = []; |
| 188 | foreach ( $this->getOutputs() as $output ) { |
| 189 | if ( !$output->logsToOutput() ) { |
| 190 | $outputs[] = $output; |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | if ( $outputs ) { |
| 195 | $stats = $this->getFunctionStats(); |
| 196 | foreach ( $outputs as $output ) { |
| 197 | $output->log( $stats ); |
| 198 | } |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | /** |
| 203 | * Log the data to the script/request output for all ProfilerOutput instances that do so |
| 204 | * |
| 205 | * @since 1.26 |
| 206 | */ |
| 207 | public function logDataPageOutputOnly() { |
| 208 | if ( !$this->allowOutput ) { |
| 209 | return; |
| 210 | } |
| 211 | |
| 212 | $outputs = []; |
| 213 | foreach ( $this->getOutputs() as $output ) { |
| 214 | if ( $output->logsToOutput() ) { |
| 215 | $outputs[] = $output; |
| 216 | } |
| 217 | } |
| 218 | |
| 219 | if ( $outputs ) { |
| 220 | $stats = $this->getFunctionStats(); |
| 221 | foreach ( $outputs as $output ) { |
| 222 | $output->log( $stats ); |
| 223 | } |
| 224 | } |
| 225 | } |
| 226 | |
| 227 | /** |
| 228 | * Get the Content-Type for deciding how to format appended profile output. |
| 229 | * |
| 230 | * Disabled by default. Enable via setAllowOutput(). |
| 231 | * |
| 232 | * @see ProfilerOutputText |
| 233 | * @since 1.25 |
| 234 | * @return string|null Returns null if disabled or no Content-Type found. |
| 235 | */ |
| 236 | public function getContentType() { |
| 237 | if ( $this->allowOutput ) { |
| 238 | foreach ( headers_list() as $header ) { |
| 239 | if ( preg_match( '#^content-type: (\w+/\w+);?#i', $header, $m ) ) { |
| 240 | return $m[1]; |
| 241 | } |
| 242 | } |
| 243 | } |
| 244 | return null; |
| 245 | } |
| 246 | |
| 247 | /** |
| 248 | * Enable appending profiles to standard output. |
| 249 | * |
| 250 | * @since 1.34 |
| 251 | */ |
| 252 | public function setAllowOutput() { |
| 253 | $this->allowOutput = true; |
| 254 | } |
| 255 | |
| 256 | /** |
| 257 | * Get the aggregated inclusive profiling data for each method |
| 258 | * |
| 259 | * The percent time for each time is based on the current "total" time |
| 260 | * used is based on all methods so far. This method can therefore be |
| 261 | * called several times in between several profiling calls without the |
| 262 | * delays in usage of the profiler skewing the results. A "-total" entry |
| 263 | * is always included in the results. |
| 264 | * |
| 265 | * When a call chain involves a method invoked within itself, any |
| 266 | * entries for the cyclic invocation should be demarked with "@". |
| 267 | * This makes filtering them out easier and follows the xhprof style. |
| 268 | * |
| 269 | * @return array[] List of method entries arrays, each having: |
| 270 | * - name : method name |
| 271 | * - calls : the number of invoking calls |
| 272 | * - real : real time elapsed (ms) |
| 273 | * - %real : percent real time |
| 274 | * - cpu : CPU time elapsed (ms) |
| 275 | * - %cpu : percent CPU time |
| 276 | * - memory : memory used (bytes) |
| 277 | * - %memory : percent memory used |
| 278 | * - min_real : min real time in a call (ms) |
| 279 | * - max_real : max real time in a call (ms) |
| 280 | * @since 1.25 |
| 281 | */ |
| 282 | abstract public function getFunctionStats(); |
| 283 | |
| 284 | /** |
| 285 | * Returns a profiling output to be stored in debug file |
| 286 | * |
| 287 | * @return string |
| 288 | */ |
| 289 | abstract public function getOutput(); |
| 290 | } |
| 291 | |
| 292 | /** @deprecated class alias since 1.46 */ |
| 293 | class_alias( Profiler::class, 'Profiler' ); |