3 * Profiler class for Mwprof.
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
25 * Profiler class for Mwprof.
27 * Mwprof is a high-performance MediaWiki profiling data collector, designed to
28 * collect profiling data from multiple hosts running in tandem. This class
29 * serializes profiling samples into MessagePack arrays and sends them to an
30 * Mwprof instance via UDP.
32 * @see https://github.com/wikimedia/operations-software-mwprof
35 class ProfilerMwprof
extends Profiler
{
37 const TYPE_SINGLE
= 1;
38 const TYPE_RUNNING
= 2;
40 protected function collateOnly() {
45 * Indicate that this Profiler subclass is persistent.
47 * Called by Parser::braceSubstitution. If true, the parser will not
48 * generate per-title profiling sections, to avoid overloading the
49 * profiling data collector.
53 public function isPersistent() {
58 * Start a profiling section.
60 * Marks the beginning of the function or code-block that should be time
61 * and logged under some specific name.
63 * @param string $inName Section to start
65 public function profileIn( $inName ) {
66 $this->mWorkStack
[] = array( $inName, count( $this->mWorkStack
),
67 $this->getTime(), $this->getTime( 'cpu' ), 0 );
71 * Close a profiling section.
73 * Marks the end of the function or code-block that should be timed and
74 * logged under some specific name.
76 * @param string $outName Section to close
78 public function profileOut( $outName ) {
79 list( $inName, $inCount, $inWall, $inCpu ) = array_pop( $this->mWorkStack
);
81 // Check for unbalanced profileIn / profileOut calls.
82 // Bad entries are logged but not sent.
83 if ( $inName !== $outName ) {
84 $this->debugGroup( 'ProfilerUnbalanced', json_encode( array( $inName, $outName ) ) );
88 $elapsedCpu = $this->getTime( 'cpu' ) - $inCpu;
89 $elapsedWall = $this->getTime() - $inWall;
90 $this->updateRunningEntry( $outName, $elapsedCpu, $elapsedWall );
91 $this->updateTrxProfiling( $outName, $elapsedWall );
95 * Update an entry with timing data.
97 * @param string $name Section name
98 * @param float $elapsedCpu elapsed CPU time
99 * @param float $elapsedWall elapsed wall-clock time
101 public function updateRunningEntry( $name, $elapsedCpu, $elapsedWall ) {
102 // If this is the first measurement for this entry, store plain values.
103 // Many profiled functions will only be called once per request.
104 if ( !isset( $this->mCollated
[$name] ) ) {
105 $this->mCollated
[$name] = array(
106 'cpu' => $elapsedCpu,
107 'wall' => $elapsedWall,
113 $entry = &$this->mCollated
[$name];
115 // If it's the second measurement, convert the plain values to
116 // RunningStat instances, so we can push the incoming values on top.
117 if ( $entry['count'] === 1 ) {
118 $cpu = new RunningStat();
119 $cpu->push( $entry['cpu'] );
120 $entry['cpu'] = $cpu;
122 $wall = new RunningStat();
123 $wall->push( $entry['wall'] );
124 $entry['wall'] = $wall;
128 $entry['cpu']->push( $elapsedCpu );
129 $entry['wall']->push( $elapsedWall );
133 * Produce an empty function report.
135 * ProfileMwprof does not provide a function report.
137 * @return string Empty string.
139 public function getFunctionReport() {
146 public function getRawData() {
147 // This method is called before shutdown in the footer method on Skins.
148 // If some outer methods have not yet called wfProfileOut(), work around
149 // that by clearing anything in the work stack to just the "-total" entry.
150 if ( count( $this->mWorkStack
) > 1 ) {
151 $oldWorkStack = $this->mWorkStack
;
152 $this->mWorkStack
= array( $this->mWorkStack
[0] ); // just the "-total" one
154 $oldWorkStack = null;
157 // If this trick is used, then the old work stack is swapped back afterwards.
158 // This means that logData() will still make use of all the method data since
159 // the missing wfProfileOut() calls should be made by the time it is called.
160 if ( $oldWorkStack ) {
161 $this->mWorkStack
= $oldWorkStack;
166 foreach ( $this->mCollated
as $fname => $data ) {
167 if ( $data['count'] == 1 ) {
170 'calls' => $data['count'],
171 'elapsed' => $data['wall'] * 1000,
172 'memory' => 0, // not supported
173 'min' => $data['wall'] * 1000,
174 'max' => $data['wall'] * 1000,
175 'overhead' => 0, // not supported
176 'periods' => array() // not supported
178 $totalWall +
= $data['wall'];
182 'calls' => $data['count'],
183 'elapsed' => $data['wall']->n
* $data['wall']->getMean() * 1000,
184 'memory' => 0, // not supported
185 'min' => $data['wall']->min
* 1000,
186 'max' => $data['wall']->max
* 1000,
187 'overhead' => 0, // not supported
188 'periods' => array() // not supported
190 $totalWall +
= $data['wall']->n
* $data['wall']->getMean();
193 $totalWall = $totalWall * 1000;
195 foreach ( $profile as &$item ) {
196 $item['percent'] = $totalWall ?
100 * $item['elapsed'] / $totalWall : 0;
197 $z+
= $item['percent'];
204 * Serialize profiling data and send to a profiling data aggregator.
206 * Individual entries are represented as arrays and then encoded using
207 * MessagePack, an efficient binary data-interchange format. Encoded
208 * entries are accumulated into a buffer and sent in batch via UDP to the
209 * profiling data aggregator.
211 public function logData() {
212 global $wgUDPProfilerHost, $wgUDPProfilerPort;
216 if ( !function_exists( 'socket_create' ) ) {
217 #trigger_error( __METHOD__ . ": function \"socket_create\" not found." );
218 return; // avoid fatal
221 $sock = socket_create( AF_INET
, SOCK_DGRAM
, SOL_UDP
);
222 socket_connect( $sock, $wgUDPProfilerHost, $wgUDPProfilerPort );
225 foreach ( $this->mCollated
as $name => $entry ) {
226 $count = $entry['count'];
227 $cpu = $entry['cpu'];
228 $wall = $entry['wall'];
230 if ( $count === 1 ) {
231 $data = array( self
::TYPE_SINGLE
, $name, $cpu, $wall );
233 $data = array( self
::TYPE_RUNNING
, $name, $count,
234 $cpu->m1
, $cpu->m2
, $cpu->min
, $cpu->max
,
235 $wall->m1
, $wall->m2
, $wall->min
, $wall->max
);
238 $encoded = MWMessagePack
::pack( $data );
239 $length = strlen( $encoded );
241 // If adding this entry would cause the size of the buffer to
242 // exceed the standard ethernet MTU size less the UDP header,
243 // send all pending data and reset the buffer. Otherwise, continue
244 // accumulating entries into the current buffer.
245 if ( $length +
$bufferLength > 1450 ) {
246 socket_send( $sock, $buffer, $bufferLength, 0 );
251 $bufferLength +
= $length;
253 if ( $bufferLength !== 0 ) {
254 socket_send( $sock, $buffer, $bufferLength, 0 );