PageRenderTime 52ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/phase3/includes/SiteStats.php

https://github.com/ChuguluGames/mediawiki-svn
PHP | 486 lines | 285 code | 56 blank | 145 comment | 34 complexity | ba10fc12b2b7559792b54b692ce39eb1 MD5 | raw file
  1. <?php
  2. /**
  3. * Static accessor class for site_stats and related things
  4. */
  5. class SiteStats {
  6. static $row, $loaded = false;
  7. static $admins, $jobs;
  8. static $pageCount = array();
  9. static $groupMemberCounts = array();
  10. static function recache() {
  11. self::load( true );
  12. }
  13. /**
  14. * @param $recache bool
  15. */
  16. static function load( $recache = false ) {
  17. if ( self::$loaded && !$recache ) {
  18. return;
  19. }
  20. self::$row = self::loadAndLazyInit();
  21. # This code is somewhat schema-agnostic, because I'm changing it in a minor release -- TS
  22. if ( !isset( self::$row->ss_total_pages ) && self::$row->ss_total_pages == -1 ) {
  23. # Update schema
  24. $u = new SiteStatsUpdate( 0, 0, 0 );
  25. $u->doUpdate();
  26. $dbr = wfGetDB( DB_SLAVE );
  27. self::$row = $dbr->selectRow( 'site_stats', '*', false, __METHOD__ );
  28. }
  29. self::$loaded = true;
  30. }
  31. /**
  32. * @return Bool|ResultWrapper
  33. */
  34. static function loadAndLazyInit() {
  35. wfDebug( __METHOD__ . ": reading site_stats from slave\n" );
  36. $row = self::doLoad( wfGetDB( DB_SLAVE ) );
  37. if( !self::isSane( $row ) ) {
  38. // Might have just been initialized during this request? Underflow?
  39. wfDebug( __METHOD__ . ": site_stats damaged or missing on slave\n" );
  40. $row = self::doLoad( wfGetDB( DB_MASTER ) );
  41. }
  42. if( !self::isSane( $row ) ) {
  43. // Normally the site_stats table is initialized at install time.
  44. // Some manual construction scenarios may leave the table empty or
  45. // broken, however, for instance when importing from a dump into a
  46. // clean schema with mwdumper.
  47. wfDebug( __METHOD__ . ": initializing damaged or missing site_stats\n" );
  48. SiteStatsInit::doAllAndCommit( wfGetDB( DB_SLAVE ) );
  49. $row = self::doLoad( wfGetDB( DB_MASTER ) );
  50. }
  51. if( !self::isSane( $row ) ) {
  52. wfDebug( __METHOD__ . ": site_stats persistently nonsensical o_O\n" );
  53. }
  54. return $row;
  55. }
  56. /**
  57. * @param $db DatabaseBase
  58. * @return Bool|ResultWrapper
  59. */
  60. static function doLoad( $db ) {
  61. return $db->selectRow( 'site_stats', '*', false, __METHOD__ );
  62. }
  63. /**
  64. * @return int
  65. */
  66. static function views() {
  67. self::load();
  68. return self::$row->ss_total_views;
  69. }
  70. /**
  71. * @return int
  72. */
  73. static function edits() {
  74. self::load();
  75. return self::$row->ss_total_edits;
  76. }
  77. /**
  78. * @return int
  79. */
  80. static function articles() {
  81. self::load();
  82. return self::$row->ss_good_articles;
  83. }
  84. /**
  85. * @return int
  86. */
  87. static function pages() {
  88. self::load();
  89. return self::$row->ss_total_pages;
  90. }
  91. /**
  92. * @return int
  93. */
  94. static function users() {
  95. self::load();
  96. return self::$row->ss_users;
  97. }
  98. /**
  99. * @return int
  100. */
  101. static function activeUsers() {
  102. self::load();
  103. return self::$row->ss_active_users;
  104. }
  105. /**
  106. * @return int
  107. */
  108. static function images() {
  109. self::load();
  110. return self::$row->ss_images;
  111. }
  112. /**
  113. * Find the number of users in a given user group.
  114. * @param $group String: name of group
  115. * @return Integer
  116. */
  117. static function numberingroup( $group ) {
  118. if ( !isset( self::$groupMemberCounts[$group] ) ) {
  119. global $wgMemc;
  120. $key = wfMemcKey( 'SiteStats', 'groupcounts', $group );
  121. $hit = $wgMemc->get( $key );
  122. if ( !$hit ) {
  123. $dbr = wfGetDB( DB_SLAVE );
  124. $hit = $dbr->selectField(
  125. 'user_groups',
  126. 'COUNT(*)',
  127. array( 'ug_group' => $group ),
  128. __METHOD__
  129. );
  130. $wgMemc->set( $key, $hit, 3600 );
  131. }
  132. self::$groupMemberCounts[$group] = $hit;
  133. }
  134. return self::$groupMemberCounts[$group];
  135. }
  136. /**
  137. * @return int
  138. */
  139. static function jobs() {
  140. if ( !isset( self::$jobs ) ) {
  141. $dbr = wfGetDB( DB_SLAVE );
  142. self::$jobs = $dbr->estimateRowCount( 'job' );
  143. /* Zero rows still do single row read for row that doesn't exist, but people are annoyed by that */
  144. if ( self::$jobs == 1 ) {
  145. self::$jobs = 0;
  146. }
  147. }
  148. return self::$jobs;
  149. }
  150. /**
  151. * @param $ns int
  152. *
  153. * @return int
  154. */
  155. static function pagesInNs( $ns ) {
  156. wfProfileIn( __METHOD__ );
  157. if( !isset( self::$pageCount[$ns] ) ) {
  158. $dbr = wfGetDB( DB_SLAVE );
  159. self::$pageCount[$ns] = (int)$dbr->selectField(
  160. 'page',
  161. 'COUNT(*)',
  162. array( 'page_namespace' => $ns ),
  163. __METHOD__
  164. );
  165. }
  166. wfProfileOut( __METHOD__ );
  167. return self::$pageCount[$ns];
  168. }
  169. /**
  170. * Is the provided row of site stats sane, or should it be regenerated?
  171. *
  172. * @param $row
  173. *
  174. * @return bool
  175. */
  176. private static function isSane( $row ) {
  177. if(
  178. $row === false
  179. || $row->ss_total_pages < $row->ss_good_articles
  180. || $row->ss_total_edits < $row->ss_total_pages
  181. ) {
  182. return false;
  183. }
  184. // Now check for underflow/overflow
  185. foreach( array( 'total_views', 'total_edits', 'good_articles',
  186. 'total_pages', 'users', 'admins', 'images' ) as $member ) {
  187. if(
  188. $row->{"ss_$member"} > 2000000000
  189. || $row->{"ss_$member"} < 0
  190. ) {
  191. return false;
  192. }
  193. }
  194. return true;
  195. }
  196. }
  197. /**
  198. * Class for handling updates to the site_stats table
  199. */
  200. class SiteStatsUpdate implements DeferrableUpdate {
  201. var $mViews, $mEdits, $mGood, $mPages, $mUsers;
  202. function __construct( $views, $edits, $good, $pages = 0, $users = 0 ) {
  203. $this->mViews = $views;
  204. $this->mEdits = $edits;
  205. $this->mGood = $good;
  206. $this->mPages = $pages;
  207. $this->mUsers = $users;
  208. }
  209. /**
  210. * @param $sql
  211. * @param $field
  212. * @param $delta
  213. */
  214. function appendUpdate( &$sql, $field, $delta ) {
  215. if ( $delta ) {
  216. if ( $sql ) {
  217. $sql .= ',';
  218. }
  219. if ( $delta < 0 ) {
  220. $sql .= "$field=$field-1";
  221. } else {
  222. $sql .= "$field=$field+1";
  223. }
  224. }
  225. }
  226. function doUpdate() {
  227. $dbw = wfGetDB( DB_MASTER );
  228. $updates = '';
  229. $this->appendUpdate( $updates, 'ss_total_views', $this->mViews );
  230. $this->appendUpdate( $updates, 'ss_total_edits', $this->mEdits );
  231. $this->appendUpdate( $updates, 'ss_good_articles', $this->mGood );
  232. $this->appendUpdate( $updates, 'ss_total_pages', $this->mPages );
  233. $this->appendUpdate( $updates, 'ss_users', $this->mUsers );
  234. if ( $updates ) {
  235. $site_stats = $dbw->tableName( 'site_stats' );
  236. $sql = "UPDATE $site_stats SET $updates";
  237. # Need a separate transaction because this a global lock
  238. $dbw->begin();
  239. $dbw->query( $sql, __METHOD__ );
  240. $dbw->commit();
  241. }
  242. }
  243. /**
  244. * @param $dbw DatabaseBase
  245. * @return bool|mixed
  246. */
  247. public static function cacheUpdate( $dbw ) {
  248. global $wgActiveUserDays;
  249. $dbr = wfGetDB( DB_SLAVE, array( 'SpecialStatistics', 'vslow' ) );
  250. # Get non-bot users than did some recent action other than making accounts.
  251. # If account creation is included, the number gets inflated ~20+ fold on enwiki.
  252. $activeUsers = $dbr->selectField(
  253. 'recentchanges',
  254. 'COUNT( DISTINCT rc_user_text )',
  255. array(
  256. 'rc_user != 0',
  257. 'rc_bot' => 0,
  258. "rc_log_type != 'newusers' OR rc_log_type IS NULL",
  259. "rc_timestamp >= '{$dbw->timestamp( wfTimestamp( TS_UNIX ) - $wgActiveUserDays*24*3600 )}'",
  260. ),
  261. __METHOD__
  262. );
  263. $dbw->update(
  264. 'site_stats',
  265. array( 'ss_active_users' => intval( $activeUsers ) ),
  266. array( 'ss_row_id' => 1 ),
  267. __METHOD__
  268. );
  269. return $activeUsers;
  270. }
  271. }
  272. /**
  273. * Class designed for counting of stats.
  274. */
  275. class SiteStatsInit {
  276. // Database connection
  277. private $db;
  278. // Various stats
  279. private $mEdits, $mArticles, $mPages, $mUsers, $mViews, $mFiles = 0;
  280. /**
  281. * Constructor
  282. * @param $database Boolean or DatabaseBase:
  283. * - Boolean: whether to use the master DB
  284. * - DatabaseBase: database connection to use
  285. */
  286. public function __construct( $database = false ) {
  287. if ( $database instanceof DatabaseBase ) {
  288. $this->db = $database;
  289. } else {
  290. $this->db = wfGetDB( $database ? DB_MASTER : DB_SLAVE );
  291. }
  292. }
  293. /**
  294. * Count the total number of edits
  295. * @return Integer
  296. */
  297. public function edits() {
  298. $this->mEdits = $this->db->selectField( 'revision', 'COUNT(*)', '', __METHOD__ );
  299. $this->mEdits += $this->db->selectField( 'archive', 'COUNT(*)', '', __METHOD__ );
  300. return $this->mEdits;
  301. }
  302. /**
  303. * Count pages in article space(s)
  304. * @return Integer
  305. */
  306. public function articles() {
  307. global $wgArticleCountMethod;
  308. $tables = array( 'page' );
  309. $conds = array(
  310. 'page_namespace' => MWNamespace::getContentNamespaces(),
  311. 'page_is_redirect' => 0,
  312. );
  313. if ( $wgArticleCountMethod == 'link' ) {
  314. $tables[] = 'pagelinks';
  315. $conds[] = 'pl_from=page_id';
  316. } elseif ( $wgArticleCountMethod == 'comma' ) {
  317. // To make a correct check for this, we would need, for each page,
  318. // to load the text, maybe uncompress it, maybe decode it and then
  319. // check if there's one comma.
  320. // But one thing we are sure is that if the page is empty, it can't
  321. // contain a comma :)
  322. $conds[] = 'page_len > 0';
  323. }
  324. $this->mArticles = $this->db->selectField( $tables, 'COUNT(DISTINCT page_id)',
  325. $conds, __METHOD__ );
  326. return $this->mArticles;
  327. }
  328. /**
  329. * Count total pages
  330. * @return Integer
  331. */
  332. public function pages() {
  333. $this->mPages = $this->db->selectField( 'page', 'COUNT(*)', '', __METHOD__ );
  334. return $this->mPages;
  335. }
  336. /**
  337. * Count total users
  338. * @return Integer
  339. */
  340. public function users() {
  341. $this->mUsers = $this->db->selectField( 'user', 'COUNT(*)', '', __METHOD__ );
  342. return $this->mUsers;
  343. }
  344. /**
  345. * Count views
  346. * @return Integer
  347. */
  348. public function views() {
  349. $this->mViews = $this->db->selectField( 'page', 'SUM(page_counter)', '', __METHOD__ );
  350. return $this->mViews;
  351. }
  352. /**
  353. * Count total files
  354. * @return Integer
  355. */
  356. public function files() {
  357. $this->mFiles = $this->db->selectField( 'image', 'COUNT(*)', '', __METHOD__ );
  358. return $this->mFiles;
  359. }
  360. /**
  361. * Do all updates and commit them. More or less a replacement
  362. * for the original initStats, but without output.
  363. *
  364. * @param $database Boolean or DatabaseBase:
  365. * - Boolean: whether to use the master DB
  366. * - DatabaseBase: database connection to use
  367. * @param $options Array of options, may contain the following values
  368. * - update Boolean: whether to update the current stats (true) or write fresh (false) (default: false)
  369. * - views Boolean: when true, do not update the number of page views (default: true)
  370. * - activeUsers Boolean: whether to update the number of active users (default: false)
  371. */
  372. public static function doAllAndCommit( $database, array $options = array() ) {
  373. $options += array( 'update' => false, 'views' => true, 'activeUsers' => false );
  374. // Grab the object and count everything
  375. $counter = new SiteStatsInit( $database );
  376. $counter->edits();
  377. $counter->articles();
  378. $counter->pages();
  379. $counter->users();
  380. $counter->files();
  381. // Only do views if we don't want to not count them
  382. if( $options['views'] ) {
  383. $counter->views();
  384. }
  385. // Update/refresh
  386. if( $options['update'] ) {
  387. $counter->update();
  388. } else {
  389. $counter->refresh();
  390. }
  391. // Count active users if need be
  392. if( $options['activeUsers'] ) {
  393. SiteStatsUpdate::cacheUpdate( wfGetDB( DB_MASTER ) );
  394. }
  395. }
  396. /**
  397. * Update the current row with the selected values
  398. */
  399. public function update() {
  400. list( $values, $conds ) = $this->getDbParams();
  401. $dbw = wfGetDB( DB_MASTER );
  402. $dbw->update( 'site_stats', $values, $conds, __METHOD__ );
  403. }
  404. /**
  405. * Refresh site_stats. Erase the current record and save all
  406. * the new values.
  407. */
  408. public function refresh() {
  409. list( $values, $conds, $views ) = $this->getDbParams();
  410. $dbw = wfGetDB( DB_MASTER );
  411. $dbw->delete( 'site_stats', $conds, __METHOD__ );
  412. $dbw->insert( 'site_stats', array_merge( $values, $conds, $views ), __METHOD__ );
  413. }
  414. /**
  415. * Return three arrays of params for the db queries
  416. * @return Array
  417. */
  418. private function getDbParams() {
  419. $values = array(
  420. 'ss_total_edits' => $this->mEdits,
  421. 'ss_good_articles' => $this->mArticles,
  422. 'ss_total_pages' => $this->mPages,
  423. 'ss_users' => $this->mUsers,
  424. 'ss_images' => $this->mFiles
  425. );
  426. $conds = array( 'ss_row_id' => 1 );
  427. $views = array( 'ss_total_views' => $this->mViews );
  428. return array( $values, $conds, $views );
  429. }
  430. }