MediaWiki  master
SiteStatsUpdate.php
Go to the documentation of this file.
1 <?php
23 use Wikimedia\Assert\Assert;
25 
31  protected $edits = 0;
33  protected $pages = 0;
35  protected $articles = 0;
37  protected $users = 0;
39  protected $images = 0;
40 
41  private const SHARDS_OFF = 1;
42  public const SHARDS_ON = 10;
43 
45  private const COUNTERS = [
46  'ss_total_edits' => 'edits',
47  'ss_total_pages' => 'pages',
48  'ss_good_articles' => 'articles',
49  'ss_users' => 'users',
50  'ss_images' => 'images'
51  ];
52 
56  public function __construct( $views, $edits, $good, $pages = 0, $users = 0 ) {
57  $this->edits = $edits;
58  $this->articles = $good;
59  $this->pages = $pages;
60  $this->users = $users;
61  }
62 
63  public function merge( MergeableUpdate $update ) {
65  Assert::parameterType( __CLASS__, $update, '$update' );
66  '@phan-var SiteStatsUpdate $update';
67 
68  foreach ( self::COUNTERS as $field ) {
69  $this->$field += $update->$field;
70  }
71  }
72 
86  public static function factory( array $deltas ) {
87  $update = new self( 0, 0, 0 );
88 
89  foreach ( $deltas as $name => $unused ) {
90  if ( !in_array( $name, self::COUNTERS ) ) { // T187585
91  throw new UnexpectedValueException( __METHOD__ . ": no field called '$name'" );
92  }
93  }
94 
95  foreach ( self::COUNTERS as $field ) {
96  $update->$field = $deltas[$field] ?? 0;
97  }
98 
99  return $update;
100  }
101 
102  public function doUpdate() {
103  $services = MediaWikiServices::getInstance();
104  $stats = $services->getStatsdDataFactory();
105  $shards = $services->getMainConfig()->get( MainConfigNames::MultiShardSiteStats ) ?
106  self::SHARDS_ON : self::SHARDS_OFF;
107 
108  $deltaByType = [];
109  foreach ( self::COUNTERS as $type ) {
110  $delta = $this->$type;
111  if ( $delta !== 0 ) {
112  $stats->updateCount( "site.$type", $delta );
113  }
114  $deltaByType[$type] = $delta;
115  }
116 
117  ( new AutoCommitUpdate(
118  $services->getDBLoadBalancer()->getConnectionRef( DB_PRIMARY ),
119  __METHOD__,
120  static function ( IDatabase $dbw, $fname ) use ( $deltaByType, $shards ) {
121  $set = [];
122  $initValues = [];
123  if ( $shards > 1 ) {
124  $shard = mt_rand( 1, $shards );
125  } else {
126  $shard = 1;
127  }
128 
129  $hasNegativeDelta = false;
130  foreach ( self::COUNTERS as $field => $type ) {
131  $delta = (int)$deltaByType[$type];
132  $initValues[$field] = $delta;
133  if ( $delta > 0 ) {
134  $set[] = "$field=" . $dbw->buildGreatest(
135  [ $field => $dbw->addIdentifierQuotes( $field ) . '+' . abs( $delta ) ],
136  0
137  );
138  } elseif ( $delta < 0 ) {
139  $hasNegativeDelta = true;
140  $set[] = "$field=" . $dbw->buildGreatest(
141  [ 'new' => $dbw->addIdentifierQuotes( $field ) . '-' . abs( $delta ) ],
142  0
143  );
144  }
145  }
146 
147  if ( $set ) {
148  if ( $hasNegativeDelta ) {
149  $dbw->newUpdateQueryBuilder()
150  ->update( 'site_stats' )
151  ->set( $set )
152  ->where( [ 'ss_row_id' => $shard ] )
153  ->caller( $fname )->execute();
154  } else {
155  $dbw->upsert(
156  'site_stats',
157  array_merge( [ 'ss_row_id' => $shard ], $initValues ),
158  'ss_row_id',
159  $set,
160  $fname
161  );
162  }
163  }
164  }
165  ) )->doUpdate();
166 
167  // Invalidate cache used by parser functions
168  SiteStats::unload();
169  }
170 
175  public static function cacheUpdate( IDatabase $dbw ) {
176  $services = MediaWikiServices::getInstance();
177  $config = $services->getMainConfig();
178 
179  $dbr = $services->getDBLoadBalancer()->getConnectionRef( DB_REPLICA, 'vslow' );
180  # Get non-bot users than did some recent action other than making accounts.
181  # If account creation is included, the number gets inflated ~20+ fold on enwiki.
182  $activeUsers = $dbr->newSelectQueryBuilder()
183  ->select( 'COUNT(DISTINCT rc_actor)' )
184  ->from( 'recentchanges' )
185  ->join( 'actor', 'actor', 'actor_id=rc_actor' )
186  ->where( [
187  'rc_type != ' . $dbr->addQuotes( RC_EXTERNAL ), // Exclude external (Wikidata)
188  'actor_user IS NOT NULL',
189  'rc_bot' => 0,
190  'rc_log_type != ' . $dbr->addQuotes( 'newusers' ) . ' OR rc_log_type IS NULL',
191  'rc_timestamp >= ' . $dbr->addQuotes(
192  $dbr->timestamp( time() - $config->get( MainConfigNames::ActiveUserDays ) * 24 * 3600 ) ),
193  ] )
194  ->caller( __METHOD__ )
195  ->fetchField();
196  $dbw->newUpdateQueryBuilder()
197  ->update( 'site_stats' )
198  ->set( [ 'ss_active_users' => intval( $activeUsers ) ] )
199  ->where( [ 'ss_row_id' => 1 ] )
200  ->caller( __METHOD__ )->execute();
201 
202  // Invalid cache used by parser functions
203  SiteStats::unload();
204 
205  return $activeUsers;
206  }
207 }
const RC_EXTERNAL
Definition: Defines.php:119
Deferrable Update for closure/callback updates that should use auto-commit mode.
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
Static accessor class for site_stats and related things.
Definition: SiteStats.php:36
Class for handling updates to the site_stats table.
static factory(array $deltas)
doUpdate()
Perform the actual work.
__construct( $views, $edits, $good, $pages=0, $users=0)
static cacheUpdate(IDatabase $dbw)
merge(MergeableUpdate $update)
Merge this enqueued update with a new MergeableUpdate of the same qualified class name.
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.
upsert( $table, array $rows, $uniqueKeys, array $set, $fname=__METHOD__)
Upsert row(s) into a table, in the provided order, while updating conflicting rows.
buildGreatest( $fields, $values)
Build a GREATEST function statement comparing columns/values.
addIdentifierQuotes( $s)
Escape a SQL identifier (e.g.
const DB_REPLICA
Definition: defines.php:26
const DB_PRIMARY
Definition: defines.php:28