diff --git a/html/input.php b/html/input.php
index 9a2221b..8880226 100644
--- a/html/input.php
+++ b/html/input.php
@@ -3,74 +3,113 @@
$coreFile = "/var/www/core.json";
-if (!file_exists($coreFile)) {
- file_put_contents($coreFile, json_encode([]));
+/* ---------------------------------------------------------
+ STATE HELPERS
+--------------------------------------------------------- */
+
+function loadCoreState(): array
+{
+ global $coreFile;
+
+ if (!file_exists($coreFile)) {
+ return ["cursor" => 0, "allocations" => []];
+ }
+
+ $state = json_decode(file_get_contents($coreFile), true);
+ return $state ?: ["cursor" => 0, "allocations" => []];
}
-/* ---------- STEP 1: READ & COUNT NUMA TOPOLOGY ---------- */
+function saveCoreState(array $state): void
+{
+ global $coreFile;
+ file_put_contents($coreFile, json_encode($state, JSON_PRETTY_PRINT));
+}
+
+/* ---------------------------------------------------------
+ NUMA + SMT TOPOLOGY (SOURCE OF TRUTH)
+--------------------------------------------------------- */
+
function getNumaTopology(): array
{
- $out = shell_exec("numactl --hardware");
$nodes = [];
- foreach (explode("\n", $out) as $line) {
- if (preg_match('/node (\d+) cpus:\s+(.*)/', $line, $m)) {
- $node = (int)$m[1];
- $cpus = array_map('intval', preg_split('/\s+/', trim($m[2])));
- sort($cpus);
+ /* discover nodes */
+ foreach (glob('/sys/devices/system/node/node*') as $nodePath) {
+ $node = (int)str_replace('node', '', basename($nodePath));
+ $nodes[$node] = [];
+ }
- $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)),
- ];
+ /* read cpu → node + core_id */
+ foreach (glob('/sys/devices/system/cpu/cpu[0-9]*') as $cpuPath) {
+ $cpu = (int)str_replace('cpu', '', basename($cpuPath));
+ $topo = "$cpuPath/topology";
+
+ if (!is_dir($topo)) {
+ continue;
+ }
+
+ $coreId = (int)trim(file_get_contents("$topo/core_id"));
+
+ $node = null;
+ foreach (glob("$cpuPath/node*") as $n) {
+ $node = (int)str_replace('node', '', basename($n));
+ break;
+ }
+
+ if ($node === null) {
+ continue;
+ }
+
+ $nodes[$node][$coreId][] = $cpu;
+ }
+
+ /* normalize ordering */
+ ksort($nodes);
+ foreach ($nodes as &$cores) {
+ ksort($cores);
+ foreach ($cores as &$threads) {
+ sort($threads); // primary thread first
}
}
- ksort($nodes);
return $nodes;
}
-/* ---------- STEP 2: BUILD GLOBAL ROUND-ROBIN PLAN ---------- */
+/* ---------------------------------------------------------
+ BUILD GLOBAL ROUND-ROBIN PLAN
+--------------------------------------------------------- */
+
function buildAllocationPlan(array $nodes): array
{
$plan = [];
- $nodeIds = array_keys($nodes);
- $nodeCount = count($nodeIds);
- if ($nodeCount === 0) {
+ if (empty($nodes)) {
return $plan;
}
- /* EVEN cores first (physical cores) */
- $maxEven = 0;
- foreach ($nodes as $n) {
- $maxEven = max($maxEven, count($n["even"]));
- }
+ $maxCores = max(array_map('count', $nodes));
- for ($coreIndex = 0; $coreIndex < $maxEven; $coreIndex++) {
- foreach ($nodeIds as $node) {
- if (isset($nodes[$node]["even"][$coreIndex])) {
+ /* PASS 1 — physical cores only */
+ for ($i = 0; $i < $maxCores; $i++) {
+ foreach ($nodes as $node => $cores) {
+ $coreIds = array_keys($cores);
+ if (isset($coreIds[$i])) {
$plan[] = [
"node" => $node,
- "cpu" => $nodes[$node]["even"][$coreIndex]
+ "cpu" => $cores[$coreIds[$i]][0]
];
}
}
}
- /* 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])) {
+ /* PASS 2 — SMT siblings */
+ for ($i = 0; $i < $maxCores; $i++) {
+ foreach ($nodes as $node => $cores) {
+ $coreIds = array_keys($cores);
+ if (isset($coreIds[$i]) && count($cores[$coreIds[$i]]) > 1) {
$plan[] = [
"node" => $node,
- "cpu" => $nodes[$node]["odd"][$coreIndex]
+ "cpu" => $cores[$coreIds[$i]][1]
];
}
}
@@ -79,12 +118,18 @@ function buildAllocationPlan(array $nodes): array
return $plan;
}
-/* ---------- STEP 3: ALLOCATE USING PRECOMPUTED PLAN ---------- */
+/* ---------------------------------------------------------
+ ALLOCATION API
+--------------------------------------------------------- */
+
function allocateCore(int $serviceId): array
{
- global $coreFile;
+ $state = loadCoreState();
+
+ if (isset($state["allocations"][$serviceId])) {
+ return $state["allocations"][$serviceId];
+ }
- $map = json_decode(file_get_contents($coreFile), true) ?: [];
$nodes = getNumaTopology();
$plan = buildAllocationPlan($nodes);
@@ -92,32 +137,29 @@ function allocateCore(int $serviceId): array
return ["node" => 0, "cpu" => 0];
}
- $index = count($map);
- $slot = $plan[$index % count($plan)];
+ $slot = $plan[$state["cursor"] % count($plan)];
+ $state["cursor"]++;
- $map[$serviceId] = $slot;
- file_put_contents($coreFile, json_encode($map, JSON_PRETTY_PRINT));
+ $state["allocations"][$serviceId] = $slot;
+ saveCoreState($state);
return $slot;
}
-/* ---------- HELPERS ---------- */
function freeCore(int $serviceId): void
{
- global $coreFile;
- $map = json_decode(file_get_contents($coreFile), true) ?: [];
+ $state = loadCoreState();
- if (isset($map[$serviceId])) {
- unset($map[$serviceId]);
- file_put_contents($coreFile, json_encode($map, JSON_PRETTY_PRINT));
+ if (isset($state["allocations"][$serviceId])) {
+ unset($state["allocations"][$serviceId]);
+ saveCoreState($state);
}
}
function getServiceCore(int $serviceId): ?array
{
- global $coreFile;
- $map = json_decode(file_get_contents($coreFile), true) ?: [];
- return $map[$serviceId] ?? null;
+ $state = loadCoreState();
+ return $state["allocations"][$serviceId] ?? null;
}
$jsonFile = __DIR__ . "/input.json";
@@ -161,42 +203,26 @@ if ($_SERVER["REQUEST_METHOD"] === "POST" && $_POST["action"] === "add") {
$core = (int)$alloc["cpu"];
$node = (int)$alloc["node"];
- $ffmpeg = 'numactl --cpunodebind=' . $node .
- ' --membind=' . $node .
- ' taskset -c ' . $core . ' ffmpeg -hide_banner -loglevel info \
- -thread_queue_size 65536 \
- -fflags +genpts+discardcorrupt+nobuffer \
- -readrate 1.0 \
- -i "udp://@' . $new["input_udp"] . '?fifo_size=100000000&buffer_size=100000000&overrun_nonfatal=1" \
- -vf "setpts=PTS-STARTPTS,yadif=mode=0:parity=0:deint=0,scale=' . $new["resolution"] . ',format=yuv420p" \
- -c:v ' . $new["video_format"] . ' \
- -threads 1 \
- -r 25 \
- -fps_mode cfr \
- -g 12 \
- -bf 0 \
- -b:v ' . $new["video_bitrate"] . 'k \
- -minrate ' . $new["video_bitrate"] . 'k \
- -maxrate ' . $new["video_bitrate"] . 'k \
- -bufsize ' . $new["video_bitrate"] . 'k \
- -c:a ' . $new["audio_format"] . ' \
- -b:a ' . $new["audio_bitrate"] . 'k \
- -ar 48000 -ac 2 \
- -af "volume=' . $new["volume"] . 'dB,aresample=async=1000" \
- -metadata service_provider="ShreeBhattJI" ';
+ $ffmpeg = 'numactl --cpunodebind=' . $node
+ . ' --membind=' . $node
+ . ' taskset -c ' . $core
+ . ' ffmpeg -hide_banner -loglevel info -thread_queue_size 65536 -fflags +genpts+discardcorrupt+nobuffer -readrate 1.0'
+ . ' -i "udp://@' . $new["input_udp"] . '?fifo_size=100000000&buffer_size=100000000&overrun_nonfatal=1"'
+ . ' -vf "yadif=mode=0:deint=0,scale=' . $new["resolution"] . ',format=yuv420p" '
+ . ' -c:v ' . $new["video_format"] . ' -flags -ildct-ilme -threads 1 -g 10 -bf 0 '
+ . ' -b:v ' . $new["video_bitrate"] . 'k -minrate ' . max(0, $new["video_bitrate"] - 500) . 'k -maxrate ' . ($new["video_bitrate"] + 500) . 'k -bufsize 1835k '
+ . ' -c:a ' . $new["audio_format"] . ' -b:a ' . $new["audio_bitrate"] . 'k -ar 48000 -ac 2 -af "volume=' . $new["volume"] . 'dB,aresample=async=1:first_pts=0" '
+ . ' -metadata service_provider="ShreeBhattJI" ';
if ($new["service_name"] !== "") {
$ffmpeg .= '-metadata service_name="' . $new["service_name"] . '" ';
}
- $ffmpeg .= '-pcr_period 20 \
- -f mpegts "udp://' . $new["output_udp"] . '?pkt_size=1316&bitrate=4500000&flush_packets=1"';
+ $ffmpeg .= ' -pcr_period 20 -f mpegts "udp://' . $new["output_udp"] . '?pkt_size=1316&bitrate=4500000&flush_packets=1"';
if ($new["service_name"] !== "")
$ffmpeg .= '-metadata service_name="' . $new["service_name"] . '"';
$ffmpeg .= ' -f mpegts "udp://@' . $new["output_udp"] . '?pkt_size=1316&bitrate=4500000"';
-
-
file_put_contents("/var/www/encoder/{$new["id"]}.sh", $ffmpeg);
if ($new["service"] === "enable") {
@@ -261,36 +287,26 @@ if ($_SERVER["REQUEST_METHOD"] === "POST" && $_POST["action"] === "edit") {
$node = (int)$alloc["node"];
- $ffmpeg = 'numactl --cpunodebind=' . $node .
- ' --membind=' . $node .
- ' taskset -c ' . $core . ' ffmpeg -hide_banner -loglevel info \
- -thread_queue_size 65536 \
- -fflags +genpts+discardcorrupt+nobuffer \
- -readrate 1.0 \
- -i "udp://@' . $new["input_udp"] . '?fifo_size=100000000&buffer_size=100000000&overrun_nonfatal=1" \
- -vf "setpts=PTS-STARTPTS,yadif=mode=0:parity=0:deint=0,scale=' . $new["resolution"] . ',format=yuv420p" \
- -c:v ' . $new["video_format"] . ' \
- -threads 1 \
- -r 25 \
- -fps_mode cfr \
- -g 12 \
- -bf 0 \
- -b:v ' . $new["video_bitrate"] . 'k \
- -minrate ' . $new["video_bitrate"] . 'k \
- -maxrate ' . $new["video_bitrate"] . 'k \
- -bufsize ' . $new["video_bitrate"] . 'k \
- -c:a ' . $new["audio_format"] . ' \
- -b:a ' . $new["audio_bitrate"] . 'k \
- -ar 48000 -ac 2 \
- -af "volume=' . $new["volume"] . 'dB,aresample=async=1000" \
- -metadata service_provider="ShreeBhattJI" ';
+ $ffmpeg = 'numactl --cpunodebind=' . $node
+ . ' --membind=' . $node
+ . ' taskset -c ' . $core
+ . ' ffmpeg -hide_banner -loglevel info -thread_queue_size 65536 -fflags +genpts+discardcorrupt+nobuffer -readrate 1.0'
+ . ' -i "udp://@' . $new["input_udp"] . '?fifo_size=100000000&buffer_size=100000000&overrun_nonfatal=1"'
+ . ' -vf "yadif=mode=0:deint=0,scale=' . $new["resolution"] . ',format=yuv420p" '
+ . ' -c:v ' . $new["video_format"] . ' -flags -ildct-ilme -threads 1 -g 10 -bf 0 '
+ . ' -b:v ' . $new["video_bitrate"] . 'k -minrate ' . max(0, $new["video_bitrate"] - 500) . 'k -maxrate ' . ($new["video_bitrate"] + 500) . 'k -bufsize 1835k '
+ . ' -c:a ' . $new["audio_format"] . ' -b:a ' . $new["audio_bitrate"] . 'k -ar 48000 -ac 2 -af "volume=' . $new["volume"] . 'dB,aresample=async=1:first_pts=0" '
+ . ' -metadata service_provider="ShreeBhattJI" ';
if ($new["service_name"] !== "") {
$ffmpeg .= '-metadata service_name="' . $new["service_name"] . '" ';
}
- $ffmpeg .= '-pcr_period 20 \
- -f mpegts "udp://' . $new["output_udp"] . '?pkt_size=1316&bitrate=4500000&flush_packets=1"';
+ $ffmpeg .= ' -pcr_period 20 -f mpegts "udp://' . $new["output_udp"] . '?pkt_size=1316&bitrate=4500000&flush_packets=1"';
+ if ($new["service_name"] !== "")
+ $ffmpeg .= '-metadata service_name="' . $new["service_name"] . '"';
+ $ffmpeg .= ' -f mpegts "udp://@' . $new["output_udp"] . '?pkt_size=1316&bitrate=4500000"';
+
file_put_contents("/var/www/encoder/$id.sh", $ffmpeg);