Merge "Add a 'revdelete-selected-file' message on Special:RevisionDelete"
[lhc/web/wiklou.git] / includes / profiler / ProfilerMwprof.php
1 <?php
2 /**
3 * Profiler class for Mwprof.
4 *
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.
9 *
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.
14 *
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
19 *
20 * @file
21 * @ingroup Profiler
22 */
23
24 /**
25 * Profiler class for Mwprof.
26 *
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.
31 *
32 * @see https://github.com/wikimedia/operations-software-mwprof
33 * @since 1.23
34 */
35 class ProfilerMwprof extends Profiler {
36
37 // Message types
38
39 const TYPE_SINGLE = 1;
40 const TYPE_RUNNING = 2;
41
42 /**
43 * Indicate that this Profiler subclass is persistent.
44 *
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.
48 *
49 * @return bool true
50 */
51 public function isPersistent() {
52 return true;
53 }
54
55 /**
56 * Start a profiling section.
57 *
58 * Marks the beginning of the function or code-block that should be time
59 * and logged under some specific name.
60 *
61 * @param string $inName Section to start
62 */
63 public function profileIn( $inName ) {
64 $this->mWorkStack[] = array( $inName, count( $this->mWorkStack ),
65 $this->getTime(), $this->getTime( 'cpu' ) );
66 }
67
68 /**
69 * Produce an empty function report.
70 *
71 * ProfileMwprof does not provide a function report.
72 *
73 * @return string Empty string.
74 */
75 public function getFunctionReport() {
76 return '';
77 }
78
79 /**
80 * Close a profiling section.
81 *
82 * Marks the end of the function or code-block that should be timed and
83 * logged under some specific name.
84 *
85 * @param string $outName Section to close
86 */
87 public function profileOut( $outName ) {
88 list( $inName, $inCount, $inWall, $inCpu ) = array_pop( $this->mWorkStack );
89
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 ) ) );
94 return;
95 }
96
97 $elapsedCpu = $this->getTime( 'cpu' ) - $inCpu;
98 $elapsedWall = $this->getTime() - $inWall;
99 $this->updateEntry( $outName, $elapsedCpu, $elapsedWall );
100 $this->updateTrxProfiling( $outName, $elapsedWall );
101 }
102
103 /**
104 * Update an entry with timing data.
105 *
106 * @param string $name Section name
107 * @param float $elapsedCpu elapsed CPU time
108 * @param float $elapsedWall elapsed wall-clock time
109 */
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,
117 'count' => 1,
118 );
119 return;
120 }
121
122 $entry = &$this->mCollated[$name];
123
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;
130
131 $wall = new RunningStat();
132 $wall->push( $entry['wall'] );
133 $entry['wall'] = $wall;
134 }
135
136 $entry['count']++;
137 $entry['cpu']->push( $elapsedCpu );
138 $entry['wall']->push( $elapsedWall );
139 }
140
141 /**
142 * Serialize profiling data and send to a profiling data aggregator.
143 *
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.
148 */
149 public function logData() {
150 global $wgUDPProfilerHost, $wgUDPProfilerPort;
151
152 $this->close();
153
154 $sock = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
155 socket_connect( $sock, $wgUDPProfilerHost, $wgUDPProfilerPort );
156 $bufferLength = 0;
157 $buffer = '';
158 foreach ( $this->mCollated as $name => $entry ) {
159 $count = $entry['count'];
160 $cpu = $entry['cpu'];
161 $wall = $entry['wall'];
162
163 if ( $count === 1 ) {
164 $data = array( self::TYPE_SINGLE, $name, $cpu, $wall );
165 } else {
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 );
169 }
170
171 $encoded = MWMessagePack::pack( $data );
172 $length = strlen( $encoded );
173
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 );
180 $buffer = '';
181 $bufferLength = 0;
182 }
183 $buffer .= $encoded;
184 $bufferLength += $length;
185 }
186 if ( $bufferLength !== 0 ) {
187 socket_send( $sock, $buffer, $bufferLength, 0 );
188 }
189 }
190 }