]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/SiteStats.php
MediaWiki 1.30.2-scripts2
[autoinstallsdev/mediawiki.git] / includes / SiteStats.php
1 <?php
2 /**
3  * Accessors and mutators for the site-wide statistics.
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18  * http://www.gnu.org/copyleft/gpl.html
19  *
20  * @file
21  */
22
23 use Wikimedia\Rdbms\Database;
24 use Wikimedia\Rdbms\IDatabase;
25 use MediaWiki\MediaWikiServices;
26
27 /**
28  * Static accessor class for site_stats and related things
29  */
30 class SiteStats {
31         /** @var bool|stdClass */
32         private static $row;
33
34         /** @var bool */
35         private static $loaded = false;
36
37         /** @var int[] */
38         private static $pageCount = [];
39
40         static function unload() {
41                 self::$loaded = false;
42         }
43
44         static function recache() {
45                 self::load( true );
46         }
47
48         /**
49          * @param bool $recache
50          */
51         static function load( $recache = false ) {
52                 if ( self::$loaded && !$recache ) {
53                         return;
54                 }
55
56                 self::$row = self::loadAndLazyInit();
57
58                 # This code is somewhat schema-agnostic, because I'm changing it in a minor release -- TS
59                 if ( !isset( self::$row->ss_total_pages ) && self::$row->ss_total_pages == -1 ) {
60                         # Update schema
61                         $u = new SiteStatsUpdate( 0, 0, 0 );
62                         $u->doUpdate();
63                         self::$row = self::doLoad( wfGetDB( DB_REPLICA ) );
64                 }
65
66                 self::$loaded = true;
67         }
68
69         /**
70          * @return bool|stdClass
71          */
72         static function loadAndLazyInit() {
73                 global $wgMiserMode;
74
75                 wfDebug( __METHOD__ . ": reading site_stats from replica DB\n" );
76                 $row = self::doLoad( wfGetDB( DB_REPLICA ) );
77
78                 if ( !self::isSane( $row ) ) {
79                         $lb = MediaWikiServices::getInstance()->getDBLoadBalancer();
80                         if ( $lb->hasOrMadeRecentMasterChanges() ) {
81                                 // Might have just been initialized during this request? Underflow?
82                                 wfDebug( __METHOD__ . ": site_stats damaged or missing on replica DB\n" );
83                                 $row = self::doLoad( wfGetDB( DB_MASTER ) );
84                         }
85                 }
86
87                 if ( !$wgMiserMode && !self::isSane( $row ) ) {
88                         // Normally the site_stats table is initialized at install time.
89                         // Some manual construction scenarios may leave the table empty or
90                         // broken, however, for instance when importing from a dump into a
91                         // clean schema with mwdumper.
92                         wfDebug( __METHOD__ . ": initializing damaged or missing site_stats\n" );
93
94                         SiteStatsInit::doAllAndCommit( wfGetDB( DB_REPLICA ) );
95
96                         $row = self::doLoad( wfGetDB( DB_MASTER ) );
97                 }
98
99                 if ( !self::isSane( $row ) ) {
100                         wfDebug( __METHOD__ . ": site_stats persistently nonsensical o_O\n" );
101                 }
102
103                 return $row;
104         }
105
106         /**
107          * @param IDatabase $db
108          * @return bool|stdClass
109          */
110         static function doLoad( $db ) {
111                 return $db->selectRow( 'site_stats', [
112                                 'ss_row_id',
113                                 'ss_total_edits',
114                                 'ss_good_articles',
115                                 'ss_total_pages',
116                                 'ss_users',
117                                 'ss_active_users',
118                                 'ss_images',
119                         ], [], __METHOD__ );
120         }
121
122         /**
123          * Return the total number of page views. Except we don't track those anymore.
124          * Stop calling this function, it will be removed some time in the future. It's
125          * kept here simply to prevent fatal errors.
126          *
127          * @deprecated since 1.25
128          * @return int
129          */
130         static function views() {
131                 wfDeprecated( __METHOD__, '1.25' );
132                 return 0;
133         }
134
135         /**
136          * @return int
137          */
138         static function edits() {
139                 self::load();
140                 return self::$row->ss_total_edits;
141         }
142
143         /**
144          * @return int
145          */
146         static function articles() {
147                 self::load();
148                 return self::$row->ss_good_articles;
149         }
150
151         /**
152          * @return int
153          */
154         static function pages() {
155                 self::load();
156                 return self::$row->ss_total_pages;
157         }
158
159         /**
160          * @return int
161          */
162         static function users() {
163                 self::load();
164                 return self::$row->ss_users;
165         }
166
167         /**
168          * @return int
169          */
170         static function activeUsers() {
171                 self::load();
172                 return self::$row->ss_active_users;
173         }
174
175         /**
176          * @return int
177          */
178         static function images() {
179                 self::load();
180                 return self::$row->ss_images;
181         }
182
183         /**
184          * Find the number of users in a given user group.
185          * @param string $group Name of group
186          * @return int
187          */
188         static function numberingroup( $group ) {
189                 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
190                 return $cache->getWithSetCallback(
191                         $cache->makeKey( 'SiteStats', 'groupcounts', $group ),
192                         $cache::TTL_HOUR,
193                         function ( $oldValue, &$ttl, array &$setOpts ) use ( $group ) {
194                                 $dbr = wfGetDB( DB_REPLICA );
195
196                                 $setOpts += Database::getCacheSetOptions( $dbr );
197
198                                 return $dbr->selectField(
199                                         'user_groups',
200                                         'COUNT(*)',
201                                         [
202                                                 'ug_group' => $group,
203                                                 'ug_expiry IS NULL OR ug_expiry >= ' . $dbr->addQuotes( $dbr->timestamp() )
204                                         ],
205                                         __METHOD__
206                                 );
207                         },
208                         [ 'pcTTL' => $cache::TTL_PROC_LONG ]
209                 );
210         }
211
212         /**
213          * Total number of jobs in the job queue.
214          * @return int
215          */
216         static function jobs() {
217                 $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
218                 return $cache->getWithSetCallback(
219                         $cache->makeKey( 'SiteStats', 'jobscount' ),
220                         $cache::TTL_MINUTE,
221                         function ( $oldValue, &$ttl, array &$setOpts ) {
222                                 try{
223                                         $jobs = array_sum( JobQueueGroup::singleton()->getQueueSizes() );
224                                 } catch ( JobQueueError $e ) {
225                                         $jobs = 0;
226                                 }
227                                 return $jobs;
228                         },
229                         [ 'pcTTL' => $cache::TTL_PROC_LONG ]
230                 );
231         }
232
233         /**
234          * @param int $ns
235          *
236          * @return int
237          */
238         static function pagesInNs( $ns ) {
239                 if ( !isset( self::$pageCount[$ns] ) ) {
240                         $dbr = wfGetDB( DB_REPLICA );
241                         self::$pageCount[$ns] = (int)$dbr->selectField(
242                                 'page',
243                                 'COUNT(*)',
244                                 [ 'page_namespace' => $ns ],
245                                 __METHOD__
246                         );
247                 }
248                 return self::$pageCount[$ns];
249         }
250
251         /**
252          * Is the provided row of site stats sane, or should it be regenerated?
253          *
254          * Checks only fields which are filled by SiteStatsInit::refresh.
255          *
256          * @param bool|object $row
257          *
258          * @return bool
259          */
260         private static function isSane( $row ) {
261                 if ( $row === false
262                         || $row->ss_total_pages < $row->ss_good_articles
263                         || $row->ss_total_edits < $row->ss_total_pages
264                 ) {
265                         return false;
266                 }
267                 // Now check for underflow/overflow
268                 foreach ( [
269                         'ss_total_edits',
270                         'ss_good_articles',
271                         'ss_total_pages',
272                         'ss_users',
273                         'ss_images',
274                 ] as $member ) {
275                         if ( $row->$member > 2000000000 || $row->$member < 0 ) {
276                                 return false;
277                         }
278                 }
279                 return true;
280         }
281 }
282
283 /**
284  * Class designed for counting of stats.
285  */
286 class SiteStatsInit {
287
288         // Database connection
289         private $db;
290
291         // Various stats
292         private $mEdits = null, $mArticles = null, $mPages = null;
293         private $mUsers = null, $mFiles = null;
294
295         /**
296          * @param bool|IDatabase $database
297          * - bool: Whether to use the master DB
298          * - IDatabase: Database connection to use
299          */
300         public function __construct( $database = false ) {
301                 if ( $database instanceof IDatabase ) {
302                         $this->db = $database;
303                 } elseif ( $database ) {
304                         $this->db = wfGetDB( DB_MASTER );
305                 } else {
306                         $this->db = wfGetDB( DB_REPLICA, 'vslow' );
307                 }
308         }
309
310         /**
311          * Count the total number of edits
312          * @return int
313          */
314         public function edits() {
315                 $this->mEdits = $this->db->selectField( 'revision', 'COUNT(*)', '', __METHOD__ );
316                 $this->mEdits += $this->db->selectField( 'archive', 'COUNT(*)', '', __METHOD__ );
317                 return $this->mEdits;
318         }
319
320         /**
321          * Count pages in article space(s)
322          * @return int
323          */
324         public function articles() {
325                 global $wgArticleCountMethod;
326
327                 $tables = [ 'page' ];
328                 $conds = [
329                         'page_namespace' => MWNamespace::getContentNamespaces(),
330                         'page_is_redirect' => 0,
331                 ];
332
333                 if ( $wgArticleCountMethod == 'link' ) {
334                         $tables[] = 'pagelinks';
335                         $conds[] = 'pl_from=page_id';
336                 } elseif ( $wgArticleCountMethod == 'comma' ) {
337                         // To make a correct check for this, we would need, for each page,
338                         // to load the text, maybe uncompress it, maybe decode it and then
339                         // check if there's one comma.
340                         // But one thing we are sure is that if the page is empty, it can't
341                         // contain a comma :)
342                         $conds[] = 'page_len > 0';
343                 }
344
345                 $this->mArticles = $this->db->selectField( $tables, 'COUNT(DISTINCT page_id)',
346                         $conds, __METHOD__ );
347                 return $this->mArticles;
348         }
349
350         /**
351          * Count total pages
352          * @return int
353          */
354         public function pages() {
355                 $this->mPages = $this->db->selectField( 'page', 'COUNT(*)', '', __METHOD__ );
356                 return $this->mPages;
357         }
358
359         /**
360          * Count total users
361          * @return int
362          */
363         public function users() {
364                 $this->mUsers = $this->db->selectField( 'user', 'COUNT(*)', '', __METHOD__ );
365                 return $this->mUsers;
366         }
367
368         /**
369          * Count total files
370          * @return int
371          */
372         public function files() {
373                 $this->mFiles = $this->db->selectField( 'image', 'COUNT(*)', '', __METHOD__ );
374                 return $this->mFiles;
375         }
376
377         /**
378          * Do all updates and commit them. More or less a replacement
379          * for the original initStats, but without output.
380          *
381          * @param IDatabase|bool $database
382          * - bool: Whether to use the master DB
383          * - IDatabase: Database connection to use
384          * @param array $options Array of options, may contain the following values
385          * - activeUsers bool: Whether to update the number of active users (default: false)
386          */
387         public static function doAllAndCommit( $database, array $options = [] ) {
388                 $options += [ 'update' => false, 'activeUsers' => false ];
389
390                 // Grab the object and count everything
391                 $counter = new SiteStatsInit( $database );
392
393                 $counter->edits();
394                 $counter->articles();
395                 $counter->pages();
396                 $counter->users();
397                 $counter->files();
398
399                 $counter->refresh();
400
401                 // Count active users if need be
402                 if ( $options['activeUsers'] ) {
403                         SiteStatsUpdate::cacheUpdate( wfGetDB( DB_MASTER ) );
404                 }
405         }
406
407         /**
408          * Refresh site_stats
409          */
410         public function refresh() {
411                 $values = [
412                         'ss_row_id' => 1,
413                         'ss_total_edits' => ( $this->mEdits === null ? $this->edits() : $this->mEdits ),
414                         'ss_good_articles' => ( $this->mArticles === null ? $this->articles() : $this->mArticles ),
415                         'ss_total_pages' => ( $this->mPages === null ? $this->pages() : $this->mPages ),
416                         'ss_users' => ( $this->mUsers === null ? $this->users() : $this->mUsers ),
417                         'ss_images' => ( $this->mFiles === null ? $this->files() : $this->mFiles ),
418                 ];
419
420                 $dbw = wfGetDB( DB_MASTER );
421                 $dbw->upsert( 'site_stats', $values, [ 'ss_row_id' ], $values, __METHOD__ );
422         }
423 }