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