Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
62.18% |
74 / 119 |
|
62.50% |
10 / 16 |
CRAP | |
0.00% |
0 / 1 |
SiteStats | |
62.71% |
74 / 118 |
|
62.50% |
10 / 16 |
80.82 | |
0.00% |
0 / 1 |
unload | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
load | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
loadAndLazyInit | |
93.33% |
14 / 15 |
|
0.00% |
0 / 1 |
5.01 | |||
edits | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
articles | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
pages | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
users | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
activeUsers | |
0.00% |
0 / 2 |
|
0.00% |
0 / 1 |
2 | |||
images | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
numberingroup | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
2 | |||
jobs | |
83.33% |
10 / 12 |
|
0.00% |
0 / 1 |
2.02 | |||
pagesInNs | |
0.00% |
0 / 16 |
|
0.00% |
0 / 1 |
2 | |||
selectFields | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
doLoadFromDB | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
5 | |||
isRowSensible | |
92.86% |
13 / 14 |
|
0.00% |
0 / 1 |
6.01 | |||
getLB | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
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 | namespace MediaWiki\SiteStats; |
24 | |
25 | use JobQueueError; |
26 | use MediaWiki\MainConfigNames; |
27 | use MediaWiki\MediaWikiServices; |
28 | use stdClass; |
29 | use Wikimedia\Rdbms\Database; |
30 | use Wikimedia\Rdbms\ILoadBalancer; |
31 | use Wikimedia\Rdbms\IReadableDatabase; |
32 | |
33 | /** |
34 | * Static accessor class for site_stats and related things |
35 | */ |
36 | class SiteStats { |
37 | /** @var stdClass|null */ |
38 | private static $row; |
39 | |
40 | /** |
41 | * Trigger a reload next time a field is accessed |
42 | */ |
43 | public static function unload() { |
44 | self::$row = null; |
45 | } |
46 | |
47 | protected static function load() { |
48 | if ( self::$row === null ) { |
49 | self::$row = self::loadAndLazyInit(); |
50 | } |
51 | } |
52 | |
53 | /** |
54 | * @return stdClass |
55 | */ |
56 | protected static function loadAndLazyInit() { |
57 | $config = MediaWikiServices::getInstance()->getMainConfig(); |
58 | |
59 | $lb = self::getLB(); |
60 | $dbr = $lb->getConnection( DB_REPLICA ); |
61 | wfDebug( __METHOD__ . ": reading site_stats from replica DB" ); |
62 | $row = self::doLoadFromDB( $dbr ); |
63 | |
64 | if ( !self::isRowSensible( $row ) && $lb->hasOrMadeRecentPrimaryChanges() ) { |
65 | // Might have just been initialized during this request? Underflow? |
66 | wfDebug( __METHOD__ . ": site_stats damaged or missing on replica DB" ); |
67 | $row = self::doLoadFromDB( $lb->getConnection( DB_PRIMARY ) ); |
68 | } |
69 | |
70 | if ( !self::isRowSensible( $row ) ) { |
71 | if ( $config->get( MainConfigNames::MiserMode ) ) { |
72 | // Start off with all zeroes, assuming that this is a new wiki or any |
73 | // repopulations where done manually via script. |
74 | SiteStatsInit::doPlaceholderInit(); |
75 | } else { |
76 | // Normally the site_stats table is initialized at install time. |
77 | // Some manual construction scenarios may leave the table empty or |
78 | // broken, however, for instance when importing from a dump into a |
79 | // clean schema with mwdumper. |
80 | wfDebug( __METHOD__ . ": initializing damaged or missing site_stats" ); |
81 | SiteStatsInit::doAllAndCommit( $dbr ); |
82 | } |
83 | |
84 | $row = self::doLoadFromDB( $lb->getConnection( DB_PRIMARY ) ); |
85 | } |
86 | |
87 | return $row; |
88 | } |
89 | |
90 | /** |
91 | * @return int |
92 | */ |
93 | public static function edits() { |
94 | self::load(); |
95 | |
96 | return (int)self::$row->ss_total_edits; |
97 | } |
98 | |
99 | /** |
100 | * @return int |
101 | */ |
102 | public static function articles() { |
103 | self::load(); |
104 | |
105 | return (int)self::$row->ss_good_articles; |
106 | } |
107 | |
108 | /** |
109 | * @return int |
110 | */ |
111 | public static function pages() { |
112 | self::load(); |
113 | |
114 | return (int)self::$row->ss_total_pages; |
115 | } |
116 | |
117 | /** |
118 | * @return int |
119 | */ |
120 | public static function users() { |
121 | self::load(); |
122 | |
123 | return (int)self::$row->ss_users; |
124 | } |
125 | |
126 | /** |
127 | * @return int |
128 | */ |
129 | public static function activeUsers() { |
130 | self::load(); |
131 | |
132 | return (int)self::$row->ss_active_users; |
133 | } |
134 | |
135 | /** |
136 | * @return int |
137 | */ |
138 | public static function images() { |
139 | self::load(); |
140 | |
141 | return (int)self::$row->ss_images; |
142 | } |
143 | |
144 | /** |
145 | * Find the number of users in a given user group. |
146 | * @param string $group Name of group |
147 | * @return int |
148 | */ |
149 | public static function numberingroup( $group ) { |
150 | $cache = MediaWikiServices::getInstance()->getMainWANObjectCache(); |
151 | $fname = __METHOD__; |
152 | |
153 | return $cache->getWithSetCallback( |
154 | $cache->makeKey( 'SiteStats', 'groupcounts', $group ), |
155 | $cache::TTL_HOUR, |
156 | static function ( $oldValue, &$ttl, array &$setOpts ) use ( $group, $fname ) { |
157 | $dbr = self::getLB()->getConnection( DB_REPLICA ); |
158 | $setOpts += Database::getCacheSetOptions( $dbr ); |
159 | return (int)$dbr->newSelectQueryBuilder() |
160 | ->select( 'COUNT(*)' ) |
161 | ->from( 'user_groups' ) |
162 | ->where( |
163 | [ |
164 | 'ug_group' => $group, |
165 | $dbr->expr( 'ug_expiry', '=', null )->or( 'ug_expiry', '>=', $dbr->timestamp() ) |
166 | ] |
167 | ) |
168 | ->caller( $fname ) |
169 | ->fetchField(); |
170 | }, |
171 | [ 'pcTTL' => $cache::TTL_PROC_LONG ] |
172 | ); |
173 | } |
174 | |
175 | /** |
176 | * Total number of jobs in the job queue. |
177 | * @return int |
178 | */ |
179 | public static function jobs() { |
180 | $cache = MediaWikiServices::getInstance()->getMainWANObjectCache(); |
181 | |
182 | return $cache->getWithSetCallback( |
183 | $cache->makeKey( 'SiteStats', 'jobscount' ), |
184 | $cache::TTL_MINUTE, |
185 | static function ( $oldValue, &$ttl, array &$setOpts ) { |
186 | try { |
187 | $jobs = array_sum( MediaWikiServices::getInstance()->getJobQueueGroup()->getQueueSizes() ); |
188 | } catch ( JobQueueError $e ) { |
189 | $jobs = 0; |
190 | } |
191 | return $jobs; |
192 | }, |
193 | [ 'pcTTL' => $cache::TTL_PROC_LONG ] |
194 | ); |
195 | } |
196 | |
197 | /** |
198 | * @param int $ns |
199 | * @return int |
200 | */ |
201 | public static function pagesInNs( $ns ) { |
202 | $cache = MediaWikiServices::getInstance()->getMainWANObjectCache(); |
203 | $fname = __METHOD__; |
204 | |
205 | return $cache->getWithSetCallback( |
206 | $cache->makeKey( 'SiteStats', 'page-in-namespace', $ns ), |
207 | $cache::TTL_HOUR, |
208 | static function ( $oldValue, &$ttl, array &$setOpts ) use ( $ns, $fname ) { |
209 | $dbr = self::getLB()->getConnection( DB_REPLICA ); |
210 | $setOpts += Database::getCacheSetOptions( $dbr ); |
211 | |
212 | return (int)$dbr->newSelectQueryBuilder() |
213 | ->select( 'COUNT(*)' ) |
214 | ->from( 'page' ) |
215 | ->where( [ 'page_namespace' => $ns ] ) |
216 | ->caller( $fname )->fetchField(); |
217 | }, |
218 | [ 'pcTTL' => $cache::TTL_PROC_LONG ] |
219 | ); |
220 | } |
221 | |
222 | /** |
223 | * @return array |
224 | */ |
225 | public static function selectFields() { |
226 | return [ |
227 | 'ss_total_edits', |
228 | 'ss_good_articles', |
229 | 'ss_total_pages', |
230 | 'ss_users', |
231 | 'ss_active_users', |
232 | 'ss_images', |
233 | ]; |
234 | } |
235 | |
236 | /** |
237 | * @param IReadableDatabase $db |
238 | * @return stdClass|false |
239 | */ |
240 | private static function doLoadFromDB( IReadableDatabase $db ) { |
241 | $fields = self::selectFields(); |
242 | $rows = $db->newSelectQueryBuilder() |
243 | ->select( $fields ) |
244 | ->from( 'site_stats' ) |
245 | ->caller( __METHOD__ ) |
246 | ->fetchResultSet(); |
247 | if ( !$rows->numRows() ) { |
248 | return false; |
249 | } |
250 | $finalRow = new stdClass(); |
251 | foreach ( $rows as $row ) { |
252 | foreach ( $fields as $field ) { |
253 | $finalRow->$field ??= 0; |
254 | if ( $row->$field ) { |
255 | $finalRow->$field += $row->$field; |
256 | } |
257 | } |
258 | |
259 | } |
260 | return $finalRow; |
261 | } |
262 | |
263 | /** |
264 | * Is the provided row of site stats sensible, or should it be regenerated? |
265 | * |
266 | * Checks only fields which are filled by SiteStatsInit::refresh. |
267 | * |
268 | * @param stdClass|false $row |
269 | * @return bool |
270 | */ |
271 | private static function isRowSensible( $row ) { |
272 | if ( $row === false |
273 | || $row->ss_total_pages < $row->ss_good_articles |
274 | || $row->ss_total_edits < $row->ss_total_pages |
275 | ) { |
276 | return false; |
277 | } |
278 | // Now check for underflow/overflow |
279 | foreach ( [ |
280 | 'ss_total_edits', |
281 | 'ss_good_articles', |
282 | 'ss_total_pages', |
283 | 'ss_users', |
284 | 'ss_images', |
285 | ] as $member ) { |
286 | if ( $row->$member < 0 ) { |
287 | return false; |
288 | } |
289 | } |
290 | |
291 | return true; |
292 | } |
293 | |
294 | /** |
295 | * @return ILoadBalancer |
296 | */ |
297 | private static function getLB() { |
298 | return MediaWikiServices::getInstance()->getDBLoadBalancer(); |
299 | } |
300 | } |
301 | |
302 | /** @deprecated class alias since 1.41 */ |
303 | class_alias( SiteStats::class, 'SiteStats' ); |