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
21/**
22 * @ingroup Profiler
23 * @since 1.33
24 */
25class ProfilerExcimer extends Profiler {
26	/** @var ExcimerProfiler */
27	private $cpuProf;
28	/** @var ExcimerProfiler */
29	private $realProf;
30	private $period;
31
32	/**
33	 * @param array $params Associative array of parameters:
34	 *    - period: The sampling period
35	 *    - maxDepth: The maximum stack depth collected
36	 *    - cpuProfiler: A pre-started ExcimerProfiler instance for CPU
37	 *      profiling of the entire request including configuration.
38	 *    - realProfiler: A pre-started ExcimerProfiler instance for wall
39	 *      clock profiling of the entire request.
40	 */
41	public function __construct( array $params = [] ) {
42		parent::__construct( $params );
43
44		$this->period = $params['period'] ?? 0.01;
45		$maxDepth = $params['maxDepth'] ?? 100;
46
47		if ( isset( $params['cpuProfiler'] ) ) {
48			$this->cpuProf = $params['cpuProfiler'];
49		} else {
50			$this->cpuProf = new ExcimerProfiler;
51			$this->cpuProf->setEventType( EXCIMER_CPU );
52			$this->cpuProf->setPeriod( $this->period );
53			$this->cpuProf->setMaxDepth( $maxDepth );
54			$this->cpuProf->start();
55		}
56
57		if ( isset( $params['realProfiler'] ) ) {
58			$this->realProf = $params['realProfiler'];
59		} else {
60			$this->realProf = new ExcimerProfiler;
61			$this->realProf->setEventType( EXCIMER_REAL );
62			$this->realProf->setPeriod( $this->period );
63			$this->realProf->setMaxDepth( $maxDepth );
64			$this->realProf->start();
65		}
66	}
67
68	public function scopedProfileIn( $section ) {
69	}
70
71	public function close() {
72		$this->cpuProf->stop();
73		$this->realProf->stop();
74	}
75
76	public function getFunctionStats() {
77		$this->close();
78		$cpuStats = $this->cpuProf->getLog()->aggregateByFunction();
79		$realStats = $this->realProf->getLog()->aggregateByFunction();
80		$allNames = array_keys( $realStats + $cpuStats );
81		$cpuSamples = $this->cpuProf->getLog()->getEventCount();
82		$realSamples = $this->realProf->getLog()->getEventCount();
83
84		$resultStats = [ [
85			'name' => '-total',
86			'calls' => 1,
87			'memory' => 0,
88			'%memory' => 0,
89			'min_real' => 0,
90			'max_real' => 0,
91			'cpu' => $cpuSamples * $this->period * 1000,
92			'%cpu' => 100,
93			'real' => $realSamples * $this->period * 1000,
94			'%real' => 100,
95		] ];
96
97		foreach ( $allNames as $funcName ) {
98			$cpuEntry = $cpuStats[$funcName] ?? false;
99			$realEntry = $realStats[$funcName] ?? false;
100			$resultEntry = [
101				'name' => $funcName,
102				'calls' => 0,
103				'memory' => 0,
104				'%memory' => 0,
105				'min_real' => 0,
106				'max_real' => 0,
107			];
108
109			if ( $cpuEntry ) {
110				$resultEntry['cpu'] = $cpuEntry['inclusive'] * $this->period * 1000;
111				$resultEntry['%cpu'] = $cpuEntry['inclusive'] / $cpuSamples * 100;
112			} else {
113				$resultEntry['cpu'] = 0;
114				$resultEntry['%cpu'] = 0;
115			}
116			if ( $realEntry ) {
117				$resultEntry['real'] = $realEntry['inclusive'] * $this->period * 1000;
118				$resultEntry['%real'] = $realEntry['inclusive'] / $realSamples * 100;
119			} else {
120				$resultEntry['real'] = 0;
121				$resultEntry['%real'] = 0;
122			}
123
124			$resultStats[] = $resultEntry;
125		}
126		return $resultStats;
127	}
128
129	public function getOutput() {
130		$this->close();
131		$cpuLog = $this->cpuProf->getLog();
132		$realLog = $this->realProf->getLog();
133		$cpuStats = $cpuLog->aggregateByFunction();
134		$realStats = $realLog->aggregateByFunction();
135		$allNames = array_keys( $cpuStats + $realStats );
136		$cpuSamples = $cpuLog->getEventCount();
137		$realSamples = $realLog->getEventCount();
138
139		$result = '';
140
141		$titleFormat = "%-70s %10s %11s %10s %11s %10s %11s %10s %11s\n";
142		$statsFormat = "%-70s %10d %10.1f%% %10d %10.1f%% %10d %10.1f%% %10d %10.1f%%\n";
143		$result .= sprintf( $titleFormat,
144			'Name',
145			'CPU incl', 'CPU incl%', 'CPU self', 'CPU self%',
146			'Real incl', 'Real incl%', 'Real self', 'Real self%'
147		);
148
149		foreach ( $allNames as $funcName ) {
150			$realEntry = $realStats[$funcName] ?? false;
151			$cpuEntry = $cpuStats[$funcName] ?? false;
152			$realIncl = $realEntry ? $realEntry['inclusive'] : 0;
153			$realSelf = $realEntry ? $realEntry['self'] : 0;
154			$cpuIncl = $cpuEntry ? $cpuEntry['inclusive'] : 0;
155			$cpuSelf = $cpuEntry ? $cpuEntry['self'] : 0;
156			$result .= sprintf( $statsFormat,
157				$funcName,
158				$cpuIncl * $this->period * 1000,
159				$cpuIncl == 0 ? 0 : $cpuIncl / $cpuSamples * 100,
160				$cpuSelf * $this->period * 1000,
161				$cpuSelf == 0 ? 0 : $cpuSelf / $cpuSamples * 100,
162				$realIncl * $this->period * 1000,
163				$realIncl == 0 ? 0 : $realIncl / $realSamples * 100,
164				$realSelf * $this->period * 1000,
165				$realSelf == 0 ? 0 : $realSelf / $realSamples * 100
166			);
167		}
168
169		return $result;
170	}
171}
172