The format of datagrams generated by StatCounter and ProfilerSimpleUDP is not described by any standard and appears to have been designed for compatibility with the logging setup of the Wikimedia Foundation, which limits reusability. This patch adds two configuration variables, $wgUDPProfilerFormatString and $wgStatsFormatString, that can be used to specify the precise format that MediaWiki will use. The default values for these variables causes MediaWiki to generate the same output as before. The secret evil plan is to change the defaults so that MediaWiki emits metrics that are compatible with the popular StatsD format (see <https://github.com/b/statsd_spec>). This would allow us to replace udpprofiler with a generic StatsD instance instead. Change-Id: Iaf00811d3f8d6b89c1c68e84fc1f2c93425d5a2d
150 lines
3.6 KiB
PHP
150 lines
3.6 KiB
PHP
<?php
|
|
/**
|
|
* @defgroup StatCounter StatCounter
|
|
*
|
|
* StatCounter is used to increment arbitrary keys for profiling reasons.
|
|
* The key/values are persisted in several possible ways (see $wgStatsMethod).
|
|
*/
|
|
|
|
/**
|
|
* Aggregator for wfIncrStats() that batches updates per request.
|
|
*
|
|
* 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 StatCounter
|
|
* @author Aaron Schulz
|
|
*/
|
|
|
|
/**
|
|
* Aggregator for wfIncrStats() that batches updates per request.
|
|
* This avoids spamming the collector many times for the same key.
|
|
*
|
|
* @ingroup StatCounter
|
|
*/
|
|
class StatCounter {
|
|
/** @var Array */
|
|
protected $deltas = array(); // (key => count)
|
|
|
|
protected function __construct() {}
|
|
|
|
/**
|
|
* @return StatCounter
|
|
*/
|
|
public static function singleton() {
|
|
static $instance = null;
|
|
if ( !$instance ) {
|
|
$instance = new self();
|
|
}
|
|
return $instance;
|
|
}
|
|
|
|
/**
|
|
* Increment a key by delta $count
|
|
*
|
|
* @param string $key
|
|
* @param integer $count
|
|
* @return void
|
|
*/
|
|
public function incr( $key, $count = 1 ) {
|
|
$this->deltas[$key] = isset( $this->deltas[$key] ) ? $this->deltas[$key] : 0;
|
|
$this->deltas[$key] += $count;
|
|
if ( PHP_SAPI === 'cli' ) {
|
|
$this->flush();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Flush all pending deltas to persistent storage
|
|
*
|
|
* @return void
|
|
*/
|
|
public function flush() {
|
|
global $wgStatsMethod;
|
|
|
|
$deltas = array_filter( $this->deltas ); // remove 0 valued entries
|
|
if ( $wgStatsMethod === 'udp' ) {
|
|
$this->sendDeltasUDP( $deltas );
|
|
} elseif ( $wgStatsMethod === 'cache' ) {
|
|
$this->sendDeltasMemc( $deltas );
|
|
} else {
|
|
// disabled
|
|
}
|
|
$this->deltas = array();
|
|
}
|
|
|
|
/**
|
|
* @param array $deltas
|
|
* @return void
|
|
*/
|
|
protected function sendDeltasUDP( array $deltas ) {
|
|
global $wgUDPProfilerHost, $wgUDPProfilerPort, $wgAggregateStatsID,
|
|
$wgStatsFormatString;
|
|
|
|
$id = strlen( $wgAggregateStatsID ) ? $wgAggregateStatsID : wfWikiID();
|
|
|
|
$lines = array();
|
|
foreach ( $deltas as $key => $count ) {
|
|
$lines[] = sprintf( $wgStatsFormatString, $id, $count, $key );
|
|
}
|
|
|
|
if ( count( $lines ) ) {
|
|
static $socket = null;
|
|
if ( !$socket ) {
|
|
$socket = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
|
|
}
|
|
$packet = '';
|
|
$packets = array();
|
|
foreach ( $lines as $line ) {
|
|
if ( ( strlen( $packet ) + strlen( $line ) ) > 1450 ) {
|
|
$packets[] = $packet;
|
|
$packet = '';
|
|
}
|
|
$packet .= $line;
|
|
}
|
|
if ( $packet != '' ) {
|
|
$packets[] = $packet;
|
|
}
|
|
foreach ( $packets as $packet ) {
|
|
wfSuppressWarnings();
|
|
socket_sendto(
|
|
$socket,
|
|
$packet,
|
|
strlen( $packet ),
|
|
0,
|
|
$wgUDPProfilerHost,
|
|
$wgUDPProfilerPort
|
|
);
|
|
wfRestoreWarnings();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param array $deltas
|
|
* @return void
|
|
*/
|
|
protected function sendDeltasMemc( array $deltas ) {
|
|
global $wgMemc;
|
|
|
|
foreach ( $deltas as $key => $count ) {
|
|
$ckey = wfMemcKey( 'stats', $key );
|
|
if ( $wgMemc->incr( $ckey, $count ) === null ) {
|
|
$wgMemc->add( $ckey, $count );
|
|
}
|
|
}
|
|
}
|
|
}
|