com phpruntests: Added a way of distributing the tasks across processors based on the times that they have taken to run in a previous run. This is
invoked with the command line option -g. Also added the command line option --log filename, if enabled this will record the time taken for each group
and which processor it was run on. Memory usage is also logged but only for serial runs: src/configuration/data/task_weight_file.csv
src/configuration/da
| From: | Zoe Slattery | Date: | Sat, 02 Mar 2013 20:32:36 +0000 |
| Subject: | com phpruntests: Added a way of distributing the tasks across processors based on the times that they have taken to run in a previous run. This is invoked with the command line option -g. Also added the command line option --log filename, if enabled this will record the time taken for each group and which processor it was run on. Memory usage is also logged but only for serial runs: src/configuration/data/task_weight_file.csv src/configuration/da |
||
| Groups: | php.qa | ||
| Request: | Send a blank email to php-qa+get-66818@lists.php.net to get a copy of this message | ||
Commit: 8c104424b9d647a530f545292b7ed117fb6d57d6
Author: zoe slattery <zoe@php.net> Sat, 2 Mar 2013 20:32:36 +0000
Parents: 88dfb30b0348aff3a7989c11d4e64125d6d67deb
Branches: master
Link: http://git.php.net/?p=phpruntests.git;a=commitdiff;h=8c104424b9d647a530f545292b7ed117fb6d57d6
Log:
Added a way of distributing the tasks across processors based on the times that they have taken to
run in a previous run. This is invoked with the command line option -g. Also added the command line
option --log filename, if enabled this will record the time taken for each group and which processor
it was run on. Memory usage is also logged but only for serial runs
Changed paths:
A src/configuration/data/task_weight_file.csv
A src/configuration/data/task_weight_file.csv_fromserial
M src/configuration/rtCommandLineOptions.php
M src/configuration/rtRuntestsConfiguration.php
M src/rtUtil.php
M src/taskScheduler/rtTaskSchedulerFile.php
M src/taskScheduler/rtTaskTestGroup.php
M src/testgroup/rtGroupConfiguration.php
M src/testgroup/rtPhpTestGroup.php
M src/testrun/rtPhpTestRun.php
M tests/rtGroupConfigurationTest.php
diff --git a/src/configuration/data/task_weight_file.csv b/src/configuration/data/task_weight_file.csv new file mode 100644 index 0000000..e6d5f61 --- /dev/null +++ b/src/configuration/data/task_weight_file.csv @@ -0,0 +1,69 @@ +sapi/tests/:0.05 +ext/standard/tests/file/windows_links/:0.09 +ext/standard/tests/:0.12 +tests/strings/:0.26 +Zend/tests/multibyte/:0.29 +tests/run-test/:0.28 +ext/standard/tests/assert/:0.32 +ext/standard/tests/time/:0.87 +ext/standard/tests/filters/:0.55 +ext/pdo_sqlite/tests/:0.97 +ext/standard/tests/image/:1 +ext/standard/tests/streams/:1.36 +ext/fileinfo/tests/:1.33 +ext/pdo/tests/:1.43 +ext/xmlwriter/tests/:1.47 +ext/tokenizer/tests/:1.56 +ext/json/tests/:1.8 +ext/ereg/tests/:2.04 +ext/mysql/tests/:1.57 +ext/phar/tests/cache_list/:2.75 +ext/standard/tests/dir/:2.67 +ext/sqlite3/tests/:3 +ext/filter/tests/:3.49 +ext/xml/tests/:3.28 +ext/standard/tests/misc/:5 +tests/func/:7.68 +tests/lang/:8.57 +ext/zlib/tests/:8.34 +ext/reflection/tests/:7.35 +ext/session/tests/:8.75 +ext/spl/tests/:17.96 +ext/standard/tests/array/:28.74 +ext/date/tests/:34.35 +Zend/tests/:162.74 +ext/standard/tests/file/:99.67 +ext/phar/tests/:33.23 +ext/standard/tests/strings/:33.58 +ext/standard/tests/general_functions/:127.13 +ext/phar/tests/tar/:14.64 +tests/classes/:11.93 +ext/dom/tests/:8.03 +ext/mysqli/tests/:8.16 +ext/standard/tests/math/:6.69 +ext/phar/tests/zip/:6.11 +ext/posix/tests/:4.82 +ext/ctype/tests/:3.3 +sapi/cli/tests/:4.03 +ext/iconv/tests/:3.41 +ext/pcre/tests/:3.07 +tests/security/:2.51 +Zend/tests/traits/:2.55 +ext/pdo_mysql/tests/:2.32 +ext/standard/tests/network/:2.59 +tests/lang/operators/:1.88 +ext/standard/tests/class_object/:1.71 +tests/basic/:1.75 +tests/output/:1.62 +ext/standard/tests/serialize/:1.41 +ext/standard/tests/url/:1.02 +ext/xmlreader/tests/:0.81 +ext/standard/tests/mail/:0.76 +ext/libxml/tests/:0.68 +sapi/cgi/tests/:0.51 +ext/standard/tests/http/:0.33 +Zend/tests/traits/bugs/:0.34 +Zend/tests/constants/:0.14 +ext/standard/tests/file/windows_acls/:0.12 +ext/standard/tests/directory/:0.11 +ext/standard/tests/versioning/:0.08 diff --git a/src/configuration/data/task_weight_file.csv_fromserial b/src/configuration/data/task_weight_file.csv_fromserial new file mode 100644 index 0000000..2ddced3 --- /dev/null +++ b/src/configuration/data/task_weight_file.csv_fromserial @@ -0,0 +1,70 @@ +#Group weightings are just run time in seconds from a serial run +Zend/tests/:123.59 +Zend/tests/constants/:0.29 +Zend/tests/multibyte/:0.32 +Zend/tests/traits/:2.77 +Zend/tests/traits/bugs/:0.35 +ext/ctype/tests/:4.49 +ext/date/tests/:42.11 +ext/dom/tests/:9.55 +ext/ereg/tests/:2.57 +ext/fileinfo/tests/:1.83 +ext/filter/tests/:4.15 +ext/iconv/tests/:3.79 +ext/json/tests/:2.32 +ext/libxml/tests/:0.83 +ext/mysql/tests/:2.7 +ext/mysqli/tests/:8.9 +ext/pcre/tests/:3.59 +ext/pdo/tests/:1.94 +ext/pdo_mysql/tests/:2.59 +ext/pdo_sqlite/tests/:1.2 +ext/phar/tests/:48.94 +ext/phar/tests/cache_list/:3.17 +ext/phar/tests/tar/:11.45 +ext/phar/tests/zip/:6.04 +ext/posix/tests/:5.05 +ext/reflection/tests/:8.95 +ext/session/tests/:9.83 +ext/spl/tests/:17.66 +ext/sqlite3/tests/:3.9 +ext/standard/tests/array/:24.73 +ext/standard/tests/assert/:0.41 +ext/standard/tests/:0.14 +ext/standard/tests/class_object/:2.02 +ext/standard/tests/dir/:3.34 +ext/standard/tests/directory/:0.13 +ext/standard/tests/file/:98.3 +ext/standard/tests/file/windows_acls/:0.15 +ext/standard/tests/file/windows_links/:0.14 +ext/standard/tests/filters/:0.84 +ext/standard/tests/general_functions/:19.32 +ext/standard/tests/http/:0.38 +ext/standard/tests/image/:1.22 +ext/standard/tests/mail/:0.88 +ext/standard/tests/math/:7.35 +ext/standard/tests/misc/:5.23 +ext/standard/tests/network/:2.44 +ext/standard/tests/serialize/:1.6 +ext/standard/tests/streams/:1.64 +ext/standard/tests/strings/:28.75 +ext/standard/tests/time/:0.8 +ext/standard/tests/url/:1.21 +ext/standard/tests/versioning/:0.1 +ext/tokenizer/tests/:2.32 +ext/xml/tests/:4.2 +ext/xmlreader/tests/:0.93 +ext/xmlwriter/tests/:1.97 +ext/zlib/tests/:8.45 +sapi/cgi/tests/:0.57 +sapi/cli/tests/:4.09 +sapi/tests/:0.06 +tests/basic/:1.95 +tests/classes/:10.54 +tests/func/:5.5 +tests/lang/:8.3 +tests/lang/operators/:2.15 +tests/output/:1.93 +tests/run-test/:0.38 +tests/security/:2.84 +tests/strings/:0.32 diff --git a/src/configuration/rtCommandLineOptions.php b/src/configuration/rtCommandLineOptions.php index 7f11481..dcb1277 100644 --- a/src/configuration/rtCommandLineOptions.php +++ b/src/configuration/rtCommandLineOptions.php @@ -24,6 +24,7 @@ class rtCommandLineOptions 'q', // quiet, no user interaction 'v', // verbose-mode level 1 'h', // help + 'g', // attempt to group tasks for parallel run ); /** @@ -54,7 +55,7 @@ class rtCommandLineOptions 'keep-out', // keep only out files 'keep-exp', // keep only exp files 'no-clean', // do not execute clean section - 'debug', // do not execute clean section + 'debug' // extra debug info ); /** @@ -62,6 +63,7 @@ class rtCommandLineOptions */ protected $longOptionsWithArgs = array( 'mopts', // memory-options (valgrind) + 'log', //log to a file name ); /** diff --git a/src/configuration/rtRuntestsConfiguration.php b/src/configuration/rtRuntestsConfiguration.php index 7db80ad..16a28d7 100644 --- a/src/configuration/rtRuntestsConfiguration.php +++ b/src/configuration/rtRuntestsConfiguration.php @@ -22,9 +22,11 @@ abstract class rtRuntestsConfiguration protected $settings; protected $environmentVariables; protected $commandLine; - protected $operatingSystem; - + protected $operatingSystem; protected $memoryTool = null; + protected $taskWeightings = array(); + const WEIGHT_FILE = "/data/task_weight_file.csv"; + protected $settingNames = array ( @@ -60,6 +62,10 @@ abstract class rtRuntestsConfiguration //create object to hold environment variables $this->environmentVariables = rtEnvironmentVariables::getInstance($this->operatingSystem); + + if($this->commandLine->hasOption('z')) { + $this->setTaskWeightings(); + } } /** @@ -168,6 +174,17 @@ abstract class rtRuntestsConfiguration return $this->memoryTool->getCommand(); } + public function setTaskWeightings() { + $this->taskWeightings = rtUtil::readConfigurationFile(__DIR__ .self::WEIGHT_FILE); + } + + public function hasWeight($k) { + return array_key_exists($k, $this->taskWeightings); + } + + public function getWeight($k) { + return $this->taskWeightings[$k]; + } } ?> diff --git a/src/rtUtil.php b/src/rtUtil.php index f7573d5..da6be7c 100644 --- a/src/rtUtil.php +++ b/src/rtUtil.php @@ -50,37 +50,37 @@ class rtUtil } - /** - * returns a list of directories containing a phpt-file - * + /** + * returns a list of directories containing a phpt-file + * * @param $path * @return array - */ - public static function parseDir($path) - { - $list = array(); - $found = false; - foreach (scandir($path) as $file) { - - - if (substr($file, 0, 1) != '.' && $file != 'CVS') { - - if (is_dir($path.'/'.$file)) { - - $list = array_merge($list, rtUtil::parseDir($path.'/'.$file)); - - } elseif ($found === false && strpos($file, '.phpt') !== false) { - - $list[] = $path.'/'; - $found = true; - } - } - } - - return $list; - } - - + */ + public static function parseDir($path) + { + $list = array(); + $found = false; + foreach (scandir($path) as $file) { + + + if (substr($file, 0, 1) != '.' && $file != 'CVS') { + + if (is_dir($path.'/'.$file)) { + + $list = array_merge($list, rtUtil::parseDir($path.'/'.$file)); + + } elseif ($found === false && strpos($file, '.phpt') !== false) { + + $list[] = $path.'/'; + $found = true; + } + } + } + + return $list; + } + + /** * This is the original version of getDirectoryList which uses PhptFilterIterator */ @@ -109,5 +109,47 @@ class rtUtil return array_unique($result); } + /* + * Returns the index associated with the minimum value in an array + * + */ + public static function getMin($a) { + $x = array_keys($a, min($a)); + return $x[0]; + } + /* + * Strip any part of the path name before one of the recognised levels + * TODO What happens when just running tests? This is only used to match + * up with a weighting table. Assume no weight will be matched. + * Would it be better to have some other way to check that it's being run from + * the root of the PHP source? Do this in rtCommandLineOptions anyway(?) + */ + public static function stripPath($t) { + $topLevelDirectory = array("Zend", "sapi", "ext", "tests"); + foreach($topLevelDirectory as $tld) { + if(preg_match("/\W{1}$tld\W{1}/", $t, $matches, PREG_OFFSET_CAPTURE)) { + $offset = $matches[0][1] + 1; + return substr($t, $offset); + } + } + return ""; + } + + /* + * Standard code to read a configuration file and return results as a key->value array + */ + public static function readConfigurationFile($fileName) { + + $a = array(); + $fc = file($fileName); + + foreach($fc as $line) { + if(substr($line, 0, 1) != '#') { + list($key, $value) = explode(':',trim($line)); + $a[$key] = $value; + } + } + return $a; + } } ?> diff --git a/src/taskScheduler/rtTaskSchedulerFile.php b/src/taskScheduler/rtTaskSchedulerFile.php index dabe233..475eba6 100644 --- a/src/taskScheduler/rtTaskSchedulerFile.php +++ b/src/taskScheduler/rtTaskSchedulerFile.php @@ -16,6 +16,7 @@ class rtTaskSchedulerFile extends rtTaskScheduler { const TMP_FILE = 'taskFile'; + const LOG_FILE = '/tmp/parallellog.csv'; protected $pidStore = array(); // stores the pids of all child-processes protected $groupTasks = false; // are the tasks already grouped by target processor @@ -80,6 +81,9 @@ class rtTaskSchedulerFile extends rtTaskScheduler */ public function run() { + //clears the logfile + //file_put_contents(self::LOG_FILE, ""); + if ($this->processCount == 0) { return parent::run(); } @@ -146,10 +150,8 @@ class rtTaskSchedulerFile extends rtTaskScheduler if ($this->groupTasks == true) { - foreach ($this->taskList as $cid => $list) { - + foreach ($this->taskList as $cid => $list) { for ($i=0; $i<sizeof($list); $i++) { - $str = serialize($list[$i])."[END]"; file_put_contents(self::TMP_FILE.$cid, $str, FILE_APPEND); } @@ -158,7 +160,6 @@ class rtTaskSchedulerFile extends rtTaskScheduler } else { for ($i=0; $i<sizeof($this->taskList); $i++) { - $cid = $i%$this->processCount; $str = serialize($this->taskList[$i])."[END]"; file_put_contents(self::TMP_FILE.$cid, $str, FILE_APPEND); @@ -213,7 +214,6 @@ class rtTaskSchedulerFile extends rtTaskScheduler file_put_contents(self::TMP_FILE.$cid, ''); $count = 0; foreach ($taskList as $task) { - $s = microtime(true); $task = unserialize($task); @@ -223,11 +223,16 @@ class rtTaskSchedulerFile extends rtTaskScheduler continue; } - $task->run(); $e = microtime(true); + //$t = round($e - $s, 2); + + //$logstring = $task->getSubDirectory() . ", " .$cid . ", " . $s . ", " . $e . ", " . $t . "\n"; + + //file_put_contents(self::LOG_FILE, $logstring, FILE_APPEND); + $taskResult = $task->getResult(); //StatusList is an array 'testname=>statusObject' diff --git a/src/taskScheduler/rtTaskTestGroup.php b/src/taskScheduler/rtTaskTestGroup.php index c8a3d6b..6a2ed51 100644 --- a/src/taskScheduler/rtTaskTestGroup.php +++ b/src/taskScheduler/rtTaskTestGroup.php @@ -42,6 +42,10 @@ class rtTaskTestGroup extends rtTask implements rtTaskInterface return true; } + public function getSubDirectory() { + return $this->subDirectory; + } + } diff --git a/src/testgroup/rtGroupConfiguration.php b/src/testgroup/rtGroupConfiguration.php index 95a61cb..45ada45 100644 --- a/src/testgroup/rtGroupConfiguration.php +++ b/src/testgroup/rtGroupConfiguration.php @@ -24,6 +24,8 @@ class rtGroupConfiguration protected $skipFile = ""; protected $hasSkipCode = false; + const SKIP_FILE_NAME = "skip_group_if.inc"; + public function __construct($directory) { @@ -44,7 +46,7 @@ class rtGroupConfiguration $this->environmentVariables = $setup['ENV']; - //Remove trailing white space and add a slash to teh directory name + //Remove trailing white space and add a slash to the directory name //Will need modification if we wanted to be able to give it a list of test directories. $dir = trim($setup['TESTS']); @@ -96,15 +98,14 @@ class rtGroupConfiguration } public function parseConfiguration() { - //Here insert code to read a config file from the test directory that determines whether the set of tests should be run - //in parallel or not? - $this->serialGroup = false; - - + //TODO Could insert code to read a config file from the test directory that determines whether the set of tests should be run + //in parallel or not. + //Code to read the directory skipif, run it and skip the directory - if(file_exists($this->testDirectory. "/skip_group_if.inc")) { + //TODO- this makes a miniscule difference to timing. + if(file_exists($this->testDirectory. "/" . self::SKIP_FILE_NAME)) { $this->hasSkipCode = true; - $this->skipFile = $this->testDirectory."/skip_group_if.inc"; + $this->skipFile = $this->testDirectory. "/" .self::SKIP_FILE_NAME; } return; @@ -129,8 +130,8 @@ class rtGroupConfiguration public function hasSkipCode() { return $this->hasSkipCode; } - public function getSkipFile() { - + public function getSkipFile() { + return $this->skipFile; } } diff --git a/src/testgroup/rtPhpTestGroup.php b/src/testgroup/rtPhpTestGroup.php index 900141c..da16e61 100644 --- a/src/testgroup/rtPhpTestGroup.php +++ b/src/testgroup/rtPhpTestGroup.php @@ -46,8 +46,7 @@ class rtPhpTestGroup extends rtTask implements rtTaskInterface } } - if($this->groupConfiguration->hasSkipCode()) { - + if($this->groupConfiguration->hasSkipCode()) { //If there is some 'skip' code run it to see if the tests should be skipped and then do nothing else $phpCommand = $this->runConfiguration->getSetting('PhpExecutable'); @@ -58,9 +57,10 @@ class rtPhpTestGroup extends rtTask implements rtTaskInterface $runner = new rtPhpRunner($phpCommand); $result = $runner->runphp(); - if (preg_match('/^\s*skip\s*(.+)\s*/i', $result, $matches)) { $this->groupResults->setSkip(true); + $this->groupResults->setAbsTime(microtime(true)); + $this->groupResults->setTime(0); } } @@ -73,9 +73,6 @@ class rtPhpTestGroup extends rtTask implements rtTaskInterface $redirectFromID = $this->groupConfiguration->getRedirectFromID(); foreach ($this->testFiles as $testFileName) { - //echo "\n" .memory_get_usage() . ", setup start". $testFileName . "\n"; - - //testFiles is a list of file names relative to the current working directory if (!file_exists($testFileName)) { echo rtText::get('invalidTestFileName', array($testFileName)); @@ -118,8 +115,7 @@ class rtPhpTestGroup extends rtTask implements rtTaskInterface $this->groupResults->setTestStatus($testFile->getTestName(), $testStatus); } - - //echo "\n" .memory_get_usage() . ", setup complete". $testFileName . "\n"; + } } } @@ -129,6 +125,9 @@ class rtPhpTestGroup extends rtTask implements rtTaskInterface $s=microtime(true); if (count($this->testCases) == 0) { + $e=microtime(true); + $this->groupResults->setTime($e-$s); + $this->groupResults->setAbsTime($e); return; } @@ -151,6 +150,7 @@ class rtPhpTestGroup extends rtTask implements rtTaskInterface $this->groupResults->setTime($e-$s); $this->groupResults->setAbsTime($e); + } public function writeGroup($outType, $cid=null) diff --git a/src/testrun/rtPhpTestRun.php b/src/testrun/rtPhpTestRun.php index f26b1f3..59cf9b6 100644 --- a/src/testrun/rtPhpTestRun.php +++ b/src/testrun/rtPhpTestRun.php @@ -27,6 +27,8 @@ class rtPhpTestRun protected $processorCount; protected $runStartTime; protected $skippedGroups = array(); + protected $logFileName; + protected $groupTasks = false; public function __construct($argv) { @@ -40,7 +42,7 @@ class rtPhpTestRun // check the operation-system (win/unix) $os = (substr(PHP_OS, 0, 3) == "WIN") ? 'Windows' : 'Unix'; - + //Configure the test environment $this->runConfiguration = rtRuntestsConfiguration::getInstance($this->commandLineArguments, $os); $this->runConfiguration->getUserEnvironment(); @@ -71,17 +73,26 @@ class rtPhpTestRun $this->processorCount = $this->requestedProcessorCount(); + if($this->runConfiguration->hasCommandLineOption('log')) { + $this->logFileName = $this->runConfiguration->getCommandLineOption('log'); + file_put_contents($this->logFileName, ""); + } + + if($this->runConfiguration->hasCommandlineOption('g')) { + $this->groupTasks = true; + } + /* * Main decision point. Either we start this with a directory (or set of directories, in which case tests are * run as a group (and in parallel if required) or...... */ if ($this->runConfiguration->getSetting('TestDirectories') != null) { - + $this->doGroupRuns(); - + } else { - + /* *... the input is a test file, or list of files and are just run as single tests * and not in parallel @@ -97,18 +108,18 @@ class rtPhpTestRun if(count($this->redirectedTestCases) > 0) { $this->doRedirectedRuns(); } - + if(($this->numberOfSerialGroups != 0) || ($this->numberOfParallelGroups != 0)) { $this->createRunOutput(); } - + } public function doGroupRuns() { $subDirectories = $this->buildSubDirectoryList($this->runConfiguration->getSetting('TestDirectories')); - - //An array of group configuration objects, one for each subdirectory. + + //An array of group configuration objects, one for each subdirectory. $groupConfigurations = $this->buildGroupConfigurations($subDirectories); @@ -134,11 +145,11 @@ class rtPhpTestRun $parallelGroups[] = $key; } } - + if(isset($serialGroups)) {$this->numberOfSerialGroups = count($serialGroups);} - + $this->numberOfParallelGroups = count($parallelGroups); - + $this->run_parallel_groups($parallelGroups, $groupConfigurations, $this->processorCount); if($this->numberOfSerialGroups > 0) { $this->run_serial_groups($serialGroups, $groupConfigurations); @@ -150,27 +161,33 @@ class rtPhpTestRun public function doRedirectedRuns() { foreach($this->redirectedTestCases as $testCase){ - + $groupConfig = new rtGroupConfiguration(null); $groupConfig->parseRedirect($testCase); - + $group = $groupConfig->getTestDirectory(); - + $this->run_serial_groups(array($group), array($group=>$groupConfig)); - + $this->numberOfSerialGroups++; } } public function run_parallel_groups($testDirectories, $groupConfigurations, $processCount) { - - // create the task-list + + //Create the task list to be executed in parallel. Either randomly or trying to order it. $taskList = array(); - foreach($testDirectories as $testGroup) { - $taskList[] = new rtTaskTestGroup($this->runConfiguration, $testGroup, $groupConfigurations[$testGroup]); + if($this->groupTasks == false) { + foreach($testDirectories as $testGroup) { + $taskList[] = new rtTaskTestGroup($this->runConfiguration, $testGroup, $groupConfigurations[$testGroup]); + } + }else { + $taskList = $this->groupTasksByWeight($testDirectories, $groupConfigurations); + } + // run the task-scheduler $scheduler = rtTaskScheduler::getInstance(); $scheduler->setTaskList($taskList); @@ -179,24 +196,29 @@ class rtPhpTestRun $scheduler->run(); foreach($scheduler->getResultList() as $groupResult) { - - if($groupResult->isSkipGroup()) { - $this->skippedGroups[] = $groupResult->getGroupName(); + + if($groupResult->isSkipGroup()) { + $this->skippedGroups[] = $groupResult->getGroupName(); } else { $this->resultList[] = $groupResult->getTestStatusList(); } - // Debug - get which group was run by which processor and how long each took - // + // Logging - get which group was run by which processor and how long each took - if($this->runConfiguration->hasCommandLineOption('debug')) { - $time = round($groupResult->getTime(), 2); + if($this->runConfiguration->hasCommandLineOption('log')) { - $absTime = $groupResult->getAbsTime() - $this->runStartTime; + $groupTime = round($groupResult->getTime(), 2); + $runTime = $groupResult->getAbsTime() - $this->runStartTime; + + $runTime = round($runTime, 2); - $absTime = round($absTime, 2); + $string = "PARLOG," . $groupResult->getGroupName() . + "," . $groupTime . + "," . $runTime . + "," . $groupResult->getProcessorId() . + "," . $groupResult->getRunOrder() ."\n"; - echo "\nPARDBG," . $absTime. "," . $time . "," . $groupResult->getProcessorId() . "," . $groupResult->getRunOrder() . "," . $groupResult->getGroupName(); + file_put_contents($this->logFileName, $string, FILE_APPEND); } @@ -212,68 +234,61 @@ class rtPhpTestRun public function run_serial_groups($testDirectories, $groupConfigurations) { - $count = 0; - - foreach($testDirectories as $subDirectory) { + $groupCount = 0; - // Memory usage debugging - //$startm = memory_get_usage(); + foreach($testDirectories as $subDirectory) { - + // Memory usage logging + $startMemory = memory_get_usage(); $testGroup = new rtPhpTestGroup($this->runConfiguration, $subDirectory, $groupConfigurations[$subDirectory]); - - - if($testGroup->isSkipGroup() == true) { + + + if($testGroup->isSkipGroup() === true) { $this->skippedGroups[] = $testGroup->getGroupName(); - } else { - + continue; + } - $testGroup->run(); - - - // Memory usage debugging - //$midm = memory_get_usage(); + $testGroup->run(); - rtTestOutputWriter::flushResult($testGroup->getGroupResults()->getTestStatusList(), $this->reportStatus); - $this->resultList[] = $testGroup->getGroupResults()->getTestStatusList(); + rtTestOutputWriter::flushResult($testGroup->getGroupResults()->getTestStatusList(), $this->reportStatus); + $this->resultList[] = $testGroup->getGroupResults()->getTestStatusList(); + + if($this->runConfiguration->hasCommandLineOption('log')) { - if($this->runConfiguration->hasCommandLineOption('debug')) { - - $time = round($testGroup->getGroupResults()->getTime(), 2); + $time = round($testGroup->getGroupResults()->getTime(), 2); - $absTime = ($testGroup->getGroupResults()->getAbsTime()) - $this->runStartTime; - $absTime = round($absTime, 2); + $absTime = ($testGroup->getGroupResults()->getAbsTime()) - $this->runStartTime; + + $absTime = round($absTime, 2); + + $string = "SERLOG," . $testGroup->getGroupName() . "," . + $time . "," . + $absTime . "," . + $testGroup->getGroupResults()->getProcessorId() . "," . + $groupCount . "\n"; - echo "\nSERDBG," . $absTime . "," . $time . "," . $testGroup->getGroupResults()->getProcessorId() . "," . $count . "," . $testGroup->getGroupResults()->getGroupName(); + file_put_contents($this->logFileName, $string, FILE_APPEND); - } - - // Memory usage debugging - //$midm2 = memory_get_usage(); - - $redirects = $testGroup->getGroupResults()->getRedirectedTestCases(); - foreach($redirects as $testCase) { - $this->redirectedTestCases[] = $testCase; - } - - - - // Memory usage debugging - //$midm3 = memory_get_usage(); - - - $testGroup->__destruct(); - unset($testGroup); - - // Memory usage debugging - //echo "\n" . $startm . ", " . $midm. ", " .$midm2. ", " .$midm3. ", " .memory_get_usage() . ", ". $subDirectory . "\n"; - $count++; } - } + + $redirects = $testGroup->getGroupResults()->getRedirectedTestCases(); + foreach($redirects as $testCase) { + $this->redirectedTestCases[] = $testCase; + } - //xdebug_stop_trace(); + + $testGroup->__destruct(); + unset($testGroup); + + if($this->runConfiguration->hasCommandLineOption('log')) { + $string = "MEMLOG," . $subDirectory . ", " . $startMemory. ", " .memory_get_usage() . "\n"; + file_put_contents($this->logFileName, $string, FILE_APPEND); + } + + $groupCount++; + } } public function run_tests($testNames) { @@ -286,19 +301,19 @@ class rtPhpTestRun echo rtText::get('invalidTestFileName', array($testName)); exit(); } - - + + //Read the test file $testFile = new rtPhpTestFile(); $testFile->doRead($testName); $testFile->normaliseLineEndings(); - + $testStatus = new rtTestStatus($testFile->getTestName()); if ($testFile->arePreconditionsMet()) { $testCase = new rtPhpTest($testFile->getContents(), $testFile->getTestName(), $testFile->getSectionHeadings(), $this->runConfiguration, $testStatus); - + //Setup and set the local environment for the test case $testCase->executeTest($this->runConfiguration); @@ -307,12 +322,12 @@ class rtPhpTestRun $summaryResults = array($testFile->getTestName() => $results->getStatus()); } elseif (in_array("REDIRECTTEST", $testFile->getSectionHeadings())) { - + //Redirect handler //Build a list of redirected test cases - + $this->redirectedTestCases[] = new rtPhpTest($testFile->getContents(), $testFile->getTestName(), $testFile->getSectionHeadings(), $this->runConfiguration, $testStatus); - + $testStatus->setTrue('redirected'); $testStatus->setMessage('redirected', $testFile->getExitMessage()); $summaryResults = array($testFile->getTestName() => $testStatus); @@ -324,7 +339,7 @@ class rtPhpTestRun } rtTestOutputWriter::flushResult($summaryResults, 3); - + } } @@ -419,6 +434,55 @@ class rtPhpTestRun } return $groupSummary; } + /* + * This, invoked by using the -g command line option, + * makes an attempt to distribute tests evenly across the processors + * based on 'weightings'. The weightings are actually just timings from a previous + * parallel run. + */ + public function groupTasksByWeight($testDirectories, $groupConfigurations) { + + //First set weights. + $weightedDirectoryList = array(); + $processorWeightSum = array(); + $taskListByProcessor = array(); + + foreach($testDirectories as $subDir) { + $key = rtUtil::stripPath($subDir); + if($this->runConfiguration->hasWeight($key)) { + $weightedDirectoryList[$subDir] = $this->runConfiguration->getWeight($key); + } else { + $weightedDirectoryList[$subDir] = 1; + } + } + + //Order subditrectories by decreasing weight. + arsort($weightedDirectoryList, SORT_NUMERIC); + + //Assign the first n tasks across n processors. Having ordered the tasls these will be the longest running tasks + for($i=0; $i<$this->processorCount; $i++) { + list($key, $value) = each($weightedDirectoryList); + $processorWeightSum[$i] =$value; + $task = new rtTaskTestGroup($this->runConfiguration, $key, $groupConfigurations[$key]); + $taskListByProcessor[$i] = array($task); + } + //Continue to assign tasks to processors based on an estimate of how long each one will take. + for ($i=$this->processorCount; $i<count($weightedDirectoryList); $i++) { + list($key, $value) = each($weightedDirectoryList); + $procID = rtUtil::getMin($processorWeightSum); + $processorWeightSum[$procID] += $value; + $task = new rtTaskTestGroup($this->runConfiguration, $key, $groupConfigurations[$key]); + array_push($taskListByProcessor[$procID], $task); + } + + //Reverse the order of even numbered lists so that not all processors are runnung big tasks at the same time + //This seems to work better - possibly the longer running tasks are resource constrined by something other than CPU? + for($i=0; $i<$this->processorCount; $i+=2) { + $taskListByProcessor[$procID] = array_reverse($taskListByProcessor[$procID]); + } + + return $taskListByProcessor; + } } ?> diff --git a/tests/rtGroupConfigurationTest.php b/tests/rtGroupConfigurationTest.php index 7d4d6cd..def901b 100644 --- a/tests/rtGroupConfigurationTest.php +++ b/tests/rtGroupConfigurationTest.php @@ -8,12 +8,12 @@ class rtGroupConfigurationTest extends PHPUnit_Framework_TestCase protected $path_to_group; public function setUp() { - $this->path_to_group = realpath(dirname(__FILE__) . '/../phpt-tests/group_of_tests'); + $this->path_to_group = realpath(dirname(__FILE__) . '/../phpt-tests/group_of_tests/'); } public function testCreateInstance() { - $config = rtRuntestsConfiguration::getInstance(array('run-tests.php', '-p', RT_PHP_PATH, 'testgroup')); + $config = rtRuntestsConfiguration::getInstance(array('run-tests.php', '-p', RT_PHP_PATH, $this->path_to_group)); $config->configure();
diff --git a/src/configuration/data/task_weight_file.csv b/src/configuration/data/task_weight_file.csv new file mode 100644 index 0000000..e6d5f61 --- /dev/null +++ b/src/configuration/data/task_weight_file.csv @@ -0,0 +1,69 @@ +sapi/tests/:0.05 +ext/standard/tests/file/windows_links/:0.09 +ext/standard/tests/:0.12 +tests/strings/:0.26 +Zend/tests/multibyte/:0.29 +tests/run-test/:0.28 +ext/standard/tests/assert/:0.32 +ext/standard/tests/time/:0.87 +ext/standard/tests/filters/:0.55 +ext/pdo_sqlite/tests/:0.97 +ext/standard/tests/image/:1 +ext/standard/tests/streams/:1.36 +ext/fileinfo/tests/:1.33 +ext/pdo/tests/:1.43 +ext/xmlwriter/tests/:1.47 +ext/tokenizer/tests/:1.56 +ext/json/tests/:1.8 +ext/ereg/tests/:2.04 +ext/mysql/tests/:1.57 +ext/phar/tests/cache_list/:2.75 +ext/standard/tests/dir/:2.67 +ext/sqlite3/tests/:3 +ext/filter/tests/:3.49 +ext/xml/tests/:3.28 +ext/standard/tests/misc/:5 +tests/func/:7.68 +tests/lang/:8.57 +ext/zlib/tests/:8.34 +ext/reflection/tests/:7.35 +ext/session/tests/:8.75 +ext/spl/tests/:17.96 +ext/standard/tests/array/:28.74 +ext/date/tests/:34.35 +Zend/tests/:162.74 +ext/standard/tests/file/:99.67 +ext/phar/tests/:33.23 +ext/standard/tests/strings/:33.58 +ext/standard/tests/general_functions/:127.13 +ext/phar/tests/tar/:14.64 +tests/classes/:11.93 +ext/dom/tests/:8.03 +ext/mysqli/tests/:8.16 +ext/standard/tests/math/:6.69 +ext/phar/tests/zip/:6.11 +ext/posix/tests/:4.82 +ext/ctype/tests/:3.3 +sapi/cli/tests/:4.03 +ext/iconv/tests/:3.41 +ext/pcre/tests/:3.07 +tests/security/:2.51 +Zend/tests/traits/:2.55 +ext/pdo_mysql/tests/:2.32 +ext/standard/tests/network/:2.59 +tests/lang/operators/:1.88 +ext/standard/tests/class_object/:1.71 +tests/basic/:1.75 +tests/output/:1.62 +ext/standard/tests/serialize/:1.41 +ext/standard/tests/url/:1.02 +ext/xmlreader/tests/:0.81 +ext/standard/tests/mail/:0.76 +ext/libxml/tests/:0.68 +sapi/cgi/tests/:0.51 +ext/standard/tests/http/:0.33 +Zend/tests/traits/bugs/:0.34 +Zend/tests/constants/:0.14 +ext/standard/tests/file/windows_acls/:0.12 +ext/standard/tests/directory/:0.11 +ext/standard/tests/versioning/:0.08 diff --git a/src/configuration/data/task_weight_file.csv_fromserial b/src/configuration/data/task_weight_file.csv_fromserial new file mode 100644 index 0000000..2ddced3 --- /dev/null +++ b/src/configuration/data/task_weight_file.csv_fromserial @@ -0,0 +1,70 @@ +#Group weightings are just run time in seconds from a serial run +Zend/tests/:123.59 +Zend/tests/constants/:0.29 +Zend/tests/multibyte/:0.32 +Zend/tests/traits/:2.77 +Zend/tests/traits/bugs/:0.35 +ext/ctype/tests/:4.49 +ext/date/tests/:42.11 +ext/dom/tests/:9.55 +ext/ereg/tests/:2.57 +ext/fileinfo/tests/:1.83 +ext/filter/tests/:4.15 +ext/iconv/tests/:3.79 +ext/json/tests/:2.32 +ext/libxml/tests/:0.83 +ext/mysql/tests/:2.7 +ext/mysqli/tests/:8.9 +ext/pcre/tests/:3.59 +ext/pdo/tests/:1.94 +ext/pdo_mysql/tests/:2.59 +ext/pdo_sqlite/tests/:1.2 +ext/phar/tests/:48.94 +ext/phar/tests/cache_list/:3.17 +ext/phar/tests/tar/:11.45 +ext/phar/tests/zip/:6.04 +ext/posix/tests/:5.05 +ext/reflection/tests/:8.95 +ext/session/tests/:9.83 +ext/spl/tests/:17.66 +ext/sqlite3/tests/:3.9 +ext/standard/tests/array/:24.73 +ext/standard/tests/assert/:0.41 +ext/standard/tests/:0.14 +ext/standard/tests/class_object/:2.02 +ext/standard/tests/dir/:3.34 +ext/standard/tests/directory/:0.13 +ext/standard/tests/file/:98.3 +ext/standard/tests/file/windows_acls/:0.15 +ext/standard/tests/file/windows_links/:0.14 +ext/standard/tests/filters/:0.84 +ext/standard/tests/general_functions/:19.32 +ext/standard/tests/http/:0.38 +ext/standard/tests/image/:1.22 +ext/standard/tests/mail/:0.88 +ext/standard/tests/math/:7.35 +ext/standard/tests/misc/:5.23 +ext/standard/tests/network/:2.44 +ext/standard/tests/serialize/:1.6 +ext/standard/tests/streams/:1.64 +ext/standard/tests/strings/:28.75 +ext/standard/tests/time/:0.8 +ext/standard/tests/url/:1.21 +ext/standard/tests/versioning/:0.1 +ext/tokenizer/tests/:2.32 +ext/xml/tests/:4.2 +ext/xmlreader/tests/:0.93 +ext/xmlwriter/tests/:1.97 +ext/zlib/tests/:8.45 +sapi/cgi/tests/:0.57 +sapi/cli/tests/:4.09 +sapi/tests/:0.06 +tests/basic/:1.95 +tests/classes/:10.54 +tests/func/:5.5 +tests/lang/:8.3 +tests/lang/operators/:2.15 +tests/output/:1.93 +tests/run-test/:0.38 +tests/security/:2.84 +tests/strings/:0.32 diff --git a/src/configuration/rtCommandLineOptions.php b/src/configuration/rtCommandLineOptions.php index 7f11481..dcb1277 100644 --- a/src/configuration/rtCommandLineOptions.php +++ b/src/configuration/rtCommandLineOptions.php @@ -24,6 +24,7 @@ class rtCommandLineOptions 'q', // quiet, no user interaction 'v', // verbose-mode level 1 'h', // help + 'g', // attempt to group tasks for parallel run ); /** @@ -54,7 +55,7 @@ class rtCommandLineOptions 'keep-out', // keep only out files 'keep-exp', // keep only exp files 'no-clean', // do not execute clean section - 'debug', // do not execute clean section + 'debug' // extra debug info ); /** @@ -62,6 +63,7 @@ class rtCommandLineOptions */ protected $longOptionsWithArgs = array( 'mopts', // memory-options (valgrind) + 'log', //log to a file name ); /** diff --git a/src/configuration/rtRuntestsConfiguration.php b/src/configuration/rtRuntestsConfiguration.php index 7db80ad..16a28d7 100644 --- a/src/configuration/rtRuntestsConfiguration.php +++ b/src/configuration/rtRuntestsConfiguration.php @@ -22,9 +22,11 @@ abstract class rtRuntestsConfiguration protected $settings; protected $environmentVariables; protected $commandLine; - protected $operatingSystem; - + protected $operatingSystem; protected $memoryTool = null; + protected $taskWeightings = array(); + const WEIGHT_FILE = "/data/task_weight_file.csv"; + protected $settingNames = array ( @@ -60,6 +62,10 @@ abstract class rtRuntestsConfiguration //create object to hold environment variables $this->environmentVariables = rtEnvironmentVariables::getInstance($this->operatingSystem); + + if($this->commandLine->hasOption('z')) { + $this->setTaskWeightings(); + } } /** @@ -168,6 +174,17 @@ abstract class rtRuntestsConfiguration return $this->memoryTool->getCommand(); } + public function setTaskWeightings() { + $this->taskWeightings = rtUtil::readConfigurationFile(__DIR__ .self::WEIGHT_FILE); + } + + public function hasWeight($k) { + return array_key_exists($k, $this->taskWeightings); + } + + public function getWeight($k) { + return $this->taskWeightings[$k]; + } } ?> diff --git a/src/rtUtil.php b/src/rtUtil.php index f7573d5..da6be7c 100644 --- a/src/rtUtil.php +++ b/src/rtUtil.php @@ -50,37 +50,37 @@ class rtUtil } - /** - * returns a list of directories containing a phpt-file - * + /** + * returns a list of directories containing a phpt-file + * * @param $path * @return array - */ - public static function parseDir($path) - { - $list = array(); - $found = false; - foreach (scandir($path) as $file) { - - - if (substr($file, 0, 1) != '.' && $file != 'CVS') { - - if (is_dir($path.'/'.$file)) { - - $list = array_merge($list, rtUtil::parseDir($path.'/'.$file)); - - } elseif ($found === false && strpos($file, '.phpt') !== false) { - - $list[] = $path.'/'; - $found = true; - } - } - } - - return $list; - } - - + */ + public static function parseDir($path) + { + $list = array(); + $found = false; + foreach (scandir($path) as $file) { + + + if (substr($file, 0, 1) != '.' && $file != 'CVS') { + + if (is_dir($path.'/'.$file)) { + + $list = array_merge($list, rtUtil::parseDir($path.'/'.$file)); + + } elseif ($found === false && strpos($file, '.phpt') !== false) { + + $list[] = $path.'/'; + $found = true; + } + } + } + + return $list; + } + + /** * This is the original version of getDirectoryList which uses PhptFilterIterator */ @@ -109,5 +109,47 @@ class rtUtil return array_unique($result); } + /* + * Returns the index associated with the minimum value in an array + * + */ + public static function getMin($a) { + $x = array_keys($a, min($a)); + return $x[0]; + } + /* + * Strip any part of the path name before one of the recognised levels + * TODO What happens when just running tests? This is only used to match + * up with a weighting table. Assume no weight will be matched. + * Would it be better to have some other way to check that it's being run from + * the root of the PHP source? Do this in rtCommandLineOptions anyway(?) + */ + public static function stripPath($t) { + $topLevelDirectory = array("Zend", "sapi", "ext", "tests"); + foreach($topLevelDirectory as $tld) { + if(preg_match("/\W{1}$tld\W{1}/", $t, $matches, PREG_OFFSET_CAPTURE)) { + $offset = $matches[0][1] + 1; + return substr($t, $offset); + } + } + return ""; + } + + /* + * Standard code to read a configuration file and return results as a key->value array + */ + public static function readConfigurationFile($fileName) { + + $a = array(); + $fc = file($fileName); + + foreach($fc as $line) { + if(substr($line, 0, 1) != '#') { + list($key, $value) = explode(':',trim($line)); + $a[$key] = $value; + } + } + return $a; + } } ?> diff --git a/src/taskScheduler/rtTaskSchedulerFile.php b/src/taskScheduler/rtTaskSchedulerFile.php index dabe233..475eba6 100644 --- a/src/taskScheduler/rtTaskSchedulerFile.php +++ b/src/taskScheduler/rtTaskSchedulerFile.php @@ -16,6 +16,7 @@ class rtTaskSchedulerFile extends rtTaskScheduler { const TMP_FILE = 'taskFile'; + const LOG_FILE = '/tmp/parallellog.csv'; protected $pidStore = array(); // stores the pids of all child-processes protected $groupTasks = false; // are the tasks already grouped by target processor @@ -80,6 +81,9 @@ class rtTaskSchedulerFile extends rtTaskScheduler */ public function run() { + //clears the logfile + //file_put_contents(self::LOG_FILE, ""); + if ($this->processCount == 0) { return parent::run(); } @@ -146,10 +150,8 @@ class rtTaskSchedulerFile extends rtTaskScheduler if ($this->groupTasks == true) { - foreach ($this->taskList as $cid => $list) { - + foreach ($this->taskList as $cid => $list) { for ($i=0; $i<sizeof($list); $i++) { - $str = serialize($list[$i])."[END]"; file_put_contents(self::TMP_FILE.$cid, $str, FILE_APPEND); } @@ -158,7 +160,6 @@ class rtTaskSchedulerFile extends rtTaskScheduler } else { for ($i=0; $i<sizeof($this->taskList); $i++) { - $cid = $i%$this->processCount; $str = serialize($this->taskList[$i])."[END]"; file_put_contents(self::TMP_FILE.$cid, $str, FILE_APPEND); @@ -213,7 +214,6 @@ class rtTaskSchedulerFile extends rtTaskScheduler file_put_contents(self::TMP_FILE.$cid, ''); $count = 0; foreach ($taskList as $task) { - $s = microtime(true); $task = unserialize($task); @@ -223,11 +223,16 @@ class rtTaskSchedulerFile extends rtTaskScheduler continue; } - $task->run(); $e = microtime(true); + //$t = round($e - $s, 2); + + //$logstring = $task->getSubDirectory() . ", " .$cid . ", " . $s . ", " . $e . ", " . $t . "\n"; + + //file_put_contents(self::LOG_FILE, $logstring, FILE_APPEND); + $taskResult = $task->getResult(); //StatusList is an array 'testname=>statusObject' diff --git a/src/taskScheduler/rtTaskTestGroup.php b/src/taskScheduler/rtTaskTestGroup.php index c8a3d6b..6a2ed51 100644 --- a/src/taskScheduler/rtTaskTestGroup.php +++ b/src/taskScheduler/rtTaskTestGroup.php @@ -42,6 +42,10 @@ class rtTaskTestGroup extends rtTask implements rtTaskInterface return true; } + public function getSubDirectory() { + return $this->subDirectory; + } + } diff --git a/src/testgroup/rtGroupConfiguration.php b/src/testgroup/rtGroupConfiguration.php index 95a61cb..45ada45 100644 --- a/src/testgroup/rtGroupConfiguration.php +++ b/src/testgroup/rtGroupConfiguration.php @@ -24,6 +24,8 @@ class rtGroupConfiguration protected $skipFile = ""; protected $hasSkipCode = false; + const SKIP_FILE_NAME = "skip_group_if.inc"; + public function __construct($directory) { @@ -44,7 +46,7 @@ class rtGroupConfiguration $this->environmentVariables = $setup['ENV']; - //Remove trailing white space and add a slash to teh directory name + //Remove trailing white space and add a slash to the directory name //Will need modification if we wanted to be able to give it a list of test directories. $dir = trim($setup['TESTS']); @@ -96,15 +98,14 @@ class rtGroupConfiguration } public function parseConfiguration() { - //Here insert code to read a config file from the test directory that determines whether the set of tests should be run - //in parallel or not? - $this->serialGroup = false; - - + //TODO Could insert code to read a config file from the test directory that determines whether the set of tests should be run + //in parallel or not. + //Code to read the directory skipif, run it and skip the directory - if(file_exists($this->testDirectory. "/skip_group_if.inc")) { + //TODO- this makes a miniscule difference to timing. + if(file_exists($this->testDirectory. "/" . self::SKIP_FILE_NAME)) { $this->hasSkipCode = true; - $this->skipFile = $this->testDirectory."/skip_group_if.inc"; + $this->skipFile = $this->testDirectory. "/" .self::SKIP_FILE_NAME; } return; @@ -129,8 +130,8 @@ class rtGroupConfiguration public function hasSkipCode() { return $this->hasSkipCode; } - public function getSkipFile() { - + public function getSkipFile() { + return $this->skipFile; } } diff --git a/src/testgroup/rtPhpTestGroup.php b/src/testgroup/rtPhpTestGroup.php index 900141c..da16e61 100644 --- a/src/testgroup/rtPhpTestGroup.php +++ b/src/testgroup/rtPhpTestGroup.php @@ -46,8 +46,7 @@ class rtPhpTestGroup extends rtTask implements rtTaskInterface } } - if($this->groupConfiguration->hasSkipCode()) { - + if($this->groupConfiguration->hasSkipCode()) { //If there is some 'skip' code run it to see if the tests should be skipped and then do nothing else $phpCommand = $this->runConfiguration->getSetting('PhpExecutable'); @@ -58,9 +57,10 @@ class rtPhpTestGroup extends rtTask implements rtTaskInterface $runner = new rtPhpRunner($phpCommand); $result = $runner->runphp(); - if (preg_match('/^\s*skip\s*(.+)\s*/i', $result, $matches)) { $this->groupResults->setSkip(true); + $this->groupResults->setAbsTime(microtime(true)); + $this->groupResults->setTime(0); } } @@ -73,9 +73,6 @@ class rtPhpTestGroup extends rtTask implements rtTaskInterface $redirectFromID = $this->groupConfiguration->getRedirectFromID(); foreach ($this->testFiles as $testFileName) { - //echo "\n" .memory_get_usage() . ", setup start". $testFileName . "\n"; - - //testFiles is a list of file names relative to the current working directory if (!file_exists($testFileName)) { echo rtText::get('invalidTestFileName', array($testFileName)); @@ -118,8 +115,7 @@ class rtPhpTestGroup extends rtTask implements rtTaskInterface $this->groupResults->setTestStatus($testFile->getTestName(), $testStatus); } - - //echo "\n" .memory_get_usage() . ", setup complete". $testFileName . "\n"; + } } } @@ -129,6 +125,9 @@ class rtPhpTestGroup extends rtTask implements rtTaskInterface $s=microtime(true); if (count($this->testCases) == 0) { + $e=microtime(true); + $this->groupResults->setTime($e-$s); + $this->groupResults->setAbsTime($e); return; } @@ -151,6 +150,7 @@ class rtPhpTestGroup extends rtTask implements rtTaskInterface $this->groupResults->setTime($e-$s); $this->groupResults->setAbsTime($e); + } public function writeGroup($outType, $cid=null) diff --git a/src/testrun/rtPhpTestRun.php b/src/testrun/rtPhpTestRun.php index f26b1f3..59cf9b6 100644 --- a/src/testrun/rtPhpTestRun.php +++ b/src/testrun/rtPhpTestRun.php @@ -27,6 +27,8 @@ class rtPhpTestRun protected $processorCount; protected $runStartTime; protected $skippedGroups = array(); + protected $logFileName; + protected $groupTasks = false; public function __construct($argv) { @@ -40,7 +42,7 @@ class rtPhpTestRun // check the operation-system (win/unix) $os = (substr(PHP_OS, 0, 3) == "WIN") ? 'Windows' : 'Unix'; - + //Configure the test environment $this->runConfiguration = rtRuntestsConfiguration::getInstance($this->commandLineArguments, $os); $this->runConfiguration->getUserEnvironment(); @@ -71,17 +73,26 @@ class rtPhpTestRun $this->processorCount = $this->requestedProcessorCount(); + if($this->runConfiguration->hasCommandLineOption('log')) { + $this->logFileName = $this->runConfiguration->getCommandLineOption('log'); + file_put_contents($this->logFileName, ""); + } + + if($this->runConfiguration->hasCommandlineOption('g')) { + $this->groupTasks = true; + } + /* * Main decision point. Either we start this with a directory (or set of directories, in which case tests are * run as a group (and in parallel if required) or...... */ if ($this->runConfiguration->getSetting('TestDirectories') != null) { - + $this->doGroupRuns(); - + } else { - + /* *... the input is a test file, or list of files and are just run as single tests * and not in parallel @@ -97,18 +108,18 @@ class rtPhpTestRun if(count($this->redirectedTestCases) > 0) { $this->doRedirectedRuns(); } - + if(($this->numberOfSerialGroups != 0) || ($this->numberOfParallelGroups != 0)) { $this->createRunOutput(); } - + } public function doGroupRuns() { $subDirectories = $this->buildSubDirectoryList($this->runConfiguration->getSetting('TestDirectories')); - - //An array of group configuration objects, one for each subdirectory. + + //An array of group configuration objects, one for each subdirectory. $groupConfigurations = $this->buildGroupConfigurations($subDirectories); @@ -134,11 +145,11 @@ class rtPhpTestRun $parallelGroups[] = $key; } } - + if(isset($serialGroups)) {$this->numberOfSerialGroups = count($serialGroups);} - + $this->numberOfParallelGroups = count($parallelGroups); - + $this->run_parallel_groups($parallelGroups, $groupConfigurations, $this->processorCount); if($this->numberOfSerialGroups > 0) { $this->run_serial_groups($serialGroups, $groupConfigurations); @@ -150,27 +161,33 @@ class rtPhpTestRun public function doRedirectedRuns() { foreach($this->redirectedTestCases as $testCase){ - + $groupConfig = new rtGroupConfiguration(null); $groupConfig->parseRedirect($testCase); - + $group = $groupConfig->getTestDirectory(); - + $this->run_serial_groups(array($group), array($group=>$groupConfig)); - + $this->numberOfSerialGroups++; } } public function run_parallel_groups($testDirectories, $groupConfigurations, $processCount) { - - // create the task-list + + //Create the task list to be executed in parallel. Either randomly or trying to order it. $taskList = array(); - foreach($testDirectories as $testGroup) { - $taskList[] = new rtTaskTestGroup($this->runConfiguration, $testGroup, $groupConfigurations[$testGroup]); + if($this->groupTasks == false) { + foreach($testDirectories as $testGroup) { + $taskList[] = new rtTaskTestGroup($this->runConfiguration, $testGroup, $groupConfigurations[$testGroup]); + } + }else { + $taskList = $this->groupTasksByWeight($testDirectories, $groupConfigurations); + } + // run the task-scheduler $scheduler = rtTaskScheduler::getInstance(); $scheduler->setTaskList($taskList); @@ -179,24 +196,29 @@ class rtPhpTestRun $scheduler->run(); foreach($scheduler->getResultList() as $groupResult) { - - if($groupResult->isSkipGroup()) { - $this->skippedGroups[] = $groupResult->getGroupName(); + + if($groupResult->isSkipGroup()) { + $this->skippedGroups[] = $groupResult->getGroupName(); } else { $this->resultList[] = $groupResult->getTestStatusList(); } - // Debug - get which group was run by which processor and how long each took - // + // Logging - get which group was run by which processor and how long each took - if($this->runConfiguration->hasCommandLineOption('debug')) { - $time = round($groupResult->getTime(), 2); + if($this->runConfiguration->hasCommandLineOption('log')) { - $absTime = $groupResult->getAbsTime() - $this->runStartTime; + $groupTime = round($groupResult->getTime(), 2); + $runTime = $groupResult->getAbsTime() - $this->runStartTime; + + $runTime = round($runTime, 2); - $absTime = round($absTime, 2); + $string = "PARLOG," . $groupResult->getGroupName() . + "," . $groupTime . + "," . $runTime . + "," . $groupResult->getProcessorId() . + "," . $groupResult->getRunOrder() ."\n"; - echo "\nPARDBG," . $absTime. "," . $time . "," . $groupResult->getProcessorId() . "," . $groupResult->getRunOrder() . "," . $groupResult->getGroupName(); + file_put_contents($this->logFileName, $string, FILE_APPEND); } @@ -212,68 +234,61 @@ class rtPhpTestRun public function run_serial_groups($testDirectories, $groupConfigurations) { - $count = 0; - - foreach($testDirectories as $subDirectory) { + $groupCount = 0; - // Memory usage debugging - //$startm = memory_get_usage(); + foreach($testDirectories as $subDirectory) { - + // Memory usage logging + $startMemory = memory_get_usage(); $testGroup = new rtPhpTestGroup($this->runConfiguration, $subDirectory, $groupConfigurations[$subDirectory]); - - - if($testGroup->isSkipGroup() == true) { + + + if($testGroup->isSkipGroup() === true) { $this->skippedGroups[] = $testGroup->getGroupName(); - } else { - + continue; + } - $testGroup->run(); - - - // Memory usage debugging - //$midm = memory_get_usage(); + $testGroup->run(); - rtTestOutputWriter::flushResult($testGroup->getGroupResults()->getTestStatusList(), $this->reportStatus); - $this->resultList[] = $testGroup->getGroupResults()->getTestStatusList(); + rtTestOutputWriter::flushResult($testGroup->getGroupResults()->getTestStatusList(), $this->reportStatus); + $this->resultList[] = $testGroup->getGroupResults()->getTestStatusList(); + + if($this->runConfiguration->hasCommandLineOption('log')) { - if($this->runConfiguration->hasCommandLineOption('debug')) { - - $time = round($testGroup->getGroupResults()->getTime(), 2); + $time = round($testGroup->getGroupResults()->getTime(), 2); - $absTime = ($testGroup->getGroupResults()->getAbsTime()) - $this->runStartTime; - $absTime = round($absTime, 2); + $absTime = ($testGroup->getGroupResults()->getAbsTime()) - $this->runStartTime; + + $absTime = round($absTime, 2); + + $string = "SERLOG," . $testGroup->getGroupName() . "," . + $time . "," . + $absTime . "," . + $testGroup->getGroupResults()->getProcessorId() . "," . + $groupCount . "\n"; - echo "\nSERDBG," . $absTime . "," . $time . "," . $testGroup->getGroupResults()->getProcessorId() . "," . $count . "," . $testGroup->getGroupResults()->getGroupName(); + file_put_contents($this->logFileName, $string, FILE_APPEND); - } - - // Memory usage debugging - //$midm2 = memory_get_usage(); - - $redirects = $testGroup->getGroupResults()->getRedirectedTestCases(); - foreach($redirects as $testCase) { - $this->redirectedTestCases[] = $testCase; - } - - - - // Memory usage debugging - //$midm3 = memory_get_usage(); - - - $testGroup->__destruct(); - unset($testGroup); - - // Memory usage debugging - //echo "\n" . $startm . ", " . $midm. ", " .$midm2. ", " .$midm3. ", " .memory_get_usage() . ", ". $subDirectory . "\n"; - $count++; } - } + + $redirects = $testGroup->getGroupResults()->getRedirectedTestCases(); + foreach($redirects as $testCase) { + $this->redirectedTestCases[] = $testCase; + } - //xdebug_stop_trace(); + + $testGroup->__destruct(); + unset($testGroup); + + if($this->runConfiguration->hasCommandLineOption('log')) { + $string = "MEMLOG," . $subDirectory . ", " . $startMemory. ", " .memory_get_usage() . "\n"; + file_put_contents($this->logFileName, $string, FILE_APPEND); + } + + $groupCount++; + } } public function run_tests($testNames) { @@ -286,19 +301,19 @@ class rtPhpTestRun echo rtText::get('invalidTestFileName', array($testName)); exit(); } - - + + //Read the test file $testFile = new rtPhpTestFile(); $testFile->doRead($testName); $testFile->normaliseLineEndings(); - + $testStatus = new rtTestStatus($testFile->getTestName()); if ($testFile->arePreconditionsMet()) { $testCase = new rtPhpTest($testFile->getContents(), $testFile->getTestName(), $testFile->getSectionHeadings(), $this->runConfiguration, $testStatus); - + //Setup and set the local environment for the test case $testCase->executeTest($this->runConfiguration); @@ -307,12 +322,12 @@ class rtPhpTestRun $summaryResults = array($testFile->getTestName() => $results->getStatus()); } elseif (in_array("REDIRECTTEST", $testFile->getSectionHeadings())) { - + //Redirect handler //Build a list of redirected test cases - + $this->redirectedTestCases[] = new rtPhpTest($testFile->getContents(), $testFile->getTestName(), $testFile->getSectionHeadings(), $this->runConfiguration, $testStatus); - + $testStatus->setTrue('redirected'); $testStatus->setMessage('redirected', $testFile->getExitMessage()); $summaryResults = array($testFile->getTestName() => $testStatus); @@ -324,7 +339,7 @@ class rtPhpTestRun } rtTestOutputWriter::flushResult($summaryResults, 3); - + } } @@ -419,6 +434,55 @@ class rtPhpTestRun } return $groupSummary; } + /* + * This, invoked by using the -g command line option, + * makes an attempt to distribute tests evenly across the processors + * based on 'weightings'. The weightings are actually just timings from a previous + * parallel run. + */ + public function groupTasksByWeight($testDirectories, $groupConfigurations) { + + //First set weights. + $weightedDirectoryList = array(); + $processorWeightSum = array(); + $taskListByProcessor = array(); + + foreach($testDirectories as $subDir) { + $key = rtUtil::stripPath($subDir); + if($this->runConfiguration->hasWeight($key)) { + $weightedDirectoryList[$subDir] = $this->runConfiguration->getWeight($key); + } else { + $weightedDirectoryList[$subDir] = 1; + } + } + + //Order subditrectories by decreasing weight. + arsort($weightedDirectoryList, SORT_NUMERIC); + + //Assign the first n tasks across n processors. Having ordered the tasls these will be the longest running tasks + for($i=0; $i<$this->processorCount; $i++) { + list($key, $value) = each($weightedDirectoryList); + $processorWeightSum[$i] =$value; + $task = new rtTaskTestGroup($this->runConfiguration, $key, $groupConfigurations[$key]); + $taskListByProcessor[$i] = array($task); + } + //Continue to assign tasks to processors based on an estimate of how long each one will take. + for ($i=$this->processorCount; $i<count($weightedDirectoryList); $i++) { + list($key, $value) = each($weightedDirectoryList); + $procID = rtUtil::getMin($processorWeightSum); + $processorWeightSum[$procID] += $value; + $task = new rtTaskTestGroup($this->runConfiguration, $key, $groupConfigurations[$key]); + array_push($taskListByProcessor[$procID], $task); + } + + //Reverse the order of even numbered lists so that not all processors are runnung big tasks at the same time + //This seems to work better - possibly the longer running tasks are resource constrined by something other than CPU? + for($i=0; $i<$this->processorCount; $i+=2) { + $taskListByProcessor[$procID] = array_reverse($taskListByProcessor[$procID]); + } + + return $taskListByProcessor; + } } ?> diff --git a/tests/rtGroupConfigurationTest.php b/tests/rtGroupConfigurationTest.php index 7d4d6cd..def901b 100644 --- a/tests/rtGroupConfigurationTest.php +++ b/tests/rtGroupConfigurationTest.php @@ -8,12 +8,12 @@ class rtGroupConfigurationTest extends PHPUnit_Framework_TestCase protected $path_to_group; public function setUp() { - $this->path_to_group = realpath(dirname(__FILE__) . '/../phpt-tests/group_of_tests'); + $this->path_to_group = realpath(dirname(__FILE__) . '/../phpt-tests/group_of_tests/'); } public function testCreateInstance() { - $config = rtRuntestsConfiguration::getInstance(array('run-tests.php', '-p', RT_PHP_PATH, 'testgroup')); + $config = rtRuntestsConfiguration::getInstance(array('run-tests.php', '-p', RT_PHP_PATH, $this->path_to_group)); $config->configure();