Current File : /home/jvzmxxx/wiki/extensions/Wikibase/repo/includes/Store/Sql/DispatchStats.php
<?php

namespace Wikibase;

/**
 * Utility class for collecting dispatch statistics.
 * Note that you must call load() before accessing any getters.
 *
 * @since 0.4
 *
 * @license GPL-2.0+
 * @author Daniel Kinzler
 * @author Thiemo Mättig
 */
class DispatchStats {

	/**
	 * @var object[]|null
	 */
	private $clientStates = null;

	/**
	 * @var object|null
	 */
	private $changeStats = null;

	/**
	 * @var object|null
	 */
	private $average = null;

	/**
	 * Loads the current dispatch status from the database and calculates statistics.
	 * Before this method is called, the behavior of the getters is undefined.
	 *
	 * @param int|string $now Timestamp to consider the current time. Mostly useful for testing.
	 *
	 * @return int the number of client wikis.
	 */
	public function load( $now = 0 ) {
		$db = wfGetDB( DB_SLAVE ); // XXX: use master?

		$now = wfTimestamp( TS_UNIX, $now );

		$this->changeStats = $db->selectRow(
			'wb_changes',
			array(
				'min( change_id ) as min_id',
				'max( change_id ) as max_id',
				'min( change_time ) as min_time',
				'max( change_time ) as max_time',
			),
			'',
			__METHOD__
		);

		$res = $db->select(
			array(
				'wb_changes_dispatch',
				'wb_changes'
			),
			array( 'chd_site',
					'chd_db',
					'chd_seen',
					'chd_touched',
					'chd_lock',
					'chd_disabled',
					'change_time',
			),
			array(
				'chd_disabled' => 0
			),
			__METHOD__,
			array(
				'ORDER BY' => 'chd_seen ASC'
			),
			array(
				'wb_changes' => array( 'LEFT JOIN', 'chd_seen = change_id' )
			)
		);

		$this->average = new \stdClass();
		$this->average->chd_untouched = 0;
		$this->average->chd_pending = 0;
		$this->average->chd_lag = 0;

		$this->clientStates = array();

		foreach ( $res as $row ) {
			if ( $this->changeStats ) {
				// time between last dispatch and now
				$row->chd_untouched = max( 0, $now
					- (int)wfTimestamp( TS_UNIX, $row->chd_touched ) );

				// time between the timestamp of the last changed processed and the last change recorded.
				if ( $row->change_time === null ) {
					// the change was already pruned, lag is "big".
					$row->chd_lag = null;
				} else {
					$row->chd_lag = max( 0, (int)wfTimestamp( TS_UNIX, $this->changeStats->max_time )
						- (int)wfTimestamp( TS_UNIX, $row->change_time ) );
				}

				// number of changes that have not been processed yet
				$row->chd_pending = (int)$this->changeStats->max_id - $row->chd_seen;
			} else {
				// if there are no changes, there is no lag
				$row->chd_untouched = 0;
				$row->chd_pending = 0;
				$row->chd_lag = 0;
			}

			$this->average->chd_untouched += $row->chd_untouched;
			$this->average->chd_pending += $row->chd_pending;

			if ( $row->chd_lag === null || $this->average->chd_lag === null ) {
				$this->average->chd_lag = null;
			} else {
				$this->average->chd_lag += $row->chd_lag;
			}

			$this->clientStates[] = $row;
		}

		$n = count( $this->clientStates );

		if ( $n > 0 ) {
			$this->average->chd_untouched = (int)( $this->average->chd_untouched / $n );
			$this->average->chd_pending = (int)( $this->average->chd_pending / $n );
			$this->average->chd_lag = $this->average->chd_lag === null
				? null
				: (int)( $this->average->chd_lag / $n );
		}

		return $n;
	}

	/**
	 * @return bool
	 */
	public function hasStats() {
		return !empty( $this->clientStates );
	}

	/**
	 * Returns the dispatch state for all client wikis.
	 * The state for each wiki is returned as an object containing the following fields:
	 *
	 * * chd_site: the client wiki's site ID
	 * * chd_untouched:  seconds since that client was last touched by a dispatcher
	 * * chd_pending: number of changes not yet dispatched to that client
	 * * chd_lag: seconds between the timestamp of the last change that got dispatched
	 *            and the latest change recorded. May be null if some of the pending changes
	 *            have already been pruned. This indicates that the average could not be
	 *            determined, but the lag is large.
	 * * chd_lock: the name of the lock currently in effect for that wiki
	 *
	 * @return object[]|null A list of objects representing the dispatch state
	 *         for each client wiki.
	 */
	public function getClientStates() {
		return $this->clientStates;
	}

	/**
	 * Returns the number of active client wikis.
	 *
	 * @return int
	 */
	public function getClientCount() {
		return $this->clientStates ? count( $this->clientStates ) : 0;
	}

	/**
	 * Returns a dispatch status object for the client wiki
	 * that was updated most recently.
	 *
	 * See getClientStates() for the structure of the status object.
	 *
	 * @return object|null
	 */
	public function getFreshest() {
		return $this->clientStates ? end( $this->clientStates ) : null;
	}

	/**
	 * Returns a dispatch status object for the client wiki
	 * that was updated least recently.
	 *
	 * See getClientStates() for the structure of the status object.
	 *
	 * @return object|null
	 */
	public function getStalest() {
		return $this->clientStates ? reset( $this->clientStates ) : null;
	}

	/**
	 * Returns a dispatch status object for the client wiki
	 * that represents the median in terms of dispatch lag.
	 *
	 * See getClientStates() for the structure of the status object.
	 *
	 * @return object|null
	 */
	public function getMedian() {
		if ( empty( $this->clientStates ) ) {
			return null;
		}

		$i = (int)( count( $this->clientStates ) / 2 );
		return $this->clientStates[$i];
	}

	/**
	 * Returns a pseudo-status object representing the average (mean) dispatch
	 * lag. The status object has the following fields:
	 *
	 * * chd_untouched:  seconds since that client was last touched by a dispatcher
	 * * chd_pending: number of changes not yet dispatched to that client
	 * * chd_lag: seconds between the timestamp of the last change that got dispatched
	 *            and the latest change recorded. May be null if some of the pending changes
	 *            have already been pruned. This indicates that the average could not be
	 *            determined, but the lag is large.
	 *
	 * @return object
	 */
	public function getAverage() {
		return $this->average;
	}

	/**
	 * Returns the number of client wikis currently locked.
	 * Note that this does not probe the locks, so they may be stale.
	 *
	 * @return int
	 */
	public function getLockedCount() {
		$c = 0;

		if ( !empty( $this->clientStates ) ) {
			foreach ( $this->clientStates as $row ) {
				if ( $row->chd_lock !== null ) {
					$c++;
				}
			}
		}

		return $c;
	}

	/**
	 * Returns the newest change ID from the changes table.
	 *
	 * @return int
	 */
	public function getMaxChangeId() {
		return (int)$this->changeStats->max_id;
	}

	/**
	 * Returns the oldest change ID from the changes table.
	 *
	 * @return int
	 */
	public function getMinChangeId() {
		return (int)$this->changeStats->min_id;
	}

	/**
	 * Returns the newest timestamp from the changes table.
	 *
	 * @return string
	 */
	public function getMaxChangeTimestamp() {
		return $this->changeStats->max_time;
	}

	/**
	 * Returns the oldest timestamp from the changes table.
	 *
	 * @return string
	 */
	public function getMinChangeTimestamp() {
		return $this->changeStats->min_time;
	}

}