Skip to content

Commit

Permalink
feat: Rework the load limit (#139)
Browse files Browse the repository at this point in the history
- Make the load limit about the _available_ resources, i.e. if there is 11 cores, 1 is reserved and 5 are busy, it will limit it based on `11-1-5=5` cores. So `1.` means 100% of 5 cores, `.5` 50% of 5 cores.
- Do not have a limit by default
  • Loading branch information
theofidry authored Aug 2, 2024
1 parent e49b6b0 commit 211b0f5
Show file tree
Hide file tree
Showing 4 changed files with 65 additions and 50 deletions.
71 changes: 38 additions & 33 deletions src/CpuCoreCounter.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,65 +43,70 @@ public function __construct(?array $finders = null)
}

/**
* @param positive-int|0 $reservedCpus Number of CPUs to reserve. This is useful when you want
* to reserve some CPUs for other processes. If the main
* process is going to be busy still, you may want to set
* this value to 1.
*
* @param positive-int $limit
* @param float $loadLimitPerCore Limits the number of CPUs based on the system load
* average per core in a range of [0., 1.].
* @param float $systemLoadAverage The system load average. If not provided, it will be
* retrieved using `sys_getloadavg()` to check the load
* of the system in the past minute. Should be a positive
* float.
* @param positive-int|0 $reservedCpus Number of CPUs to reserve. This is useful when you want
* to reserve some CPUs for other processes. If the main
* process is going to be busy still, you may want to set
* this value to 1.
* @param positive-int $limit
* @param float|null $loadLimit Element of [0., 1.]. Percentage representing the
* amount of cores that should be used among the available
* resources. For instance, if set to 0.7, it will use 70%
* of the available cores, i.e. if 1 core is reserved, 11
* cores are available and 5 are busy, it will use 70%
* of (11-1-5)=5 cores, so 3 cores. Set this parameter to null
* to skip this check. Beware that 1 does not mean "no limit",
* but 100% of the _available_ resources, i.e. with the
* previous example, it will return 5 cores. How busy is
* the system is determined by the system load average
* (see $systemLoadAverage).
* @param float|null $systemLoadAverage The system load average. If not provided, it will be
* retrieved using `sys_getloadavg()` to check the load
* of the system in the past minute. Should be a positive
* float.
*
* @see https://php.net/manual/en/function.sys-getloadavg.php
*/
public function getAvailableForParallelisation(
int $reservedCpus = 0,
?int $limit = null,
?float $loadLimitPerCore = .9,
?float $loadLimit = null,
?float $systemLoadAverage = null
): ParallelisationResult {
self::checkLoadLimitPerCore($loadLimitPerCore);
self::checkLoadLimit($loadLimit);
self::checkSystemLoadAverage($systemLoadAverage);

$correctedLimit = null === $limit
? self::getKubernetesLimit()
: $limit;

$totalCoresCount = $this->getCountWithFallback(1);

$availableCpus = max(1, $totalCoresCount - $reservedCpus);

$correctedSystemLoadAverage = null === $systemLoadAverage
? sys_getloadavg()[0] ?? 0.
: $systemLoadAverage;
$systemLoadAveragePerCore = $correctedSystemLoadAverage / $availableCpus;
$totalCoreCount = $this->getCountWithFallback(1);
$availableCores = max(1, $totalCoreCount - $reservedCpus);

// Adjust available CPUs based on current load
if (null !== $loadLimitPerCore && $systemLoadAveragePerCore > $loadLimitPerCore) {
$adjustedCpus = max(
if (null !== $loadLimit) {
$correctedSystemLoadAverage = null === $systemLoadAverage
? sys_getloadavg()[0] ?? 0.
: $systemLoadAverage;

$availableCores = max(
1,
(1 - $systemLoadAveragePerCore) * $availableCpus
$loadLimit * ($availableCores - $correctedSystemLoadAverage)
);
$availableCpus = min($availableCpus, $adjustedCpus);
}

if (null !== $correctedLimit && $availableCpus > $correctedLimit) {
$availableCpus = $correctedLimit;
if (null !== $correctedLimit && $availableCores > $correctedLimit) {
$availableCores = $correctedLimit;
}

return new ParallelisationResult(
$reservedCpus,
$limit,
$loadLimitPerCore,
$loadLimit,
$systemLoadAverage,
$correctedLimit,
$correctedSystemLoadAverage,
$totalCoresCount,
(int) $availableCpus
$correctedSystemLoadAverage ?? $systemLoadAverage,
$totalCoreCount,
(int) $availableCores
);
}

Expand Down Expand Up @@ -205,7 +210,7 @@ public static function getKubernetesLimit(): ?int
return $finder->find();
}

private static function checkLoadLimitPerCore(?float $loadLimitPerCore): void
private static function checkLoadLimit(?float $loadLimitPerCore): void
{
if (null === $loadLimitPerCore) {
return;
Expand Down
10 changes: 5 additions & 5 deletions src/ParallelisationResult.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ final class ParallelisationResult
/**
* @var float|null
*/
public $passedLoadLimitPerCore;
public $passedLoadLimit;

/**
* @var float|null
Expand All @@ -44,7 +44,7 @@ final class ParallelisationResult
public $correctedLimit;

/**
* @var float
* @var float|null
*/
public $correctedSystemLoadAverage;

Expand All @@ -67,16 +67,16 @@ final class ParallelisationResult
public function __construct(
int $passedReservedCpus,
?int $passedLimit,
?float $passedLoadLimitPerCore,
?float $passedLoadLimit,
?float $passedSystemLoadAverage,
?int $correctedLimit,
float $correctedSystemLoadAverage,
?float $correctedSystemLoadAverage,
int $totalCoresCount,
int $availableCpus
) {
$this->passedReservedCpus = $passedReservedCpus;
$this->passedLimit = $passedLimit;
$this->passedLoadLimitPerCore = $passedLoadLimitPerCore;
$this->passedLoadLimit = $passedLoadLimit;
$this->passedSystemLoadAverage = $passedSystemLoadAverage;
$this->correctedLimit = $correctedLimit;
$this->correctedSystemLoadAverage = $correctedSystemLoadAverage;
Expand Down
8 changes: 4 additions & 4 deletions tests/AvailableCpuCoresScenario.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,15 @@ public function __construct(
array $environmentVariables,
int $reservedCpus,
?int $limit,
?float $loadLimitPerCore,
?float $loadLimit,
?float $systemLoadAverage,
int $expected
) {
$this->finders = $finders;
$this->environmentVariables = $environmentVariables;
$this->reservedCpus = $reservedCpus;
$this->limit = $limit;
$this->loadLimitPerCore = $loadLimitPerCore;
$this->loadLimitPerCore = $loadLimit;
$this->systemLoadAverage = $systemLoadAverage;
$this->expected = $expected;
}
Expand All @@ -76,7 +76,7 @@ public static function create(
array $environmentVariables,
?int $reservedCpus,
?int $limit,
?float $loadLimitPerCore,
?float $loadLimit,
?float $systemLoadAverage,
int $expected
): array {
Expand All @@ -90,7 +90,7 @@ public static function create(
$environmentVariables,
$reservedCpus ?? 0,
$limit,
$loadLimitPerCore,
$loadLimit,
$systemLoadAverage ?? 0.,
$expected
),
Expand Down
26 changes: 18 additions & 8 deletions tests/CpuCoreCounterTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -299,16 +299,26 @@ public static function availableCpuCoreProvider(): iterable
1
);

yield 'CPU count found, over half the cores are used' => AvailableCpuCoresScenario::create(
yield 'CPU count found, over half the cores are used and no limit is set' => AvailableCpuCoresScenario::create(
11,
[],
1,
null,
.9,
null,
6.,
10
);

yield 'CPU count found, over half the cores are used and a limit is set' => AvailableCpuCoresScenario::create(
11,
[],
1,
null,
1.,
6.,
4
);

yield 'CPU count found, the CPUs are overloaded' => AvailableCpuCoresScenario::create(
11,
[],
Expand All @@ -319,24 +329,24 @@ public static function availableCpuCoreProvider(): iterable
1
);

yield 'CPU count found, the CPUs are being the limit set, but there is several CPUs available still' => AvailableCpuCoresScenario::create(
yield 'CPU count found, the load limit is set, but there is several CPUs available still' => AvailableCpuCoresScenario::create(
11,
[],
1,
null,
.5,
6.,
4
2
);

yield 'CPU count found, the CPUs are at the limit of being overloaded' => AvailableCpuCoresScenario::create(
yield 'CPU count found, the CPUs are at completely overloaded' => AvailableCpuCoresScenario::create(
11,
[],
1,
null,
.9,
9.,
10
.5,
11.,
1
);

yield 'CPU count found, the CPUs are overloaded but no load limit per CPU' => AvailableCpuCoresScenario::create(
Expand Down

0 comments on commit 211b0f5

Please sign in to comment.