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.
Basic database interface for live and lazy-loaded relation database handles.
Definition IDatabase.php:39
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.