summaryrefslogtreecommitdiff
path: root/www/wiki/includes/libs/Timing.php
blob: 7b1a9140dfb43467030f52eb2d931e52856ee11e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
<?php
/**
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 * http://www.gnu.org/copyleft/gpl.html
 *
 * @file
 */

use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;

/**
 * An interface to help developers measure the performance of their applications.
 * This interface closely matches the W3C's User Timing specification.
 * The key differences are:
 *
 * - The reference point for all measurements which do not explicitly specify
 *   a start time is $_SERVER['REQUEST_TIME_FLOAT'], not navigationStart.
 * - Successive calls to mark() and measure() with the same entry name cause
 *   the previous entry to be overwritten. This ensures that there is a 1:1
 *   mapping between names and entries.
 * - Because there is a 1:1 mapping, instead of getEntriesByName(), we have
 *   getEntryByName().
 *
 * The in-line documentation incorporates content from the User Timing Specification
 * https://www.w3.org/TR/user-timing/
 * Copyright © 2013 World Wide Web Consortium, (MIT, ERCIM, Keio, Beihang).
 * https://www.w3.org/Consortium/Legal/2015/doc-license
 *
 * @since 1.27
 */
class Timing implements LoggerAwareInterface {

	/** @var array[] */
	private $entries = [];

	/** @var LoggerInterface */
	protected $logger;

	public function __construct( array $params = [] ) {
		$this->clearMarks();
		$this->setLogger( isset( $params['logger'] ) ? $params['logger'] : new NullLogger() );
	}

	/**
	 * Sets a logger instance on the object.
	 *
	 * @param LoggerInterface $logger
	 * @return null
	 */
	public function setLogger( LoggerInterface $logger ) {
		$this->logger = $logger;
	}

	/**
	 * Store a timestamp with the associated name (a "mark")
	 *
	 * @param string $markName The name associated with the timestamp.
	 *  If there already exists an entry by that name, it is overwritten.
	 * @return array The mark that has been created.
	 */
	public function mark( $markName ) {
		$this->entries[$markName] = [
			'name'      => $markName,
			'entryType' => 'mark',
			'startTime' => microtime( true ),
			'duration'  => 0,
		];
		return $this->entries[$markName];
	}

	/**
	 * @param string $markName The name of the mark that should
	 *  be cleared. If not specified, all marks will be cleared.
	 */
	public function clearMarks( $markName = null ) {
		if ( $markName !== null ) {
			unset( $this->entries[$markName] );
		} else {
			$this->entries = [
				'requestStart' => [
					'name'      => 'requestStart',
					'entryType' => 'mark',
					'startTime' => $_SERVER['REQUEST_TIME_FLOAT'],
					'duration'  => 0,
				],
			];
		}
	}

	/**
	 * This method stores the duration between two marks along with
	 * the associated name (a "measure").
	 *
	 * If neither the startMark nor the endMark argument is specified,
	 * measure() will store the duration from $_SERVER['REQUEST_TIME_FLOAT'] to
	 * the current time.
	 * If the startMark argument is specified, but the endMark argument is not
	 * specified, measure() will store the duration from the most recent
	 * occurrence of the start mark to the current time.
	 * If both the startMark and endMark arguments are specified, measure()
	 * will store the duration from the most recent occurrence of the start
	 * mark to the most recent occurrence of the end mark.
	 *
	 * @param string $measureName
	 * @param string $startMark
	 * @param string $endMark
	 * @return array|bool The measure that has been created, or false if either
	 *  the start mark or the end mark do not exist.
	 */
	public function measure( $measureName, $startMark = 'requestStart', $endMark = null ) {
		$start = $this->getEntryByName( $startMark );
		if ( $start === null ) {
			$this->logger->error( __METHOD__ . ": The mark '$startMark' does not exist" );
			return false;
		}
		$startTime = $start['startTime'];

		if ( $endMark ) {
			$end = $this->getEntryByName( $endMark );
			if ( $end === null ) {
				$this->logger->error( __METHOD__ . ": The mark '$endMark' does not exist" );
				return false;
			}
			$endTime = $end['startTime'];
		} else {
			$endTime = microtime( true );
		}

		$this->entries[$measureName] = [
			'name'      => $measureName,
			'entryType' => 'measure',
			'startTime' => $startTime,
			'duration'  => $endTime - $startTime,
		];

		return $this->entries[$measureName];
	}

	/**
	 * Sort entries in chronological order with respect to startTime.
	 */
	private function sortEntries() {
		uasort( $this->entries, function ( $a, $b ) {
			return 10000 * ( $a['startTime'] - $b['startTime'] );
		} );
	}

	/**
	 * @return array[] All entries in chronological order.
	 */
	public function getEntries() {
		$this->sortEntries();
		return $this->entries;
	}

	/**
	 * @param string $entryType
	 * @return array[] Entries (in chronological order) that have the same value
	 *  for the entryType attribute as the $entryType parameter.
	 */
	public function getEntriesByType( $entryType ) {
		$this->sortEntries();
		$entries = [];
		foreach ( $this->entries as $entry ) {
			if ( $entry['entryType'] === $entryType ) {
				$entries[] = $entry;
			}
		}
		return $entries;
	}

	/**
	 * @param string $name
	 * @return array|null Entry named $name or null if it does not exist.
	 */
	public function getEntryByName( $name ) {
		return isset( $this->entries[$name] ) ? $this->entries[$name] : null;
	}
}