PageRenderTime 22ms CodeModel.GetById 2ms app.highlight 15ms RepoModel.GetById 1ms app.codeStats 1ms

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