├── .envrc ├── LICENSE.md ├── README.md ├── bin ├── diagnose.php ├── execute.php └── trace.php ├── composer.json └── src ├── CpuCoreCounter.php ├── Diagnoser.php ├── Executor ├── ProcOpenExecutor.php └── ProcessExecutor.php ├── Finder ├── CmiCmdletLogicalFinder.php ├── CmiCmdletPhysicalFinder.php ├── CpuCoreFinder.php ├── CpuInfoFinder.php ├── DummyCpuCoreFinder.php ├── EnvVariableFinder.php ├── FinderRegistry.php ├── HwLogicalFinder.php ├── HwPhysicalFinder.php ├── LscpuLogicalFinder.php ├── LscpuPhysicalFinder.php ├── NProcFinder.php ├── NProcessorFinder.php ├── NullCpuCoreFinder.php ├── OnlyInPowerShellFinder.php ├── OnlyOnOSFamilyFinder.php ├── ProcOpenBasedFinder.php ├── SkipOnOSFamilyFinder.php ├── WindowsRegistryLogicalFinder.php ├── WmicLogicalFinder.php ├── WmicPhysicalFinder.php └── _NProcessorFinder.php ├── NumberOfCpuCoreNotFound.php └── ParallelisationResult.php /.envrc: -------------------------------------------------------------------------------- 1 | use nix --packages \ 2 | gnumake \ 3 | yamllint 4 | 5 | source_env_if_exists .envrc.local 6 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Théo FIDRY 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 6 | documentation files (the _Software_), to deal in the Software without restriction, including without limitation the 7 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit 8 | persons to whom the Software is furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the 11 | Software. 12 | 13 | THE SOFTWARE IS PROVIDED **AS IS**, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE 14 | WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 15 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 16 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 17 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CPU Core Counter 2 | 3 | This package is a tiny utility to get the number of CPU cores. 4 | 5 | ```sh 6 | composer require fidry/cpu-core-counter 7 | ``` 8 | 9 | 10 | ## Usage 11 | 12 | ```php 13 | use Fidry\CpuCoreCounter\CpuCoreCounter; 14 | use Fidry\CpuCoreCounter\NumberOfCpuCoreNotFound; 15 | use Fidry\CpuCoreCounter\Finder\DummyCpuCoreFinder; 16 | 17 | $counter = new CpuCoreCounter(); 18 | 19 | // For knowing the number of cores you can use for launching parallel processes: 20 | $counter->getAvailableForParallelisation()->availableCpus; 21 | 22 | // Get the number of CPU cores (by default it will use the logical cores count): 23 | try { 24 | $counter->getCount(); // e.g. 8 25 | } catch (NumberOfCpuCoreNotFound) { 26 | return 1; // Fallback value 27 | } 28 | 29 | // An alternative form where we not want to catch the exception: 30 | 31 | $counter = new CpuCoreCounter([ 32 | ...CpuCoreCounter::getDefaultFinders(), 33 | new DummyCpuCoreFinder(1), // Fallback value 34 | ]); 35 | 36 | // A type-safe alternative form: 37 | $counter->getCountWithFallback(1); 38 | 39 | // Note that the result is memoized. 40 | $counter->getCount(); // e.g. 8 41 | 42 | ``` 43 | 44 | 45 | ## Advanced usage 46 | 47 | ### Changing the finders 48 | 49 | When creating `CpuCoreCounter`, you may want to change the order of the finders 50 | used or disable a specific finder. You can easily do so by passing the finders 51 | you want 52 | 53 | ```php 54 | // Remove WindowsWmicFinder 55 | $finders = array_filter( 56 | CpuCoreCounter::getDefaultFinders(), 57 | static fn (CpuCoreFinder $finder) => !($finder instanceof WindowsWmicFinder) 58 | ); 59 | 60 | $cores = (new CpuCoreCounter($finders))->getCount(); 61 | ``` 62 | 63 | ```php 64 | // Use CPUInfo first & don't use Nproc 65 | $finders = [ 66 | new CpuInfoFinder(), 67 | new WindowsWmicFinder(), 68 | new HwLogicalFinder(), 69 | ]; 70 | 71 | $cores = (new CpuCoreCounter($finders))->getCount(); 72 | ``` 73 | 74 | ### Choosing only logical or physical finders 75 | 76 | `FinderRegistry` provides two helpful entries: 77 | 78 | - `::getDefaultLogicalFinders()`: gives an ordered list of finders that will 79 | look for the _logical_ CPU cores count. 80 | - `::getDefaultPhysicalFinders()`: gives an ordered list of finders that will 81 | look for the _physical_ CPU cores count. 82 | 83 | By default, when using `CpuCoreCounter`, it will use the logical finders since 84 | it is more likely what you are looking for and is what is used by PHP source to 85 | build the PHP binary. 86 | 87 | 88 | ### Checks what finders find what on your system 89 | 90 | You have three scrips available that provides insight about what the finders 91 | can find: 92 | 93 | ```shell 94 | # Checks what each given finder will find on your system with details about the 95 | # information it had. 96 | make diagnose # From this repository 97 | ./vendor/fidry/cpu-core-counter/bin/diagnose.php # From the library 98 | ``` 99 | 100 | And: 101 | ```shell 102 | # Execute all finders and display the result they found. 103 | make execute # From this repository 104 | ./vendor/fidry/cpu-core-counter/bin/execute.php # From the library 105 | ``` 106 | 107 | 108 | ### Debug the results found 109 | 110 | You have 3 methods available to help you find out what happened: 111 | 112 | 1. If you are using the default configuration of finder registries, you can check 113 | the previous section which will provide plenty of information. 114 | 2. If what you are interested in is how many CPU cores were found, you can use 115 | the `CpuCoreCounter::trace()` method. 116 | 3. If what you are interested in is how the calculation of CPU cores available 117 | for parallelisation was done, you can inspect the values of `ParallelisationResult` 118 | returned by `CpuCoreCounter::getAvailableForParallelisation()`. 119 | 120 | 121 | ## Backward Compatibility Promise (BCP) 122 | 123 | The policy is for the major part following the same as [Symfony's one][symfony-bc-policy]. 124 | Note that the code marked as `@private` or `@internal` are excluded from the BCP. 125 | 126 | The following elements are also excluded: 127 | 128 | - The `diagnose` and `execute` commands: those are for debugging/inspection purposes only 129 | - `FinderRegistry::get*Finders()`: new finders may be added or the order of finders changed at any time 130 | 131 | 132 | ## License 133 | 134 | This package is licensed using the MIT License. 135 | 136 | Please have a look at [`LICENSE.md`](LICENSE.md). 137 | 138 | [symfony-bc-policy]: https://symfony.com/doc/current/contributing/code/bc.html 139 | -------------------------------------------------------------------------------- /bin/diagnose.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | use Fidry\CpuCoreCounter\Diagnoser; 16 | use Fidry\CpuCoreCounter\Finder\FinderRegistry; 17 | 18 | require_once __DIR__.'/../vendor/autoload.php'; 19 | 20 | echo 'Running diagnosis...'.PHP_EOL.PHP_EOL; 21 | echo Diagnoser::diagnose(FinderRegistry::getAllVariants()).PHP_EOL; 22 | 23 | echo 'Logical CPU cores finders...'.PHP_EOL.PHP_EOL; 24 | echo Diagnoser::diagnose(FinderRegistry::getDefaultLogicalFinders()).PHP_EOL; 25 | 26 | echo 'Physical CPU cores finders...'.PHP_EOL.PHP_EOL; 27 | echo Diagnoser::diagnose(FinderRegistry::getDefaultPhysicalFinders()).PHP_EOL; 28 | -------------------------------------------------------------------------------- /bin/execute.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | use Fidry\CpuCoreCounter\Diagnoser; 16 | use Fidry\CpuCoreCounter\Finder\FinderRegistry; 17 | 18 | require_once __DIR__.'/../vendor/autoload.php'; 19 | 20 | echo 'Executing finders...'.PHP_EOL.PHP_EOL; 21 | echo Diagnoser::execute(FinderRegistry::getAllVariants()).PHP_EOL; 22 | -------------------------------------------------------------------------------- /bin/trace.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 8 | * 9 | * For the full copyright and license information, please view the LICENSE 10 | * file that was distributed with this source code. 11 | */ 12 | 13 | declare(strict_types=1); 14 | 15 | use Fidry\CpuCoreCounter\CpuCoreCounter; 16 | use Fidry\CpuCoreCounter\Finder\FinderRegistry; 17 | 18 | require_once __DIR__.'/../vendor/autoload.php'; 19 | 20 | $separator = str_repeat('–', 80); 21 | 22 | echo 'With all finders...'.PHP_EOL.PHP_EOL; 23 | echo (new CpuCoreCounter(FinderRegistry::getAllVariants()))->trace().PHP_EOL; 24 | echo $separator.PHP_EOL.PHP_EOL; 25 | 26 | echo 'Logical CPU cores finders...'.PHP_EOL.PHP_EOL; 27 | echo (new CpuCoreCounter(FinderRegistry::getDefaultLogicalFinders()))->trace().PHP_EOL; 28 | echo $separator.PHP_EOL.PHP_EOL; 29 | 30 | echo 'Physical CPU cores finders...'.PHP_EOL.PHP_EOL; 31 | echo (new CpuCoreCounter(FinderRegistry::getDefaultPhysicalFinders()))->trace().PHP_EOL; 32 | echo $separator.PHP_EOL.PHP_EOL; 33 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fidry/cpu-core-counter", 3 | "description": "Tiny utility to get the number of CPU cores.", 4 | "license": "MIT", 5 | "type": "library", 6 | "keywords": [ 7 | "cpu", 8 | "core" 9 | ], 10 | "authors": [ 11 | { 12 | "name": "Théo FIDRY", 13 | "email": "theo.fidry@gmail.com" 14 | } 15 | ], 16 | "require": { 17 | "php": "^7.2 || ^8.0" 18 | }, 19 | "require-dev": { 20 | "fidry/makefile": "^0.2.0", 21 | "fidry/php-cs-fixer-config": "^1.1.2", 22 | "phpstan/extension-installer": "^1.2.0", 23 | "phpstan/phpstan": "^1.9.2", 24 | "phpstan/phpstan-deprecation-rules": "^1.0.0", 25 | "phpstan/phpstan-phpunit": "^1.2.2", 26 | "phpstan/phpstan-strict-rules": "^1.4.4", 27 | "phpunit/phpunit": "^8.5.31 || ^9.5.26", 28 | "webmozarts/strict-phpunit": "^7.5" 29 | }, 30 | "autoload": { 31 | "psr-4": { 32 | "Fidry\\CpuCoreCounter\\": "src/" 33 | } 34 | }, 35 | "autoload-dev": { 36 | "psr-4": { 37 | "Fidry\\CpuCoreCounter\\Test\\": "tests/" 38 | } 39 | }, 40 | "config": { 41 | "allow-plugins": { 42 | "ergebnis/composer-normalize": true, 43 | "infection/extension-installer": true, 44 | "phpstan/extension-installer": true 45 | }, 46 | "sort-packages": true 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/CpuCoreCounter.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter; 15 | 16 | use Fidry\CpuCoreCounter\Finder\CpuCoreFinder; 17 | use Fidry\CpuCoreCounter\Finder\EnvVariableFinder; 18 | use Fidry\CpuCoreCounter\Finder\FinderRegistry; 19 | use InvalidArgumentException; 20 | use function implode; 21 | use function max; 22 | use function sprintf; 23 | use function sys_getloadavg; 24 | use const PHP_EOL; 25 | 26 | final class CpuCoreCounter 27 | { 28 | /** 29 | * @var list 30 | */ 31 | private $finders; 32 | 33 | /** 34 | * @var positive-int|null 35 | */ 36 | private $count; 37 | 38 | /** 39 | * @param list|null $finders 40 | */ 41 | public function __construct(?array $finders = null) 42 | { 43 | $this->finders = $finders ?? FinderRegistry::getDefaultLogicalFinders(); 44 | } 45 | 46 | /** 47 | * @param positive-int|0 $reservedCpus Number of CPUs to reserve. This is useful when you want 48 | * to reserve some CPUs for other processes. If the main 49 | * process is going to be busy still, you may want to set 50 | * this value to 1. 51 | * @param non-zero-int|null $countLimit The maximum number of CPUs to return. If not provided, it 52 | * may look for a limit in the environment variables, e.g. 53 | * KUBERNETES_CPU_LIMIT. If negative, the limit will be 54 | * the total number of cores found minus the absolute value. 55 | * For instance if the system has 10 cores and countLimit=-2, 56 | * then the effective limit considered will be 8. 57 | * @param float|null $loadLimit Element of [0., 1.]. Percentage representing the 58 | * amount of cores that should be used among the available 59 | * resources. For instance, if set to 0.7, it will use 70% 60 | * of the available cores, i.e. if 1 core is reserved, 11 61 | * cores are available and 5 are busy, it will use 70% 62 | * of (11-1-5)=5 cores, so 3 cores. Set this parameter to null 63 | * to skip this check. Beware that 1 does not mean "no limit", 64 | * but 100% of the _available_ resources, i.e. with the 65 | * previous example, it will return 5 cores. How busy is 66 | * the system is determined by the system load average 67 | * (see $systemLoadAverage). 68 | * @param float|null $systemLoadAverage The system load average. If passed, it will use 69 | * this information to limit the available cores based 70 | * on the _available_ resources. For instance, if there 71 | * is 10 cores but 3 are busy, then only 7 cores will 72 | * be considered for further calculation. If set to 73 | * `null`, it will use `sys_getloadavg()` to check the 74 | * load of the system in the past minute. You can 75 | * otherwise pass an arbitrary value. Should be a 76 | * positive float. 77 | * 78 | * @see https://php.net/manual/en/function.sys-getloadavg.php 79 | */ 80 | public function getAvailableForParallelisation( 81 | int $reservedCpus = 0, 82 | ?int $countLimit = null, 83 | ?float $loadLimit = null, 84 | ?float $systemLoadAverage = 0. 85 | ): ParallelisationResult { 86 | self::checkCountLimit($countLimit); 87 | self::checkLoadLimit($loadLimit); 88 | self::checkSystemLoadAverage($systemLoadAverage); 89 | 90 | $totalCoreCount = $this->getCountWithFallback(1); 91 | $availableCores = max(1, $totalCoreCount - $reservedCpus); 92 | 93 | // Adjust available CPUs based on current load 94 | if (null !== $loadLimit) { 95 | $correctedSystemLoadAverage = null === $systemLoadAverage 96 | ? sys_getloadavg()[0] ?? 0. 97 | : $systemLoadAverage; 98 | 99 | $availableCores = max( 100 | 1, 101 | $loadLimit * ($availableCores - $correctedSystemLoadAverage) 102 | ); 103 | } 104 | 105 | if (null === $countLimit) { 106 | $correctedCountLimit = self::getKubernetesLimit(); 107 | } else { 108 | $correctedCountLimit = $countLimit > 0 109 | ? $countLimit 110 | : max(1, $totalCoreCount + $countLimit); 111 | } 112 | 113 | if (null !== $correctedCountLimit && $availableCores > $correctedCountLimit) { 114 | $availableCores = $correctedCountLimit; 115 | } 116 | 117 | return new ParallelisationResult( 118 | $reservedCpus, 119 | $countLimit, 120 | $loadLimit, 121 | $systemLoadAverage, 122 | $correctedCountLimit, 123 | $correctedSystemLoadAverage ?? $systemLoadAverage, 124 | $totalCoreCount, 125 | (int) $availableCores 126 | ); 127 | } 128 | 129 | /** 130 | * @throws NumberOfCpuCoreNotFound 131 | * 132 | * @return positive-int 133 | */ 134 | public function getCount(): int 135 | { 136 | // Memoize result 137 | if (null === $this->count) { 138 | $this->count = $this->findCount(); 139 | } 140 | 141 | return $this->count; 142 | } 143 | 144 | /** 145 | * @param positive-int $fallback 146 | * 147 | * @return positive-int 148 | */ 149 | public function getCountWithFallback(int $fallback): int 150 | { 151 | try { 152 | return $this->getCount(); 153 | } catch (NumberOfCpuCoreNotFound $exception) { 154 | return $fallback; 155 | } 156 | } 157 | 158 | /** 159 | * This method is mostly for debugging purposes. 160 | */ 161 | public function trace(): string 162 | { 163 | $output = []; 164 | 165 | foreach ($this->finders as $finder) { 166 | $output[] = sprintf( 167 | 'Executing the finder "%s":', 168 | $finder->toString() 169 | ); 170 | $output[] = $finder->diagnose(); 171 | 172 | $cores = $finder->find(); 173 | 174 | if (null !== $cores) { 175 | $output[] = 'Result found: '.$cores; 176 | 177 | break; 178 | } 179 | 180 | $output[] = '–––'; 181 | } 182 | 183 | return implode(PHP_EOL, $output); 184 | } 185 | 186 | /** 187 | * @throws NumberOfCpuCoreNotFound 188 | * 189 | * @return positive-int 190 | */ 191 | private function findCount(): int 192 | { 193 | foreach ($this->finders as $finder) { 194 | $cores = $finder->find(); 195 | 196 | if (null !== $cores) { 197 | return $cores; 198 | } 199 | } 200 | 201 | throw NumberOfCpuCoreNotFound::create(); 202 | } 203 | 204 | /** 205 | * @throws NumberOfCpuCoreNotFound 206 | * 207 | * @return array{CpuCoreFinder, positive-int} 208 | */ 209 | public function getFinderAndCores(): array 210 | { 211 | foreach ($this->finders as $finder) { 212 | $cores = $finder->find(); 213 | 214 | if (null !== $cores) { 215 | return [$finder, $cores]; 216 | } 217 | } 218 | 219 | throw NumberOfCpuCoreNotFound::create(); 220 | } 221 | 222 | /** 223 | * @return positive-int|null 224 | */ 225 | public static function getKubernetesLimit(): ?int 226 | { 227 | $finder = new EnvVariableFinder('KUBERNETES_CPU_LIMIT'); 228 | 229 | return $finder->find(); 230 | } 231 | 232 | private static function checkCountLimit(?int $countLimit): void 233 | { 234 | if (0 === $countLimit) { 235 | throw new InvalidArgumentException( 236 | 'The count limit must be a non zero integer. Got "0".' 237 | ); 238 | } 239 | } 240 | 241 | private static function checkLoadLimit(?float $loadLimit): void 242 | { 243 | if (null === $loadLimit) { 244 | return; 245 | } 246 | 247 | if ($loadLimit < 0. || $loadLimit > 1.) { 248 | throw new InvalidArgumentException( 249 | sprintf( 250 | 'The load limit must be in the range [0., 1.], got "%s".', 251 | $loadLimit 252 | ) 253 | ); 254 | } 255 | } 256 | 257 | private static function checkSystemLoadAverage(?float $systemLoadAverage): void 258 | { 259 | if (null !== $systemLoadAverage && $systemLoadAverage < 0.) { 260 | throw new InvalidArgumentException( 261 | sprintf( 262 | 'The system load average must be a positive float, got "%s".', 263 | $systemLoadAverage 264 | ) 265 | ); 266 | } 267 | } 268 | } 269 | -------------------------------------------------------------------------------- /src/Diagnoser.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter; 15 | 16 | use Fidry\CpuCoreCounter\Finder\CpuCoreFinder; 17 | use function array_map; 18 | use function explode; 19 | use function implode; 20 | use function max; 21 | use function str_repeat; 22 | use const PHP_EOL; 23 | 24 | /** 25 | * Utility to debug. 26 | * 27 | * @private 28 | */ 29 | final class Diagnoser 30 | { 31 | /** 32 | * Provides an aggregated diagnosis based on each finders diagnosis. 33 | * 34 | * @param list $finders 35 | */ 36 | public static function diagnose(array $finders): string 37 | { 38 | $diagnoses = array_map( 39 | static function (CpuCoreFinder $finder): string { 40 | return self::diagnoseFinder($finder); 41 | }, 42 | $finders 43 | ); 44 | 45 | return implode(PHP_EOL, $diagnoses); 46 | } 47 | 48 | /** 49 | * Executes each finders. 50 | * 51 | * @param list $finders 52 | */ 53 | public static function execute(array $finders): string 54 | { 55 | $diagnoses = array_map( 56 | static function (CpuCoreFinder $finder): string { 57 | $coresCount = $finder->find(); 58 | 59 | return implode( 60 | '', 61 | [ 62 | $finder->toString(), 63 | ': ', 64 | null === $coresCount ? 'NULL' : $coresCount, 65 | ] 66 | ); 67 | }, 68 | $finders 69 | ); 70 | 71 | return implode(PHP_EOL, $diagnoses); 72 | } 73 | 74 | private static function diagnoseFinder(CpuCoreFinder $finder): string 75 | { 76 | $diagnosis = $finder->diagnose(); 77 | 78 | $maxLineLength = max( 79 | array_map( 80 | 'strlen', 81 | explode(PHP_EOL, $diagnosis) 82 | ) 83 | ); 84 | 85 | $separator = str_repeat('-', $maxLineLength); 86 | 87 | return implode( 88 | '', 89 | [ 90 | $finder->toString().':'.PHP_EOL, 91 | $separator.PHP_EOL, 92 | $diagnosis.PHP_EOL, 93 | $separator.PHP_EOL, 94 | ] 95 | ); 96 | } 97 | 98 | private function __construct() 99 | { 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/Executor/ProcOpenExecutor.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter\Executor; 15 | 16 | use function fclose; 17 | use function function_exists; 18 | use function is_resource; 19 | use function proc_close; 20 | use function proc_open; 21 | use function stream_get_contents; 22 | 23 | final class ProcOpenExecutor implements ProcessExecutor 24 | { 25 | public function execute(string $command): ?array 26 | { 27 | if (!function_exists('proc_open')) { 28 | return null; 29 | } 30 | 31 | $pipes = []; 32 | 33 | $process = @proc_open( 34 | $command, 35 | [ 36 | ['pipe', 'rb'], 37 | ['pipe', 'wb'], // stdout 38 | ['pipe', 'wb'], // stderr 39 | ], 40 | $pipes 41 | ); 42 | 43 | if (!is_resource($process)) { 44 | return null; 45 | } 46 | 47 | fclose($pipes[0]); 48 | 49 | $stdout = (string) stream_get_contents($pipes[1]); 50 | $stderr = (string) stream_get_contents($pipes[2]); 51 | 52 | proc_close($process); 53 | 54 | return [$stdout, $stderr]; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Executor/ProcessExecutor.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter\Executor; 15 | 16 | interface ProcessExecutor 17 | { 18 | /** 19 | * @return array{string, string}|null STDOUT & STDERR tuple 20 | */ 21 | public function execute(string $command): ?array; 22 | } 23 | -------------------------------------------------------------------------------- /src/Finder/CmiCmdletLogicalFinder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter\Finder; 15 | 16 | use function preg_match; 17 | 18 | /** 19 | * Find the number of logical CPU cores for Windows leveraging the Get-CimInstance 20 | * cmdlet, which is a newer version that is recommended over Get-WmiObject. 21 | */ 22 | final class CmiCmdletLogicalFinder extends ProcOpenBasedFinder 23 | { 24 | private const CPU_CORE_COUNT_REGEX = '/NumberOfLogicalProcessors[\s\n]-+[\s\n]+(?\d+)/'; 25 | 26 | protected function getCommand(): string 27 | { 28 | return 'Get-CimInstance -ClassName Win32_ComputerSystem | Select-Object -Property NumberOfLogicalProcessors'; 29 | } 30 | 31 | public function toString(): string 32 | { 33 | return 'CmiCmdletLogicalFinder'; 34 | } 35 | 36 | protected function countCpuCores(string $process): ?int 37 | { 38 | if (0 === preg_match(self::CPU_CORE_COUNT_REGEX, $process, $matches)) { 39 | return parent::countCpuCores($process); 40 | } 41 | 42 | $count = $matches['count']; 43 | 44 | return parent::countCpuCores($count); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Finder/CmiCmdletPhysicalFinder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter\Finder; 15 | 16 | use function preg_match; 17 | 18 | /** 19 | * Find the number of physical CPU cores for Windows. 20 | * 21 | * @see https://github.com/paratestphp/paratest/blob/c163539818fd96308ca8dc60f46088461e366ed4/src/Runners/PHPUnit/Options.php#L912-L916 22 | */ 23 | final class CmiCmdletPhysicalFinder extends ProcOpenBasedFinder 24 | { 25 | private const CPU_CORE_COUNT_REGEX = '/NumberOfCores[\s\n]-+[\s\n]+(?\d+)/'; 26 | 27 | protected function getCommand(): string 28 | { 29 | return 'Get-CimInstance -ClassName Win32_Processor | Select-Object -Property NumberOfCores'; 30 | } 31 | 32 | public function toString(): string 33 | { 34 | return 'CmiCmdletPhysicalFinder'; 35 | } 36 | 37 | protected function countCpuCores(string $process): ?int 38 | { 39 | if (0 === preg_match(self::CPU_CORE_COUNT_REGEX, $process, $matches)) { 40 | return parent::countCpuCores($process); 41 | } 42 | 43 | $count = $matches['count']; 44 | 45 | return parent::countCpuCores($count); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Finder/CpuCoreFinder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter\Finder; 15 | 16 | interface CpuCoreFinder 17 | { 18 | /** 19 | * Provides an explanation which may offer some insight as to what the finder 20 | * will be able to find. 21 | * 22 | * This is practical to have an idea of what each finder will find collect 23 | * information for the unit tests, since integration tests are quite complicated 24 | * as dependent on complex infrastructures. 25 | */ 26 | public function diagnose(): string; 27 | 28 | /** 29 | * Find the number of CPU cores. If it could not find it, returns null. The 30 | * means used to find the cores are at the implementation discretion. 31 | * 32 | * @return positive-int|null 33 | */ 34 | public function find(): ?int; 35 | 36 | public function toString(): string; 37 | } 38 | -------------------------------------------------------------------------------- /src/Finder/CpuInfoFinder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter\Finder; 15 | 16 | use function file_get_contents; 17 | use function is_file; 18 | use function sprintf; 19 | use function substr_count; 20 | use const PHP_EOL; 21 | 22 | /** 23 | * Find the number of CPU cores looking up at the cpuinfo file which is available 24 | * on Linux systems and Windows systems with a Linux sub-system. 25 | * 26 | * @see https://github.com/paratestphp/paratest/blob/c163539818fd96308ca8dc60f46088461e366ed4/src/Runners/PHPUnit/Options.php#L903-L909 27 | * @see https://unix.stackexchange.com/questions/146051/number-of-processors-in-proc-cpuinfo 28 | */ 29 | final class CpuInfoFinder implements CpuCoreFinder 30 | { 31 | private const CPU_INFO_PATH = '/proc/cpuinfo'; 32 | 33 | public function diagnose(): string 34 | { 35 | if (!is_file(self::CPU_INFO_PATH)) { 36 | return sprintf( 37 | 'The file "%s" could not be found.', 38 | self::CPU_INFO_PATH 39 | ); 40 | } 41 | 42 | $cpuInfo = file_get_contents(self::CPU_INFO_PATH); 43 | 44 | if (false === $cpuInfo) { 45 | return sprintf( 46 | 'Could not get the content of the file "%s".', 47 | self::CPU_INFO_PATH 48 | ); 49 | } 50 | 51 | return sprintf( 52 | 'Found the file "%s" with the content:%s%s%sWill return "%s".', 53 | self::CPU_INFO_PATH, 54 | PHP_EOL, 55 | $cpuInfo, 56 | PHP_EOL, 57 | self::countCpuCores($cpuInfo) 58 | ); 59 | } 60 | 61 | /** 62 | * @return positive-int|null 63 | */ 64 | public function find(): ?int 65 | { 66 | $cpuInfo = self::getCpuInfo(); 67 | 68 | return null === $cpuInfo ? null : self::countCpuCores($cpuInfo); 69 | } 70 | 71 | public function toString(): string 72 | { 73 | return 'CpuInfoFinder'; 74 | } 75 | 76 | private static function getCpuInfo(): ?string 77 | { 78 | if (!@is_file(self::CPU_INFO_PATH)) { 79 | return null; 80 | } 81 | 82 | $cpuInfo = @file_get_contents(self::CPU_INFO_PATH); 83 | 84 | return false === $cpuInfo 85 | ? null 86 | : $cpuInfo; 87 | } 88 | 89 | /** 90 | * @internal 91 | * 92 | * @return positive-int|null 93 | */ 94 | public static function countCpuCores(string $cpuInfo): ?int 95 | { 96 | $processorCount = substr_count($cpuInfo, 'processor'); 97 | 98 | return $processorCount > 0 ? $processorCount : null; 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/Finder/DummyCpuCoreFinder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter\Finder; 15 | 16 | use function sprintf; 17 | 18 | /** 19 | * This finder returns whatever value you gave to it. This is useful for testing 20 | * or as a fallback to avoid to catch the NumberOfCpuCoreNotFound exception. 21 | */ 22 | final class DummyCpuCoreFinder implements CpuCoreFinder 23 | { 24 | /** 25 | * @var positive-int 26 | */ 27 | private $count; 28 | 29 | public function diagnose(): string 30 | { 31 | return sprintf( 32 | 'Will return "%d".', 33 | $this->count 34 | ); 35 | } 36 | 37 | /** 38 | * @param positive-int $count 39 | */ 40 | public function __construct(int $count) 41 | { 42 | $this->count = $count; 43 | } 44 | 45 | public function find(): ?int 46 | { 47 | return $this->count; 48 | } 49 | 50 | public function toString(): string 51 | { 52 | return sprintf( 53 | 'DummyCpuCoreFinder(value=%d)', 54 | $this->count 55 | ); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Finder/EnvVariableFinder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter\Finder; 15 | 16 | use function getenv; 17 | use function preg_match; 18 | use function sprintf; 19 | use function var_export; 20 | 21 | final class EnvVariableFinder implements CpuCoreFinder 22 | { 23 | /** @var string */ 24 | private $environmentVariableName; 25 | 26 | public function __construct(string $environmentVariableName) 27 | { 28 | $this->environmentVariableName = $environmentVariableName; 29 | } 30 | 31 | public function diagnose(): string 32 | { 33 | $value = getenv($this->environmentVariableName); 34 | 35 | return sprintf( 36 | 'parse(getenv(%s)=%s)=%s', 37 | $this->environmentVariableName, 38 | var_export($value, true), 39 | self::isPositiveInteger($value) ? $value : 'null' 40 | ); 41 | } 42 | 43 | public function find(): ?int 44 | { 45 | $value = getenv($this->environmentVariableName); 46 | 47 | return self::isPositiveInteger($value) 48 | ? (int) $value 49 | : null; 50 | } 51 | 52 | public function toString(): string 53 | { 54 | return sprintf( 55 | 'getenv(%s)', 56 | $this->environmentVariableName 57 | ); 58 | } 59 | 60 | /** 61 | * @param string|false $value 62 | */ 63 | private static function isPositiveInteger($value): bool 64 | { 65 | return false !== $value 66 | && 1 === preg_match('/^\d+$/', $value) 67 | && (int) $value > 0; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Finder/FinderRegistry.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter\Finder; 15 | 16 | final class FinderRegistry 17 | { 18 | /** 19 | * @return list List of all the known finders with all their variants. 20 | */ 21 | public static function getAllVariants(): array 22 | { 23 | return [ 24 | new CpuInfoFinder(), 25 | new DummyCpuCoreFinder(1), 26 | new HwLogicalFinder(), 27 | new HwPhysicalFinder(), 28 | new LscpuLogicalFinder(), 29 | new LscpuPhysicalFinder(), 30 | new _NProcessorFinder(), 31 | new NProcessorFinder(), 32 | new NProcFinder(true), 33 | new NProcFinder(false), 34 | new NullCpuCoreFinder(), 35 | SkipOnOSFamilyFinder::forWindows( 36 | new DummyCpuCoreFinder(1) 37 | ), 38 | OnlyOnOSFamilyFinder::forWindows( 39 | new DummyCpuCoreFinder(1) 40 | ), 41 | new OnlyInPowerShellFinder(new CmiCmdletLogicalFinder()), 42 | new OnlyInPowerShellFinder(new CmiCmdletPhysicalFinder()), 43 | new WindowsRegistryLogicalFinder(), 44 | new WmicPhysicalFinder(), 45 | new WmicLogicalFinder(), 46 | ]; 47 | } 48 | 49 | /** 50 | * @return list 51 | */ 52 | public static function getDefaultLogicalFinders(): array 53 | { 54 | return [ 55 | OnlyOnOSFamilyFinder::forWindows( 56 | new OnlyInPowerShellFinder( 57 | new CmiCmdletLogicalFinder() 58 | ) 59 | ), 60 | OnlyOnOSFamilyFinder::forWindows(new WindowsRegistryLogicalFinder()), 61 | OnlyOnOSFamilyFinder::forWindows(new WmicLogicalFinder()), 62 | new NProcFinder(), 63 | new HwLogicalFinder(), 64 | new _NProcessorFinder(), 65 | new NProcessorFinder(), 66 | new LscpuLogicalFinder(), 67 | new CpuInfoFinder(), 68 | ]; 69 | } 70 | 71 | /** 72 | * @return list 73 | */ 74 | public static function getDefaultPhysicalFinders(): array 75 | { 76 | return [ 77 | OnlyOnOSFamilyFinder::forWindows( 78 | new OnlyInPowerShellFinder( 79 | new CmiCmdletPhysicalFinder() 80 | ) 81 | ), 82 | OnlyOnOSFamilyFinder::forWindows(new WmicPhysicalFinder()), 83 | new HwPhysicalFinder(), 84 | new LscpuPhysicalFinder(), 85 | ]; 86 | } 87 | 88 | private function __construct() 89 | { 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Finder/HwLogicalFinder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter\Finder; 15 | 16 | /** 17 | * Find the number of logical CPU cores for Linux, BSD and OSX. 18 | * 19 | * @see https://github.com/paratestphp/paratest/blob/c163539818fd96308ca8dc60f46088461e366ed4/src/Runners/PHPUnit/Options.php#L903-L909 20 | * @see https://opensource.apple.com/source/xnu/xnu-792.2.4/libkern/libkern/sysctl.h.auto.html 21 | */ 22 | final class HwLogicalFinder extends ProcOpenBasedFinder 23 | { 24 | protected function getCommand(): string 25 | { 26 | return 'sysctl -n hw.logicalcpu'; 27 | } 28 | 29 | public function toString(): string 30 | { 31 | return 'HwLogicalFinder'; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Finder/HwPhysicalFinder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter\Finder; 15 | 16 | /** 17 | * Find the number of physical CPU cores for Linux, BSD and OSX. 18 | * 19 | * @see https://github.com/paratestphp/paratest/blob/c163539818fd96308ca8dc60f46088461e366ed4/src/Runners/PHPUnit/Options.php#L903-L909 20 | * @see https://opensource.apple.com/source/xnu/xnu-792.2.4/libkern/libkern/sysctl.h.auto.html 21 | */ 22 | final class HwPhysicalFinder extends ProcOpenBasedFinder 23 | { 24 | protected function getCommand(): string 25 | { 26 | return 'sysctl -n hw.physicalcpu'; 27 | } 28 | 29 | public function toString(): string 30 | { 31 | return 'HwPhysicalFinder'; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Finder/LscpuLogicalFinder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter\Finder; 15 | 16 | use function count; 17 | use function explode; 18 | use function is_array; 19 | use function preg_grep; 20 | use const PHP_EOL; 21 | 22 | /** 23 | * The number of logical cores. 24 | * 25 | * @see https://stackoverflow.com/a/23378780/5846754 26 | */ 27 | final class LscpuLogicalFinder extends ProcOpenBasedFinder 28 | { 29 | public function getCommand(): string 30 | { 31 | return 'lscpu -p'; 32 | } 33 | 34 | protected function countCpuCores(string $process): ?int 35 | { 36 | $lines = explode(PHP_EOL, $process); 37 | $actualLines = preg_grep('/^\d+,/', $lines); 38 | 39 | if (!is_array($actualLines)) { 40 | return null; 41 | } 42 | 43 | $count = count($actualLines); 44 | 45 | return 0 === $count ? null : $count; 46 | } 47 | 48 | public function toString(): string 49 | { 50 | return 'LscpuLogicalFinder'; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/Finder/LscpuPhysicalFinder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter\Finder; 15 | 16 | use function count; 17 | use function explode; 18 | use function is_array; 19 | use function preg_grep; 20 | use function strtok; 21 | use const PHP_EOL; 22 | 23 | /** 24 | * The number of physical processors. 25 | * 26 | * @see https://stackoverflow.com/a/23378780/5846754 27 | */ 28 | final class LscpuPhysicalFinder extends ProcOpenBasedFinder 29 | { 30 | public function toString(): string 31 | { 32 | return 'LscpuPhysicalFinder'; 33 | } 34 | 35 | public function getCommand(): string 36 | { 37 | return 'lscpu -p'; 38 | } 39 | 40 | protected function countCpuCores(string $process): ?int 41 | { 42 | $lines = explode(PHP_EOL, $process); 43 | $actualLines = preg_grep('/^\d+/', $lines); 44 | 45 | if (!is_array($actualLines)) { 46 | return null; 47 | } 48 | 49 | $cores = []; 50 | foreach ($actualLines as $line) { 51 | strtok($line, ','); 52 | $core = strtok(','); 53 | 54 | if (false === $core) { 55 | continue; 56 | } 57 | 58 | $cores[$core] = true; 59 | } 60 | unset($cores['-']); 61 | 62 | $count = count($cores); 63 | 64 | return 0 === $count ? null : $count; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Finder/NProcFinder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter\Finder; 15 | 16 | use Fidry\CpuCoreCounter\Executor\ProcessExecutor; 17 | use function sprintf; 18 | 19 | /** 20 | * The number of (logical) cores. 21 | * 22 | * @see https://github.com/infection/infection/blob/fbd8c44/src/Resource/Processor/CpuCoresCountProvider.php#L69-L82 23 | * @see https://unix.stackexchange.com/questions/146051/number-of-processors-in-proc-cpuinfo 24 | */ 25 | final class NProcFinder extends ProcOpenBasedFinder 26 | { 27 | /** 28 | * @var bool 29 | */ 30 | private $all; 31 | 32 | /** 33 | * @param bool $all If disabled will give the number of cores available for the current process 34 | * only. This is disabled by default as it is known to be "buggy" on virtual 35 | * environments as the virtualization tool, e.g. VMWare, might over-commit 36 | * resources by default. 37 | */ 38 | public function __construct( 39 | bool $all = false, 40 | ?ProcessExecutor $executor = null 41 | ) { 42 | parent::__construct($executor); 43 | 44 | $this->all = $all; 45 | } 46 | 47 | public function toString(): string 48 | { 49 | return sprintf( 50 | 'NProcFinder(all=%s)', 51 | $this->all ? 'true' : 'false' 52 | ); 53 | } 54 | 55 | protected function getCommand(): string 56 | { 57 | return 'nproc'.($this->all ? ' --all' : ''); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/Finder/NProcessorFinder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter\Finder; 15 | 16 | /** 17 | * Find the number of logical CPU cores for FreeSBD, Solaris and the likes. 18 | * 19 | * @see https://twitter.com/freebsdfrau/status/1052016199452700678?s=20&t=M2pHkRqmmna-UF68lfL2hw 20 | */ 21 | final class NProcessorFinder extends ProcOpenBasedFinder 22 | { 23 | protected function getCommand(): string 24 | { 25 | return 'getconf NPROCESSORS_ONLN'; 26 | } 27 | 28 | public function toString(): string 29 | { 30 | return 'NProcessorFinder'; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Finder/NullCpuCoreFinder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter\Finder; 15 | 16 | /** 17 | * This finder returns whatever value you gave to it. This is useful for testing. 18 | */ 19 | final class NullCpuCoreFinder implements CpuCoreFinder 20 | { 21 | public function diagnose(): string 22 | { 23 | return 'Will return "null".'; 24 | } 25 | 26 | public function find(): ?int 27 | { 28 | return null; 29 | } 30 | 31 | public function toString(): string 32 | { 33 | return 'NullCpuCoreFinder'; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Finder/OnlyInPowerShellFinder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter\Finder; 15 | 16 | use function getenv; 17 | use function sprintf; 18 | 19 | final class OnlyInPowerShellFinder implements CpuCoreFinder 20 | { 21 | /** 22 | * @var CpuCoreFinder 23 | */ 24 | private $decoratedFinder; 25 | 26 | public function __construct(CpuCoreFinder $decoratedFinder) 27 | { 28 | $this->decoratedFinder = $decoratedFinder; 29 | } 30 | 31 | public function diagnose(): string 32 | { 33 | $powerShellModulePath = getenv('PSModulePath'); 34 | 35 | return $this->skip() 36 | ? sprintf( 37 | 'Skipped; no power shell module path detected ("%s").', 38 | $powerShellModulePath 39 | ) 40 | : $this->decoratedFinder->diagnose(); 41 | } 42 | 43 | public function find(): ?int 44 | { 45 | return $this->skip() 46 | ? null 47 | : $this->decoratedFinder->find(); 48 | } 49 | 50 | public function toString(): string 51 | { 52 | return sprintf( 53 | 'OnlyInPowerShellFinder(%s)', 54 | $this->decoratedFinder->toString() 55 | ); 56 | } 57 | 58 | private function skip(): bool 59 | { 60 | return false === getenv('PSModulePath'); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/Finder/OnlyOnOSFamilyFinder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter\Finder; 15 | 16 | use function implode; 17 | use function sprintf; 18 | use const PHP_OS_FAMILY; 19 | 20 | final class OnlyOnOSFamilyFinder implements CpuCoreFinder 21 | { 22 | /** 23 | * @var list 24 | */ 25 | private $skippedOSFamilies; 26 | 27 | /** 28 | * @var CpuCoreFinder 29 | */ 30 | private $decoratedFinder; 31 | 32 | /** 33 | * @param string|list $skippedOSFamilyOrFamilies 34 | */ 35 | public function __construct( 36 | $skippedOSFamilyOrFamilies, 37 | CpuCoreFinder $decoratedFinder 38 | ) { 39 | $this->skippedOSFamilies = (array) $skippedOSFamilyOrFamilies; 40 | $this->decoratedFinder = $decoratedFinder; 41 | } 42 | 43 | public static function forWindows(CpuCoreFinder $decoratedFinder): self 44 | { 45 | return new self( 46 | 'Windows', 47 | $decoratedFinder 48 | ); 49 | } 50 | 51 | public static function forBSD(CpuCoreFinder $decoratedFinder): self 52 | { 53 | return new self( 54 | 'BSD', 55 | $decoratedFinder 56 | ); 57 | } 58 | 59 | public static function forDarwin(CpuCoreFinder $decoratedFinder): self 60 | { 61 | return new self( 62 | 'Darwin', 63 | $decoratedFinder 64 | ); 65 | } 66 | 67 | public static function forSolaris(CpuCoreFinder $decoratedFinder): self 68 | { 69 | return new self( 70 | 'Solaris', 71 | $decoratedFinder 72 | ); 73 | } 74 | 75 | public static function forLinux(CpuCoreFinder $decoratedFinder): self 76 | { 77 | return new self( 78 | 'Linux', 79 | $decoratedFinder 80 | ); 81 | } 82 | 83 | public function diagnose(): string 84 | { 85 | return $this->skip() 86 | ? sprintf( 87 | 'Skipped platform detected ("%s").', 88 | PHP_OS_FAMILY 89 | ) 90 | : $this->decoratedFinder->diagnose(); 91 | } 92 | 93 | public function find(): ?int 94 | { 95 | return $this->skip() 96 | ? null 97 | : $this->decoratedFinder->find(); 98 | } 99 | 100 | public function toString(): string 101 | { 102 | return sprintf( 103 | 'OnlyOnOSFamilyFinder(only=(%s),%s)', 104 | implode(',', $this->skippedOSFamilies), 105 | $this->decoratedFinder->toString() 106 | ); 107 | } 108 | 109 | private function skip(): bool 110 | { 111 | return !in_array(PHP_OS_FAMILY, $this->skippedOSFamilies, true); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Finder/ProcOpenBasedFinder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter\Finder; 15 | 16 | use Fidry\CpuCoreCounter\Executor\ProcessExecutor; 17 | use Fidry\CpuCoreCounter\Executor\ProcOpenExecutor; 18 | use function filter_var; 19 | use function function_exists; 20 | use function is_int; 21 | use function sprintf; 22 | use function trim; 23 | use const FILTER_VALIDATE_INT; 24 | use const PHP_EOL; 25 | 26 | abstract class ProcOpenBasedFinder implements CpuCoreFinder 27 | { 28 | /** 29 | * @var ProcessExecutor 30 | */ 31 | private $executor; 32 | 33 | public function __construct(?ProcessExecutor $executor = null) 34 | { 35 | $this->executor = $executor ?? new ProcOpenExecutor(); 36 | } 37 | 38 | public function diagnose(): string 39 | { 40 | if (!function_exists('proc_open')) { 41 | return 'The function "proc_open" is not available.'; 42 | } 43 | 44 | $command = $this->getCommand(); 45 | $output = $this->executor->execute($command); 46 | 47 | if (null === $output) { 48 | return sprintf( 49 | 'Failed to execute the command "%s".', 50 | $command 51 | ); 52 | } 53 | 54 | [$stdout, $stderr] = $output; 55 | $failed = '' !== trim($stderr); 56 | 57 | return $failed 58 | ? sprintf( 59 | 'Executed the command "%s" which wrote the following output to the STDERR:%s%s%sWill return "null".', 60 | $command, 61 | PHP_EOL, 62 | $stderr, 63 | PHP_EOL 64 | ) 65 | : sprintf( 66 | 'Executed the command "%s" and got the following (STDOUT) output:%s%s%sWill return "%s".', 67 | $command, 68 | PHP_EOL, 69 | $stdout, 70 | PHP_EOL, 71 | $this->countCpuCores($stdout) ?? 'null' 72 | ); 73 | } 74 | 75 | /** 76 | * @return positive-int|null 77 | */ 78 | public function find(): ?int 79 | { 80 | $output = $this->executor->execute($this->getCommand()); 81 | 82 | if (null === $output) { 83 | return null; 84 | } 85 | 86 | [$stdout, $stderr] = $output; 87 | $failed = '' !== trim($stderr); 88 | 89 | return $failed 90 | ? null 91 | : $this->countCpuCores($stdout); 92 | } 93 | 94 | /** 95 | * @internal 96 | * 97 | * @return positive-int|null 98 | */ 99 | protected function countCpuCores(string $process): ?int 100 | { 101 | $cpuCount = filter_var($process, FILTER_VALIDATE_INT); 102 | 103 | return is_int($cpuCount) && $cpuCount > 0 ? $cpuCount : null; 104 | } 105 | 106 | abstract protected function getCommand(): string; 107 | } 108 | -------------------------------------------------------------------------------- /src/Finder/SkipOnOSFamilyFinder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter\Finder; 15 | 16 | use function implode; 17 | use function in_array; 18 | use function sprintf; 19 | 20 | final class SkipOnOSFamilyFinder implements CpuCoreFinder 21 | { 22 | /** 23 | * @var list 24 | */ 25 | private $skippedOSFamilies; 26 | 27 | /** 28 | * @var CpuCoreFinder 29 | */ 30 | private $decoratedFinder; 31 | 32 | /** 33 | * @param string|list $skippedOSFamilyOrFamilies 34 | */ 35 | public function __construct( 36 | $skippedOSFamilyOrFamilies, 37 | CpuCoreFinder $decoratedFinder 38 | ) { 39 | $this->skippedOSFamilies = (array) $skippedOSFamilyOrFamilies; 40 | $this->decoratedFinder = $decoratedFinder; 41 | } 42 | 43 | public static function forWindows(CpuCoreFinder $decoratedFinder): self 44 | { 45 | return new self( 46 | 'Windows', 47 | $decoratedFinder 48 | ); 49 | } 50 | 51 | public static function forBSD(CpuCoreFinder $decoratedFinder): self 52 | { 53 | return new self( 54 | 'BSD', 55 | $decoratedFinder 56 | ); 57 | } 58 | 59 | public static function forDarwin(CpuCoreFinder $decoratedFinder): self 60 | { 61 | return new self( 62 | 'Darwin', 63 | $decoratedFinder 64 | ); 65 | } 66 | 67 | public static function forSolaris(CpuCoreFinder $decoratedFinder): self 68 | { 69 | return new self( 70 | 'Solaris', 71 | $decoratedFinder 72 | ); 73 | } 74 | 75 | public static function forLinux(CpuCoreFinder $decoratedFinder): self 76 | { 77 | return new self( 78 | 'Linux', 79 | $decoratedFinder 80 | ); 81 | } 82 | 83 | public function diagnose(): string 84 | { 85 | return $this->skip() 86 | ? sprintf( 87 | 'Skipped platform detected ("%s").', 88 | PHP_OS_FAMILY 89 | ) 90 | : $this->decoratedFinder->diagnose(); 91 | } 92 | 93 | public function find(): ?int 94 | { 95 | return $this->skip() 96 | ? null 97 | : $this->decoratedFinder->find(); 98 | } 99 | 100 | public function toString(): string 101 | { 102 | return sprintf( 103 | 'SkipOnOSFamilyFinder(skip=(%s),%s)', 104 | implode(',', $this->skippedOSFamilies), 105 | $this->decoratedFinder->toString() 106 | ); 107 | } 108 | 109 | private function skip(): bool 110 | { 111 | return in_array(PHP_OS_FAMILY, $this->skippedOSFamilies, true); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Finder/WindowsRegistryLogicalFinder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter\Finder; 15 | 16 | use function array_filter; 17 | use function count; 18 | use function explode; 19 | use const PHP_EOL; 20 | 21 | /** 22 | * Find the number of logical CPU cores for Windows. 23 | * 24 | * @see https://knowledge.informatica.com/s/article/151521 25 | */ 26 | final class WindowsRegistryLogicalFinder extends ProcOpenBasedFinder 27 | { 28 | protected function getCommand(): string 29 | { 30 | return 'reg query HKEY_LOCAL_MACHINE\HARDWARE\DESCRIPTION\System\CentralProcessor'; 31 | } 32 | 33 | public function toString(): string 34 | { 35 | return 'WindowsRegistryLogicalFinder'; 36 | } 37 | 38 | protected function countCpuCores(string $process): ?int 39 | { 40 | $count = count( 41 | array_filter( 42 | explode(PHP_EOL, $process), 43 | static function (string $line): bool { 44 | return '' !== trim($line); 45 | } 46 | ) 47 | ); 48 | 49 | return $count > 0 ? $count : null; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Finder/WmicLogicalFinder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter\Finder; 15 | 16 | use function preg_match; 17 | 18 | /** 19 | * Find the number of logical CPU cores for Windows. 20 | * 21 | * @see https://github.com/paratestphp/paratest/blob/c163539818fd96308ca8dc60f46088461e366ed4/src/Runners/PHPUnit/Options.php#L912-L916 22 | */ 23 | final class WmicLogicalFinder extends ProcOpenBasedFinder 24 | { 25 | private const CPU_CORE_COUNT_REGEX = '/NumberOfLogicalProcessors[\s\n]+(?\d+)/'; 26 | 27 | protected function getCommand(): string 28 | { 29 | return 'wmic cpu get NumberOfLogicalProcessors'; 30 | } 31 | 32 | public function toString(): string 33 | { 34 | return 'WmicLogicalFinder'; 35 | } 36 | 37 | protected function countCpuCores(string $process): ?int 38 | { 39 | if (0 === preg_match(self::CPU_CORE_COUNT_REGEX, $process, $matches)) { 40 | return parent::countCpuCores($process); 41 | } 42 | 43 | $count = $matches['count']; 44 | 45 | return parent::countCpuCores($count); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Finder/WmicPhysicalFinder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter\Finder; 15 | 16 | use function preg_match; 17 | 18 | /** 19 | * Find the number of physical CPU cores for Windows. 20 | * 21 | * @see https://github.com/paratestphp/paratest/blob/c163539818fd96308ca8dc60f46088461e366ed4/src/Runners/PHPUnit/Options.php#L912-L916 22 | */ 23 | final class WmicPhysicalFinder extends ProcOpenBasedFinder 24 | { 25 | private const CPU_CORE_COUNT_REGEX = '/NumberOfCores[\s\n]+(?\d+)/'; 26 | 27 | protected function getCommand(): string 28 | { 29 | return 'wmic cpu get NumberOfCores'; 30 | } 31 | 32 | public function toString(): string 33 | { 34 | return 'WmicPhysicalFinder'; 35 | } 36 | 37 | protected function countCpuCores(string $process): ?int 38 | { 39 | if (0 === preg_match(self::CPU_CORE_COUNT_REGEX, $process, $matches)) { 40 | return parent::countCpuCores($process); 41 | } 42 | 43 | $count = $matches['count']; 44 | 45 | return parent::countCpuCores($count); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Finder/_NProcessorFinder.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter\Finder; 15 | 16 | /** 17 | * Find the number of logical CPU cores for Linux and the likes. 18 | * 19 | * @see https://twitter.com/freebsdfrau/status/1052016199452700678?s=20&t=M2pHkRqmmna-UF68lfL2hw 20 | */ 21 | final class _NProcessorFinder extends ProcOpenBasedFinder 22 | { 23 | protected function getCommand(): string 24 | { 25 | return 'getconf _NPROCESSORS_ONLN'; 26 | } 27 | 28 | public function toString(): string 29 | { 30 | return '_NProcessorFinder'; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/NumberOfCpuCoreNotFound.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter; 15 | 16 | use RuntimeException; 17 | 18 | final class NumberOfCpuCoreNotFound extends RuntimeException 19 | { 20 | public static function create(): self 21 | { 22 | return new self( 23 | 'Could not find the number of CPU cores available.' 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/ParallelisationResult.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | declare(strict_types=1); 13 | 14 | namespace Fidry\CpuCoreCounter; 15 | 16 | /** 17 | * @readonly 18 | */ 19 | final class ParallelisationResult 20 | { 21 | /** 22 | * @var positive-int|0 23 | */ 24 | public $passedReservedCpus; 25 | 26 | /** 27 | * @var non-zero-int|null 28 | */ 29 | public $passedCountLimit; 30 | 31 | /** 32 | * @var float|null 33 | */ 34 | public $passedLoadLimit; 35 | 36 | /** 37 | * @var float|null 38 | */ 39 | public $passedSystemLoadAverage; 40 | 41 | /** 42 | * @var non-zero-int|null 43 | */ 44 | public $correctedCountLimit; 45 | 46 | /** 47 | * @var float|null 48 | */ 49 | public $correctedSystemLoadAverage; 50 | 51 | /** 52 | * @var positive-int 53 | */ 54 | public $totalCoresCount; 55 | 56 | /** 57 | * @var positive-int 58 | */ 59 | public $availableCpus; 60 | 61 | /** 62 | * @param positive-int|0 $passedReservedCpus 63 | * @param non-zero-int|null $passedCountLimit 64 | * @param non-zero-int|null $correctedCountLimit 65 | * @param positive-int $totalCoresCount 66 | * @param positive-int $availableCpus 67 | */ 68 | public function __construct( 69 | int $passedReservedCpus, 70 | ?int $passedCountLimit, 71 | ?float $passedLoadLimit, 72 | ?float $passedSystemLoadAverage, 73 | ?int $correctedCountLimit, 74 | ?float $correctedSystemLoadAverage, 75 | int $totalCoresCount, 76 | int $availableCpus 77 | ) { 78 | $this->passedReservedCpus = $passedReservedCpus; 79 | $this->passedCountLimit = $passedCountLimit; 80 | $this->passedLoadLimit = $passedLoadLimit; 81 | $this->passedSystemLoadAverage = $passedSystemLoadAverage; 82 | $this->correctedCountLimit = $correctedCountLimit; 83 | $this->correctedSystemLoadAverage = $correctedSystemLoadAverage; 84 | $this->totalCoresCount = $totalCoresCount; 85 | $this->availableCpus = $availableCpus; 86 | } 87 | } 88 | --------------------------------------------------------------------------------