X-Git-Url: https://git.heureux-cyclage.org/?p=lhc%2Fweb%2Fwiklou.git;a=blobdiff_plain;f=includes%2FSiteStats.php;h=e3cb617e3fa97cb6d92a18e85acad220c3243cc8;hp=ce87596a5fbd298386b9a89bc0d66ac0b0e16073;hb=e73328601d51674e8fef89c3db12b30ceafd702f;hpb=971a50c4f3c61fb3a4bec60cd712317bb8ddcb9a diff --git a/includes/SiteStats.php b/includes/SiteStats.php index ce87596a5f..e3cb617e3f 100644 --- a/includes/SiteStats.php +++ b/includes/SiteStats.php @@ -23,161 +23,123 @@ use Wikimedia\Rdbms\Database; use Wikimedia\Rdbms\IDatabase; use MediaWiki\MediaWikiServices; +use Wikimedia\Rdbms\LoadBalancer; /** * Static accessor class for site_stats and related things */ class SiteStats { - /** @var bool|stdClass */ + /** @var stdClass */ private static $row; - /** @var bool */ - private static $loaded = false; - - /** @var int[] */ - private static $pageCount = []; - - static function unload() { - self::$loaded = false; - } - - static function recache() { - self::load( true ); - } - /** - * @param bool $recache + * Trigger a reload next time a field is accessed */ - static function load( $recache = false ) { - if ( self::$loaded && !$recache ) { - return; - } - - self::$row = self::loadAndLazyInit(); + public static function unload() { + self::$row = null; + } - # This code is somewhat schema-agnostic, because I'm changing it in a minor release -- TS - if ( !isset( self::$row->ss_total_pages ) && self::$row->ss_total_pages == -1 ) { - # Update schema - $u = new SiteStatsUpdate( 0, 0, 0 ); - $u->doUpdate(); - self::$row = self::doLoad( wfGetDB( DB_REPLICA ) ); + protected static function load() { + if ( self::$row === null ) { + self::$row = self::loadAndLazyInit(); } - - self::$loaded = true; } /** - * @return bool|stdClass + * @return stdClass */ - static function loadAndLazyInit() { - global $wgMiserMode; + protected static function loadAndLazyInit() { + $config = MediaWikiServices::getInstance()->getMainConfig(); + $lb = self::getLB(); + $dbr = $lb->getConnection( DB_REPLICA ); wfDebug( __METHOD__ . ": reading site_stats from replica DB\n" ); - $row = self::doLoad( wfGetDB( DB_REPLICA ) ); - - if ( !self::isSane( $row ) ) { - $lb = MediaWikiServices::getInstance()->getDBLoadBalancer(); - if ( $lb->hasOrMadeRecentMasterChanges() ) { - // Might have just been initialized during this request? Underflow? - wfDebug( __METHOD__ . ": site_stats damaged or missing on replica DB\n" ); - $row = self::doLoad( wfGetDB( DB_MASTER ) ); - } - } + $row = self::doLoadFromDB( $dbr ); - if ( !$wgMiserMode && !self::isSane( $row ) ) { - // Normally the site_stats table is initialized at install time. - // Some manual construction scenarios may leave the table empty or - // broken, however, for instance when importing from a dump into a - // clean schema with mwdumper. - wfDebug( __METHOD__ . ": initializing damaged or missing site_stats\n" ); + if ( !self::isRowSane( $row ) && $lb->hasOrMadeRecentMasterChanges() ) { + // Might have just been initialized during this request? Underflow? + wfDebug( __METHOD__ . ": site_stats damaged or missing on replica DB\n" ); + $row = self::doLoadFromDB( $lb->getConnection( DB_MASTER ) ); + } - SiteStatsInit::doAllAndCommit( wfGetDB( DB_REPLICA ) ); + if ( !self::isRowSane( $row ) ) { + if ( $config->get( 'MiserMode' ) ) { + // Start off with all zeroes, assuming that this is a new wiki or any + // repopulations where done manually via script. + SiteStatsInit::doPlaceholderInit(); + } else { + // Normally the site_stats table is initialized at install time. + // Some manual construction scenarios may leave the table empty or + // broken, however, for instance when importing from a dump into a + // clean schema with mwdumper. + wfDebug( __METHOD__ . ": initializing damaged or missing site_stats\n" ); + SiteStatsInit::doAllAndCommit( $dbr ); + } - $row = self::doLoad( wfGetDB( DB_MASTER ) ); + $row = self::doLoadFromDB( $lb->getConnection( DB_MASTER ) ); } - if ( !self::isSane( $row ) ) { + if ( !self::isRowSane( $row ) ) { wfDebug( __METHOD__ . ": site_stats persistently nonsensical o_O\n" ); + // Always return a row-like object + $row = self::salvageInsaneRow( $row ); } return $row; } - /** - * @param IDatabase $db - * @return bool|stdClass - */ - static function doLoad( $db ) { - return $db->selectRow( 'site_stats', [ - 'ss_row_id', - 'ss_total_edits', - 'ss_good_articles', - 'ss_total_pages', - 'ss_users', - 'ss_active_users', - 'ss_images', - ], [], __METHOD__ ); - } - - /** - * Return the total number of page views. Except we don't track those anymore. - * Stop calling this function, it will be removed some time in the future. It's - * kept here simply to prevent fatal errors. - * - * @deprecated since 1.25 - * @return int - */ - static function views() { - wfDeprecated( __METHOD__, '1.25' ); - return 0; - } - /** * @return int */ - static function edits() { + public static function edits() { self::load(); - return self::$row->ss_total_edits; + + return (int)self::$row->ss_total_edits; } /** * @return int */ - static function articles() { + public static function articles() { self::load(); - return self::$row->ss_good_articles; + + return (int)self::$row->ss_good_articles; } /** * @return int */ - static function pages() { + public static function pages() { self::load(); - return self::$row->ss_total_pages; + + return (int)self::$row->ss_total_pages; } /** * @return int */ - static function users() { + public static function users() { self::load(); - return self::$row->ss_users; + + return (int)self::$row->ss_users; } /** * @return int */ - static function activeUsers() { + public static function activeUsers() { self::load(); - return self::$row->ss_active_users; + + return (int)self::$row->ss_active_users; } /** * @return int */ - static function images() { + public static function images() { self::load(); - return self::$row->ss_images; + + return (int)self::$row->ss_images; } /** @@ -185,24 +147,25 @@ class SiteStats { * @param string $group Name of group * @return int */ - static function numberingroup( $group ) { + public static function numberingroup( $group ) { $cache = MediaWikiServices::getInstance()->getMainWANObjectCache(); + $fname = __METHOD__; + return $cache->getWithSetCallback( $cache->makeKey( 'SiteStats', 'groupcounts', $group ), $cache::TTL_HOUR, - function ( $oldValue, &$ttl, array &$setOpts ) use ( $group ) { - $dbr = wfGetDB( DB_REPLICA ); - + function ( $oldValue, &$ttl, array &$setOpts ) use ( $group, $fname ) { + $dbr = self::getLB()->getConnection( DB_REPLICA ); $setOpts += Database::getCacheSetOptions( $dbr ); - return $dbr->selectField( + return (int)$dbr->selectField( 'user_groups', 'COUNT(*)', [ 'ug_group' => $group, 'ug_expiry IS NULL OR ug_expiry >= ' . $dbr->addQuotes( $dbr->timestamp() ) ], - __METHOD__ + $fname ); }, [ 'pcTTL' => $cache::TTL_PROC_LONG ] @@ -213,8 +176,9 @@ class SiteStats { * Total number of jobs in the job queue. * @return int */ - static function jobs() { + public static function jobs() { $cache = MediaWikiServices::getInstance()->getMainWANObjectCache(); + return $cache->getWithSetCallback( $cache->makeKey( 'SiteStats', 'jobscount' ), $cache::TTL_MINUTE, @@ -232,20 +196,55 @@ class SiteStats { /** * @param int $ns - * * @return int */ - static function pagesInNs( $ns ) { - if ( !isset( self::$pageCount[$ns] ) ) { - $dbr = wfGetDB( DB_REPLICA ); - self::$pageCount[$ns] = (int)$dbr->selectField( - 'page', - 'COUNT(*)', - [ 'page_namespace' => $ns ], - __METHOD__ - ); - } - return self::$pageCount[$ns]; + public static function pagesInNs( $ns ) { + $cache = MediaWikiServices::getInstance()->getMainWANObjectCache(); + $fname = __METHOD__; + + return $cache->getWithSetCallback( + $cache->makeKey( 'SiteStats', 'page-in-namespace', $ns ), + $cache::TTL_HOUR, + function ( $oldValue, &$ttl, array &$setOpts ) use ( $ns, $fname ) { + $dbr = self::getLB()->getConnection( DB_REPLICA ); + $setOpts += Database::getCacheSetOptions( $dbr ); + + return (int)$dbr->selectField( + 'page', + 'COUNT(*)', + [ 'page_namespace' => $ns ], + $fname + ); + }, + [ 'pcTTL' => $cache::TTL_PROC_LONG ] + ); + } + + /** + * @return array + */ + public static function selectFields() { + return [ + 'ss_total_edits', + 'ss_good_articles', + 'ss_total_pages', + 'ss_users', + 'ss_active_users', + 'ss_images', + ]; + } + + /** + * @param IDatabase $db + * @return stdClass|bool + */ + private static function doLoadFromDB( IDatabase $db ) { + return $db->selectRow( + 'site_stats', + self::selectFields(), + [ 'ss_row_id' => 1 ], + __METHOD__ + ); } /** @@ -254,10 +253,9 @@ class SiteStats { * Checks only fields which are filled by SiteStatsInit::refresh. * * @param bool|object $row - * * @return bool */ - private static function isSane( $row ) { + private static function isRowSane( $row ) { if ( $row === false || $row->ss_total_pages < $row->ss_good_articles || $row->ss_total_edits < $row->ss_total_pages @@ -272,152 +270,34 @@ class SiteStats { 'ss_users', 'ss_images', ] as $member ) { - if ( $row->$member > 2000000000 || $row->$member < 0 ) { + if ( $row->$member < 0 ) { return false; } } - return true; - } -} - -/** - * Class designed for counting of stats. - */ -class SiteStatsInit { - - // Database connection - private $db; - - // Various stats - private $mEdits = null, $mArticles = null, $mPages = null; - private $mUsers = null, $mFiles = null; - - /** - * @param bool|IDatabase $database - * - bool: Whether to use the master DB - * - IDatabase: Database connection to use - */ - public function __construct( $database = false ) { - if ( $database instanceof IDatabase ) { - $this->db = $database; - } elseif ( $database ) { - $this->db = wfGetDB( DB_MASTER ); - } else { - $this->db = wfGetDB( DB_REPLICA, 'vslow' ); - } - } - /** - * Count the total number of edits - * @return int - */ - public function edits() { - $this->mEdits = $this->db->selectField( 'revision', 'COUNT(*)', '', __METHOD__ ); - $this->mEdits += $this->db->selectField( 'archive', 'COUNT(*)', '', __METHOD__ ); - return $this->mEdits; + return true; } /** - * Count pages in article space(s) - * @return int + * @param stdClass|bool $row + * @return stdClass */ - public function articles() { - global $wgArticleCountMethod; - - $tables = [ 'page' ]; - $conds = [ - 'page_namespace' => MWNamespace::getContentNamespaces(), - 'page_is_redirect' => 0, - ]; - - if ( $wgArticleCountMethod == 'link' ) { - $tables[] = 'pagelinks'; - $conds[] = 'pl_from=page_id'; - } elseif ( $wgArticleCountMethod == 'comma' ) { - // To make a correct check for this, we would need, for each page, - // to load the text, maybe uncompress it, maybe decode it and then - // check if there's one comma. - // But one thing we are sure is that if the page is empty, it can't - // contain a comma :) - $conds[] = 'page_len > 0'; + private static function salvageInsaneRow( $row ) { + $map = $row ? (array)$row : []; + // Fill in any missing values with zero + $map += array_fill_keys( self::selectFields(), 0 ); + // Convert negative values to zero + foreach ( $map as $field => $value ) { + $map[$field] = max( 0, $value ); } - $this->mArticles = $this->db->selectField( $tables, 'COUNT(DISTINCT page_id)', - $conds, __METHOD__ ); - return $this->mArticles; - } - - /** - * Count total pages - * @return int - */ - public function pages() { - $this->mPages = $this->db->selectField( 'page', 'COUNT(*)', '', __METHOD__ ); - return $this->mPages; - } - - /** - * Count total users - * @return int - */ - public function users() { - $this->mUsers = $this->db->selectField( 'user', 'COUNT(*)', '', __METHOD__ ); - return $this->mUsers; - } - - /** - * Count total files - * @return int - */ - public function files() { - $this->mFiles = $this->db->selectField( 'image', 'COUNT(*)', '', __METHOD__ ); - return $this->mFiles; - } - - /** - * Do all updates and commit them. More or less a replacement - * for the original initStats, but without output. - * - * @param IDatabase|bool $database - * - bool: Whether to use the master DB - * - IDatabase: Database connection to use - * @param array $options Array of options, may contain the following values - * - activeUsers bool: Whether to update the number of active users (default: false) - */ - public static function doAllAndCommit( $database, array $options = [] ) { - $options += [ 'update' => false, 'activeUsers' => false ]; - - // Grab the object and count everything - $counter = new SiteStatsInit( $database ); - - $counter->edits(); - $counter->articles(); - $counter->pages(); - $counter->users(); - $counter->files(); - - $counter->refresh(); - - // Count active users if need be - if ( $options['activeUsers'] ) { - SiteStatsUpdate::cacheUpdate( wfGetDB( DB_MASTER ) ); - } + return (object)$row; } /** - * Refresh site_stats + * @return LoadBalancer */ - public function refresh() { - $values = [ - 'ss_row_id' => 1, - 'ss_total_edits' => ( $this->mEdits === null ? $this->edits() : $this->mEdits ), - 'ss_good_articles' => ( $this->mArticles === null ? $this->articles() : $this->mArticles ), - 'ss_total_pages' => ( $this->mPages === null ? $this->pages() : $this->mPages ), - 'ss_users' => ( $this->mUsers === null ? $this->users() : $this->mUsers ), - 'ss_images' => ( $this->mFiles === null ? $this->files() : $this->mFiles ), - ]; - - $dbw = wfGetDB( DB_MASTER ); - $dbw->upsert( 'site_stats', $values, [ 'ss_row_id' ], $values, __METHOD__ ); + private static function getLB() { + return MediaWikiServices::getInstance()->getDBLoadBalancer(); } }