MediaWiki
REL1_19
|
00001 <?php 00002 00006 class SiteStats { 00007 static $row, $loaded = false; 00008 static $jobs; 00009 static $pageCount = array(); 00010 static $groupMemberCounts = array(); 00011 00012 static function recache() { 00013 self::load( true ); 00014 } 00015 00019 static function load( $recache = false ) { 00020 if ( self::$loaded && !$recache ) { 00021 return; 00022 } 00023 00024 self::$row = self::loadAndLazyInit(); 00025 00026 # This code is somewhat schema-agnostic, because I'm changing it in a minor release -- TS 00027 if ( !isset( self::$row->ss_total_pages ) && self::$row->ss_total_pages == -1 ) { 00028 # Update schema 00029 $u = new SiteStatsUpdate( 0, 0, 0 ); 00030 $u->doUpdate(); 00031 $dbr = wfGetDB( DB_SLAVE ); 00032 self::$row = $dbr->selectRow( 'site_stats', '*', false, __METHOD__ ); 00033 } 00034 00035 self::$loaded = true; 00036 } 00037 00041 static function loadAndLazyInit() { 00042 wfDebug( __METHOD__ . ": reading site_stats from slave\n" ); 00043 $row = self::doLoad( wfGetDB( DB_SLAVE ) ); 00044 00045 if( !self::isSane( $row ) ) { 00046 // Might have just been initialized during this request? Underflow? 00047 wfDebug( __METHOD__ . ": site_stats damaged or missing on slave\n" ); 00048 $row = self::doLoad( wfGetDB( DB_MASTER ) ); 00049 } 00050 00051 if( !self::isSane( $row ) ) { 00052 // Normally the site_stats table is initialized at install time. 00053 // Some manual construction scenarios may leave the table empty or 00054 // broken, however, for instance when importing from a dump into a 00055 // clean schema with mwdumper. 00056 wfDebug( __METHOD__ . ": initializing damaged or missing site_stats\n" ); 00057 00058 SiteStatsInit::doAllAndCommit( wfGetDB( DB_SLAVE ) ); 00059 00060 $row = self::doLoad( wfGetDB( DB_MASTER ) ); 00061 } 00062 00063 if( !self::isSane( $row ) ) { 00064 wfDebug( __METHOD__ . ": site_stats persistently nonsensical o_O\n" ); 00065 } 00066 return $row; 00067 } 00068 00073 static function doLoad( $db ) { 00074 return $db->selectRow( 'site_stats', '*', false, __METHOD__ ); 00075 } 00076 00080 static function views() { 00081 self::load(); 00082 return self::$row->ss_total_views; 00083 } 00084 00088 static function edits() { 00089 self::load(); 00090 return self::$row->ss_total_edits; 00091 } 00092 00096 static function articles() { 00097 self::load(); 00098 return self::$row->ss_good_articles; 00099 } 00100 00104 static function pages() { 00105 self::load(); 00106 return self::$row->ss_total_pages; 00107 } 00108 00112 static function users() { 00113 self::load(); 00114 return self::$row->ss_users; 00115 } 00116 00120 static function activeUsers() { 00121 self::load(); 00122 return self::$row->ss_active_users; 00123 } 00124 00128 static function images() { 00129 self::load(); 00130 return self::$row->ss_images; 00131 } 00132 00138 static function numberingroup( $group ) { 00139 if ( !isset( self::$groupMemberCounts[$group] ) ) { 00140 global $wgMemc; 00141 $key = wfMemcKey( 'SiteStats', 'groupcounts', $group ); 00142 $hit = $wgMemc->get( $key ); 00143 if ( !$hit ) { 00144 $dbr = wfGetDB( DB_SLAVE ); 00145 $hit = $dbr->selectField( 00146 'user_groups', 00147 'COUNT(*)', 00148 array( 'ug_group' => $group ), 00149 __METHOD__ 00150 ); 00151 $wgMemc->set( $key, $hit, 3600 ); 00152 } 00153 self::$groupMemberCounts[$group] = $hit; 00154 } 00155 return self::$groupMemberCounts[$group]; 00156 } 00157 00161 static function jobs() { 00162 if ( !isset( self::$jobs ) ) { 00163 $dbr = wfGetDB( DB_SLAVE ); 00164 self::$jobs = $dbr->estimateRowCount( 'job' ); 00165 /* Zero rows still do single row read for row that doesn't exist, but people are annoyed by that */ 00166 if ( self::$jobs == 1 ) { 00167 self::$jobs = 0; 00168 } 00169 } 00170 return self::$jobs; 00171 } 00172 00178 static function pagesInNs( $ns ) { 00179 wfProfileIn( __METHOD__ ); 00180 if( !isset( self::$pageCount[$ns] ) ) { 00181 $dbr = wfGetDB( DB_SLAVE ); 00182 self::$pageCount[$ns] = (int)$dbr->selectField( 00183 'page', 00184 'COUNT(*)', 00185 array( 'page_namespace' => $ns ), 00186 __METHOD__ 00187 ); 00188 } 00189 wfProfileOut( __METHOD__ ); 00190 return self::$pageCount[$ns]; 00191 } 00192 00200 private static function isSane( $row ) { 00201 if( 00202 $row === false 00203 || $row->ss_total_pages < $row->ss_good_articles 00204 || $row->ss_total_edits < $row->ss_total_pages 00205 ) { 00206 return false; 00207 } 00208 // Now check for underflow/overflow 00209 foreach( array( 'total_views', 'total_edits', 'good_articles', 00210 'total_pages', 'users', 'images' ) as $member ) { 00211 if( 00212 $row->{"ss_$member"} > 2000000000 00213 || $row->{"ss_$member"} < 0 00214 ) { 00215 return false; 00216 } 00217 } 00218 return true; 00219 } 00220 } 00221 00225 class SiteStatsUpdate implements DeferrableUpdate { 00226 00227 var $mViews, $mEdits, $mGood, $mPages, $mUsers; 00228 00229 function __construct( $views, $edits, $good, $pages = 0, $users = 0 ) { 00230 $this->mViews = $views; 00231 $this->mEdits = $edits; 00232 $this->mGood = $good; 00233 $this->mPages = $pages; 00234 $this->mUsers = $users; 00235 } 00236 00242 function appendUpdate( &$sql, $field, $delta ) { 00243 if ( $delta ) { 00244 if ( $sql ) { 00245 $sql .= ','; 00246 } 00247 if ( $delta < 0 ) { 00248 $sql .= "$field=$field-1"; 00249 } else { 00250 $sql .= "$field=$field+1"; 00251 } 00252 } 00253 } 00254 00255 function doUpdate() { 00256 $dbw = wfGetDB( DB_MASTER ); 00257 00258 $updates = ''; 00259 00260 $this->appendUpdate( $updates, 'ss_total_views', $this->mViews ); 00261 $this->appendUpdate( $updates, 'ss_total_edits', $this->mEdits ); 00262 $this->appendUpdate( $updates, 'ss_good_articles', $this->mGood ); 00263 $this->appendUpdate( $updates, 'ss_total_pages', $this->mPages ); 00264 $this->appendUpdate( $updates, 'ss_users', $this->mUsers ); 00265 00266 if ( $updates ) { 00267 $site_stats = $dbw->tableName( 'site_stats' ); 00268 $sql = "UPDATE $site_stats SET $updates"; 00269 00270 # Need a separate transaction because this a global lock 00271 $dbw->begin( __METHOD__ ); 00272 $dbw->query( $sql, __METHOD__ ); 00273 $dbw->commit( __METHOD__ ); 00274 } 00275 } 00276 00281 public static function cacheUpdate( $dbw ) { 00282 global $wgActiveUserDays; 00283 $dbr = wfGetDB( DB_SLAVE, array( 'SpecialStatistics', 'vslow' ) ); 00284 # Get non-bot users than did some recent action other than making accounts. 00285 # If account creation is included, the number gets inflated ~20+ fold on enwiki. 00286 $activeUsers = $dbr->selectField( 00287 'recentchanges', 00288 'COUNT( DISTINCT rc_user_text )', 00289 array( 00290 'rc_user != 0', 00291 'rc_bot' => 0, 00292 "rc_log_type != 'newusers' OR rc_log_type IS NULL", 00293 "rc_timestamp >= '{$dbw->timestamp( wfTimestamp( TS_UNIX ) - $wgActiveUserDays*24*3600 )}'", 00294 ), 00295 __METHOD__ 00296 ); 00297 $dbw->update( 00298 'site_stats', 00299 array( 'ss_active_users' => intval( $activeUsers ) ), 00300 array( 'ss_row_id' => 1 ), 00301 __METHOD__ 00302 ); 00303 return $activeUsers; 00304 } 00305 } 00306 00310 class SiteStatsInit { 00311 00312 // Database connection 00313 private $db; 00314 00315 // Various stats 00316 private $mEdits, $mArticles, $mPages, $mUsers, $mViews, $mFiles = 0; 00317 00324 public function __construct( $database = false ) { 00325 if ( $database instanceof DatabaseBase ) { 00326 $this->db = $database; 00327 } else { 00328 $this->db = wfGetDB( $database ? DB_MASTER : DB_SLAVE ); 00329 } 00330 } 00331 00336 public function edits() { 00337 $this->mEdits = $this->db->selectField( 'revision', 'COUNT(*)', '', __METHOD__ ); 00338 $this->mEdits += $this->db->selectField( 'archive', 'COUNT(*)', '', __METHOD__ ); 00339 return $this->mEdits; 00340 } 00341 00346 public function articles() { 00347 global $wgArticleCountMethod; 00348 00349 $tables = array( 'page' ); 00350 $conds = array( 00351 'page_namespace' => MWNamespace::getContentNamespaces(), 00352 'page_is_redirect' => 0, 00353 ); 00354 00355 if ( $wgArticleCountMethod == 'link' ) { 00356 $tables[] = 'pagelinks'; 00357 $conds[] = 'pl_from=page_id'; 00358 } elseif ( $wgArticleCountMethod == 'comma' ) { 00359 // To make a correct check for this, we would need, for each page, 00360 // to load the text, maybe uncompress it, maybe decode it and then 00361 // check if there's one comma. 00362 // But one thing we are sure is that if the page is empty, it can't 00363 // contain a comma :) 00364 $conds[] = 'page_len > 0'; 00365 } 00366 00367 $this->mArticles = $this->db->selectField( $tables, 'COUNT(DISTINCT page_id)', 00368 $conds, __METHOD__ ); 00369 return $this->mArticles; 00370 } 00371 00376 public function pages() { 00377 $this->mPages = $this->db->selectField( 'page', 'COUNT(*)', '', __METHOD__ ); 00378 return $this->mPages; 00379 } 00380 00385 public function users() { 00386 $this->mUsers = $this->db->selectField( 'user', 'COUNT(*)', '', __METHOD__ ); 00387 return $this->mUsers; 00388 } 00389 00394 public function views() { 00395 $this->mViews = $this->db->selectField( 'page', 'SUM(page_counter)', '', __METHOD__ ); 00396 return $this->mViews; 00397 } 00398 00403 public function files() { 00404 $this->mFiles = $this->db->selectField( 'image', 'COUNT(*)', '', __METHOD__ ); 00405 return $this->mFiles; 00406 } 00407 00420 public static function doAllAndCommit( $database, array $options = array() ) { 00421 $options += array( 'update' => false, 'views' => true, 'activeUsers' => false ); 00422 00423 // Grab the object and count everything 00424 $counter = new SiteStatsInit( $database ); 00425 00426 $counter->edits(); 00427 $counter->articles(); 00428 $counter->pages(); 00429 $counter->users(); 00430 $counter->files(); 00431 00432 // Only do views if we don't want to not count them 00433 if( $options['views'] ) { 00434 $counter->views(); 00435 } 00436 00437 // Update/refresh 00438 if( $options['update'] ) { 00439 $counter->update(); 00440 } else { 00441 $counter->refresh(); 00442 } 00443 00444 // Count active users if need be 00445 if( $options['activeUsers'] ) { 00446 SiteStatsUpdate::cacheUpdate( wfGetDB( DB_MASTER ) ); 00447 } 00448 } 00449 00453 public function update() { 00454 list( $values, $conds ) = $this->getDbParams(); 00455 $dbw = wfGetDB( DB_MASTER ); 00456 $dbw->update( 'site_stats', $values, $conds, __METHOD__ ); 00457 } 00458 00463 public function refresh() { 00464 list( $values, $conds, $views ) = $this->getDbParams(); 00465 $dbw = wfGetDB( DB_MASTER ); 00466 $dbw->delete( 'site_stats', $conds, __METHOD__ ); 00467 $dbw->insert( 'site_stats', array_merge( $values, $conds, $views ), __METHOD__ ); 00468 } 00469 00474 private function getDbParams() { 00475 $values = array( 00476 'ss_total_edits' => $this->mEdits, 00477 'ss_good_articles' => $this->mArticles, 00478 'ss_total_pages' => $this->mPages, 00479 'ss_users' => $this->mUsers, 00480 'ss_images' => $this->mFiles 00481 ); 00482 $conds = array( 'ss_row_id' => 1 ); 00483 $views = array( 'ss_total_views' => $this->mViews ); 00484 return array( $values, $conds, $views ); 00485 } 00486 }