Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
90.20% |
46 / 51 |
|
88.89% |
8 / 9 |
CRAP | |
0.00% |
0 / 1 |
Timing | |
90.20% |
46 / 51 |
|
88.89% |
8 / 9 |
15.21 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
setLogger | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
mark | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
clearMarks | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
2 | |||
measure | |
73.68% |
14 / 19 |
|
0.00% |
0 / 1 |
4.29 | |||
sortEntries | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
getEntries | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
getEntriesByType | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
getEntryByName | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
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 | * https://www.w3.org/TR/user-timing/ |
40 | * Copyright © 2013 World Wide Web Consortium, (MIT, ERCIM, Keio, Beihang). |
41 | * https://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( $params['logger'] ?? new NullLogger() ); |
56 | } |
57 | |
58 | /** |
59 | * Sets a logger instance on the object. |
60 | * |
61 | * @param LoggerInterface $logger |
62 | */ |
63 | public function setLogger( LoggerInterface $logger ) { |
64 | $this->logger = $logger; |
65 | } |
66 | |
67 | /** |
68 | * Store a timestamp with the associated name (a "mark") |
69 | * |
70 | * @param string $markName The name associated with the timestamp. |
71 | * If there already exists an entry by that name, it is overwritten. |
72 | * @return array The mark that has been created. |
73 | */ |
74 | public function mark( $markName ) { |
75 | $this->entries[$markName] = [ |
76 | 'name' => $markName, |
77 | 'entryType' => 'mark', |
78 | 'startTime' => microtime( true ), |
79 | 'duration' => 0, |
80 | ]; |
81 | return $this->entries[$markName]; |
82 | } |
83 | |
84 | /** |
85 | * @param string|null $markName The name of the mark that should |
86 | * be cleared. If not specified, all marks will be cleared. |
87 | */ |
88 | public function clearMarks( $markName = null ) { |
89 | if ( $markName !== null ) { |
90 | unset( $this->entries[$markName] ); |
91 | } else { |
92 | $this->entries = [ |
93 | 'requestStart' => [ |
94 | 'name' => 'requestStart', |
95 | 'entryType' => 'mark', |
96 | 'startTime' => $_SERVER['REQUEST_TIME_FLOAT'], |
97 | 'duration' => 0, |
98 | ], |
99 | ]; |
100 | } |
101 | } |
102 | |
103 | /** |
104 | * This method stores the duration between two marks along with |
105 | * the associated name (a "measure"). |
106 | * |
107 | * If neither the startMark nor the endMark argument is specified, |
108 | * measure() will store the duration from $_SERVER['REQUEST_TIME_FLOAT'] to |
109 | * the current time. |
110 | * If the startMark argument is specified, but the endMark argument is not |
111 | * specified, measure() will store the duration from the most recent |
112 | * occurrence of the start mark to the current time. |
113 | * If both the startMark and endMark arguments are specified, measure() |
114 | * will store the duration from the most recent occurrence of the start |
115 | * mark to the most recent occurrence of the end mark. |
116 | * |
117 | * @param string $measureName |
118 | * @param string $startMark |
119 | * @param string|null $endMark |
120 | * @return array|bool The measure that has been created, or false if either |
121 | * the start mark or the end mark do not exist. |
122 | */ |
123 | public function measure( $measureName, $startMark = 'requestStart', $endMark = null ) { |
124 | $start = $this->getEntryByName( $startMark ); |
125 | if ( $start === null ) { |
126 | $this->logger->error( __METHOD__ . ": The mark '$startMark' does not exist" ); |
127 | return false; |
128 | } |
129 | $startTime = $start['startTime']; |
130 | |
131 | if ( $endMark ) { |
132 | $end = $this->getEntryByName( $endMark ); |
133 | if ( $end === null ) { |
134 | $this->logger->error( __METHOD__ . ": The mark '$endMark' does not exist" ); |
135 | return false; |
136 | } |
137 | $endTime = $end['startTime']; |
138 | } else { |
139 | $endTime = microtime( true ); |
140 | } |
141 | |
142 | $this->entries[$measureName] = [ |
143 | 'name' => $measureName, |
144 | 'entryType' => 'measure', |
145 | 'startTime' => $startTime, |
146 | 'duration' => $endTime - $startTime, |
147 | ]; |
148 | |
149 | return $this->entries[$measureName]; |
150 | } |
151 | |
152 | /** |
153 | * Sort entries in chronological order with respect to startTime. |
154 | */ |
155 | private function sortEntries() { |
156 | uasort( $this->entries, static function ( $a, $b ) { |
157 | return $a['startTime'] <=> $b['startTime']; |
158 | } ); |
159 | } |
160 | |
161 | /** |
162 | * @return array[] All entries in chronological order. |
163 | */ |
164 | public function getEntries() { |
165 | $this->sortEntries(); |
166 | return $this->entries; |
167 | } |
168 | |
169 | /** |
170 | * @param string $entryType |
171 | * @return array[] Entries (in chronological order) that have the same value |
172 | * for the entryType attribute as the $entryType parameter. |
173 | */ |
174 | public function getEntriesByType( $entryType ) { |
175 | $this->sortEntries(); |
176 | $entries = []; |
177 | foreach ( $this->entries as $entry ) { |
178 | if ( $entry['entryType'] === $entryType ) { |
179 | $entries[] = $entry; |
180 | } |
181 | } |
182 | return $entries; |
183 | } |
184 | |
185 | /** |
186 | * @param string $name |
187 | * @return array|null Entry named $name or null if it does not exist. |
188 | */ |
189 | public function getEntryByName( $name ) { |
190 | return $this->entries[$name] ?? null; |
191 | } |
192 | } |