SiteStats.php 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  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. static function load( $recache = false ) {
  14. if ( self::$loaded && !$recache ) {
  15. return;
  16. }
  17. self::$row = self::loadAndLazyInit();
  18. # This code is somewhat schema-agnostic, because I'm changing it in a minor release -- TS
  19. if ( !isset( self::$row->ss_total_pages ) && self::$row->ss_total_pages == -1 ) {
  20. # Update schema
  21. $u = new SiteStatsUpdate( 0, 0, 0 );
  22. $u->doUpdate();
  23. $dbr = wfGetDB( DB_SLAVE );
  24. self::$row = $dbr->selectRow( 'site_stats', '*', false, __METHOD__ );
  25. }
  26. self::$loaded = true;
  27. }
  28. static function loadAndLazyInit() {
  29. wfDebug( __METHOD__ . ": reading site_stats from slave\n" );
  30. $row = self::doLoad( wfGetDB( DB_SLAVE ) );
  31. if( !self::isSane( $row ) ) {
  32. // Might have just been initialized during this request? Underflow?
  33. wfDebug( __METHOD__ . ": site_stats damaged or missing on slave\n" );
  34. $row = self::doLoad( wfGetDB( DB_MASTER ) );
  35. }
  36. if( !self::isSane( $row ) ) {
  37. // Normally the site_stats table is initialized at install time.
  38. // Some manual construction scenarios may leave the table empty or
  39. // broken, however, for instance when importing from a dump into a
  40. // clean schema with mwdumper.
  41. wfDebug( __METHOD__ . ": initializing damaged or missing site_stats\n" );
  42. global $IP;
  43. require_once "$IP/maintenance/initStats.inc";
  44. ob_start();
  45. wfInitStats();
  46. ob_end_clean();
  47. $row = self::doLoad( wfGetDB( DB_MASTER ) );
  48. }
  49. if( !self::isSane( $row ) ) {
  50. wfDebug( __METHOD__ . ": site_stats persistently nonsensical o_O\n" );
  51. }
  52. return $row;
  53. }
  54. static function doLoad( $db ) {
  55. return $db->selectRow( 'site_stats', '*', false, __METHOD__ );
  56. }
  57. static function views() {
  58. self::load();
  59. return self::$row->ss_total_views;
  60. }
  61. static function edits() {
  62. self::load();
  63. return self::$row->ss_total_edits;
  64. }
  65. static function articles() {
  66. self::load();
  67. return self::$row->ss_good_articles;
  68. }
  69. static function pages() {
  70. self::load();
  71. return self::$row->ss_total_pages;
  72. }
  73. static function users() {
  74. self::load();
  75. return self::$row->ss_users;
  76. }
  77. static function activeUsers() {
  78. self::load();
  79. return self::$row->ss_active_users;
  80. }
  81. static function images() {
  82. self::load();
  83. return self::$row->ss_images;
  84. }
  85. /**
  86. * @deprecated Use self::numberingroup('sysop') instead
  87. */
  88. static function admins() {
  89. wfDeprecated(__METHOD__);
  90. return self::numberingroup('sysop');
  91. }
  92. /**
  93. * Find the number of users in a given user group.
  94. * @param string $group Name of group
  95. * @return int
  96. */
  97. static function numberingroup($group) {
  98. if ( !isset( self::$groupMemberCounts[$group] ) ) {
  99. global $wgMemc;
  100. $key = wfMemcKey( 'SiteStats', 'groupcounts', $group );
  101. $hit = $wgMemc->get( $key );
  102. if ( !$hit ) {
  103. $dbr = wfGetDB( DB_SLAVE );
  104. $hit = $dbr->selectField( 'user_groups', 'COUNT(*)',
  105. array( 'ug_group' => $group ), __METHOD__ );
  106. $wgMemc->set( $key, $hit, 3600 );
  107. }
  108. self::$groupMemberCounts[$group] = $hit;
  109. }
  110. return self::$groupMemberCounts[$group];
  111. }
  112. static function jobs() {
  113. if ( !isset( self::$jobs ) ) {
  114. $dbr = wfGetDB( DB_SLAVE );
  115. self::$jobs = $dbr->estimateRowCount('job');
  116. /* Zero rows still do single row read for row that doesn't exist, but people are annoyed by that */
  117. if (self::$jobs == 1) {
  118. self::$jobs = 0;
  119. }
  120. }
  121. return self::$jobs;
  122. }
  123. static function pagesInNs( $ns ) {
  124. wfProfileIn( __METHOD__ );
  125. if( !isset( self::$pageCount[$ns] ) ) {
  126. $dbr = wfGetDB( DB_SLAVE );
  127. $pageCount[$ns] = (int)$dbr->selectField( 'page', 'COUNT(*)', array( 'page_namespace' => $ns ), __METHOD__ );
  128. }
  129. wfProfileOut( __METHOD__ );
  130. return $pageCount[$ns];
  131. }
  132. /** Is the provided row of site stats sane, or should it be regenerated? */
  133. private static function isSane( $row ) {
  134. // wikireader doesn't care about site stats, so just return true
  135. return true;
  136. if(
  137. $row === false
  138. or $row->ss_total_pages < $row->ss_good_articles
  139. or $row->ss_total_edits < $row->ss_total_pages
  140. or $row->ss_users < $row->ss_admins
  141. ) {
  142. return false;
  143. }
  144. // Now check for underflow/overflow
  145. foreach( array( 'total_views', 'total_edits', 'good_articles',
  146. 'total_pages', 'users', 'admins', 'images' ) as $member ) {
  147. if(
  148. $row->{"ss_$member"} > 2000000000
  149. or $row->{"ss_$member"} < 0
  150. ) {
  151. return false;
  152. }
  153. }
  154. return true;
  155. }
  156. }
  157. /**
  158. *
  159. */
  160. class SiteStatsUpdate {
  161. var $mViews, $mEdits, $mGood, $mPages, $mUsers;
  162. function __construct( $views, $edits, $good, $pages = 0, $users = 0 ) {
  163. $this->mViews = $views;
  164. $this->mEdits = $edits;
  165. $this->mGood = $good;
  166. $this->mPages = $pages;
  167. $this->mUsers = $users;
  168. }
  169. function appendUpdate( &$sql, $field, $delta ) {
  170. if ( $delta ) {
  171. if ( $sql ) {
  172. $sql .= ',';
  173. }
  174. if ( $delta < 0 ) {
  175. $sql .= "$field=$field-1";
  176. } else {
  177. $sql .= "$field=$field+1";
  178. }
  179. }
  180. }
  181. function doUpdate() {
  182. $fname = 'SiteStatsUpdate::doUpdate';
  183. $dbw = wfGetDB( DB_MASTER );
  184. $updates = '';
  185. $this->appendUpdate( $updates, 'ss_total_views', $this->mViews );
  186. $this->appendUpdate( $updates, 'ss_total_edits', $this->mEdits );
  187. $this->appendUpdate( $updates, 'ss_good_articles', $this->mGood );
  188. $this->appendUpdate( $updates, 'ss_total_pages', $this->mPages );
  189. $this->appendUpdate( $updates, 'ss_users', $this->mUsers );
  190. if ( $updates ) {
  191. $site_stats = $dbw->tableName( 'site_stats' );
  192. $sql = "UPDATE $site_stats SET $updates";
  193. # Need a separate transaction because this a global lock
  194. $dbw->begin();
  195. $dbw->query( $sql, $fname );
  196. $dbw->commit();
  197. }
  198. }
  199. public static function cacheUpdate( $dbw ) {
  200. $dbr = wfGetDB( DB_SLAVE, array( 'SpecialStatistics', 'vslow') );
  201. # Get non-bot users than did some recent action other than making accounts.
  202. # If account creation is included, the number gets inflated ~20+ fold on enwiki.
  203. $activeUsers = $dbr->selectField( 'recentchanges', 'COUNT( DISTINCT rc_user_text )',
  204. array( 'rc_user != 0', 'rc_bot' => 0, "rc_log_type != 'newusers' OR rc_log_type IS NULL" ),
  205. __METHOD__ );
  206. $dbw->update( 'site_stats',
  207. array( 'ss_active_users' => intval($activeUsers) ),
  208. array( 'ss_row_id' => 1 ), __METHOD__
  209. );
  210. }
  211. }