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 $metric = $services->getStatsFactory()->getCounter( 'site_stats_total' );
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 $metric->setLabel( 'engagement', $type )
117 ->copyToStatsdAt( "site.$type" )
118 ->incrementBy( $delta );
119 }
120 $deltaByType[$type] = $delta;
121 }
122
123 ( new AutoCommitUpdate(
124 $services->getConnectionProvider()->getPrimaryDatabase(),
125 __METHOD__,
126 static function ( IDatabase $dbw, $fname ) use ( $deltaByType, $shards ) {
127 $set = [];
128 $initValues = [];
129 if ( $shards > 1 ) {
130 $shard = mt_rand( 1, $shards );
131 } else {
132 $shard = 1;
133 }
134
135 $hasNegativeDelta = false;
136 foreach ( self::COUNTERS as $field => $type ) {
137 $delta = (int)$deltaByType[$type];
138 $initValues[$field] = $delta;
139 if ( $delta > 0 ) {
140 $set[] = "$field=" . $dbw->buildGreatest(
141 [ $field => $dbw->addIdentifierQuotes( $field ) . '+' . abs( $delta ) ],
142 0
143 );
144 } elseif ( $delta < 0 ) {
145 $hasNegativeDelta = true;
146 $set[] = "$field=" . $dbw->buildGreatest(
147 [ 'new' => $dbw->addIdentifierQuotes( $field ) . '-' . abs( $delta ) ],
148 0
149 );
150 }
151 }
152
153 if ( $set ) {
154 if ( $hasNegativeDelta ) {
156 ->update( 'site_stats' )
157 ->set( $set )
158 ->where( [ 'ss_row_id' => $shard ] )
159 ->caller( $fname )->execute();
160 } else {
162 ->insertInto( 'site_stats' )
163 ->row( array_merge( [ 'ss_row_id' => $shard ], $initValues ) )
164 ->onDuplicateKeyUpdate()
165 ->uniqueIndexFields( [ 'ss_row_id' ] )
166 ->set( $set )
167 ->caller( $fname )->execute();
168 }
169 }
170 }
171 ) )->doUpdate();
172
173 // Invalidate cache used by parser functions
174 SiteStats::unload();
175 }
176
181 public static function cacheUpdate( IDatabase $dbw ) {
182 $services = MediaWikiServices::getInstance();
183 $config = $services->getMainConfig();
184
185 $dbr = $services->getConnectionProvider()->getReplicaDatabase( false, 'vslow' );
186 # Get non-bot users than did some recent action other than making accounts.
187 # If account creation is included, the number gets inflated ~20+ fold on enwiki.
188 $activeUsers = $dbr->newSelectQueryBuilder()
189 ->select( 'COUNT(DISTINCT rc_actor)' )
190 ->from( 'recentchanges' )
191 ->join( 'actor', 'actor', 'actor_id=rc_actor' )
192 ->where( [
193 $dbr->expr( 'rc_type', '!=', RC_EXTERNAL ), // Exclude external (Wikidata)
194 $dbr->expr( 'actor_user', '!=', null ),
195 $dbr->expr( 'rc_bot', '=', 0 ),
196 $dbr->expr( 'rc_log_type', '!=', 'newusers' )->or( 'rc_log_type', '=', null ),
197 $dbr->expr( 'rc_timestamp', '>=',
198 $dbr->timestamp( time() - $config->get( MainConfigNames::ActiveUserDays ) * 24 * 3600 ) )
199 ] )
200 ->caller( __METHOD__ )
201 ->fetchField();
203 ->update( 'site_stats' )
204 ->set( [ 'ss_active_users' => intval( $activeUsers ) ] )
205 ->where( [ 'ss_row_id' => 1 ] )
206 ->caller( __METHOD__ )->execute();
207
208 // Invalid cache used by parser functions
209 SiteStats::unload();
210
211 return $activeUsers;
212 }
213}
214
216class_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.