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