Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
65.00% covered (warning)
65.00%
65 / 100
69.23% covered (warning)
69.23%
9 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
SiteStatsInit
65.66% covered (warning)
65.66%
65 / 99
69.23% covered (warning)
69.23%
9 / 13
38.86
0.00% covered (danger)
0.00%
0 / 1
 __construct
75.00% covered (warning)
75.00%
6 / 8
0.00% covered (danger)
0.00%
0 / 1
3.14
 edits
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 countTableRows
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 articles
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
2
 pages
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 users
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 files
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 doAllAndCommit
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
6
 doPlaceholderInit
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
2
 getShardedValue
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 refresh
48.48% covered (danger)
48.48%
16 / 33
0.00% covered (danger)
0.00%
0 / 1
4.23
 getReplicaDB
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 getPrimaryDB
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * @license GPL-2.0-or-later
4 * @file
5 */
6
7namespace MediaWiki\SiteStats;
8
9use MediaWiki\Deferred\LinksUpdate\PageLinksTable;
10use MediaWiki\Deferred\SiteStatsUpdate;
11use MediaWiki\MainConfigNames;
12use MediaWiki\MediaWikiServices;
13use Wikimedia\Rdbms\IDatabase;
14use Wikimedia\Rdbms\IReadableDatabase;
15
16/**
17 * Class designed for counting of stats.
18 */
19class SiteStatsInit {
20    /** @var IReadableDatabase */
21    private $dbr;
22    /** @var IReadableDatabase */
23    private $pageLinksDbr;
24    /** @var int */
25    private $edits;
26    /** @var int */
27    private $articles;
28    /** @var int */
29    private $pages;
30    /** @var int */
31    private $users;
32    /** @var int */
33    private $files;
34
35    /**
36     * @param bool|IReadableDatabase $database
37     * - bool: Whether to use the primary DB
38     * - IReadableDatabase: Database connection to use
39     */
40    public function __construct( $database = false ) {
41        if ( $database instanceof IReadableDatabase ) {
42            $this->dbr = $database;
43            $this->pageLinksDbr = $database;
44        } elseif ( $database ) {
45            $this->dbr = self::getPrimaryDB();
46            $this->pageLinksDbr = self::getPrimaryDB( PageLinksTable::VIRTUAL_DOMAIN );
47        } else {
48            $this->dbr = self::getReplicaDB();
49            $this->pageLinksDbr = self::getReplicaDB( PageLinksTable::VIRTUAL_DOMAIN );
50        }
51    }
52
53    /**
54     * Count the total number of edits
55     * @return int
56     */
57    public function edits() {
58        $this->edits = $this->countTableRows( 'revision' );
59        $this->edits += $this->countTableRows( 'archive' );
60
61        return $this->edits;
62    }
63
64    private function countTableRows( string $tableName ): int {
65        return (int)$this->dbr->newSelectQueryBuilder()
66            ->select( 'COUNT(*)' )
67            ->from( $tableName )
68            ->caller( __METHOD__ )->fetchField();
69    }
70
71    /**
72     * Count pages in article space(s)
73     * @return int
74     */
75    public function articles() {
76        $services = MediaWikiServices::getInstance();
77        $queryBuilder = $this->pageLinksDbr->newSelectQueryBuilder()
78            ->select( 'COUNT(DISTINCT page_id)' )
79            ->from( 'page' )
80            ->where( [
81                    'page_namespace' => $services->getNamespaceInfo()->getContentNamespaces(),
82                    'page_is_redirect' => 0,
83                ] );
84
85        if ( $services->getMainConfig()->get( MainConfigNames::ArticleCountMethod ) == 'link' ) {
86            $queryBuilder->join( 'pagelinks', null, 'pl_from=page_id' );
87        }
88
89        $this->articles = $queryBuilder->caller( __METHOD__ )->fetchField();
90
91        return $this->articles;
92    }
93
94    /**
95     * Count total pages
96     * @return int
97     */
98    public function pages() {
99        $this->pages = $this->countTableRows( 'page' );
100
101        return $this->pages;
102    }
103
104    /**
105     * Count total users
106     * @return int
107     */
108    public function users() {
109        $this->users = $this->countTableRows( 'user' );
110
111        return $this->users;
112    }
113
114    /**
115     * Count total files
116     * @return int
117     */
118    public function files() {
119        $this->files = $this->countTableRows( 'image' );
120
121        return $this->files;
122    }
123
124    /**
125     * Do all updates and commit them. More or less a replacement
126     * for the original initStats, but without output.
127     *
128     * @param IReadableDatabase|bool $database
129     * - bool: Whether to use the primary DB
130     * - IReadableDatabase: Database connection to use
131     * @param array $options Array of options, may contain the following values
132     * - activeUsers bool: Whether to update the number of active users (default: false)
133     */
134    public static function doAllAndCommit( $database, array $options = [] ) {
135        $options += [ 'update' => false, 'activeUsers' => false ];
136
137        // Grab the object and count everything
138        $counter = new self( $database );
139
140        $counter->edits();
141        $counter->articles();
142        $counter->pages();
143        $counter->users();
144        $counter->files();
145
146        $counter->refresh();
147
148        // Count active users if need be
149        if ( $options['activeUsers'] ) {
150            SiteStatsUpdate::cacheUpdate( self::getPrimaryDB() );
151        }
152    }
153
154    /**
155     * Insert a dummy row with all zeroes if no row is present
156     */
157    public static function doPlaceholderInit() {
158        $dbw = self::getPrimaryDB();
159        $exists = (bool)$dbw->newSelectQueryBuilder()
160            ->select( '1' )
161            ->from( 'site_stats' )
162            ->where( [ 'ss_row_id' => 1 ] )
163            ->caller( __METHOD__ )->fetchField();
164        if ( !$exists ) {
165            $dbw->newInsertQueryBuilder()
166                ->insertInto( 'site_stats' )
167                ->ignore()
168                ->row( [ 'ss_row_id' => 1 ] + array_fill_keys( SiteStats::selectFields(), 0 ) )
169                ->caller( __METHOD__ )->execute();
170        }
171    }
172
173    private function getShardedValue( int $value, int $noShards, int $rowId ): int {
174        $remainder = $value % $noShards;
175        $quotient = (int)( ( $value - $remainder ) / $noShards );
176        // Add the remainder to the first row
177        if ( $rowId === 1 ) {
178            return $quotient + $remainder;
179        }
180        return $quotient;
181    }
182
183    /**
184     * Refresh site_stats
185     */
186    public function refresh() {
187        if ( MediaWikiServices::getInstance()->getMainConfig()->get( MainConfigNames::MultiShardSiteStats ) ) {
188            $shardCnt = SiteStatsUpdate::SHARDS_ON;
189            for ( $i = 1; $i <= $shardCnt; $i++ ) {
190                $set = [
191                    'ss_total_edits' => $this->getShardedValue( $this->edits ?? $this->edits(), $shardCnt, $i ),
192                    'ss_good_articles' => $this->getShardedValue( $this->articles ?? $this->articles(), $shardCnt, $i ),
193                    'ss_total_pages' => $this->getShardedValue( $this->pages ?? $this->pages(), $shardCnt, $i ),
194                    'ss_users' => $this->getShardedValue( $this->users ?? $this->users(), $shardCnt, $i ),
195                    'ss_images' => $this->getShardedValue( $this->files ?? $this->files(), $shardCnt, $i ),
196                ];
197                $row = [ 'ss_row_id' => $i ] + $set;
198                self::getPrimaryDB()->newInsertQueryBuilder()
199                    ->insertInto( 'site_stats' )
200                    ->row( $row )
201                    ->onDuplicateKeyUpdate()
202                    ->uniqueIndexFields( [ 'ss_row_id' ] )
203                    ->set( $set )
204                    ->caller( __METHOD__ )->execute();
205            }
206        } else {
207            $set = [
208                'ss_total_edits' => $this->edits ?? $this->edits(),
209                'ss_good_articles' => $this->articles ?? $this->articles(),
210                'ss_total_pages' => $this->pages ?? $this->pages(),
211                'ss_users' => $this->users ?? $this->users(),
212                'ss_images' => $this->files ?? $this->files(),
213            ];
214            $row = [ 'ss_row_id' => 1 ] + $set;
215
216            self::getPrimaryDB()->newInsertQueryBuilder()
217                ->insertInto( 'site_stats' )
218                ->row( $row )
219                ->onDuplicateKeyUpdate()
220                ->uniqueIndexFields( [ 'ss_row_id' ] )
221                ->set( $set )
222                ->caller( __METHOD__ )->execute();
223        }
224    }
225
226    /**
227     * Get a replica database connection
228     *
229     * @param string|false $virtualDomain
230     * @return IReadableDatabase
231     */
232    private static function getReplicaDB( $virtualDomain = false ): IReadableDatabase {
233        return MediaWikiServices::getInstance()
234            ->getConnectionProvider()
235            ->getReplicaDatabase( $virtualDomain, 'vslow' );
236    }
237
238    /**
239     * Get a primary database connection
240     *
241     * @param string|false $virtualDomain
242     * @return IDatabase
243     */
244    private static function getPrimaryDB( $virtualDomain = false ): IDatabase {
245        return MediaWikiServices::getInstance()
246            ->getConnectionProvider()
247            ->getPrimaryDatabase( $virtualDomain );
248    }
249}
250
251/** @deprecated class alias since 1.41 */
252class_alias( SiteStatsInit::class, 'SiteStatsInit' );