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