MediaWiki master
SiteStatsUpdate.php
Go to the documentation of this file.
1<?php
7namespace MediaWiki\Deferred;
8
12use UnexpectedValueException;
13use Wikimedia\Assert\Assert;
16
22 protected $edits = 0;
24 protected $pages = 0;
26 protected $articles = 0;
28 protected $users = 0;
30 protected $images = 0;
31
32 private const SHARDS_OFF = 1;
33 public const SHARDS_ON = 10;
34
36 private const COUNTERS = [
37 'ss_total_edits' => 'edits',
38 'ss_total_pages' => 'pages',
39 'ss_good_articles' => 'articles',
40 'ss_users' => 'users',
41 'ss_images' => 'images'
42 ];
43
47 public function __construct( $views, $edits, $good, $pages = 0, $users = 0 ) {
48 $this->edits = $edits;
49 $this->articles = $good;
50 $this->pages = $pages;
51 $this->users = $users;
52 }
53
54 public function merge( MergeableUpdate $update ) {
56 Assert::parameterType( __CLASS__, $update, '$update' );
57 '@phan-var SiteStatsUpdate $update';
58
59 foreach ( self::COUNTERS as $field ) {
60 $this->$field += $update->$field;
61 }
62 }
63
77 public static function factory( array $deltas ) {
78 $update = new self( 0, 0, 0 );
79
80 foreach ( $deltas as $name => $unused ) {
81 if ( !in_array( $name, self::COUNTERS ) ) { // T187585
82 throw new UnexpectedValueException( __METHOD__ . ": no field called '$name'" );
83 }
84 }
85
86 foreach ( self::COUNTERS as $field ) {
87 $update->$field = $deltas[$field] ?? 0;
88 }
89
90 return $update;
91 }
92
93 public function doUpdate() {
95 $stats = $services->getStatsFactory();
96 $shards = $services->getMainConfig()->get( MainConfigNames::MultiShardSiteStats )
98 : self::SHARDS_OFF;
99
100 $deltaByType = [];
101 foreach ( self::COUNTERS as $type ) {
102 $delta = $this->$type;
103 $deltaByType[$type] = $delta;
104
105 // T392258: This is an operational metric for site activity and server load,
106 // e.g. edit submissions and account creations.
107 // When MediaWiki adjusts the "total" downward, e.g. after a re-count or
108 // page deletion, we should ignore that. We have to anyway, as Prometheus
109 // requires counters to monotonically increase.
110 // https://prometheus.io/docs/concepts/metric_types/#counter
111 if ( $delta > 0 ) {
112 $stats->getCounter( 'site_stats_total' )
113 ->setLabel( 'engagement', $type )
114 ->incrementBy( $delta );
115 }
116 }
117
118 ( new AutoCommitUpdate(
119 $services->getConnectionProvider()->getPrimaryDatabase(),
120 __METHOD__,
121 static function ( IDatabase $dbw, $fname ) use ( $deltaByType, $shards ) {
122 $set = [];
123 $initValues = [];
124 if ( $shards > 1 ) {
125 $shard = mt_rand( 1, $shards );
126 } else {
127 $shard = 1;
128 }
129
130 $hasNegativeDelta = false;
131 foreach ( self::COUNTERS as $field => $type ) {
132 $delta = (int)$deltaByType[$type];
133 $initValues[$field] = $delta;
134 if ( $delta > 0 ) {
135 $set[$field] = new RawSQLValue( $dbw->addIdentifierQuotes( $field ) . '+' . abs( $delta ) );
136 } elseif ( $delta < 0 ) {
137 $hasNegativeDelta = true;
138 $set[$field] = new RawSQLValue( $dbw->buildGreatest(
139 [ 'new' => $dbw->addIdentifierQuotes( $field ) ],
140 abs( $delta )
141 ) . '-' . abs( $delta ) );
142 }
143 }
144
145 if ( $set ) {
146 if ( $hasNegativeDelta ) {
148 ->update( 'site_stats' )
149 ->set( $set )
150 ->where( [ 'ss_row_id' => $shard ] )
151 ->caller( $fname )->execute();
152 } else {
154 ->insertInto( 'site_stats' )
155 ->row( $initValues + [ 'ss_row_id' => $shard ] )
156 ->onDuplicateKeyUpdate()
157 ->uniqueIndexFields( [ 'ss_row_id' ] )
158 ->set( $set )
159 ->caller( $fname )->execute();
160 }
161 }
162 }
163 ) )->doUpdate();
164
165 // Invalidate cache used by parser functions
166 SiteStats::unload();
167 }
168
173 public static function cacheUpdate( IDatabase $dbw ) {
174 $services = MediaWikiServices::getInstance();
175 $config = $services->getMainConfig();
176
177 $rcl = $services->getRecentChangeLookup();
178 $dbr = $services->getConnectionProvider()->getReplicaDatabase( false, 'vslow' );
179 # Get non-bot users that did some recent action other than making accounts.
180 # If account creation is included, the number gets inflated ~20+ fold on enwiki.
181 $activeUsers = $dbr->newSelectQueryBuilder()
182 ->select( 'COUNT(DISTINCT rc_actor)' )
183 ->from( 'recentchanges' )
184 ->join( 'actor', 'actor', 'actor_id=rc_actor' )
185 ->where( [
186 $dbr->expr( 'rc_source', '=', $rcl->getPrimarySources() ),
187 $dbr->expr( 'actor_user', '!=', null ),
188 $dbr->expr( 'rc_bot', '=', 0 ),
189 $dbr->expr( 'rc_log_type', '!=', 'newusers' )->or( 'rc_log_type', '=', null ),
190 $dbr->expr( 'rc_timestamp', '>=',
191 $dbr->timestamp( time() - $config->get( MainConfigNames::ActiveUserDays ) * 24 * 3600 ) )
192 ] )
193 ->caller( __METHOD__ )
194 ->fetchField();
196 ->update( 'site_stats' )
197 ->set( [ 'ss_active_users' => intval( $activeUsers ) ] )
198 ->where( [ 'ss_row_id' => 1 ] )
199 ->caller( __METHOD__ )->execute();
200
201 // Invalid cache used by parser functions
202 SiteStats::unload();
203
204 return $activeUsers;
205 }
206}
207
209class_alias( SiteStatsUpdate::class, 'SiteStatsUpdate' );
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:22
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:31
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.