Skip to content

Commit

Permalink
Introduce the NProc finder (#13)
Browse files Browse the repository at this point in the history
Introduce `NProcFinder`, inspired from Infection.
  • Loading branch information
theofidry authored Dec 4, 2022
1 parent a389a3a commit e33ff4f
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 0 deletions.
28 changes: 28 additions & 0 deletions src/Exec/ExecException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

/*
* This file is part of the Fidry CPUCounter Config package.
*
* (c) Théo FIDRY <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Fidry\CpuCounter\Exec;

use ErrorException;
use function error_get_last;

// see https://github.com/thecodingmachine/safe/blob/master/generated/Exceptions/ExecException.php
final class ExecException extends ErrorException
{
public static function createFromPhpError(): self
{
$error = error_get_last();

return new self($error['message'] ?? 'An error occured', 0, $error['type'] ?? 1);
}
}
46 changes: 46 additions & 0 deletions src/Exec/ShellExec.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

/*
* This file is part of the Fidry CPUCounter Config package.
*
* (c) Théo FIDRY <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Fidry\CpuCounter\Exec;

use function error_clear_last;
use function shell_exec;

/**
* Mimicks Safe\shell_exec: this is to avoid to add an extra dependency to this
* micro package.
*
* @see https://github.com/thecodingmachine/safe/blob/0653752f6c2d45e0640fa24bf789cae367a501d3/generated/exec.php#L114
*/
final class ShellExec
{
private function __construct()
{
}

/**
* @throws ExecException
*/
public static function execute(string $command): string
{
error_clear_last();

$safeResult = shell_exec($command);

if (null === $safeResult || false === $safeResult) {
throw ExecException::createFromPhpError();
}

return $safeResult;
}
}
71 changes: 71 additions & 0 deletions src/NProcFinder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
<?php

/*
* This file is part of the Fidry CPUCounter Config package.
*
* (c) Théo FIDRY <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Fidry\CpuCounter;

use Fidry\CpuCounter\Exec\ExecException;
use Fidry\CpuCounter\Exec\ShellExec;
use function filter_var;
use function is_int;
use function trim;
use const FILTER_VALIDATE_INT;

/**
* @see https://github.com/infection/infection/blob/fbd8c44/src/Resource/Processor/CpuCoresCountProvider.php#L69-L82
* @see https://unix.stackexchange.com/questions/146051/number-of-processors-in-proc-cpuinfo
*/
final class NProcFinder
{
private function __construct()
{
}

/**
* @return positive-int|null
*/
public static function find(): ?int
{
if (!self::supportsNproc()) {
return null;
}

try {
$nproc = ShellExec::execute('nproc --all');
} catch (ExecException $nprocFailed) {
return null;
}

return self::countCpuCores($nproc);
}

private static function supportsNproc(): bool
{
try {
$commandNproc = ShellExec::execute('command -v nproc');
} catch (ExecException $noNprocCommand) {
return false;
}

return '' !== trim($commandNproc);
}

/**
* @return positive-int|null
*/
public static function countCpuCores(string $nproc): ?int
{
$cpuCount = filter_var(trim($nproc), FILTER_VALIDATE_INT);

return is_int($cpuCount) && $cpuCount > 0 ? $cpuCount : null;
}
}
64 changes: 64 additions & 0 deletions tests/NProcFinderTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<?php

/*
* This file is part of the Fidry CPUCounter Config package.
*
* (c) Théo FIDRY <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

declare(strict_types=1);

namespace Fidry\CpuCounter\Test;

use Fidry\CpuCounter\NProcFinder;
use PHPUnit\Framework\TestCase;

/**
* @covers \Fidry\CpuCounter\NProcFinder
*
* @internal
*/
final class NProcFinderTest extends TestCase
{
/**
* @dataProvider nprocProvider
*/
public function test_it_can_count_the_number_of_cpu_cores(
string $nproc,
?int $expected
): void {
$actual = NProcFinder::countCpuCores($nproc);

self::assertSame($expected, $actual);
}

public static function nprocProvider(): iterable
{
yield 'empty' => [
<<<'EOF'

EOF,
null,
];

// $ docker run --tty --rm --platform linux/amd64 alpine:3.14 nproc --all
yield 'example from an alpine Docker image' => [
<<<'EOF'
3

EOF,
3,
];

yield 'no processor' => [
<<<'EOF'
0

EOF,
null,
];
}
}

0 comments on commit e33ff4f

Please sign in to comment.