diff --git a/html/input.php b/html/input.php
index b519f05..9a2221b 100644
--- a/html/input.php
+++ b/html/input.php
@@ -7,7 +7,7 @@ if (!file_exists($coreFile)) {
file_put_contents($coreFile, json_encode([]));
}
-/* Read NUMA topology */
+/* ---------- STEP 1: READ & COUNT NUMA TOPOLOGY ---------- */
function getNumaTopology(): array
{
$out = shell_exec("numactl --hardware");
@@ -18,7 +18,12 @@ function getNumaTopology(): array
$node = (int)$m[1];
$cpus = array_map('intval', preg_split('/\s+/', trim($m[2])));
sort($cpus);
- $nodes[$node] = $cpus;
+
+ $nodes[$node] = [
+ "all" => $cpus,
+ "even" => array_values(array_filter($cpus, fn($c) => $c % 2 === 0)),
+ "odd" => array_values(array_filter($cpus, fn($c) => $c % 2 === 1)),
+ ];
}
}
@@ -26,82 +31,77 @@ function getNumaTopology(): array
return $nodes;
}
-/* Allocate core with EVEN-first, ODD-later, safe round robin */
+/* ---------- STEP 2: BUILD GLOBAL ROUND-ROBIN PLAN ---------- */
+function buildAllocationPlan(array $nodes): array
+{
+ $plan = [];
+ $nodeIds = array_keys($nodes);
+ $nodeCount = count($nodeIds);
+
+ if ($nodeCount === 0) {
+ return $plan;
+ }
+
+ /* EVEN cores first (physical cores) */
+ $maxEven = 0;
+ foreach ($nodes as $n) {
+ $maxEven = max($maxEven, count($n["even"]));
+ }
+
+ for ($coreIndex = 0; $coreIndex < $maxEven; $coreIndex++) {
+ foreach ($nodeIds as $node) {
+ if (isset($nodes[$node]["even"][$coreIndex])) {
+ $plan[] = [
+ "node" => $node,
+ "cpu" => $nodes[$node]["even"][$coreIndex]
+ ];
+ }
+ }
+ }
+
+ /* ODD cores next (hyper-threads) */
+ $maxOdd = 0;
+ foreach ($nodes as $n) {
+ $maxOdd = max($maxOdd, count($n["odd"]));
+ }
+
+ for ($coreIndex = 0; $coreIndex < $maxOdd; $coreIndex++) {
+ foreach ($nodeIds as $node) {
+ if (isset($nodes[$node]["odd"][$coreIndex])) {
+ $plan[] = [
+ "node" => $node,
+ "cpu" => $nodes[$node]["odd"][$coreIndex]
+ ];
+ }
+ }
+ }
+
+ return $plan;
+}
+
+/* ---------- STEP 3: ALLOCATE USING PRECOMPUTED PLAN ---------- */
function allocateCore(int $serviceId): array
{
global $coreFile;
$map = json_decode(file_get_contents($coreFile), true) ?: [];
$nodes = getNumaTopology();
+ $plan = buildAllocationPlan($nodes);
- if (empty($nodes)) {
+ if (empty($plan)) {
return ["node" => 0, "cpu" => 0];
}
- /* Split CPUs per node */
- $even = $odd = [];
- foreach ($nodes as $n => $cpus) {
- $even[$n] = array_values(array_filter($cpus, fn($c) => $c % 2 === 0));
- $odd[$n] = array_values(array_filter($cpus, fn($c) => $c % 2 === 1));
- }
-
$index = count($map);
+ $slot = $plan[$index % count($plan)];
- /* Count total EVEN cores */
- $totalEven = 0;
- foreach ($even as $cpus) {
- $totalEven += count($cpus);
- }
-
- /* Select phase */
- if ($index < $totalEven) {
- $phaseCpus = $even;
- $phaseIndex = $index;
- } else {
- $phaseCpus = $odd;
- $phaseIndex = $index - $totalEven;
- }
-
- /* Build list of nodes that actually have CPUs in this phase */
- $activeNodes = [];
- foreach ($phaseCpus as $n => $cpus) {
- if (!empty($cpus)) {
- $activeNodes[$n] = $cpus;
- }
- }
-
- if (empty($activeNodes)) {
- // Absolute fallback (should never happen)
- return ["node" => 0, "cpu" => 0];
- }
-
- $nodeIds = array_keys($activeNodes);
- $nodeCount = count($nodeIds);
-
- /*
- * Correct round-robin math
- * First exhaust core index across all nodes,
- * then move to next core.
- */
- $nodeIndex = $phaseIndex % $nodeCount;
- $coreIndex = intdiv($phaseIndex, $nodeCount);
-
- $node = $nodeIds[$nodeIndex];
- $cpuList = $activeNodes[$node];
-
- // Safe wrap (never divide by zero now)
- $cpu = $cpuList[$coreIndex % count($cpuList)];
-
- $map[$serviceId] = [
- "node" => $node,
- "cpu" => $cpu
- ];
-
+ $map[$serviceId] = $slot;
file_put_contents($coreFile, json_encode($map, JSON_PRETTY_PRINT));
- return $map[$serviceId];
+
+ return $slot;
}
-/* Free core */
+/* ---------- HELPERS ---------- */
function freeCore(int $serviceId): void
{
global $coreFile;
@@ -113,7 +113,6 @@ function freeCore(int $serviceId): void
}
}
-/* Get assigned core */
function getServiceCore(int $serviceId): ?array
{
global $coreFile;