diff --git a/src/Cli.php b/src/Cli.php index 9fc906f..2ef2183 100644 --- a/src/Cli.php +++ b/src/Cli.php @@ -26,6 +26,7 @@ use Symfony\Component\Console\Output\OutputInterface; use function JBZoo\Utils\bool; +use function JBZoo\Utils\int; /** * Class CliHelper @@ -75,6 +76,8 @@ class Cli */ private int $prevMemory; + private ?int $numberOfCpuCores = null; + /** * @param InputInterface $input * @param OutputInterface $output @@ -384,4 +387,53 @@ public function isProgressBarDisabled(): bool { return bool($this->getInput()->getOption('no-progress')); } + + /** + * @return int + * @see https://github.com/phpstan/phpstan-src/blob/f8be122188/src/Process/CpuCoreCounter.php + */ + public function getNumberOfCpuCores(): int + { + if ($this->numberOfCpuCores !== null) { + return $this->numberOfCpuCores; + } + + if (!\function_exists('proc_open')) { + return $this->numberOfCpuCores = 1; + } + + // from brianium/paratest + // Linux (and potentially Windows with linux sub systems) + if (\is_file('/proc/cpuinfo')) { + $cpuinfo = \file_get_contents('/proc/cpuinfo'); + if ($cpuinfo !== false) { + \preg_match_all('/^processor/m', $cpuinfo, $matches); + return $this->numberOfCpuCores = \count($matches[0]); + } + } + + // Windows + if (\DIRECTORY_SEPARATOR === '\\') { + $process = \popen('wmic cpu get NumberOfLogicalProcessors', 'rb'); + if (\is_resource($process)) { + /** @phan-suppress-next-line PhanPluginUseReturnValueInternalKnown */ + \fgets($process); + $cores = int(\fgets($process)); + \pclose($process); + + return $this->numberOfCpuCores = $cores; + } + } + + // *nix (Linux, BSD and Mac) + $process = \popen('sysctl -n hw.ncpu', 'rb'); + if (\is_resource($process)) { + $cores = int(\fgets($process)); + \pclose($process); + + return $this->numberOfCpuCores = $cores; + } + + return $this->numberOfCpuCores = 2; + } } diff --git a/src/CliCommandMultiProc.php b/src/CliCommandMultiProc.php index aa714e2..3c65cd5 100644 --- a/src/CliCommandMultiProc.php +++ b/src/CliCommandMultiProc.php @@ -24,16 +24,17 @@ use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Process\Process; +use function JBZoo\Utils\int; + /** * Class CliCommandMultiProc * @package JBZoo\Cli */ abstract class CliCommandMultiProc extends CliCommand { - private const PM_DEFAULT_MAX_PROCESSES = 20; - private const PM_DEFAULT_INTERVAL = 100; - private const PM_DEFAULT_START_DELAY = 1; - private const PM_DEFAULT_TIMEOUT = 7200; + private const PM_DEFAULT_INTERVAL = 100; + private const PM_DEFAULT_START_DELAY = 1; + private const PM_DEFAULT_TIMEOUT = 7200; /** * @var array @@ -56,7 +57,7 @@ protected function configure(): void null, InputOption::VALUE_REQUIRED, 'Process Manager. The number of processes to execute in parallel (os isolated processes)', - self::PM_DEFAULT_MAX_PROCESSES + 'auto' ) ->addOption( 'pm-interval', @@ -137,8 +138,19 @@ protected function executeAction(): int */ protected function executeMultiProcessAction(): int { + $procNum = $this->getNumberOfProcesses(); + $cpuCores = $this->helper->getNumberOfCpuCores(); + $this->_("Max number of sub-processes: {$procNum}", OutLvl::DEBUG); + if ($procNum > $cpuCores) { + $this->_( + "The specified number of processes (--pm-max={$procNum}) " + . "is more than the found number of CPU cores in the system ({$cpuCores}).", + OutLvl::WARNING + ); + } + $procManager = $this->initProcManager( - $this->getOptInt('pm-max') ?: self::PM_DEFAULT_MAX_PROCESSES, + $procNum, $this->getOptInt('pm-interval') ?: self::PM_DEFAULT_INTERVAL, $this->getOptInt('pm-start-delay') ?: self::PM_DEFAULT_START_DELAY ); @@ -343,4 +355,21 @@ private function getMaxTimeout(): int { return $this->getOptInt('pm-max-timeout') ?: self::PM_DEFAULT_TIMEOUT; } + + /** + * @return int + */ + private function getNumberOfProcesses(): int + { + $pmMax = \strtolower($this->getOptString('pm-max')); + $cpuCores = $this->helper->getNumberOfCpuCores(); + + if ($pmMax === 'auto') { + return $cpuCores; + } + + $pmMaxInt = \abs(int($pmMax)); + + return $pmMaxInt > 0 ? $pmMaxInt : $cpuCores; + } } diff --git a/tests/Cli/CliMultiProcessTest.php b/tests/Cli/CliMultiProcessTest.php index bb16089..55dc3d2 100644 --- a/tests/Cli/CliMultiProcessTest.php +++ b/tests/Cli/CliMultiProcessTest.php @@ -30,7 +30,7 @@ public function testAsRealExecution() $start = microtime(true); $result = Helper::executeReal( 'test:sleep-multi 123 " qwerty "', - ['sleep' => 1, 'no-progress' => null], + ['sleep' => 1, 'no-progress' => null, 'pm-max' => 50], 'JBZOO_TEST_VAR=123456' ); @@ -61,7 +61,7 @@ public function testAsRealExecution() public function testAsVirtalExecution() { $start = microtime(true); - $result = Helper::executeVirtaul('test:sleep-multi', ['sleep' => 1, 'no-progress' => null]); + $result = Helper::executeVirtaul('test:sleep-multi', ['sleep' => 1, 'no-progress' => null, 'pm-max' => 5]); $time = microtime(true) - $start; $outputAsArray = json($result)->getArrayCopy(); @@ -88,7 +88,10 @@ public function testAsVirtalExecution() public function testException() { $start = microtime(true); - $result = Helper::executeReal('test:sleep-multi 123 456 789', ['sleep' => 2, 'no-progress' => null]); + $result = Helper::executeReal( + 'test:sleep-multi 123 456 789', + ['sleep' => 2, 'no-progress' => null, 'pm-max' => 5] + ); $time = microtime(true) - $start; $outputAsArray = json($result[1])->getArrayCopy(); @@ -112,4 +115,19 @@ public function testException() isTrue($time < 5, "Total time: {$time}"); } + + public function testNumberOfCpuCores() + { + $result = Helper::executeReal( + 'test:sleep-multi 123 " qwerty "', + ['sleep' => 1, 'no-progress' => null, 'pm-max' => 50, '-vvv' => null] + ); + + isContain('Debug: Max number of sub-processes: 50', $result[1]); + isContain( + 'Warning: The specified number of processes (--pm-max=50) ' + . 'is more than the found number of CPU cores in the system', + $result[1] + ); + } }