MediaWiki master
SiteStatsUpdate.php
Go to the documentation of this file.
1<?php
21namespace MediaWiki\Deferred;
22
26use UnexpectedValueException;
27use Wikimedia\Assert\Assert;
29
35 protected $edits = 0;
37 protected $pages = 0;
39 protected $articles = 0;
41 protected $users = 0;
43 protected $images = 0;
44
45 private const SHARDS_OFF = 1;
46 public const SHARDS_ON = 10;
47
49 private const COUNTERS = [
50 'ss_total_edits' => 'edits',
51 'ss_total_pages' => 'pages',
52 'ss_good_articles' => 'articles',
53 'ss_users' => 'users',
54 'ss_images' => 'images'
55 ];
56
60 public function __construct( $views, $edits, $good, $pages = 0, $users = 0 ) {
61 $this->edits = $edits;
62 $this->articles = $good;
63 $this->pages = $pages;
64 $this->users = $users;
65 }
66
67 public function merge( MergeableUpdate $update ) {
69 Assert::parameterType( __CLASS__, $update, '$update' );
70 '@phan-var SiteStatsUpdate $update';
71
72 foreach ( self::COUNTERS as $field ) {
73 $this->$field += $update->$field;
74 }
75 }
76
90 public static function factory( array $deltas ) {
91 $update = new self( 0, 0, 0 );
92
93 foreach ( $deltas as $name => $unused ) {
94 if ( !in_array( $name, self::COUNTERS ) ) { // T187585
95 throw new UnexpectedValueException( __METHOD__ . ": no field called '$name'" );
96 }
97 }
98
99 foreach ( self::COUNTERS as $field ) {
100 $update->$field = $deltas[$field] ?? 0;
101 }
102
103 return $update;
104 }
105
106 public function doUpdate() {
107 $services = MediaWikiServices::getInstance();
108 $stats = $services->getStatsdDataFactory();
109 $shards = $services->getMainConfig()->get( MainConfigNames::MultiShardSiteStats ) ?
110 self::SHARDS_ON : self::SHARDS_OFF;
111
112 $deltaByType = [];
113 foreach ( self::COUNTERS as $type ) {
114 $delta = $this->$type;
115 if ( $delta !== 0 ) {
116 $stats->updateCount( "site.$type", $delta );
117 }
118 $deltaByType[$type] = $delta;
119 }
120
121 ( new AutoCommitUpdate(
122 $services->getConnectionProvider()->getPrimaryDatabase(),
123 __METHOD__,
124 static function ( IDatabase $dbw, $fname ) use ( $deltaByType, $shards ) {
125 $set = [];
126 $initValues = [];
127 if ( $shards > 1 ) {
128 $shard = mt_rand( 1, $shards );
129 } else {
130 $shard = 1;
131 }
132
133 $hasNegativeDelta = false;
134 foreach ( self::COUNTERS as $field => $type ) {
135 $delta = (int)$deltaByType[$type];
136 $initValues[$field] = $delta;
137 if ( $delta > 0 ) {
138 $set[] = "$field=" . $dbw->buildGreatest(
139 [ $field => $dbw->addIdentifierQuotes( $field ) . '+' . abs( $delta ) ],
140 0
141 );
142 } elseif ( $delta < 0 ) {
143 $hasNegativeDelta = true;
144 $set[] = "$field=" . $dbw->buildGreatest(
145 [ 'new' => $dbw->addIdentifierQuotes( $field ) . '-' . abs( $delta ) ],
146 0
147 );
148 }
149 }
150
151 if ( $set ) {
152 if ( $hasNegativeDelta ) {
154 ->update( 'site_stats' )
155 ->set( $set )
156 ->where( [ 'ss_row_id' => $shard ] )
157 ->caller( $fname )->execute();
158 } else {
160 ->insertInto( 'site_stats' )
161 ->row( array_merge( [ 'ss_row_id' => $shard ], $initValues ) )
162 ->onDuplicateKeyUpdate()
163 ->uniqueIndexFields( [ 'ss_row_id' ] )
164 ->set( $set )
165 ->caller( $fname )->execute();
166 }
167 }
168 }
169 ) )->doUpdate();
170
171 // Invalidate cache used by parser functions
172 SiteStats::unload();
173 }
174
179 public static function cacheUpdate( IDatabase $dbw ) {
180 $services = MediaWikiServices::getInstance();
181 $config = $services->getMainConfig();
182
183 $dbr = $services->getConnectionProvider()->getReplicaDatabase( false, 'vslow' );
184 # Get non-bot users than did some recent action other than making accounts.
185 # If account creation is included, the number gets inflated ~20+ fold on enwiki.
186 $activeUsers = $dbr->newSelectQueryBuilder()
187 ->select( 'COUNT(DISTINCT rc_actor)' )
188 ->from( 'recentchanges' )
189 ->join( 'actor', 'actor', 'actor_id=rc_actor' )
190 ->where( [
191 $dbr->expr( 'rc_type', '!=', RC_EXTERNAL ), // Exclude external (Wikidata)
192 $dbr->expr( 'actor_user', '!=', null ),
193 $dbr->expr( 'rc_bot', '=', 0 ),
194 $dbr->expr( 'rc_log_type', '!=', 'newusers' )->or( 'rc_log_type', '=', null ),
195 $dbr->expr( 'rc_timestamp', '>=',
196 $dbr->timestamp( time() - $config->get( MainConfigNames::ActiveUserDays ) * 24 * 3600 ) )
197 ] )
198 ->caller( __METHOD__ )
199 ->fetchField();
201 ->update( 'site_stats' )
202 ->set( [ 'ss_active_users' => intval( $activeUsers ) ] )
203 ->where( [ 'ss_row_id' => 1 ] )
204 ->caller( __METHOD__ )->execute();
205
206 // Invalid cache used by parser functions
207 SiteStats::unload();
208
209 return $activeUsers;
210 }
211}
212
214class_alias( SiteStatsUpdate::class, 'SiteStatsUpdate' );
const RC_EXTERNAL
Definition Defines.php:119
Deferrable Update for closure/callback updates that should use auto-commit mode.
Class for handling updates to the site_stats table.
doUpdate()
Perform the actual work.
merge(MergeableUpdate $update)
Merge this enqueued update with a new MergeableUpdate of the same qualified class name.
__construct( $views, $edits, $good, $pages=0, $users=0)
A class containing constants representing the names of configuration variables.
const MultiShardSiteStats
Name constant for the MultiShardSiteStats setting, for use with Config::get()
const ActiveUserDays
Name constant for the ActiveUserDays setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
Static accessor class for site_stats and related things.
Definition SiteStats.php:36
Interface that deferrable updates should implement.
Interface that deferrable updates can implement to signal that updates can be combined.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:36
newUpdateQueryBuilder()
Get an UpdateQueryBuilder bound to this connection.
newInsertQueryBuilder()
Get an InsertQueryBuilder bound to this connection.
buildGreatest( $fields, $values)
Build a GREATEST function statement comparing columns/values.
addIdentifierQuotes( $s)
Escape a SQL identifier (e.g.