Avoid sending multiple UDP packets for the same key in wfIncrStats().
authorAaron Schulz <aschulz@wikimedia.org>
Thu, 28 Mar 2013 20:18:30 +0000 (13:18 -0700)
committerAaron Schulz <aschulz@wikimedia.org>
Fri, 29 Mar 2013 19:01:42 +0000 (12:01 -0700)
* This should help reduce collector data loss.

Change-Id: Ibe55648422d1b8aac86dd6fa83973d3c8715b0aa

includes/AutoLoader.php
includes/GlobalFunctions.php
includes/StatCounter.php [new file with mode: 0644]

index 0b3c788..9ef3165 100644 (file)
@@ -243,6 +243,7 @@ $wgAutoloadLocalClasses = array(
        'SpecialRedirectToSpecial' => 'includes/SpecialPage.php',
        'SquidPurgeClient' => 'includes/SquidPurgeClient.php',
        'SquidPurgeClientPool' => 'includes/SquidPurgeClient.php',
+       'StatCounter' => 'includes/StatCounter.php',
        'Status' => 'includes/Status.php',
        'StreamFile' => 'includes/StreamFile.php',
        'StringUtils' => 'includes/StringUtils.php',
index 9042926..1a4b985 100644 (file)
@@ -1177,6 +1177,8 @@ function wfLogProfilingData() {
        global $wgRequestTime, $wgDebugLogFile, $wgDebugRawPage, $wgRequest;
        global $wgProfileLimit, $wgUser;
 
+       StatCounter::singleton()->flush();
+
        $profiler = Profiler::instance();
 
        # Profiling must actually be enabled...
@@ -1240,51 +1242,7 @@ function wfLogProfilingData() {
  * @return void
  */
 function wfIncrStats( $key, $count = 1 ) {
-       global $wgStatsMethod;
-
-       $count = intval( $count );
-       if ( $count == 0 ) {
-               return;
-       }
-
-       if( $wgStatsMethod == 'udp' ) {
-               global $wgUDPProfilerHost, $wgUDPProfilerPort, $wgAggregateStatsID;
-               static $socket;
-
-               $id = $wgAggregateStatsID !== false ? $wgAggregateStatsID : wfWikiID();
-
-               if ( !$socket ) {
-                       $socket = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
-                       $statline = "stats/{$id} - 1 1 1 1 1 -total\n";
-                       socket_sendto(
-                               $socket,
-                               $statline,
-                               strlen( $statline ),
-                               0,
-                               $wgUDPProfilerHost,
-                               $wgUDPProfilerPort
-                       );
-               }
-               $statline = "stats/{$id} - {$count} 1 1 1 1 {$key}\n";
-               wfSuppressWarnings();
-               socket_sendto(
-                       $socket,
-                       $statline,
-                       strlen( $statline ),
-                       0,
-                       $wgUDPProfilerHost,
-                       $wgUDPProfilerPort
-               );
-               wfRestoreWarnings();
-       } elseif( $wgStatsMethod == 'cache' ) {
-               global $wgMemc;
-               $key = wfMemcKey( 'stats', $key );
-               if ( is_null( $wgMemc->incr( $key, $count ) ) ) {
-                       $wgMemc->add( $key, $count );
-               }
-       } else {
-               // Disabled
-       }
+       StatCounter::singleton()->incr( $key, $count );
 }
 
 /**
diff --git a/includes/StatCounter.php b/includes/StatCounter.php
new file mode 100644 (file)
index 0000000..30e5042
--- /dev/null
@@ -0,0 +1,141 @@
+<?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
+ */
+
+/**
+ * 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() {}
+
+       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 ) {
+               if ( PHP_SAPI === 'cli' ) {
+                       $this->sendDelta( $key, $count );
+               } else {
+                       if ( !isset( $this->deltas[$key] ) ) {
+                               $this->deltas[$key] = 0;
+                       }
+                       $this->deltas[$key] += $count;
+               }
+       }
+
+       /**
+        * Flush all pending deltas to persistent storage
+        *
+        * @return void
+        */
+       public function flush() {
+               try {
+                       foreach ( $this->deltas as $key => $count ) {
+                               $this->sendDelta( $key, $count );
+                       }
+               } catch ( MWException $e ) {
+                       trigger_error( "Caught exception: {$e->getMessage()}");
+               }
+               $this->deltas = array();
+       }
+
+       /**
+        * @param string $key
+        * @param string $count
+        * @return void
+        */
+       protected function sendDelta( $key, $count ) {
+               global $wgStatsMethod;
+
+               $count = intval( $count );
+               if ( $count == 0 ) {
+                       return;
+               }
+
+               if ( $wgStatsMethod == 'udp' ) {
+                       global $wgUDPProfilerHost, $wgUDPProfilerPort, $wgAggregateStatsID;
+                       static $socket;
+
+                       $id = $wgAggregateStatsID !== false ? $wgAggregateStatsID : wfWikiID();
+
+                       if ( !$socket ) {
+                               $socket = socket_create( AF_INET, SOCK_DGRAM, SOL_UDP );
+                               $statline = "stats/{$id} - 1 1 1 1 1 -total\n";
+                               socket_sendto(
+                                       $socket,
+                                       $statline,
+                                       strlen( $statline ),
+                                       0,
+                                       $wgUDPProfilerHost,
+                                       $wgUDPProfilerPort
+                               );
+                       }
+                       $statline = "stats/{$id} - {$count} 1 1 1 1 {$key}\n";
+                       wfSuppressWarnings();
+                       socket_sendto(
+                               $socket,
+                               $statline,
+                               strlen( $statline ),
+                               0,
+                               $wgUDPProfilerHost,
+                               $wgUDPProfilerPort
+                       );
+                       wfRestoreWarnings();
+               } elseif ( $wgStatsMethod == 'cache' ) {
+                       global $wgMemc;
+                       $key = wfMemcKey( 'stats', $key );
+                       if ( is_null( $wgMemc->incr( $key, $count ) ) ) {
+                               $wgMemc->add( $key, $count );
+                       }
+               } else {
+                       // Disabled
+               }
+       }
+
+       function __destruct() {
+               $this->flush();
+       }
+}