summaryrefslogtreecommitdiff
path: root/includes/profiler/ProfilerMwprof.php
diff options
context:
space:
mode:
Diffstat (limited to 'includes/profiler/ProfilerMwprof.php')
-rw-r--r--includes/profiler/ProfilerMwprof.php256
1 files changed, 256 insertions, 0 deletions
diff --git a/includes/profiler/ProfilerMwprof.php b/includes/profiler/ProfilerMwprof.php
new file mode 100644
index 00000000..af3c7741
--- /dev/null
+++ b/includes/profiler/ProfilerMwprof.php
@@ -0,0 +1,256 @@
+<?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 {
+ /** @var array Queue of open profile calls with start data */
+ protected $mWorkStack = array();
+
+ /** @var array Map of (function name => aggregate data array) */
+ protected $mCollated = array();
+ /** @var array Cache of a standard broken collation entry */
+ protected $mErrorEntry;
+
+ // Message types
+ const TYPE_SINGLE = 1;
+ const TYPE_RUNNING = 2;
+
+ public function isStub() {
+ return false;
+ }
+
+ 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' ), 0 );
+ }
+
+ /**
+ * 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 ) {
+ $this->debugGroup( 'ProfilerUnbalanced', json_encode( array( $inName, $outName ) ) );
+ return;
+ }
+
+ $elapsedCpu = $this->getTime( 'cpu' ) - $inCpu;
+ $elapsedWall = $this->getTime() - $inWall;
+ $this->updateRunningEntry( $outName, $elapsedCpu, $elapsedWall );
+ $this->trxProfiler->recordFunctionCompletion( $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 updateRunningEntry( $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 );
+ }
+
+ /**
+ * @return array
+ */
+ public function getRawData() {
+ // This method is called before shutdown in the footer method on Skins.
+ // If some outer methods have not yet called wfProfileOut(), work around
+ // that by clearing anything in the work stack to just the "-total" entry.
+ if ( count( $this->mWorkStack ) > 1 ) {
+ $oldWorkStack = $this->mWorkStack;
+ $this->mWorkStack = array( $this->mWorkStack[0] ); // just the "-total" one
+ } else {
+ $oldWorkStack = null;
+ }
+ $this->close();
+ // If this trick is used, then the old work stack is swapped back afterwards.
+ // This means that logData() will still make use of all the method data since
+ // the missing wfProfileOut() calls should be made by the time it is called.
+ if ( $oldWorkStack ) {
+ $this->mWorkStack = $oldWorkStack;
+ }
+
+ $totalWall = 0.0;
+ $profile = array();
+ foreach ( $this->mCollated as $fname => $data ) {
+ if ( $data['count'] == 1 ) {
+ $profile[] = array(
+ 'name' => $fname,
+ 'calls' => $data['count'],
+ 'elapsed' => $data['wall'] * 1000,
+ 'memory' => 0, // not supported
+ 'min' => $data['wall'] * 1000,
+ 'max' => $data['wall'] * 1000,
+ 'overhead' => 0, // not supported
+ 'periods' => array() // not supported
+ );
+ $totalWall += $data['wall'];
+ } else {
+ $profile[] = array(
+ 'name' => $fname,
+ 'calls' => $data['count'],
+ 'elapsed' => $data['wall']->n * $data['wall']->getMean() * 1000,
+ 'memory' => 0, // not supported
+ 'min' => $data['wall']->min * 1000,
+ 'max' => $data['wall']->max * 1000,
+ 'overhead' => 0, // not supported
+ 'periods' => array() // not supported
+ );
+ $totalWall += $data['wall']->n * $data['wall']->getMean();
+ }
+ }
+ $totalWall = $totalWall * 1000;
+
+ foreach ( $profile as &$item ) {
+ $item['percent'] = $totalWall ? 100 * $item['elapsed'] / $totalWall : 0;
+ }
+
+ return $profile;
+ }
+
+ /**
+ * 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();
+
+ if ( !function_exists( 'socket_create' ) ) {
+ return; // avoid fatal
+ }
+
+ $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 );
+ }
+ }
+
+ /**
+ * Close opened profiling sections
+ */
+ public function close() {
+ while ( count( $this->mWorkStack ) ) {
+ $this->profileOut( 'close' );
+ }
+ }
+
+ public function getOutput() {
+ return ''; // no report
+ }
+}