├── .State ├── .gitignore ├── bors.toml ├── .travis.yml ├── composer.json ├── Exception.php ├── Dtrace ├── Stat.d └── Execution.d ├── CHANGELOG.md ├── Test └── Unit │ └── Mark.php ├── README.md ├── Mark.php ├── Bench.php └── Documentation ├── En └── Index.xyl └── Fr └── Index.xyl /.State: -------------------------------------------------------------------------------- 1 | finalized 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /composer.lock 3 | -------------------------------------------------------------------------------- /bors.toml: -------------------------------------------------------------------------------- 1 | status = [ 2 | "continuous-integration/travis-ci/push" 3 | ] 4 | timeout_sec = 1800 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | branches: 4 | only: 5 | - staging 6 | - trying 7 | - master 8 | 9 | matrix: 10 | include: 11 | - php: 5.5 12 | - php: 5.6 13 | - php: 7.0 14 | - php: 7.1 15 | env: 16 | - ENABLE_XDEBUG=true 17 | - php: 7.1 18 | env: 19 | - ENABLE_DEVTOOLS=true 20 | - php: nightly 21 | allow_failures: 22 | - php: nightly 23 | fast_finish: true 24 | 25 | os: 26 | - linux 27 | 28 | notifications: 29 | irc: "chat.freenode.net#hoaproject" 30 | 31 | sudo: false 32 | 33 | env: 34 | global: 35 | - secure: "AAAAB3NzaC1yc2EAAAADAQABAAAAgQCP/MRQTkDEQdlnhiVbW5dl3dSPBI8KO0EkCLRJ8mEOJ6gm9VH0yy2IyiBuGa+Oyj+cbdKkASN4B/nMvPS+POG9Qd+z9aSmgYZd1ZwVbmu1r0ag53qhQAiodLudzBpjS3RA0MJyX3IJu7HdMNo8qhx0M9WF+vGkcOAYqbsifakO8Q==" 36 | 37 | cache: 38 | directories: 39 | - vendor/ 40 | 41 | before_script: 42 | - export PATH="$PATH:$HOME/.composer/vendor/bin" 43 | - if [[ ! $ENABLE_XDEBUG ]]; then 44 | phpenv config-rm xdebug.ini || echo "ext-xdebug is not available, cannot remove it."; 45 | fi 46 | 47 | script: 48 | - composer install 49 | - vendor/bin/hoa test:run 50 | - if [[ $ENABLE_DEVTOOLS ]]; then 51 | composer global require friendsofphp/php-cs-fixer; 52 | vendor/bin/hoa devtools:cs --diff --dry-run .; 53 | fi 54 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name" : "hoa/bench", 3 | "description": "The Hoa\\Bench library.", 4 | "type" : "library", 5 | "keywords" : ["library", "bench", "performance", "time", "dtrace", 6 | "trace", "analyze"], 7 | "homepage" : "https://hoa-project.net/", 8 | "license" : "BSD-3-Clause", 9 | "authors" : [ 10 | { 11 | "name" : "Ivan Enderlin", 12 | "email": "ivan.enderlin@hoa-project.net" 13 | }, 14 | { 15 | "name" : "Hoa community", 16 | "homepage": "https://hoa-project.net/" 17 | } 18 | ], 19 | "support": { 20 | "email" : "support@hoa-project.net", 21 | "irc" : "irc://chat.freenode.net/hoaproject", 22 | "forum" : "https://users.hoa-project.net/", 23 | "docs" : "https://central.hoa-project.net/Documentation/Library/Bench", 24 | "source": "https://central.hoa-project.net/Resource/Library/Bench" 25 | }, 26 | "require": { 27 | "hoa/consistency": "~1.0", 28 | "hoa/exception" : "~1.0", 29 | "hoa/iterator" : "~2.0" 30 | }, 31 | "require-dev": { 32 | "hoa/test" : "~2.0" 33 | }, 34 | "autoload": { 35 | "psr-4": { 36 | "Hoa\\Bench\\": "." 37 | } 38 | }, 39 | "extra": { 40 | "branch-alias": { 41 | "dev-master": "3.x-dev" 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Exception.php: -------------------------------------------------------------------------------- 1 | i = 0; 53 | } 54 | 55 | php*:::execute-entry 56 | { 57 | execute_start = timestamp; 58 | } 59 | 60 | php*:::function-entry 61 | /copyinstr(arg0) != ""/ 62 | { 63 | calls[self->i].name = strjoin( 64 | copyinstr(arg3), 65 | strjoin(copyinstr(arg4), copyinstr(arg0)) 66 | ); 67 | calls[self->i].time = timestamp; 68 | calls[self->i].depth = self->i; 69 | 70 | self->i++; 71 | } 72 | 73 | php*:::function-return 74 | /copyinstr(arg0) != ""/ 75 | { 76 | self->i--; 77 | 78 | @c[calls[self->i].name] = count(); 79 | @a[calls[self->i].name] = quantize( 80 | (timestamp - calls[self->i].time) / 1000 81 | ); 82 | } 83 | 84 | php*:::execute-return 85 | { 86 | execute_stop = timestamp; 87 | } 88 | 89 | END 90 | { 91 | printf("Count calls:\n"); 92 | printa(" • %-80s%@u\n", @c); 93 | 94 | printf("\nExecution distribution (values are in nanoseconds):\n"); 95 | printa(@a); 96 | 97 | printf("Total execution time: %dms", (execute_stop - execute_start) / 1000); 98 | 99 | exit(0); 100 | } 101 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 3.17.01.11 2 | 3 | * Quality: Happy new year! (Alexis von Glasow, 2017-01-09T21:37:32+01:00) 4 | 5 | # 3.16.10.24 6 | 7 | * Documentation: New `README.md` file. (Ivan Enderlin, 2016-10-17T20:34:40+02:00) 8 | * Documentation: Update `support` properties. (Ivan Enderlin, 2016-10-05T15:43:12+02:00) 9 | 10 | # 3.16.03.15 11 | 12 | * Update copyright. (Ivan Enderlin, 2016-01-17T14:12:35+01:00) 13 | * README: Simplify an example. (Ivan Enderlin, 2016-01-17T14:02:47+01:00) 14 | 15 | # 3.16.01.11 16 | 17 | * Quality: Drop PHP5.4. (Ivan Enderlin, 2016-01-11T09:15:26+01:00) 18 | * Quality: Run devtools:cs. (Ivan Enderlin, 2016-01-09T08:56:45+01:00) 19 | * Documentation: Use `hoa protocol:resolve`. (Ivan Enderlin, 2016-01-09T08:37:41+01:00) 20 | * Core: Remove `Hoa\Core`. (Ivan Enderlin, 2016-01-09T07:58:29+01:00) 21 | * Consistency: Use `Hoa\Consistency`. (Ivan Enderlin, 2015-12-08T10:51:08+01:00) 22 | * Exception: Use `Hoa\Exception`. (Ivan Enderlin, 2015-11-20T07:07:21+01:00) 23 | 24 | # 2.15.10.21 25 | 26 | * Documentation: Fix typo. (Raphaël Emourgeon, 2015-09-14T21:15:43+02:00) 27 | * Documentation: Introduce `pause` and `resume` methods. (Raphaël Emourgeon, 2015-09-14T19:09:10+02:00) 28 | * Documentation: Describe the `pause` & `resume` methods. (Ivan Enderlin, 2015-09-14T08:21:30+02:00) 29 | 30 | # 2.15.08.13 31 | 32 | * Update API documentation and re-order methods. (Ivan Enderlin, 2015-08-12T09:03:30+02:00) 33 | * The `resume` method is now static. (Ivan Enderlin, 2015-08-12T09:02:22+02:00) 34 | * Introduce the `pause` and `resume` methods. (Aymeric GERLIER, 2015-08-03T17:23:57+02:00) 35 | * Add a `.gitignore` file. (Stéphane HULARD, 2015-08-03T11:20:04+02:00) 36 | 37 | # 2.15.05.29 38 | 39 | * Fix English typography. (Ivan Enderlin, 2015-05-12T08:46:01+02:00) 40 | * Fix CS. (Ivan Enderlin, 2015-04-20T10:10:01+02:00) 41 | * Move the CHANGELOG to PSR-1 and PSR-2. (Ivan Enderlin, 2015-04-20T09:34:20+02:00) 42 | * Move documentation to PSR-1 and PSR-2. (Ivan Enderlin, 2015-04-20T09:31:12+02:00) 43 | * Move to PSR-1 and PSR-2. (Ivan Enderlin, 2015-04-20T09:23:09+02:00) 44 | 45 | # 2.15.04.13 46 | 47 | * Forget to translate a header text. (Ivan Enderlin, 2015-04-09T15:40:56+02:00) 48 | 49 | # 2.15.02.17 50 | 51 | * Add the CHANGELOG.md file. (Ivan Enderlin, 2015-02-17T09:20:07+01:00) 52 | * Happy new year! (Ivan Enderlin, 2015-01-05T14:18:01+01:00) 53 | 54 | # 2.14.12.10 55 | 56 | * Move to PSR-4. (Ivan Enderlin, 2014-12-09T13:34:12+01:00) 57 | 58 | # 2.14.11.21 59 | 60 | * Fix semi-colon missing (Alexis von Glasow, 2014-11-20T09:15:25+01:00) 61 | 62 | # 2.14.11.09 63 | 64 | * Use `hoa/iterator` ~1.0. (Ivan Enderlin, 2014-11-09T10:56:19+01:00) 65 | * Use `Hoa\Iterator`. (Ivan Enderlin, 2014-10-05T11:07:46+02:00) 66 | * Remove `from`/`import` and update to PHP5.4. (Ivan Enderlin, 2014-10-05T11:00:14+02:00) 67 | * Add links around `hoa://` in the documentation. (Ivan Enderlin, 2014-09-26T10:32:00+02:00) 68 | 69 | # 2.14.09.23 70 | 71 | * Add `branch-alias`. (Stéphane PY, 2014-09-23T11:56:19+02:00) 72 | 73 | # 2.14.09.17 74 | 75 | * Drop PHP5.3. (Ivan Enderlin, 2014-09-17T17:38:03+02:00) 76 | * Add the installation section. (Ivan Enderlin, 2014-09-17T17:37:54+02:00) 77 | 78 | (first snapshot) 79 | -------------------------------------------------------------------------------- /Test/Unit/Mark.php: -------------------------------------------------------------------------------- 1 | object( 56 | $mark = new CUT($id = uniqid()) 57 | )->string($mark->getId())->isEqualTo($id) 58 | ->boolean($mark->isRunning())->isFalse() 59 | ->boolean($mark->isPause())->isFalse() 60 | ->assert('Start mark') 61 | ->when($SUT = $mark->start()) 62 | ->object($SUT)->isInstanceOf('Hoa\Bench\Mark') 63 | ->boolean($mark->isRunning())->isTrue() 64 | ->boolean($mark->isPause())->isFalse() 65 | ->assert('Pause mark') 66 | ->when($SUT = $mark->pause()) 67 | ->object($SUT)->isInstanceOf('Hoa\Bench\Mark') 68 | ->boolean($mark->isRunning())->isTrue() 69 | ->boolean($mark->isPause())->isTrue() 70 | ->assert('Restart mark after pause') 71 | ->when($SUT = $mark->start()) 72 | ->object($SUT)->isInstanceOf('Hoa\Bench\Mark') 73 | ->boolean($mark->isRunning())->isTrue() 74 | ->boolean($mark->isPause())->isFalse() 75 | ->assert('Stop mark') 76 | ->when($SUT = $mark->stop()) 77 | ->object($SUT)->isInstanceOf('Hoa\Bench\Mark') 78 | ->boolean($mark->isRunning())->isFalse() 79 | ->boolean($mark->isPause())->isFalse() 80 | ; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /Dtrace/Execution.d: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env dtrace -s 2 | 3 | /** 4 | * Hoa 5 | * 6 | * 7 | * @license 8 | * 9 | * New BSD License 10 | * 11 | * Copyright © 2007-2017, Hoa community. All rights reserved. 12 | * 13 | * Redistribution and use in source and binary forms, with or without 14 | * modification, are permitted provided that the following conditions are met: 15 | * * Redistributions of source code must retain the above copyright 16 | * notice, this list of conditions and the following disclaimer. 17 | * * Redistributions in binary form must reproduce the above copyright 18 | * notice, this list of conditions and the following disclaimer in the 19 | * documentation and/or other materials provided with the distribution. 20 | * * Neither the name of the Hoa nor the names of its contributors may be 21 | * used to endorse or promote products derived from this software without 22 | * specific prior written permission. 23 | * 24 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 27 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE 28 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 29 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 30 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 31 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 32 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 33 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 34 | * POSSIBILITY OF SUCH DAMAGE. 35 | */ 36 | 37 | #pragma D option quiet 38 | 39 | BEGIN 40 | { 41 | } 42 | 43 | php*:::request-startup 44 | { 45 | printf("Request start\n"); 46 | self->up = 0; 47 | self->depth = 0; 48 | } 49 | 50 | php*:::function-entry 51 | /self->up == 0/ 52 | { 53 | self->time_last = timestamp; 54 | } 55 | 56 | php*:::function-entry 57 | { 58 | self->depth += 2; 59 | printf( 60 | "%6dms %*s %s%s%s()\t\t\t%s/%s:%03d\n", 61 | (timestamp - self->time_last) / 1000, 62 | self->depth, "➜", 63 | copyinstr(arg3), 64 | copyinstr(arg4), 65 | copyinstr(arg0), 66 | dirname(copyinstr(arg1)), 67 | basename(copyinstr(arg1)), 68 | arg2 69 | ); 70 | self->up = 1; 71 | self->time_last = timestamp; 72 | } 73 | 74 | php*:::exception-thrown 75 | /arg0 != NULL/ 76 | { 77 | printf( 78 | "● %*s %s has been thrown\n", 79 | self->depth, "", 80 | copyinstr(arg0) 81 | ); 82 | } 83 | 84 | php*:::exception-caught 85 | /arg0 != NULL/ 86 | { 87 | printf( 88 | "✔ %*s %s has been caught\n", 89 | self->depth, "", 90 | copyinstr(arg0) 91 | ); 92 | } 93 | 94 | php*:::error 95 | { 96 | printf( 97 | "✖ %*s %s \t%s/%s:%03d\n", 98 | self->depth, "", 99 | copyinstr(arg0), 100 | dirname(copyinstr(arg1)), 101 | basename(copyinstr(arg1)), 102 | arg2 103 | ); 104 | } 105 | 106 | php*:::function-return 107 | { 108 | printf( 109 | "%6dms %*s %s%s%s()\n", 110 | (timestamp - self->time_last) / 1000, 111 | self->depth, "←", 112 | copyinstr(arg3), 113 | copyinstr(arg4), 114 | copyinstr(arg0) 115 | ); 116 | self->depth -= 2; 117 | } 118 | 119 | php*:::request-shutdown 120 | { 121 | printf("Request end"); 122 | self->up = 0; 123 | } 124 | 125 | END 126 | { 127 | exit(0); 128 | } 129 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | Hoa 3 |

4 | 5 | --- 6 | 7 |

8 | Build status 9 | Code coverage 10 | Packagist 11 | License 12 |

13 |

14 | Hoa is a modular, extensible and 15 | structured set of PHP libraries.
16 | Moreover, Hoa aims at being a bridge between industrial and research worlds. 17 |

18 | 19 | # Hoa\Bench 20 | 21 | [![Help on IRC](https://img.shields.io/badge/help-%23hoaproject-ff0066.svg)](https://webchat.freenode.net/?channels=#hoaproject) 22 | [![Help on Gitter](https://img.shields.io/badge/help-gitter-ff0066.svg)](https://gitter.im/hoaproject/central) 23 | [![Documentation](https://img.shields.io/badge/documentation-hack_book-ff0066.svg)](https://central.hoa-project.net/Documentation/Library/Bench) 24 | [![Board](https://img.shields.io/badge/organisation-board-ff0066.svg)](https://waffle.io/hoaproject/bench) 25 | 26 | This library allows to analyze performance of algorithms or programs by placing 27 | some “marks” in the code. Furthermore, this library provides some 28 | [DTrace](http://dtrace.org/guide/) programs. 29 | 30 | [Learn more](https://central.hoa-project.net/Documentation/Library/Bench). 31 | 32 | ## Installation 33 | 34 | With [Composer](https://getcomposer.org/), to include this library into 35 | your dependencies, you need to 36 | require [`hoa/bench`](https://packagist.org/packages/hoa/bench): 37 | 38 | ```sh 39 | $ composer require hoa/bench '~3.0' 40 | ``` 41 | 42 | For more installation procedures, please read [the Source 43 | page](https://hoa-project.net/Source.html). 44 | 45 | ## Testing 46 | 47 | Before running the test suites, the development dependencies must be installed: 48 | 49 | ```sh 50 | $ composer install 51 | ``` 52 | 53 | Then, to run all the test suites: 54 | 55 | ```sh 56 | $ vendor/bin/hoa test:run 57 | ``` 58 | 59 | For more information, please read the [contributor 60 | guide](https://hoa-project.net/Literature/Contributor/Guide.html). 61 | 62 | ## Quick usage 63 | 64 | We propose a quick overview of two usages: The library itself and one DTrace 65 | program. 66 | 67 | ### Benchmark 68 | 69 | All we have to do is to place different marks in the code. A mark can be 70 | started, paused, stopped and reset. The class `Hoa\Bench\Bench` proposes a quick 71 | statistic graph that could be helpful: 72 | 73 | ```php 74 | $bench = new Hoa\Bench\Bench(); 75 | 76 | // Start two marks: “one” and “two”. 77 | $bench->one->start(); 78 | $bench->two->start(); 79 | 80 | usleep(50000); 81 | 82 | // Stop the mark “two” and start the mark “three”. 83 | $bench->two->stop(); 84 | $bench->three->start(); 85 | 86 | usleep(25000); 87 | 88 | // Stop all marks. 89 | $bench->three->stop(); 90 | $bench->one->stop(); 91 | 92 | // Print statistics. 93 | echo $bench; 94 | 95 | /** 96 | * Will output: 97 | * __global__ |||||||||||||||||||||||||||||||||||||||||||||||||||| 77ms, 100.0% 98 | * one |||||||||||||||||||||||||||||||||||||||||||||||||||| 77ms, 99.8% 99 | * two |||||||||||||||||||||||||||||||||| 51ms, 65.9% 100 | * three |||||||||||||||||| 26ms, 33.9% 101 | */ 102 | ``` 103 | 104 | More operations are available, such as iterating over all marks, deleting a 105 | mark, filters marks etc. 106 | 107 | ### DTrace 108 | 109 | An interesting DTrace program is `hoa://Library/Bench/Dtrace/Execution.d` that 110 | shows the call trace, errors and exceptions during an execution. For example, if 111 | we consider the `Dtrace.php` file that contains the following code: 112 | 113 | ```php 114 | setId($id); 109 | 110 | return; 111 | } 112 | 113 | /** 114 | * Set the mark ID. 115 | * 116 | * @param string $id The mark ID. 117 | * @return string 118 | */ 119 | protected function setId($id) 120 | { 121 | $old = $this->_id; 122 | $this->_id = $id; 123 | 124 | return $old; 125 | } 126 | 127 | /** 128 | * Get the mark ID. 129 | * 130 | * @return string 131 | */ 132 | public function getId() 133 | { 134 | return $this->_id; 135 | } 136 | 137 | /** 138 | * Start the mark. 139 | * A mark can be started if it is in pause, stopped, or if it is the first start. 140 | * Else, an exception will be thrown. 141 | * 142 | * @return \Hoa\Bench\Mark 143 | * @throws \Hoa\Bench\Exception 144 | */ 145 | public function start() 146 | { 147 | if (true === $this->isStarted()) { 148 | if (false === $this->isPause()) { 149 | throw new Exception( 150 | 'Cannot start the %s mark, because it is started.', 151 | 0, 152 | $this->getId() 153 | ); 154 | } 155 | } 156 | 157 | if (true === $this->isPause()) { 158 | $this->pause += microtime(true) - $this->stop; 159 | } else { 160 | $this->reset(); 161 | $this->start = microtime(true); 162 | } 163 | 164 | $this->_started = true; 165 | $this->_pause = false; 166 | 167 | return $this; 168 | } 169 | 170 | /** 171 | * Stop the mark. 172 | * A mark can be stopped if it is in pause, or started. Else, an exception 173 | * will be thrown (or not, according to the $silent argument). 174 | * 175 | * @param bool $silent If set to true and if the mark is not started, 176 | * no exception will be thrown. 177 | * @return \Hoa\Bench\Mark 178 | * @throws \Hoa\Bench\Exception 179 | */ 180 | public function stop($silent = false) 181 | { 182 | if (false === $this->isStarted()) { 183 | if (false === $silent) { 184 | throw new Exception( 185 | 'Cannot stop the %s mark, because it is not started.', 186 | 1, 187 | $this->getId() 188 | ); 189 | } else { 190 | return $this; 191 | } 192 | } 193 | 194 | $this->stop = microtime(true); 195 | $this->_started = false; 196 | $this->_pause = false; 197 | 198 | return $this; 199 | } 200 | 201 | /** 202 | * Reset the mark. 203 | * 204 | * @return \Hoa\Bench\Mark 205 | */ 206 | public function reset() 207 | { 208 | $this->start = 0.0; 209 | $this->stop = 0.0; 210 | $this->pause = 0.0; 211 | $this->_started = false; 212 | $this->_pause = false; 213 | 214 | return $this; 215 | } 216 | 217 | /** 218 | * Pause the mark. 219 | * A mark can be in pause if it is started. Else, an exception will be 220 | * thrown (or not, according to the $silent argument). 221 | * 222 | * @param bool $silent If set to true and the mark is not started, 223 | * no exception will be throw. Idem if the mark 224 | * is in pause. 225 | * @return \Hoa\Bench\Mark 226 | * @throws \Hoa\Bench\Exception 227 | */ 228 | public function pause($silent = false) 229 | { 230 | if (false === $this->isStarted()) { 231 | if (false === $silent) { 232 | throw new Exception( 233 | 'Cannot stop the %s mark, because it is not started.', 234 | 2, 235 | $this->getId() 236 | ); 237 | } else { 238 | return $this; 239 | } 240 | } 241 | 242 | if (true === $this->isPause()) { 243 | if (false === $silent) { 244 | throw new Exception( 245 | 'The %s mark is still in pause. Cannot pause it again.', 246 | 3, 247 | $this->getId() 248 | ); 249 | } else { 250 | return $this; 251 | } 252 | } 253 | 254 | $this->stop = microtime(true); 255 | $this->_pause = true; 256 | 257 | return $this; 258 | } 259 | 260 | /** 261 | * Get the difference between $stop and $start. 262 | * If the mark is still running (it contains the pause case), the current 263 | * microtime will be used in stay of $stop. 264 | * 265 | * @return float 266 | */ 267 | public function diff() 268 | { 269 | if (false === $this->isStarted() || true === $this->isPause()) { 270 | return $this->stop - $this->start - $this->pause; 271 | } 272 | 273 | return microtime(true) - $this->start - $this->pause; 274 | } 275 | 276 | /** 277 | * Compare to mark. 278 | * $a op $b : return -1 if $a < $b, 0 if $a == $b, and 1 if $a > $b. We 279 | * compare the difference between $start and $stop, i.e. we call the diff() 280 | * method. 281 | * 282 | * @param \Hoa\Bench\Mark $mark The mark to compare to. 283 | * @return int 284 | */ 285 | public function compareTo(Mark $mark) 286 | { 287 | $a = $this->diff(); 288 | $b = $mark->diff(); 289 | 290 | if ($a < $b) { 291 | return -1; 292 | } elseif ($a == $b) { 293 | return 0; 294 | } else { 295 | return 1; 296 | } 297 | } 298 | 299 | /** 300 | * Check if the mark is running. 301 | * 302 | * @deprecated use `isStarted` instead 303 | * @return bool 304 | */ 305 | public function isRunning() 306 | { 307 | return $this->isStarted(); 308 | } 309 | 310 | /** 311 | * Check if the mark is started. 312 | * 313 | * @return bool 314 | */ 315 | public function isStarted() 316 | { 317 | return $this->_started; 318 | } 319 | 320 | /** 321 | * Check if the mark is in pause. 322 | * 323 | * @return bool 324 | */ 325 | public function isPause() 326 | { 327 | return $this->_pause; 328 | } 329 | 330 | /** 331 | * Alias of the diff() method, but return a string, not a float. 332 | * 333 | * @return string 334 | */ 335 | public function __toString() 336 | { 337 | return (string) $this->diff(); 338 | } 339 | } 340 | -------------------------------------------------------------------------------- /Bench.php: -------------------------------------------------------------------------------- 1 | start(); 99 | } 100 | 101 | if (true === $this->markExists($id)) { 102 | return self::$_mark[$id]; 103 | } 104 | 105 | $mark = new Mark($id); 106 | self::$_mark[$id] = $mark; 107 | 108 | return $mark; 109 | } 110 | 111 | /** 112 | * Check if a mark exists. 113 | * Alias of the protected markExist method. 114 | * 115 | * @param string $id The mark ID. 116 | * @return bool 117 | */ 118 | public function __isset($id) 119 | { 120 | return $this->markExists($id); 121 | } 122 | 123 | /** 124 | * Destroy a mark. 125 | * 126 | * @param string $id The mark ID. 127 | * @return void 128 | */ 129 | public function __unset($id) 130 | { 131 | unset(self::$_mark[$id]); 132 | 133 | return; 134 | } 135 | 136 | /** 137 | * Destroy all mark. 138 | * 139 | * @return void 140 | */ 141 | public function unsetAll() 142 | { 143 | self::$_mark = []; 144 | 145 | return; 146 | } 147 | 148 | /** 149 | * Check if a mark already exists. 150 | * 151 | * @param string $id The mark ID. 152 | * @return bool 153 | */ 154 | protected function markExists($id) 155 | { 156 | return isset(self::$_mark[$id]); 157 | } 158 | 159 | /** 160 | * Get the current mark for the iterator. 161 | * 162 | * @return \Hoa\Bench\Mark 163 | */ 164 | public function current() 165 | { 166 | return current(self::$_mark); 167 | } 168 | 169 | /** 170 | * Get the current mark ID for the iterator. 171 | * 172 | * @return string 173 | */ 174 | public function key() 175 | { 176 | return key(self::$_mark); 177 | } 178 | 179 | /** 180 | * Advance the internal mark collection pointer, and return the current 181 | * mark. 182 | * 183 | * @return \Hoa\Bench\Mark 184 | */ 185 | public function next() 186 | { 187 | return next(self::$_mark); 188 | } 189 | 190 | /** 191 | * Rewind the internal mark collection pointer, and return the first mark. 192 | * 193 | * @return \Hoa\Bench\Mark 194 | */ 195 | public function rewind() 196 | { 197 | return reset(self::$_mark); 198 | } 199 | 200 | /** 201 | * Check if there is a current element after calls the rewind or the next 202 | * methods. 203 | * 204 | * @return bool 205 | */ 206 | public function valid() 207 | { 208 | if (empty(self::$_mark)) { 209 | return false; 210 | } 211 | 212 | $key = key(self::$_mark); 213 | $return = (next(self::$_mark) ? true : false); 214 | prev(self::$_mark); 215 | 216 | if (false === $return) { 217 | end(self::$_mark); 218 | 219 | if ($key === key(self::$_mark)) { 220 | $return = true; 221 | } 222 | } 223 | 224 | return $return; 225 | } 226 | 227 | /** 228 | * Pause all marks and return only previously started tasks. 229 | * 230 | * @return array 231 | */ 232 | public function pause() 233 | { 234 | $startedMarks = []; 235 | 236 | foreach ($this as $mark) { 237 | if (true === $mark->isStarted() && false === $mark->isPause()) { 238 | $startedMarks[] = $mark; 239 | } 240 | } 241 | 242 | foreach ($startedMarks as $mark) { 243 | $mark->pause(); 244 | } 245 | 246 | return $startedMarks; 247 | } 248 | 249 | /** 250 | * Resume a specific set of marks. 251 | * 252 | * @param array $marks The marks to resume. 253 | * @return void 254 | */ 255 | public static function resume(array $marks) 256 | { 257 | foreach ($marks as $mark) { 258 | $mark->start(); 259 | } 260 | 261 | return; 262 | } 263 | 264 | /** 265 | * Add a filter. 266 | * Used in the self::getStatistic() method, no in iterator. 267 | * A filter is a callable that will receive 3 values about a mark: ID, time 268 | * result, and time pourcent. The callable must return a boolean. 269 | * 270 | * @param mixed $callable Callable. 271 | * @return void 272 | */ 273 | public function filter($callable) 274 | { 275 | $this->_filters[] = xcallable($callable); 276 | 277 | return $this; 278 | } 279 | 280 | /** 281 | * Return all filters. 282 | * 283 | * @return array 284 | */ 285 | public function getFilters() 286 | { 287 | return $this->_filters; 288 | } 289 | 290 | /** 291 | * Get statistic. 292 | * Return an associative array : id => sub-array. The sub-array contains the 293 | * result time in second (given by the constant self::STAT_RESULT), and the 294 | * result pourcent (given by the constant self::START_POURCENT). 295 | * 296 | * @param bool $considerFilters Whether we should consider filters or 297 | * not. 298 | * @return array 299 | */ 300 | public function getStatistic($considerFilters = true) 301 | { 302 | if (empty(self::$_mark)) { 303 | return []; 304 | } 305 | 306 | $startedMarks = $this->pause(); 307 | 308 | $max = $this->getLongest()->diff(); 309 | $out = []; 310 | 311 | foreach ($this as $id => $mark) { 312 | $result = $mark->diff(); 313 | $pourcent = ($result * 100) / $max; 314 | 315 | if (true === $considerFilters) { 316 | foreach ($this->getFilters() as $filter) { 317 | if (true !== $filter($id, $result, $pourcent)) { 318 | continue 2; 319 | } 320 | } 321 | } 322 | 323 | $out[$id] = [ 324 | self::STAT_RESULT => $result, 325 | self::STAT_POURCENT => $pourcent 326 | ]; 327 | } 328 | 329 | static::resume($startedMarks); 330 | 331 | return $out; 332 | } 333 | 334 | /** 335 | * Get the maximum, i.e. the longest mark in time. 336 | * 337 | * @return \Hoa\Bench\Mark 338 | */ 339 | public function getLongest() 340 | { 341 | $max = 0; 342 | $outMark = null; 343 | 344 | foreach ($this as $id => $mark) { 345 | if ($mark->diff() > $max) { 346 | $outMark = $mark; 347 | $max = $mark->diff(); 348 | } 349 | } 350 | 351 | return $outMark; 352 | } 353 | 354 | /** 355 | * Draw statistic in text mode. 356 | * 357 | * @param int $width The graphic width. 358 | * @return string 359 | * @throws \Hoa\Bench\Exception 360 | */ 361 | public function drawStatistic($width = 80) 362 | { 363 | if (empty(self::$_mark)) { 364 | return ''; 365 | } 366 | 367 | if ($width < 1) { 368 | throw new Exception( 369 | 'The graphic width must be positive, given %d.', 370 | 0, 371 | $width 372 | ); 373 | } 374 | 375 | $out = null; 376 | $stats = $this->getStatistic(); 377 | $margin = 0; 378 | 379 | foreach ($stats as $id => $foo) { 380 | strlen($id) > $margin and $margin = strlen($id); 381 | } 382 | 383 | $width = $width - $margin - 18; 384 | $format = '%-' . $margin . 's %-' . $width . 's %5dms, %5.1f%%' . "\n"; 385 | 386 | foreach ($stats as $id => $stat) { 387 | $out .= sprintf( 388 | $format, 389 | $id, 390 | str_repeat( 391 | '|', 392 | round(($stat[self::STAT_POURCENT] * $width) / 100) 393 | ), 394 | round(1000 * $stat[self::STAT_RESULT]), 395 | round($stat[self::STAT_POURCENT], 3) 396 | ); 397 | } 398 | 399 | return $out; 400 | } 401 | 402 | /** 403 | * Count the number of mark. 404 | * 405 | * @return int 406 | */ 407 | public function count() 408 | { 409 | return count(self::$_mark); 410 | } 411 | 412 | /** 413 | * Alias of drawStatistic() method. 414 | * 415 | * @return string 416 | */ 417 | public function __toString() 418 | { 419 | return $this->drawStatistic(); 420 | } 421 | } 422 | 423 | /** 424 | * Flex entity. 425 | */ 426 | Consistency::flexEntity('Hoa\Bench\Bench'); 427 | -------------------------------------------------------------------------------- /Documentation/En/Index.xyl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

Analyzing performances of algorithms or programs is a very 7 | common task, to which Hoa\Bench answers.

8 | 9 |

Table of contents

10 | 11 | 12 | 13 |

Introduction

14 | 15 |

Hoa\Bench provides two ways to analyze an 16 | application: with PHP itself, or with 17 | DTrace, thanks to program written in 18 | the 19 | hoa://Library/Bench/Dtrace/ 20 | directory.

21 | 22 |

Place marks

23 | 24 |

The Hoa\Bench library has a very intuitive use: we only need 25 | to place mark in the program and that's all. Each time a mark 26 | is executed, it collects informations we will exploit.

27 |

There is two classes to know: Hoa\Bench\Bench which allows to 28 | group a set of marks, and Hoa\Bench\Mark which represents a 29 | single mark.

30 | 31 |

Quick usage

32 | 33 |

The most common usage of Hoa\Bench\Bench is to place some 34 | marks and print their statistics. Thus:

35 |
$bench = new Hoa\Bench\Bench();
 36 | 
 37 | // Start two marks: “one” and “two”.
 38 | $bench->one->start();
 39 | $bench->two->start();
 40 | 
 41 | usleep(50000);
 42 | 
 43 | // Stop the mark “two” and start the mark “three”.
 44 | $bench->two->stop();
 45 | $bench->three->start();
 46 | 
 47 | usleep(25000);
 48 | 
 49 | // Stop all marks.
 50 | $bench->three->stop();
 51 | $bench->one->stop();
 52 | 
 53 | // Print statistics.
 54 | echo $bench;
 55 | 
 56 | /**
 57 |  * Will output:
 58 |  *     __global__  ||||||||||||||||||||||||||||||||||||||||||||||||||||    77ms, 100.0%
 59 |  *     one         ||||||||||||||||||||||||||||||||||||||||||||||||||||    77ms,  99.8%
 60 |  *     two         ||||||||||||||||||||||||||||||||||                      51ms,  65.9%
 61 |  *     three       ||||||||||||||||||                                      26ms,  33.9%
 62 |  */
63 |

Note, to declare a mark we simply call an attribute on the class 64 | Hoa\Bench\Bench, which will return a mark (or create if it does 65 | not exist). Once we have a mark, we can start it by calling the 66 | Hoa\Bench\Mark::start method. To stop it, we call the 67 | Hoa\Bench\Mark::stop method. Finally, printing the 68 | Hoa\Bench\Bench object will print the statistics of marks: their 69 | name, a little graph, their execution time and a percentage according to all 70 | marks.

71 | 72 |

Manipulate marks

73 | 74 |

Marks can be in three different states:

75 |
    76 |
  • started, thanks to the Hoa\Bench\Mark::start method,
  • 77 |
  • paused, thanks to the Hoa\Bench\Mark::pause method, only if 78 | it was previously started,
  • 79 |
  • stopped, thanks to the Hoa\Bench\Bench::stop method, only 80 | if it was previously started.
  • 81 |
82 |

If we start and stop a mark, and then start it again, it will be reset. 83 | Notwithstanding, it exists the Hoa\Bench\Mark::reset method that 84 | will have the same behavior while forcing the reset whatever the state of the 85 | mark. Thus:

86 |
$bench = new Hoa\Bench\Bench();
 87 | $mark  = $bench->aMark;
 88 | $mark->start(); // =  0ms
 89 | usleep(10000);
 90 | $mark->pause(); // = 10ms
 91 | usleep(10000);
 92 | $mark->start(); // = 10ms
 93 | usleep(10000);
 94 | $mark->stop();  // = 20ms
 95 | echo $bench;
 96 | 
 97 | /**
 98 |  * Will output:
 99 |  *     __global__  ||||||||||||||||||||||||||||||||||||||||||||||||||||    33ms, 100.0%
100 |  *     aMark       ||||||||||||||||||||||||||||||||||                      22ms,  66.3%
101 |  */
102 | 
103 | $mark->reset();
104 | $mark->start(); // =  0ms
105 | usleep(10000);
106 | $mark->stop();  // = 10ms
107 | echo $bench;
108 | 
109 | /**
110 |  * Will output:
111 |  *     __global__  ||||||||||||||||||||||||||||||||||||||||||||||||||||    45ms, 100.0%
112 |  *     aMark       |||||||||||||                                           11ms,  24.8%
113 |  */
114 |

We can also pause all marks and get which marks were started with the 115 | Hoa\Bench\Bench::pause method. The 116 | Hoa\Bench\Bench::resume method allows to restart a set of marks.

117 |

Other operations are available, such as:

118 |
    119 |
  • get the execution time, thanks to the Hoa\Bench\Mark::diff 120 | method,
  • 121 |
  • compare the execution time between two marks, thanks to the 122 | Hoa\Bench\Mark::compareTo method,
  • 123 |
  • check whether a mark is started, thanks to the 124 | Hoa\Bench\Mark::isStarted method,
  • 125 |
  • check whether a mark is paused, thanks to the 126 | Hoa\Bench\Mark::isPause method.
  • 127 |
128 |

We said that an object Hoa\Bench\Bench allows to group several 129 | marks together. We have seen how to declare a mark, but we can also check if a 130 | mark exists or remove it with PHP native constructions:

131 |
$bench = new Hoa\Bench\Bench();
132 | 
133 | // Create a mark.
134 | $bench->foo;
135 | 
136 | // Is “foo” exists?
137 | if (isset($bench->foo)) {
138 |     // Then, delete it, for example.
139 |     unset($bench->foo);
140 | }
141 |

Obviously, we can remove all marks thanks to the 142 | Hoa\Bench\Bench::unsetAll method. In addition, the 143 | Hoa\Bench\Bench class behaves like an iterator on all marks, 144 | thus:

145 |
foreach ($bench as $id => $mark) {
146 |     echo $id, ' => ', ($mark->isStarted() ? 'started' : 'stopped'), "\n";
147 | }
148 | 
149 | /**
150 |  * Could output:
151 |  *     one => stopped
152 |  *     two => started
153 |  *     three => started
154 |  */
155 |

We can also count the number of marks:

156 |
var_dump(count($bench));
157 | 
158 | /**
159 |  * Will output:
160 |  *     int(3)
161 |  */
162 |

Finally, we can get statistics thanks to the 163 | Hoa\Bench\Bench::getStatistic or 164 | Hoa\Bench\Bench::getLongest methods, respectively to get 165 | statistics in an array corresponding to the output previously seen and to get 166 | the longest mark in term of execution time.

167 | 168 |

Filter marks

169 | 170 |

The Hoa\Bench\Bench class allows to filter 171 | marks during the computation of results (example with the 172 | Hoa\Bench\Bench::getStatistic or 173 | Hoa\Bench\Bench::__toString to draw the graph). A filter is a 174 | callable (for example a function) which receives three arguments: the 175 | identifier of the mark, its execution time 176 | in milliseconds and a percentage (according to the longest mark). A filter is 177 | also a predicate, it means that its result is a boolean: true to 178 | accept the mark, false to reject it.

179 |

For example, if we have a lot of marks and we would like to filter the 180 | “noise”, it means marks with a low execution time (let say, lower than 5%), we 181 | can use the following filter:

182 |
$bench->filter(function ($id, $time, $pourcent) {
183 |     return $pourcent > 5;
184 | });
185 |

If we name our marks with a specific classification, we can filter 186 | according to the name of identifiers. For example, we would like all the marks 187 | that contain the sub-word _critical:

188 |
$bench->filter(function ($id, $time, $pourcent ) {
189 |     return 0 !== preg_match('#_critical#', $id);
190 | });
191 |

Obviously, we can add many filters.

192 |

Notice that the Hoa\Bench\Bench::getStatistic method allows to 193 | not consider filters thanks to its sole argument 194 | $considerFilters, which is set to true by default. 195 | To get all the result, we have to set it to false.

196 | 197 |

DTrace

198 | 199 |

The Hoa\Bench library also provides DTrace programs in the 200 | hoa://Library/Bench/Dtrace/ 201 | directory. DTrace allows to generate traces in order to 202 | detect issues in realtime in a production environment. It is integrated into 203 | the kernel level and applicative level, which ensures excellent 204 | performances. DTrace uses 205 | the D language. We also recommend 206 | reading the DTraceBook. Attention, DTrace 207 | is not available on all platforms.

208 |

PHP is able to use DTrace if it has been compiled with the 209 | --enable-dtrace option (see 210 | DTraces probes for PHP).

211 |

We will start by analyzing the execution of the following 212 | Dtrace.php file:

213 |
&lt;?php
214 | 
215 | function f() { g(); h(); }
216 | function g() { h();      }
217 | function h() {           }
218 | 
219 | f();
220 |

This file is very simple but it will be our test to execute the first 221 | DTrace program, namely 222 | hoa://Library/Bench/Dtrace/Execution.d. 223 | Attention, we have to execute DTrace as a super-user; thus:

224 |
$ exed=`hoa protocol:resolve hoa://Library/Bench/Dtrace/Execution.d`
225 | $ sudo $exed -c "php Dtrace.php"
226 | Request start
227 |      2ms ➜ f()        …/Dtrace.php:007
228 |     37ms   ➜ g()      …/Dtrace.php:003
229 |     26ms     ➜ h()    …/Dtrace.php:004
230 |     28ms     ← h()
231 |     37ms   ← g()
232 |     44ms   ➜ h()      …/Dtrace.php:003
233 |     25ms   ← h()
234 |     30ms ← f()
235 | Request end
236 |

Let's see what happens if we add errors and exceptions in 237 | Dtrace.php:

238 |
&lt;?php
239 | 
240 | function f() { ++$i; g(); h();             }
241 | function g() { h();                        }
242 | function h() { throw new Exception('foo'); }
243 | 
244 | try                   { f(); }
245 | catch (\Exception $e) {      }
246 |   
247 |

And now, reexecute the DTrace program:

248 |
$ sudo $exed -c "php Dtrace.php"
249 | Request start
250 |      2ms ➜ f()                              …/Dtrace.php:007
251 | ✖          Undefined variable: i            …/Dtrace.php:003
252 |     37ms   ➜ g()                            …/Dtrace.php:003
253 |     26ms     ➜ h()                          …/Dtrace.php:004
254 | ●              Exception has been thrown
255 |     28ms     ← h()
256 |     37ms   ← g()
257 |     44ms   ➜ h()                            …/Dtrace.php:003
258 |     25ms   ← h()
259 |     30ms ← f()
260 | ✔          Exception has been caught
261 | Request end
262 |

The second DTrace program is 263 | hoa://Library/Bench/Dtrace/Stat.d 264 | and allows to collect some statistics on an execution such as the list of all 265 | called functions and methods with their respective invocations number and the 266 | distribution of the execution time. For example, if we try with our first 267 | version of Dtrace.php, we will have:

268 |
$ exed=`hoa protocol:resolve hoa://Library/Bench/Dtrace/Stat.d`
269 | $ sudo $exed -c "php Dtrace.php"
270 | Count calls:
271 |   • h                                                                        2
272 |   • g                                                                        1
273 |   • f                                                                        1
274 | 
275 | Execution distribution (values are in nanoseconds):
276 | 
277 |   f
278 |            value  ------------- Distribution ------------- count
279 |              256 |                                         0
280 |              512 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 1
281 |             1024 |                                         0
282 | 
283 |   h
284 |            value  ------------- Distribution ------------- count
285 |                4 |                                         0
286 |                8 |@@@@@@@@@@@@@@@@@@@@                     1
287 |               16 |                                         0
288 |               32 |                                         0
289 |               64 |                                         0
290 |              128 |                                         0
291 |              256 |@@@@@@@@@@@@@@@@@@@@                     1
292 |              512 |                                         0
293 | 
294 |   g
295 |            value  ------------- Distribution ------------- count
296 |              128 |                                         0
297 |              256 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 1
298 |              512 |                                         0
299 | 
300 | Total execution time: 108ms
301 |

This DTrace program is very useful to know what are the most invoked functions or 302 | methods in your program and to also know their average execution time. This 303 | program is a true and quick answer to the problem of detecting critical 304 | sections regarding performances!

305 | 306 |

Conclusion

307 | 308 |

Hoa\Bench allows to analyze the execution of PHP programs 309 | thanks to PHP itself or DTrace. These tools are useful to improve 310 | performances by allowing to detect bottlenecks, too numerous 311 | calls, slow executions etc., in order to fix them. In short, all you need to 312 | get quality.

313 | 314 |
315 |
316 | -------------------------------------------------------------------------------- /Documentation/Fr/Index.xyl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |

Analyser les performances de ses 7 | algorithmes ou de ses programmes est une tâche courante, à laquelle 8 | Hoa\Bench permet de répondre.

9 | 10 |

Table des matières

11 | 12 | 13 | 14 |

Introduction

15 | 16 |

Hoa\Bench propose deux manières d'analyser 17 | son application : avec PHP lui-même, ou alors avec 18 | DTrace, grâce à des programmes présents 19 | dans le dossier 20 | hoa://Library/Bench/Dtrace/.

21 | 22 |

Placer des marques

23 | 24 |

La bibliothèque Hoa\Bench fonctionne de manière très 25 | intuitive : il suffit de placer des marques dans notre 26 | programme et c'est tout. À chaque fois qu'une marque est exécutée, elle 27 | récolte des informations que nous pouvons exploiter par la suite.

28 |

Il y a deux classes à considérer : Hoa\Bench\Bench qui permet 29 | de regroupe un ensemble de marques, et Hoa\Bench\Mark qui 30 | représente une seule marque.

31 | 32 |

Utilisation rapide

33 | 34 |

L'utilisation la plus courante de Hoa\Bench\Bench sera la 35 | suivante : placer quelques marques et afficher leurs statistiques. Ainsi :

36 |
$bench = new Hoa\Bench\Bench();
 37 | 
 38 | // Start two marks: “one” and “two”.
 39 | $bench->one->start();
 40 | $bench->two->start();
 41 | 
 42 | usleep(50000);
 43 | 
 44 | // Stop the mark “two” and start the mark “three”.
 45 | $bench->two->stop();
 46 | $bench->three->start();
 47 | 
 48 | usleep(25000);
 49 | 
 50 | // Stop all marks.
 51 | $bench->three->stop();
 52 | $bench->one->stop();
 53 | 
 54 | // Print statistics.
 55 | echo $bench;
 56 | 
 57 | /**
 58 |  * Will output:
 59 |  *     __global__  ||||||||||||||||||||||||||||||||||||||||||||||||||||    77ms, 100.0%
 60 |  *     one         ||||||||||||||||||||||||||||||||||||||||||||||||||||    77ms,  99.8%
 61 |  *     two         ||||||||||||||||||||||||||||||||||                      51ms,  65.9%
 62 |  *     three       ||||||||||||||||||                                      26ms,  33.9%
 63 |  */
64 |

Nous remarquons que pour déclarer une marque, nous faisons simplement appel 65 | à un attribut sur la classe Hoa\Bench\Bench ce qui va retourner 66 | une marque (ou la créer avant si elle n'existe pas). Une fois que nous avons 67 | notre marque, nous pouvons la démarrer avec la méthode 68 | Hoa\Bench\Mark::start. Pour arrêter les marques, nous appelons la 69 | méthode Hoa\Bench\Mark::stop. Enfin, afficher l'objet 70 | Hoa\Bench\Bench va afficher les statistiques des marques : leur 71 | nom, un petit graphique, leur temps d'exécution et le pourcentage de ce temps 72 | par rapport aux autres marques.

73 | 74 |

Bien manipuler les 75 | marques

76 | 77 |

Les marques peuvent être dans trois états différents :

78 |
    79 |
  • en marche, grâce à la méthode Hoa\Bench\Mark::start ;
  • 80 |
  • en pause, grâce à la méthode Hoa\Bench\Mark::pause, 81 | uniquement si elle était en marche précédemment ;
  • 82 |
  • à l'arrêt, grâce à la méthode Hoa\Bench\Mark::stop, 83 | uniquement si elle est en marche.
  • 84 |
85 |

Si nous démarrons puis arrêtons une marque, pour ensuite la démarrer à 86 | nouveau, elle sera remise à zéro. Toutefois, il existe la méthode 87 | Hoa\Bench\Mark::reset qui aura le même effet mais en forçant une 88 | remise à zéro quelque soit l'état de la marque. Ainsi :

89 |
$bench = new Hoa\Bench\Bench();
 90 | $mark  = $bench->aMark;
 91 | $mark->start(); // =  0ms
 92 | usleep(10000);
 93 | $mark->pause(); // = 10ms
 94 | usleep(10000);
 95 | $mark->start(); // = 10ms
 96 | usleep(10000);
 97 | $mark->stop();  // = 20ms
 98 | echo $bench;
 99 | 
100 | /**
101 |  * Will output:
102 |  *     __global__  ||||||||||||||||||||||||||||||||||||||||||||||||||||    33ms, 100.0%
103 |  *     aMark       ||||||||||||||||||||||||||||||||||                      22ms,  66.3%
104 |  */
105 | 
106 | $mark->reset();
107 | $mark->start(); // =  0ms
108 | usleep(10000);
109 | $mark->stop();  // = 10ms
110 | echo $bench;
111 | 
112 | /**
113 |  * Will output:
114 |  *     __global__  ||||||||||||||||||||||||||||||||||||||||||||||||||||    45ms, 100.0%
115 |  *     aMark       |||||||||||||                                           11ms,  24.8%
116 |  */
117 |

Nous pouvons également mettre en pause toutes les marques et récupérer 118 | quelles marques étaient en cours d'exécution à ce moment grâce à la méthode 119 | Hoa\Bench\Bench::pause. La méthode 120 | Hoa\Bench\Bench::resume permet de redémarrer un ensemble de 121 | marques.

122 |

D'autres opérations sont possibles, comme :

123 |
    124 |
  • obtenir la durée d'exécution, grâce à la méthode 125 | Hoa\Bench\Mark::diff ;
  • 126 |
  • comparer la durée d'exécution entre deux marques, grâce à la méthode 127 | Hoa\Bench\Mark::compareTo ;
  • 128 |
  • savoir si une marque est en cours d'exécution, grâce à la méthode 129 | Hoa\Bench\Mark::isStarted ;
  • 130 |
  • savoir si une marque est en pause, grâce à la méthode 131 | Hoa\Bench\Mark::isPause.
  • 132 |
133 |

Nous avons précisé qu'un objet Hoa\Bench\Bench permettait de 134 | regrouper plusieurs marques ensemble. Nous avons vu comment déclarer une 135 | marque, mais nous avons d'autres opérations comme savoir si une marque existe 136 | ou alors la supprimer avec les constructions de PHP correspondantes :

137 |
$bench = new Hoa\Bench\Bench();
138 | 
139 | // Create a mark.
140 | $bench->foo;
141 | 
142 | // Is “foo” exists?
143 | if (isset($bench->foo)) {
144 |     // Then, delete it, for example.
145 |     unset($bench->foo);
146 | }
147 |

Bien entendu, nous pouvons supprimer toutes les marques grâce à la méthode 148 | Hoa\Bench\Bench::unsetAll. De plus, la classe 149 | Hoa\Bench\Bench se comporte comme un itérateur sur toutes les 150 | marques, ainsi :

151 |
foreach ($bench as $id => $mark) {
152 |     echo $id, ' => ', ($mark->isStarted() ? 'started' : 'stopped'), "\n";
153 | }
154 | 
155 | /**
156 |  * Could output:
157 |  *     one => stopped
158 |  *     two => started
159 |  *     three => started
160 |  */
161 |

Nous pouvons également compter le nombre de marques de la manière 162 | suivante :

163 |
var_dump(count($bench));
164 | 
165 | /**
166 |  * Will output:
167 |  *     int(3)
168 |  */
169 |

Enfin, nous pouvons obtenir des statistiques grâce aux méthodes 170 | Hoa\Bench\Bench::getStatistic et 171 | Hoa\Bench\Bench::getLongest, respectivement pour obtenir les 172 | statistiques sous forme de tableau correspondant au graphique vu précédemment 173 | et pour obtenir la marque qui a la plus longue exécution.

174 | 175 |

Filtrer les marques

176 | 177 |

La classe Hoa\Bench\Bench permet de filtrer 178 | les marques lors de calculs de résultats (par example avec la méthode 179 | Hoa\Bench\Bench::getStatistic ou 180 | Hoa\Bench\Bench::__toString pour dessiner le graphique). Un 181 | filtre est un callable (par exemple une fonction) qui 182 | reçoit trois arguments : l'identifiant de la marque, sa 183 | durée d'exécution en millisecondes et en pourcentage (par 184 | rapport à la marque la plus longue). Un filtre est également un prédicat, 185 | c'est à dire que son résultat est un booléen : true pour accepter 186 | la marque, false pour la rejeter.

187 |

Par exemple, si nous avons beaucoup de marques et que nous voulons filtrer 188 | le « bruit », c'est à dire les marques avec des petits temps d'exécutions 189 | (disons inférieur à 5%), nous pouvons utiliser le filtre suivant :

190 |
$bench->filter(function ($id, $time, $pourcent) {
191 |     return $pourcent > 5;
192 | });
193 |

Si nous nommons nos marques avec une nomenclature particulière, nous 194 | pouvons filtrer à partir de leurs identifiants. Par exemple, nous voulons 195 | toutes les marques comportant le sous-mot _critical :

196 |
$bench->filter(function ($id, $time, $pourcent) {
197 |     return 0 !== preg_match('#_critical#', $id);
198 | });
199 |

Nous pouvons bien sûr ajouter plusieurs filtres.

200 |

Notons que la méthode Hoa\Bench\Bench::getStatistic permet de 201 | ne pas considérer les filtres grâce à son seul argument 202 | $considerFilters, par défaut à true. Il suffit de le 203 | passer à false pour avoir tous les résultats.

204 | 205 |

DTrace

206 | 207 |

La bibliothèque Hoa\Bench fournit également des programmes 208 | DTrace dans le dossier 209 | hoa://Library/Bench/Dtrace/. DTrace 210 | permet de générer des traces afin de détecter des problèmes 211 | en temps réel dans un environnement de production. Il est intégré au niveau du 212 | noyau et au niveau applicatif, ce qui lui assure d'excellentes 213 | performances. DTrace utilise 214 | le langage D. Nous conseillons 215 | également la lecture de DTraceBook. 216 | Attention, DTrace n'est pas disponible sur toutes les plateformes.

217 |

PHP peut utiliser DTrace s'il a été compilé avec l'option 218 | --enable-dtrace (voir 219 | DTraces probes for 220 | PHP).

221 |

Nous allons commencer par analyser l'exécution du fichier 222 | Dtrace.php suivant :

223 |
&lt;?php
224 | 
225 | function f() { g(); h(); }
226 | function g() { h();      }
227 | function h() {           }
228 | 
229 | f();
230 |

Ce fichier est très simple mais il va nous servir de test pour exécuter le 231 | premier programme DTrace, à savoir 232 | hoa://Library/Bench/Dtrace/Execution.d. 233 | Attention, il faut exécuter DTrace en étant super-utilisateur ; ainsi :

234 |
$ exed=`hoa protocol:resolve hoa://Library/Bench/Dtrace/Execution.d`
235 | $ sudo $exed -c "php Dtrace.php"
236 | Request start
237 |      2ms ➜ f()        …/Dtrace.php:007
238 |     37ms   ➜ g()      …/Dtrace.php:003
239 |     26ms     ➜ h()    …/Dtrace.php:004
240 |     28ms     ← h()
241 |     37ms   ← g()
242 |     44ms   ➜ h()      …/Dtrace.php:003
243 |     25ms   ← h()
244 |     30ms ← f()
245 | Request end
246 |

Voyons si nous ajoutons des erreurs et des exceptions dans notre fichier 247 | Dtrace.php :

248 |
&lt;?php
249 | 
250 | function f() { ++$i; g(); h();             }
251 | function g() { h();                        }
252 | function h() { throw new Exception('foo'); }
253 | 
254 | try                   { f(); }
255 | catch (\Exception $e) {      }
256 |   
257 |

Et réexécutons notre programme DTrace :

258 |
$ sudo $exed -c "php Dtrace.php"
259 | Request start
260 |      2ms ➜ f()                              …/Dtrace.php:007
261 | ✖          Undefined variable: i            …/Dtrace.php:003
262 |     37ms   ➜ g()                            …/Dtrace.php:003
263 |     26ms     ➜ h()                          …/Dtrace.php:004
264 | ●              Exception has been thrown
265 |     28ms     ← h()
266 |     37ms   ← g()
267 |     44ms   ➜ h()                            …/Dtrace.php:003
268 |     25ms   ← h()
269 |     30ms ← f()
270 | ✔          Exception has been caught
271 | Request end
272 |

Le second programme DTrace est 273 | hoa://Library/Bench/Dtrace/Stat.d 274 | et permet de collecter quelques statistiques sur l'exécution comme la liste de 275 | toutes les fonctions et méthodes appelées avec leur nombre d'appels et la 276 | distribution du temps d'exécution. Par exemple, si nous essayons sur notre 277 | première version de Dtrace.php, nous allons avoir :

278 |
$ exed=`hoa protocol:resolve hoa://Library/Bench/Dtrace/Stat.d`
279 | $ sudo $exed -c "php Dtrace.php"
280 | Count calls:
281 |   • h                                                                        2
282 |   • g                                                                        1
283 |   • f                                                                        1
284 | 
285 | Execution distribution (values are in nanoseconds):
286 | 
287 |   f
288 |            value  ------------- Distribution ------------- count
289 |              256 |                                         0
290 |              512 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 1
291 |             1024 |                                         0
292 | 
293 |   h
294 |            value  ------------- Distribution ------------- count
295 |                4 |                                         0
296 |                8 |@@@@@@@@@@@@@@@@@@@@                     1
297 |               16 |                                         0
298 |               32 |                                         0
299 |               64 |                                         0
300 |              128 |                                         0
301 |              256 |@@@@@@@@@@@@@@@@@@@@                     1
302 |              512 |                                         0
303 | 
304 |   g
305 |            value  ------------- Distribution ------------- count
306 |              128 |                                         0
307 |              256 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 1
308 |              512 |                                         0
309 | 
310 | Total execution time: 108ms
311 |

Ce programme DTrace est très utile pour connaître quelles sont les 312 | fonctions ou méthodes les plus appelées dans votre programme et de connaître 313 | également leur temps d'exécution moyen. Autant dire que pour détecter les 314 | sections critiques d'un point de vue performance, ce programme apporte une 315 | véritable réponse et rapidement !

316 | 317 |

Conclusion

318 | 319 |

Hoa\Bench permet d'analyser l'exécution de programmes PHP à 320 | l'aide de PHP lui-même ou de DTrace. Ces outils sont utiles pour améliorer les 321 | performances en permettant de détecter des goulots 322 | d'étranglements, des appels trop nombreux, des ralentissements etc., afin de 323 | les corriger. Bref, tout ce qu'il faut pour obtenir des programmes de 324 | qualité.

325 | 326 |
327 |
328 | --------------------------------------------------------------------------------