Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 69
0.00% covered (danger)
0.00%
0 / 11
CRAP
0.00% covered (danger)
0.00%
0 / 1
Profiler
0.00% covered (danger)
0.00%
0 / 68
0.00% covered (danger)
0.00%
0 / 11
1482
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 init
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
56
 instance
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getProfileID
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 scopedProfileIn
n/a
0 / 0
n/a
0 / 0
0
 scopedProfileOut
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getTransactionProfiler
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 close
n/a
0 / 0
n/a
0 / 0
0
 getOutputs
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
30
 logData
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
56
 logDataPageOutputOnly
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
42
 getContentType
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
20
 setAllowOutput
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
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
7namespace MediaWiki\Profiler;
8
9use MediaWiki\Logger\LoggerFactory;
10use MediaWiki\Profiler\Output\ProfilerOutput;
11use MediaWiki\WikiMap\WikiMap;
12use Psr\Log\LoggerInterface;
13use UnexpectedValueException;
14use 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 */
26abstract 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 */
293class_alias( Profiler::class, 'Profiler' );