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 $stats = $services->getStatsFactory();
110 $shards = $services->getMainConfig()->get( MainConfigNames::MultiShardSiteStats )
112 : self::SHARDS_OFF;
113
114 $deltaByType = [];
115 foreach ( self::COUNTERS as $type ) {
116 $delta = $this->$type;
117 $deltaByType[$type] = $delta;
118
119 // T392258: This is an operational metric for site activity and server load,
120 // e.g. edit submissions and account creations.
121 // When MediaWiki adjusts the "total" downward, e.g. after a re-count or
122 // page deletion, we should ignore that. We have to anyway, as Prometheus
123 // requires counters to monotonically increase.
124 // https://prometheus.io/docs/concepts/metric_types/#counter
125 if ( $delta > 0 ) {
126 $stats->getCounter( 'site_stats_total' )
127 ->setLabel( 'engagement', $type )
128 ->copyToStatsdAt( "site.$type" )
129 ->incrementBy( $delta );
130 }
131 }
132
133 ( new AutoCommitUpdate(
134 $services->getConnectionProvider()->getPrimaryDatabase(),
135 __METHOD__,
136 static function ( IDatabase $dbw, $fname ) use ( $deltaByType, $shards ) {
137 $set = [];
138 $initValues = [];
139 if ( $shards > 1 ) {
140 $shard = mt_rand( 1, $shards );
141 } else {
142 $shard = 1;
143 }
144
145 $hasNegativeDelta = false;
146 foreach ( self::COUNTERS as $field => $type ) {
147 $delta = (int)$deltaByType[$type];
148 $initValues[$field] = $delta;
149 if ( $delta > 0 ) {
150 $set[$field] = new RawSQLValue( $dbw->addIdentifierQuotes( $field ) . '+' . abs( $delta ) );
151 } elseif ( $delta < 0 ) {
152 $hasNegativeDelta = true;
153 $set[$field] = new RawSQLValue( $dbw->buildGreatest(
154 [ 'new' => $dbw->addIdentifierQuotes( $field ) ],
155 abs( $delta )
156 ) . '-' . abs( $delta ) );
157 }
158 }
159
160 if ( $set ) {
161 if ( $hasNegativeDelta ) {
163 ->update( 'site_stats' )
164 ->set( $set )
165 ->where( [ 'ss_row_id' => $shard ] )
166 ->caller( $fname )->execute();
167 } else {
169 ->insertInto( 'site_stats' )
170 ->row( array_merge( [ 'ss_row_id' => $shard ], $initValues ) )
171 ->onDuplicateKeyUpdate()
172 ->uniqueIndexFields( [ 'ss_row_id' ] )
173 ->set( $set )
174 ->caller( $fname )->execute();
175 }
176 }
177 }
178 ) )->doUpdate();
179
180 // Invalidate cache used by parser functions
181 SiteStats::unload();
182 }
183
188 public static function cacheUpdate( IDatabase $dbw ) {
189 $services = MediaWikiServices::getInstance();
190 $config = $services->getMainConfig();
191
192 $dbr = $services->getConnectionProvider()->getReplicaDatabase( false, 'vslow' );
193 # Get non-bot users than did some recent action other than making accounts.
194 # If account creation is included, the number gets inflated ~20+ fold on enwiki.
195 $activeUsers = $dbr->newSelectQueryBuilder()
196 ->select( 'COUNT(DISTINCT rc_actor)' )
197 ->from( 'recentchanges' )
198 ->join( 'actor', 'actor', 'actor_id=rc_actor' )
199 ->where( [
200 $dbr->expr( 'rc_type', '!=', RC_EXTERNAL ), // Exclude external (Wikidata)
201 $dbr->expr( 'actor_user', '!=', null ),
202 $dbr->expr( 'rc_bot', '=', 0 ),
203 $dbr->expr( 'rc_log_type', '!=', 'newusers' )->or( 'rc_log_type', '=', null ),
204 $dbr->expr( 'rc_timestamp', '>=',
205 $dbr->timestamp( time() - $config->get( MainConfigNames::ActiveUserDays ) * 24 * 3600 ) )
206 ] )
207 ->caller( __METHOD__ )
208 ->fetchField();
210 ->update( 'site_stats' )
211 ->set( [ 'ss_active_users' => intval( $activeUsers ) ] )
212 ->where( [ 'ss_row_id' => 1 ] )
213 ->caller( __METHOD__ )->execute();
214
215 // Invalid cache used by parser functions
216 SiteStats::unload();
217
218 return $activeUsers;
219 }
220}
221
223class_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:45
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.