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
{
39 const TYPE_SINGLE
= 1;
40 const TYPE_RUNNING
= 2;
43 * Indicate that this Profiler subclass is persistent.
45 * Called by Parser::braceSubstitution. If true, the parser will not
46 * generate per-title profiling sections, to avoid overloading the
47 * profiling data collector.
51 public function isPersistent() {
56 * Start a profiling section.
58 * Marks the beginning of the function or code-block that should be time
59 * and logged under some specific name.
61 * @param string $inName Section to start
63 public function profileIn( $inName ) {
64 $this->mWorkStack
[] = array( $inName, count( $this->mWorkStack
),
65 $this->getTime(), $this->getTime( 'cpu' ) );
69 * Produce an empty function report.
71 * ProfileMwprof does not provide a function report.
73 * @return string Empty string.
75 public function getFunctionReport() {
80 * Close a profiling section.
82 * Marks the end of the function or code-block that should be timed and
83 * logged under some specific name.
85 * @param string $outName Section to close
87 public function profileOut( $outName ) {
88 list( $inName, $inCount, $inWall, $inCpu ) = array_pop( $this->mWorkStack
);
90 // Check for unbalanced profileIn / profileOut calls.
91 // Bad entries are logged but not sent.
92 if ( $inName !== $outName ) {
93 $this->debugGroup( 'ProfilerUnbalanced', json_encode( array( $inName, $outName ) ) );
97 $elapsedCpu = $this->getTime( 'cpu' ) - $inCpu;
98 $elapsedWall = $this->getTime() - $inWall;
99 $this->updateEntry( $outName, $elapsedCpu, $elapsedWall );
100 $this->updateTrxProfiling( $outName, $elapsedWall );
104 * Update an entry with timing data.
106 * @param string $name Section name
107 * @param float $elapsedCpu elapsed CPU time
108 * @param float $elapsedWall elapsed wall-clock time
110 public function updateEntry( $name, $elapsedCpu, $elapsedWall ) {
111 // If this is the first measurement for this entry, store plain values.
112 // Many profiled functions will only be called once per request.
113 if ( !isset( $this->mCollated
[$name] ) ) {
114 $this->mCollated
[$name] = array(
115 'cpu' => $elapsedCpu,
116 'wall' => $elapsedWall,
122 $entry = &$this->mCollated
[$name];
124 // If it's the second measurement, convert the plain values to
125 // RunningStat instances, so we can push the incoming values on top.
126 if ( $entry['count'] === 1 ) {
127 $cpu = new RunningStat();
128 $cpu->push( $entry['cpu'] );
129 $entry['cpu'] = $cpu;
131 $wall = new RunningStat();
132 $wall->push( $entry['wall'] );
133 $entry['wall'] = $wall;
137 $entry['cpu']->push( $elapsedCpu );
138 $entry['wall']->push( $elapsedWall );
142 * Serialize profiling data and send to a profiling data aggregator.
144 * Individual entries are represented as arrays and then encoded using
145 * MessagePack, an efficient binary data-interchange format. Encoded
146 * entries are accumulated into a buffer and sent in batch via UDP to the
147 * profiling data aggregator.
149 public function logData() {
150 global $wgUDPProfilerHost, $wgUDPProfilerPort;
154 $sock = socket_create( AF_INET
, SOCK_DGRAM
, SOL_UDP
);
155 socket_connect( $sock, $wgUDPProfilerHost, $wgUDPProfilerPort );
158 foreach ( $this->mCollated
as $name => $entry ) {
159 $count = $entry['count'];
160 $cpu = $entry['cpu'];
161 $wall = $entry['wall'];
163 if ( $count === 1 ) {
164 $data = array( self
::TYPE_SINGLE
, $name, $cpu, $wall );
166 $data = array( self
::TYPE_RUNNING
, $name, $count,
167 $cpu->m1
, $cpu->m2
, $cpu->min
, $cpu->max
,
168 $wall->m1
, $wall->m2
, $wall->min
, $wall->max
);
171 $encoded = MWMessagePack
::pack( $data );
172 $length = strlen( $encoded );
174 // If adding this entry would cause the size of the buffer to
175 // exceed the standard ethernet MTU size less the UDP header,
176 // send all pending data and reset the buffer. Otherwise, continue
177 // accumulating entries into the current buffer.
178 if ( $length +
$bufferLength > 1450 ) {
179 socket_send( $sock, $buffer, $bufferLength, 0 );
184 $bufferLength +
= $length;
186 if ( $bufferLength !== 0 ) {
187 socket_send( $sock, $buffer, $bufferLength, 0 );