PageRenderTime 49ms CodeModel.GetById 21ms RepoModel.GetById 1ms app.codeStats 0ms

/test/subjects/benchmarks/php-benchmarks/benchcli/bench.php

http://phc.googlecode.com/
PHP | 432 lines | 236 code | 38 blank | 158 comment | 32 complexity | daed078c81cf8886a54e49d1aff18dbb MD5 | raw file
Possible License(s): GPL-2.0, 0BSD, BSD-3-Clause, Unlicense, MPL-2.0-no-copyleft-exception, LGPL-2.1
  1. <?php
  2. // +----------------------------------------------------------------------+
  3. // | PHP Version 5 |
  4. // +----------------------------------------------------------------------+
  5. // | Copyright (c) 2009 The PHP Group |
  6. // +----------------------------------------------------------------------+
  7. // | This source file is subject to version 3.0 of the PHP license, |
  8. // | that is available through the world-wide-web at the following url: |
  9. // | http://www.php.net/license/3_0.txt. |
  10. // | If you were unable to obtain it though the world-wide-web, please |
  11. // | send a note to license@php.net so we can mail you a copy immediately |
  12. // +----------------------------------------------------------------------+
  13. // | Author: Alexander Hjalmarsson <hjalle@php.net> |
  14. // +----------------------------------------------------------------------+
  15. //
  16. /**
  17. * This benchmark measures the performance of PHP
  18. * Example of usage:
  19. * bench.php --log log.txt -d -m 256
  20. *
  21. * @author Alexander Hjalmarsson <hjalle@php.net>
  22. * @copyright 2009
  23. * @license http://www.php.net/license/3_0.txt PHP License 3.0
  24. */
  25. error_reporting(E_ALL | E_NOTICE);
  26. set_time_limit(0);
  27. /**
  28. * This is the main class of the benchmark script
  29. */
  30. class Benchmark
  31. {
  32. /**
  33. * Maximum memory usage for php test files.
  34. * @var int
  35. */
  36. var $memory_limit;
  37. /**
  38. * Debug switch
  39. * @var boolean
  40. */
  41. var $debug;
  42. /**
  43. * Directories to search for php test files.
  44. * @var array
  45. */
  46. var $include;
  47. /**
  48. * Path to the php binary
  49. * @var string
  50. */
  51. var $php;
  52. /**
  53. * The config for command line arguments
  54. * @var array
  55. */
  56. var $config;
  57. /**
  58. * The log file for output
  59. * @var string
  60. */
  61. var $log_file;
  62. /**
  63. * Produce output or not
  64. */
  65. var $quite;
  66. /**
  67. * Holds the test files that will be tested
  68. * @var array
  69. */
  70. var $test_files;
  71. /**
  72. * Whether cachegrind should be used or not
  73. * @var boolean
  74. */
  75. var $cachegrind;
  76. /**
  77. * Class for parsing and sorting Smaps-files
  78. * @var object
  79. */
  80. var $smapsparser;
  81. /**
  82. * Switch for showing memory usage or not
  83. * @var boolean
  84. */
  85. var $mem_usage;
  86. /**
  87. * Constructor for the benchmark class. Uses the PEAR package
  88. * Console_getargs for command line handling.
  89. *
  90. * @return void
  91. */
  92. function Benchmark()
  93. {
  94. include_once 'misc/getargs.php';
  95. include_once 'misc/timer.php';
  96. include_once 'misc/smapsparser.php';
  97. include_once 'misc/cachegrindparser.php';
  98. $this->setConfig();
  99. $args = &Console_Getargs::factory($this->config);
  100. $this->smapsparser = new Smapsparser();
  101. if (PEAR::isError($args) || $args->getValue('help')) {
  102. $this->printHelp($args);
  103. exit;
  104. }
  105. $this->debug = $args->getValue('debug');
  106. $this->memory_limit = $args->getValue('memory-limit');
  107. if ($this->memory_limit == "") {
  108. $this->memory_limit = 128;
  109. }
  110. $this->include = $args->getValue('include');
  111. if (is_array($this->include)) {
  112. $this->include[] = "tests"; // Append default directory
  113. } else {
  114. //user has one directory as input, therefore
  115. //$this->include is recognized as a string
  116. $tmpdir = $this->include;
  117. $this->include = array();
  118. $this->include[] = $tmpdir;
  119. $this->include[] = "tests";
  120. }
  121. $this->php = $args->getValue('php');
  122. if ($this->php == "") {
  123. $this->php = "php";
  124. }
  125. $this->log_file = $args->getValue('log');
  126. if ($this->log_file == "") {
  127. $this->log_file = false;
  128. }
  129. $this->quite = $args->getValue('quite');
  130. $this->mem_usage = $args->getValue('memory-usage');
  131. $this->cachegrind = $args->getValue('cachegrind');
  132. $this->setTestFiles();
  133. }
  134. /**
  135. * Prints message to the screen
  136. *
  137. * @param string $msg The message to print
  138. * @param bool $print Whether to print or not print
  139. *
  140. * @return void
  141. */
  142. function console($msg, $print=true)
  143. {
  144. if (!$this->quite) {
  145. if ($print) {
  146. echo $msg;
  147. }
  148. }
  149. if ($this->log_file && $print) {
  150. file_put_contents($this->log_file, $msg, FILE_APPEND);
  151. }
  152. }
  153. /**
  154. * Sets the config for command line arguments
  155. *
  156. * @return void
  157. */
  158. function setConfig()
  159. {
  160. $this->config = array(
  161. 'memory-limit' => array('short' => 'ml',
  162. 'min' => 0,
  163. 'max' => 1,
  164. 'default' => 128,
  165. 'desc' => 'Set the maximum memory usage.'),
  166. 'debug' => array('short' => 'd',
  167. 'max' => 0,
  168. 'desc' => 'Switch to debug mode.'),
  169. 'include' => array('min' => 0,
  170. 'max' => -1,
  171. 'desc' => 'Include additional test directories'),
  172. 'help' => array('short' => 'h',
  173. 'max' => 0,
  174. 'desc' => 'Print this help message'),
  175. 'php' => array('min' => 0,
  176. 'max' => 1,
  177. 'default' => 'php',
  178. 'desc' => 'PHP binary path'),
  179. 'quite' => array('short' => 'q',
  180. 'max' => 0,
  181. 'desc' => 'Don\'t produce any output'),
  182. 'memory-usage' => array('short' => 'mu',
  183. 'max' => 0,
  184. 'desc' => 'Show memory statistics. This requires linux with kernel 2.6.14 or newer'),
  185. 'cachegrind' => array('max' => 0,
  186. 'desc' => 'Enable the valgrind tool that a cache simulation of the test'),
  187. 'log' => array('min' => 0,
  188. 'max' => 1,
  189. 'default' => 'benchlog.txt',
  190. 'desc' => 'Log file path')
  191. );
  192. }
  193. /**
  194. * Prints the help message for the benchmark
  195. *
  196. * @param array $args The arguments to be listed
  197. *
  198. * @return void
  199. */
  200. function printHelp($args)
  201. {
  202. $header = "Php Benchmark Example\n".
  203. 'Usage: '.basename($_SERVER['SCRIPT_NAME'])." [options]\n\n";
  204. if ($args->getCode() === CONSOLE_GETARGS_ERROR_USER) {
  205. echo Console_Getargs::getHelp($this->config, $header, $args->getMessage())."\n";
  206. } else if ($args->getCode() === CONSOLE_GETARGS_HELP) {
  207. echo Console_Getargs::getHelp($this->config, $header)."\n";
  208. }
  209. }
  210. /**
  211. * Return an array with all the paths to the test files found
  212. * from the array of directories to search. It searches for files
  213. * with the structure of test_*.php. It then strips the full path
  214. * so the elements in the returning array will hold both the full
  215. * path and the name of the script.
  216. * Example:
  217. *
  218. * A file called test_array.php that lies in /home/user/tests can
  219. * make the returning array look like this:
  220. *
  221. * array([0] => array ( [filename] => "/home/user/tests/test_array.php",
  222. * [name] => "array.php")
  223. * );
  224. *
  225. * @return void
  226. */
  227. function setTestFiles()
  228. {
  229. $files = array();
  230. foreach ($this->include as $dir) {
  231. foreach (glob("$dir/test_*.php") as $filename) {
  232. $t['filename'] = $filename;
  233. preg_match('/test_(.+)[.]php$/i', $filename, $matches);
  234. $t['name'] = $matches[1];
  235. $files[] = $t;
  236. }
  237. }
  238. $this->test_files = $files;
  239. }
  240. /**
  241. * Runs the benchmark
  242. *
  243. * @return void
  244. */
  245. function run()
  246. {
  247. $timer = new Timer();
  248. $totaltime = 0;
  249. $datetime = date("Y-m-d H:i:s", time());
  250. $startstring = "";
  251. $this->console("--------------- Bench.php ".$datetime."--------------\n");
  252. foreach ($this->test_files as $test) {
  253. $output = array();
  254. if ($this->cachegrind) {
  255. $startstring = "valgrind --tool=cachegrind --branch-sim=yes";
  256. }
  257. $cmd = "$startstring {$this->php} -d memory_limit={$this->memory_limit}M ".$test['filename'];
  258. $this->console("$cmd\n", $this->debug);
  259. $timer->start();
  260. list($out, $err, $exit) = $this->completeExec($cmd, null, 0);
  261. if ($this->mem_usage) {
  262. if (!$this->quite) {
  263. $this->smapsparser->printMaxUsage(10);
  264. }
  265. if ($this->log_file) {
  266. $this->smapsparser->printMaxUsage(10, $this->log_file);
  267. }
  268. $this->smapsparser->clear();
  269. }
  270. if ($this->cachegrind) {
  271. $cacheparser = new Cachegrindparser();
  272. $result = $cacheparser->parse($err);
  273. print_r($result);
  274. }
  275. $timer->stop();
  276. $this->console($out, $this->debug);
  277. $this->console("Results from ".$test['name'].": ".$timer->elapsed."\n");
  278. $totaltime += $timer->elapsed;
  279. }
  280. $this->console("Total time for the benchmark: ".$totaltime." seconds\n");
  281. $datetime = date("Y-m-d H:i:s", time());
  282. $this->console("-------------- END ".$datetime."---------------------\n");
  283. }
  284. /**
  285. * Executes a program in proper way. The function is borrowed
  286. * the php compiler project, http://www.phpcompiler.org.
  287. *
  288. * @param string $command The command to be executed
  289. * @param string $stdin stdin
  290. * @param int $timeout Seconds until it timeouts
  291. *
  292. * @return array
  293. */
  294. function completeExec($command, $stdin = null, $timeout = 20)
  295. {
  296. $descriptorspec = array(0 => array("pipe", "r"),
  297. 1 => array("pipe", "w"),
  298. 2 => array("pipe", "w"));
  299. $pipes = array();
  300. $handle = proc_open($command, $descriptorspec, &$pipes, getcwd());
  301. // read stdin into the process
  302. if ($stdin !== null) {
  303. fwrite($pipes[0], $stdin);
  304. }
  305. fclose($pipes[0]);
  306. unset($pipes[0]);
  307. // set non blocking to avoid infinite loops on stuck programs
  308. stream_set_blocking($pipes[1], 0);
  309. stream_set_blocking($pipes[2], 0);
  310. $out = "";
  311. $err = "";
  312. $start_time = time();
  313. do {
  314. $status = proc_get_status($handle);
  315. // It seems that with a large amount fo output, the process
  316. // won't finish unless the buffers are periodically cleared.
  317. // (This doesn't seem to be the case is async_test. I don't
  318. // know why).
  319. $new_out = stream_get_contents($pipes[1]);
  320. $new_err = stream_get_contents($pipes[2]);
  321. $out .= $new_out;
  322. $err .= $new_err;
  323. $pid = $this->getChildren($handle, $pipes);
  324. if ($this->mem_usage) {
  325. if ($data = $this->smapsparser->readSmapsData($pid[0])) {
  326. $this->smapsparser->parseSmapsData($data);
  327. }
  328. }
  329. if ($timeout != 0 && time() > $start_time + $timeout) {
  330. $out = stream_get_contents($pipes[1]);
  331. $err = stream_get_contents($pipes[2]);
  332. $this->killProperly($handle, $pipes);
  333. return array("Timeout", $out, $err);
  334. }
  335. // Since we use non-blocking, the for loop could well take 100%
  336. // CPU. time of 1000 - 10000 seems OK. 100000 slows down the
  337. // program by 50%.
  338. usleep(7000);
  339. } while ($status["running"]);
  340. stream_set_blocking($pipes[1], 1);
  341. stream_set_blocking($pipes[2], 1);
  342. $out .= stream_get_contents($pipes[1]);
  343. $err .= stream_get_contents($pipes[2]);
  344. $exit_code = $status["exitcode"];
  345. $this->killProperly($handle, $pipes);
  346. return array($out, $err, $exit_code);
  347. }
  348. /**
  349. * Get's the child processes of a shell execution.
  350. *
  351. * @param handler &$handle The handler
  352. * @param array &$pipes The pipes
  353. *
  354. * @return array All children processes pid-number.
  355. */
  356. function getChildren(&$handle, &$pipes)
  357. {
  358. $status = proc_get_status($handle);
  359. $ppid = $status["pid"];
  360. $pids = preg_split("/\s+/", trim(`ps -o pid --no-heading --ppid $ppid`));
  361. return $pids;
  362. }
  363. /**
  364. * Kills a process properly
  365. *
  366. * @param handler &$handle The handler
  367. * @param array &$pipes The pipes
  368. *
  369. * @return void
  370. */
  371. function killProperly(&$handle, &$pipes)
  372. {
  373. // proc_terminate kills the shell process, but won't kill a runaway infinite
  374. // loop. Get the child processes using ps, before killing the parent.
  375. $pids = $this->getChildren($handle, $pipes);
  376. // if we dont close pipes, we can create deadlock, leaving zombie processes.
  377. foreach ($pipes as &$pipe) {
  378. fclose($pipe);
  379. }
  380. proc_terminate($handle);
  381. proc_close($handle);
  382. // Not necessarily available.
  383. if (function_exists("posix_kill")) {
  384. foreach ($pids as $pid) {
  385. if (is_numeric($pid)) {
  386. posix_kill($pid, 9);
  387. }
  388. }
  389. }
  390. }
  391. }
  392. $bench = new Benchmark();
  393. $bench->run();
  394. ?>