diff options
-rw-r--r-- | includes/AutoLoader.php | 1 | ||||
-rw-r--r-- | includes/profiler/ProfilerMwprof.php | 190 |
2 files changed, 191 insertions, 0 deletions
diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index e6ce693ca19a..4905f1857209 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -826,6 +826,7 @@ $wgAutoloadLocalClasses = array( # includes/profiler 'Profiler' => 'includes/profiler/Profiler.php', + 'ProfilerMwprof' => 'includes/profiler/ProfilerMwprof.php', 'ProfilerSimple' => 'includes/profiler/ProfilerSimple.php', 'ProfilerSimpleText' => 'includes/profiler/ProfilerSimpleText.php', 'ProfilerSimpleTrace' => 'includes/profiler/ProfilerSimpleTrace.php', diff --git a/includes/profiler/ProfilerMwprof.php b/includes/profiler/ProfilerMwprof.php new file mode 100644 index 000000000000..e81c6ecc8c85 --- /dev/null +++ b/includes/profiler/ProfilerMwprof.php @@ -0,0 +1,190 @@ +<?php +/** + * Profiler class for Mwprof. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @ingroup Profiler + */ + +/** + * Profiler class for Mwprof. + * + * Mwprof is a high-performance MediaWiki profiling data collector, designed to + * collect profiling data from multiple hosts running in tandem. This class + * serializes profiling samples into MessagePack arrays and sends them to an + * Mwprof instance via UDP. + * + * @see https://github.com/wikimedia/operations-software-mwprof + * @since 1.23 + */ +class ProfilerMwprof extends Profiler { + + // Message types + + const TYPE_SINGLE = 1; + const TYPE_RUNNING = 2; + + /** + * Indicate that this Profiler subclass is persistent. + * + * Called by Parser::braceSubstitution. If true, the parser will not + * generate per-title profiling sections, to avoid overloading the + * profiling data collector. + * + * @return bool true + */ + public function isPersistent() { + return true; + } + + /** + * Start a profiling section. + * + * Marks the beginning of the function or code-block that should be time + * and logged under some specific name. + * + * @param string $inName Section to start + */ + public function profileIn( $inName ) { + $this->mWorkStack[] = array( $inName, count( $this->mWorkStack ), + $this->getTime(), $this->getTime( 'cpu' ) ); + } + + /** + * Produce an empty function report. + * + * ProfileMwprof does not provide a function report. + * + * @return string Empty string. + */ + public function getFunctionReport() { + return ''; + } + + /** + * Close a profiling section. + * + * Marks the end of the function or code-block that should be timed and + * logged under some specific name. + * + * @param string $outName Section to close + */ + public function profileOut( $outName ) { + list( $inName, $inCount, $inWall, $inCpu ) = array_pop( $this->mWorkStack ); + + // Check for unbalanced profileIn / profileOut calls. + // Bad entries are logged but not sent. + if ( $inName !== $outName ) { + wfDebugLog( 'ProfilerUnbalanced', json_encode( array( $inName, $outName ) ) ); + return; + } + + $elapsedCpu = $this->getTime( 'cpu' ) - $inCpu; + $elapsedWall = $this->getTime() - $inWall; + $this->updateEntry( $outName, $elapsedCpu, $elapsedWall ); + $this->updateTrxProfiling( $outName, $elapsedWall ); + } + + /** + * Update an entry with timing data. + * + * @param string $name Section name + * @param float $elapsedCpu elapsed CPU time + * @param float $elapsedWall elapsed wall-clock time + */ + public function updateEntry( $name, $elapsedCpu, $elapsedWall ) { + // If this is the first measurement for this entry, store plain values. + // Many profiled functions will only be called once per request. + if ( !isset( $this->mCollated[$name] ) ) { + $this->mCollated[$name] = array( + 'cpu' => $elapsedCpu, + 'wall' => $elapsedWall, + 'count' => 1, + ); + return; + } + + $entry = &$this->mCollated[$name]; + + // If it's the second measurement, convert the plain values to + // RunningStat instances, so we can push the incoming values on top. + if ( $entry['count'] === 1 ) { + $cpu = new RunningStat(); + $cpu->push( $entry['cpu'] ); + $entry['cpu'] = $cpu; + + $wall = new RunningStat(); + $wall->push( $entry['wall'] ); + $entry['wall'] = $wall; + } + + $entry['count']++; + $entry['cpu']->push( $elapsedCpu ); + $entry['wall']->push( $elapsedWall ); + } + + /** + * Serialize profiling data and send to a profiling data aggregator. + * + * Individual entries are represented as arrays and then encoded using + * MessagePack, an efficient binary data-interchange format. Encoded + * entries are accumulated into a buffer and sent in batch via UDP to the + * profiling data aggregator. + */ + public function logData() { + global $wgUDPProfilerHost, $wgUDPProfilerPort; + + $this->close(); + + $sock = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP ); + socket_connect( $sock, $wgUDPProfilerHost, $wgUDPProfilerPort ); + $bufferLength = 0; + $buffer = ''; + foreach ( $this->mCollated as $name => $entry ) { + $count = $entry['count']; + $cpu = $entry['cpu']; + $wall = $entry['wall']; + + if ( $count === 1 ) { + $data = array( self::TYPE_SINGLE, $name, $cpu, $wall ); + } else { + $data = array( self::TYPE_RUNNING, $name, $count, + $cpu->m1, $cpu->m2, $cpu->min, $cpu->max, + $wall->m1, $wall->m2, $wall->min, $wall->max ); + } + + $encoded = MWMessagePack::pack( $data ); + $length = strlen( $encoded ); + + // If adding this entry would cause the size of the buffer to + // exceed the standard ethernet MTU size less the UDP header, + // send all pending data and reset the buffer. Otherwise, continue + // accumulating entries into the current buffer. + if ( $length + $bufferLength > 1450 ) { + socket_send( $sock, $buffer, $bufferLength, 0 ); + $buffer = ''; + $bufferLength = 0; + } + $buffer .= $encoded; + $bufferLength += $length; + } + if ( $bufferLength !== 0 ) { + socket_send( $sock, $buffer, $bufferLength, 0 ); + } + } +} |