Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
64.95% |
63 / 97 |
|
69.23% |
9 / 13 |
CRAP | |
0.00% |
0 / 1 |
SiteStatsInit | |
65.62% |
63 / 96 |
|
69.23% |
9 / 13 |
38.91 | |
0.00% |
0 / 1 |
__construct | |
80.00% |
4 / 5 |
|
0.00% |
0 / 1 |
3.07 | |||
edits | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
countTableRows | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
articles | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
2 | |||
pages | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
users | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
files | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
doAllAndCommit | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
6 | |||
doPlaceholderInit | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
2 | |||
getShardedValue | |
0.00% |
0 / 5 |
|
0.00% |
0 / 1 |
6 | |||
refresh | |
48.48% |
16 / 33 |
|
0.00% |
0 / 1 |
4.23 | |||
getReplicaDB | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getPrimaryDB | |
100.00% |
3 / 3 |
|
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 | |
21 | namespace MediaWiki\SiteStats; |
22 | |
23 | use MediaWiki\Deferred\SiteStatsUpdate; |
24 | use MediaWiki\MainConfigNames; |
25 | use MediaWiki\MediaWikiServices; |
26 | use Wikimedia\Rdbms\IDatabase; |
27 | use Wikimedia\Rdbms\IReadableDatabase; |
28 | |
29 | /** |
30 | * Class designed for counting of stats. |
31 | */ |
32 | class 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 | * - IReadableDatabase: 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 IReadableDatabase|bool $database |
137 | * - bool: Whether to use the primary DB |
138 | * - IReadableDatabase: 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 */ |
248 | class_alias( SiteStatsInit::class, 'SiteStatsInit' ); |