MediaWiki master
SiteStatsUpdate.php
Go to the documentation of this file.
1<?php
21namespace MediaWiki\Deferred;
22
26use UnexpectedValueException;
27use Wikimedia\Assert\Assert;
30
36 protected $edits = 0;
38 protected $pages = 0;
40 protected $articles = 0;
42 protected $users = 0;
44 protected $images = 0;
45
46 private const SHARDS_OFF = 1;
47 public const SHARDS_ON = 10;
48
50 private const COUNTERS = [
51 'ss_total_edits' => 'edits',
52 'ss_total_pages' => 'pages',
53 'ss_good_articles' => 'articles',
54 'ss_users' => 'users',
55 'ss_images' => 'images'
56 ];
57
61 public function __construct( $views, $edits, $good, $pages = 0, $users = 0 ) {
62 $this->edits = $edits;
63 $this->articles = $good;
64 $this->pages = $pages;
65 $this->users = $users;
66 }
67
68 public function merge( MergeableUpdate $update ) {
70 Assert::parameterType( __CLASS__, $update, '$update' );
71 '@phan-var SiteStatsUpdate $update';
72
73 foreach ( self::COUNTERS as $field ) {
74 $this->$field += $update->$field;
75 }
76 }
77
91 public static function factory( array $deltas ) {
92 $update = new self( 0, 0, 0 );
93
94 foreach ( $deltas as $name => $unused ) {
95 if ( !in_array( $name, self::COUNTERS ) ) { // T187585
96 throw new UnexpectedValueException( __METHOD__ . ": no field called '$name'" );
97 }
98 }
99
100 foreach ( self::COUNTERS as $field ) {
101 $update->$field = $deltas[$field] ?? 0;
102 }
103
104 return $update;
105 }
106
107 public function doUpdate() {
108 $services = MediaWikiServices::getInstance();
109 $metric = $services->getStatsFactory()->getCounter( 'site_stats_total' );
110 $shards = $services->getMainConfig()->get( MainConfigNames::MultiShardSiteStats ) ?
111 self::SHARDS_ON : self::SHARDS_OFF;
112
113 $deltaByType = [];
114 foreach ( self::COUNTERS as $type ) {
115 $delta = $this->$type;
116 if ( $delta !== 0 ) {
117 $metric->setLabel( 'engagement', $type )
118 ->copyToStatsdAt( "site.$type" )
119 ->incrementBy( $delta );
120 }
121 $deltaByType[$type] = $delta;
122 }
123
124 ( new AutoCommitUpdate(
125 $services->getConnectionProvider()->getPrimaryDatabase(),
126 __METHOD__,
127 static function ( IDatabase $dbw, $fname ) use ( $deltaByType, $shards ) {
128 $set = [];
129 $initValues = [];
130 if ( $shards > 1 ) {
131 $shard = mt_rand( 1, $shards );
132 } else {
133 $shard = 1;
134 }
135
136 $hasNegativeDelta = false;
137 foreach ( self::COUNTERS as $field => $type ) {
138 $delta = (int)$deltaByType[$type];
139 $initValues[$field] = $delta;
140 if ( $delta > 0 ) {
141 $set[$field] = new RawSQLValue( $dbw->buildGreatest(
142 [ $field => $dbw->addIdentifierQuotes( $field ) . '+' . abs( $delta ) ],
143 0
144 ) );
145 } elseif ( $delta < 0 ) {
146 $hasNegativeDelta = true;
147 $set[$field] = new RawSQLValue( $dbw->buildGreatest(
148 [ 'new' => $dbw->addIdentifierQuotes( $field ) . '-' . abs( $delta ) ],
149 0
150 ) );
151 }
152 }
153
154 if ( $set ) {
155 if ( $hasNegativeDelta ) {
157 ->update( 'site_stats' )
158 ->set( $set )
159 ->where( [ 'ss_row_id' => $shard ] )
160 ->caller( $fname )->execute();
161 } else {
163 ->insertInto( 'site_stats' )
164 ->row( array_merge( [ 'ss_row_id' => $shard ], $initValues ) )
165 ->onDuplicateKeyUpdate()
166 ->uniqueIndexFields( [ 'ss_row_id' ] )
167 ->set( $set )
168 ->caller( $fname )->execute();
169 }
170 }
171 }
172 ) )->doUpdate();
173
174 // Invalidate cache used by parser functions
175 SiteStats::unload();
176 }
177
182 public static function cacheUpdate( IDatabase $dbw ) {
183 $services = MediaWikiServices::getInstance();
184 $config = $services->getMainConfig();
185
186 $dbr = $services->getConnectionProvider()->getReplicaDatabase( false, 'vslow' );
187 # Get non-bot users than did some recent action other than making accounts.
188 # If account creation is included, the number gets inflated ~20+ fold on enwiki.
189 $activeUsers = $dbr->newSelectQueryBuilder()
190 ->select( 'COUNT(DISTINCT rc_actor)' )
191 ->from( 'recentchanges' )
192 ->join( 'actor', 'actor', 'actor_id=rc_actor' )
193 ->where( [
194 $dbr->expr( 'rc_type', '!=', RC_EXTERNAL ), // Exclude external (Wikidata)
195 $dbr->expr( 'actor_user', '!=', null ),
196 $dbr->expr( 'rc_bot', '=', 0 ),
197 $dbr->expr( 'rc_log_type', '!=', 'newusers' )->or( 'rc_log_type', '=', null ),
198 $dbr->expr( 'rc_timestamp', '>=',
199 $dbr->timestamp( time() - $config->get( MainConfigNames::ActiveUserDays ) * 24 * 3600 ) )
200 ] )
201 ->caller( __METHOD__ )
202 ->fetchField();
204 ->update( 'site_stats' )
205 ->set( [ 'ss_active_users' => intval( $activeUsers ) ] )
206 ->where( [ 'ss_row_id' => 1 ] )
207 ->caller( __METHOD__ )->execute();
208
209 // Invalid cache used by parser functions
210 SiteStats::unload();
211
212 return $activeUsers;
213 }
214}
215
217class_alias( SiteStatsUpdate::class, 'SiteStatsUpdate' );
const RC_EXTERNAL
Definition Defines.php:120
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
Raw SQL value to be used in query builders.
Interface that deferrable updates should implement.
Interface that deferrable updates can implement to signal that updates can be combined.
Interface to a relational database.
Definition IDatabase.php:48
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.