Completed
Branch master (939199)
by
unknown
39:35
created

includes/libs/Timing.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * This program is free software; you can redistribute it and/or modify
4
 * it under the terms of the GNU General Public License as published by
5
 * the Free Software Foundation; either version 2 of the License, or
6
 * (at your option) any later version.
7
 *
8
 * This program is distributed in the hope that it will be useful,
9
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
 * GNU General Public License for more details.
12
 *
13
 * You should have received a copy of the GNU General Public License along
14
 * with this program; if not, write to the Free Software Foundation, Inc.,
15
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16
 * http://www.gnu.org/copyleft/gpl.html
17
 *
18
 * @file
19
 */
20
21
use Psr\Log\LoggerAwareInterface;
22
use Psr\Log\LoggerInterface;
23
use Psr\Log\NullLogger;
24
25
/**
26
 * An interface to help developers measure the performance of their applications.
27
 * This interface closely matches the W3C's User Timing specification.
28
 * The key differences are:
29
 *
30
 * - The reference point for all measurements which do not explicitly specify
31
 *   a start time is $_SERVER['REQUEST_TIME_FLOAT'], not navigationStart.
32
 * - Successive calls to mark() and measure() with the same entry name cause
33
 *   the previous entry to be overwritten. This ensures that there is a 1:1
34
 *   mapping between names and entries.
35
 * - Because there is a 1:1 mapping, instead of getEntriesByName(), we have
36
 *   getEntryByName().
37
 *
38
 * The in-line documentation incorporates content from the User Timing Specification
39
 * http://www.w3.org/TR/user-timing/
40
 * Copyright © 2013 World Wide Web Consortium, (MIT, ERCIM, Keio, Beihang).
41
 * http://www.w3.org/Consortium/Legal/2015/doc-license
42
 *
43
 * @since 1.27
44
 */
45
class Timing implements LoggerAwareInterface {
46
47
	/** @var array[] */
48
	private $entries = [];
49
50
	/** @var LoggerInterface */
51
	protected $logger;
52
53
	public function __construct( array $params = [] ) {
54
		$this->clearMarks();
55
		$this->setLogger( isset( $params['logger'] ) ? $params['logger'] : new NullLogger() );
56
	}
57
58
	/**
59
	 * Sets a logger instance on the object.
60
	 *
61
	 * @param LoggerInterface $logger
62
	 * @return null
63
	 */
64
	public function setLogger( LoggerInterface $logger ) {
65
		$this->logger = $logger;
66
	}
67
68
	/**
69
	 * Store a timestamp with the associated name (a "mark")
70
	 *
71
	 * @param string $markName The name associated with the timestamp.
72
	 *  If there already exists an entry by that name, it is overwritten.
73
	 * @return array The mark that has been created.
74
	 */
75
	public function mark( $markName ) {
76
		$this->entries[$markName] = [
77
			'name'      => $markName,
78
			'entryType' => 'mark',
79
			'startTime' => microtime( true ),
80
			'duration'  => 0,
81
		];
82
		return $this->entries[$markName];
83
	}
84
85
	/**
86
	 * @param string $markName The name of the mark that should
87
	 *  be cleared. If not specified, all marks will be cleared.
88
	 */
89
	public function clearMarks( $markName = null ) {
0 ignored issues
show
clearMarks uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
90
		if ( $markName !== null ) {
91
			unset( $this->entries[$markName] );
92
		} else {
93
			$this->entries = [
0 ignored issues
show
Documentation Bug introduced by
It seems like array('requestStart' => ...ME'], 'duration' => 0)) of type array<string,array<strin...tion\":\"integer\"}>"}> is incompatible with the declared type array<integer,array> of property $entries.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
94
				'requestStart' => [
95
					'name'      => 'requestStart',
96
					'entryType' => 'mark',
97
					'startTime' => isset( $_SERVER['REQUEST_TIME_FLOAT'] )
98
						? $_SERVER['REQUEST_TIME_FLOAT']
99
						: $_SERVER['REQUEST_TIME'],
100
					'duration'  => 0,
101
				],
102
			];
103
		}
104
	}
105
106
	/**
107
	 * This method stores the duration between two marks along with
108
	 * the associated name (a "measure").
109
	 *
110
	 * If neither the startMark nor the endMark argument is specified,
111
	 * measure() will store the duration from $_SERVER['REQUEST_TIME_FLOAT'] to
112
	 * the current time.
113
	 * If the startMark argument is specified, but the endMark argument is not
114
	 * specified, measure() will store the duration from the most recent
115
	 * occurrence of the start mark to the current time.
116
	 * If both the startMark and endMark arguments are specified, measure()
117
	 * will store the duration from the most recent occurrence of the start
118
	 * mark to the most recent occurrence of the end mark.
119
	 *
120
	 * @param string $measureName
121
	 * @param string $startMark
122
	 * @param string $endMark
123
	 * @return array|bool The measure that has been created, or false if either
124
	 *  the start mark or the end mark do not exist.
125
	 */
126
	public function measure( $measureName, $startMark = 'requestStart', $endMark = null ) {
127
		$start = $this->getEntryByName( $startMark );
128
		if ( $start === null ) {
129
			$this->logger->error( __METHOD__ . ": The mark '$startMark' does not exist" );
130
			return false;
131
		}
132
		$startTime = $start['startTime'];
133
134
		if ( $endMark ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $endMark of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
135
			$end = $this->getEntryByName( $endMark );
136
			if ( $end === null ) {
137
				$this->logger->error( __METHOD__ . ": The mark '$endMark' does not exist" );
138
				return false;
139
			}
140
			$endTime = $end['startTime'];
141
		} else {
142
			$endTime = microtime( true );
143
		}
144
145
		$this->entries[$measureName] = [
146
			'name'      => $measureName,
147
			'entryType' => 'measure',
148
			'startTime' => $startTime,
149
			'duration'  => $endTime - $startTime,
150
		];
151
152
		return $this->entries[$measureName];
153
	}
154
155
	/**
156
	 * Sort entries in chronological order with respect to startTime.
157
	 */
158
	private function sortEntries() {
159
		uasort( $this->entries, function ( $a, $b ) {
160
			return 10000 * ( $a['startTime'] - $b['startTime'] );
161
		} );
162
	}
163
164
	/**
165
	 * @return array[] All entries in chronological order.
166
	 */
167
	public function getEntries() {
168
		$this->sortEntries();
169
		return $this->entries;
170
	}
171
172
	/**
173
	 * @param string $entryType
174
	 * @return array[] Entries (in chronological order) that have the same value
175
	 *  for the entryType attribute as the $entryType parameter.
176
	 */
177
	public function getEntriesByType( $entryType ) {
178
		$this->sortEntries();
179
		$entries = [];
180
		foreach ( $this->entries as $entry ) {
181
			if ( $entry['entryType'] === $entryType ) {
182
				$entries[] = $entry;
183
			}
184
		}
185
		return $entries;
186
	}
187
188
	/**
189
	 * @param string $name
190
	 * @return array|null Entry named $name or null if it does not exist.
191
	 */
192
	public function getEntryByName( $name ) {
193
		return isset( $this->entries[$name] ) ? $this->entries[$name] : null;
194
	}
195
}
196