fix: use cumulative CPU average for ffmpeg monitoring
Delta sampling over 2s returns 0-1 integer ticks for low-CPU processes (ffmpeg copy mode ~0.3% CPU), always rounding to 0.0%. Switch to cumulative average (total_cpu_ticks / clk_tck / elapsed) — same method ps uses — which gives stable, accurate readings. Also show 2 decimal places so values like 0.34% are visible.
This commit is contained in:
+27
-25
@@ -105,9 +105,10 @@ class BroadcastGroup:
|
|||||||
self._last_data_at: float = time.time()
|
self._last_data_at: float = time.time()
|
||||||
|
|
||||||
# ffmpeg process tracking
|
# ffmpeg process tracking
|
||||||
self._ffmpeg_pid: int | None = None
|
self._ffmpeg_pid: int | None = None
|
||||||
self._ffmpeg_cpu: float = 0.0
|
self._ffmpeg_start_time: float | None = None # wall time when ffmpeg started
|
||||||
self._cpu_task: asyncio.Task | None = None
|
self._ffmpeg_cpu: float = 0.0
|
||||||
|
self._cpu_task: asyncio.Task | None = None
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Public API
|
# Public API
|
||||||
@@ -210,7 +211,7 @@ class BroadcastGroup:
|
|||||||
"stream_info": self.stream_info,
|
"stream_info": self.stream_info,
|
||||||
# ffmpeg process info
|
# ffmpeg process info
|
||||||
"ffmpeg_pid": self._ffmpeg_pid,
|
"ffmpeg_pid": self._ffmpeg_pid,
|
||||||
"ffmpeg_cpu_pct": round(self._ffmpeg_cpu, 1),
|
"ffmpeg_cpu_pct": round(self._ffmpeg_cpu, 2),
|
||||||
}
|
}
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
@@ -218,32 +219,31 @@ class BroadcastGroup:
|
|||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
|
|
||||||
async def _monitor_cpu(self) -> None:
|
async def _monitor_cpu(self) -> None:
|
||||||
"""Sample /proc/{pid}/stat every 2 s and update self._ffmpeg_cpu (%)."""
|
"""Update self._ffmpeg_cpu every 3 s using cumulative average since start.
|
||||||
|
|
||||||
|
Delta-based sampling fails for low-CPU processes (copy-mode ffmpeg uses
|
||||||
|
<1% CPU) because the tick delta in 2 s is 0-1 integer ticks, making the
|
||||||
|
result always 0.0%. Cumulative average ((total_cpu_ticks / clk_tck) /
|
||||||
|
elapsed_wall) gives a stable, accurate reading — the same method ps uses.
|
||||||
|
"""
|
||||||
clk_tck = float(os.sysconf(os.sysconf_names.get("SC_CLK_TCK", 2)) or 100)
|
clk_tck = float(os.sysconf(os.sysconf_names.get("SC_CLK_TCK", 2)) or 100)
|
||||||
prev_ticks: int | None = None
|
|
||||||
prev_wall: float | None = None
|
|
||||||
|
|
||||||
while self._running:
|
while self._running:
|
||||||
await asyncio.sleep(2)
|
await asyncio.sleep(3)
|
||||||
pid = self._ffmpeg_pid
|
pid = self._ffmpeg_pid
|
||||||
if pid is None:
|
start_time = self._ffmpeg_start_time
|
||||||
|
if pid is None or start_time is None:
|
||||||
self._ffmpeg_cpu = 0.0
|
self._ffmpeg_cpu = 0.0
|
||||||
prev_ticks = prev_wall = None
|
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
with open(f"/proc/{pid}/stat") as f:
|
with open(f"/proc/{pid}/stat") as f:
|
||||||
parts = f.read().split()
|
parts = f.read().split()
|
||||||
ticks = int(parts[13]) + int(parts[14]) # utime + stime
|
cpu_ticks = int(parts[13]) + int(parts[14]) # utime + stime
|
||||||
wall = time.time()
|
elapsed = time.time() - start_time
|
||||||
if prev_ticks is not None and prev_wall is not None:
|
if elapsed > 0:
|
||||||
dt = wall - prev_wall
|
self._ffmpeg_cpu = cpu_ticks / clk_tck / elapsed * 100
|
||||||
if dt > 0:
|
|
||||||
self._ffmpeg_cpu = (ticks - prev_ticks) / clk_tck / dt * 100
|
|
||||||
prev_ticks = ticks
|
|
||||||
prev_wall = wall
|
|
||||||
except (FileNotFoundError, IndexError, ValueError, OSError):
|
except (FileNotFoundError, IndexError, ValueError, OSError):
|
||||||
self._ffmpeg_cpu = 0.0
|
self._ffmpeg_cpu = 0.0
|
||||||
prev_ticks = prev_wall = None
|
|
||||||
|
|
||||||
async def _probe_info(self) -> None:
|
async def _probe_info(self) -> None:
|
||||||
from .probe import probe_stream
|
from .probe import probe_stream
|
||||||
@@ -328,9 +328,10 @@ class BroadcastGroup:
|
|||||||
stderr=asyncio.subprocess.PIPE,
|
stderr=asyncio.subprocess.PIPE,
|
||||||
)
|
)
|
||||||
|
|
||||||
self._status = "ok"
|
self._status = "ok"
|
||||||
self._last_data_at = time.time()
|
self._last_data_at = time.time()
|
||||||
self._ffmpeg_pid = proc.pid
|
self._ffmpeg_pid = proc.pid
|
||||||
|
self._ffmpeg_start_time = time.time()
|
||||||
logger.info(
|
logger.info(
|
||||||
f"[ch{self.channel_id}] ffmpeg started pid={proc.pid} "
|
f"[ch{self.channel_id}] ffmpeg started pid={proc.pid} "
|
||||||
f"(reconnects so far: {self._reconnect_count})"
|
f"(reconnects so far: {self._reconnect_count})"
|
||||||
@@ -368,8 +369,9 @@ class BroadcastGroup:
|
|||||||
await self._dispatch(chunk, now)
|
await self._dispatch(chunk, now)
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
self._ffmpeg_pid = None
|
self._ffmpeg_pid = None
|
||||||
self._ffmpeg_cpu = 0.0
|
self._ffmpeg_start_time = None
|
||||||
|
self._ffmpeg_cpu = 0.0
|
||||||
stderr_task.cancel()
|
stderr_task.cancel()
|
||||||
try:
|
try:
|
||||||
proc.kill()
|
proc.kill()
|
||||||
|
|||||||
@@ -237,7 +237,7 @@ export default function Dashboard() {
|
|||||||
<StatCard
|
<StatCard
|
||||||
icon={<Cpu size={20} className={totalCpu > 80 ? 'text-red-400' : totalCpu > 40 ? 'text-yellow-400' : 'text-cyan-400'} />}
|
icon={<Cpu size={20} className={totalCpu > 80 ? 'text-red-400' : totalCpu > 40 ? 'text-yellow-400' : 'text-cyan-400'} />}
|
||||||
label="ffmpeg CPU total"
|
label="ffmpeg CPU total"
|
||||||
value={`${totalCpu.toFixed(1)}%`}
|
value={`${totalCpu.toFixed(2)}%`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -330,7 +330,7 @@ export default function Dashboard() {
|
|||||||
s.ffmpeg_cpu_pct > 40 ? 'text-yellow-400' :
|
s.ffmpeg_cpu_pct > 40 ? 'text-yellow-400' :
|
||||||
'text-gray-400'
|
'text-gray-400'
|
||||||
}`}>
|
}`}>
|
||||||
{s.ffmpeg_cpu_pct.toFixed(1)}%
|
{s.ffmpeg_cpu_pct.toFixed(2)}%
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user