Fetching processes from PROCFS_PATH in docker container.
Some checks are pending
CodeQL / Analyze (push) Waiting to run
Controller Tests / controller-tests (push) Waiting to run
Jest Tests / jest-tests (push) Waiting to run

This commit is contained in:
Roland 2026-05-30 15:59:14 -06:00
parent 03a2596799
commit cccbac9ae9
No known key found for this signature in database
GPG key ID: 1BA620CE6C9A4680
2 changed files with 44 additions and 10 deletions

View file

@ -9,11 +9,21 @@ can seamlessly switch between data sources.
"""
import statistics
import time
from pathlib import Path
from typing import Optional
import psutil
from controller.lib.config import is_docker_mode
# In the Docker development environment the compose file bind-mounts the host's
# /proc and /sys read-only so the Controller can report real host metrics
# instead of the tiny container namespace. We must tell psutil about it.
# Note: PROCFS_PATH only exists on Linux/SunOS/AIX; guard the access.
if is_docker_mode() and hasattr(psutil, "PROCFS_PATH"):
psutil.PROCFS_PATH = "/host/proc"
# State for bandwidth rate calculations
_previous_network_stats: dict[str, dict] = {}
_previous_disk_stats: dict[str, dict] = {}
@ -26,10 +36,15 @@ def reset_bandwidth_stats():
_previous_disk_stats = {}
# CPU frequency paths
MIN_FREQUENCY_PATH = Path('/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq')
MAX_FREQUENCY_PATH = Path('/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq')
CUR_FREQUENCY_PATH = Path('/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq')
# CPU frequency paths (remapped in Docker dev to the host mounts)
if is_docker_mode():
MIN_FREQUENCY_PATH = Path('/host/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq')
MAX_FREQUENCY_PATH = Path('/host/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq')
CUR_FREQUENCY_PATH = Path('/host/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq')
else:
MIN_FREQUENCY_PATH = Path('/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_min_freq')
MAX_FREQUENCY_PATH = Path('/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq')
CUR_FREQUENCY_PATH = Path('/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq')
def get_cpu_status() -> dict:
@ -357,14 +372,25 @@ def get_processes_status() -> list[dict]:
processes = []
try:
# Get processes sorted by CPU usage
for proc in psutil.process_iter(['pid', 'name', 'cpu_percent', 'memory_percent', 'cmdline']):
# Two-pass sampling is required for accurate cpu_percent.
# First pass: prime the per-process CPU time counters.
procs = []
for proc in psutil.process_iter(['pid', 'name', 'memory_percent', 'cmdline']):
try:
info = proc.info
cpu_percent = info.get('cpu_percent', 0)
proc.cpu_percent() # prime (result ignored)
procs.append(proc)
except (psutil.NoSuchProcess, psutil.AccessDenied):
pass
# Only include processes using > 1% CPU
# Short sample interval. 0.2s is a good balance for "top" display.
time.sleep(0.2)
# Second pass: read the actual percentage since the prime call.
for proc in procs:
try:
cpu_percent = proc.cpu_percent()
if cpu_percent and cpu_percent > 1:
info = proc.info
cmdline = info.get('cmdline') or [info.get('name', '')]
command = ' '.join(cmdline) if cmdline else info.get('name', '')
@ -376,7 +402,7 @@ def get_processes_status() -> list[dict]:
"pid": info.get('pid'),
"percent_cpu": int(cpu_percent),
"percent_mem": int(info.get('memory_percent', 0)),
"command": command[:512], # Limit command length
"command": command[:512],
})
except (psutil.NoSuchProcess, psutil.AccessDenied):
pass

View file

@ -71,6 +71,14 @@ async def lifespan(app: FastAPI):
logger.info("WROLPi Controller v%s starting...", __version__)
logger.info("Docker mode: %s", is_docker_mode())
# When running under Docker dev, status.py will have already remapped
# psutil.PROCFS_PATH to /host/proc (and the sysfs paths) so we report
# real host metrics instead of the container's tiny namespace.
if is_docker_mode():
import psutil
procfs_path = getattr(psutil, "PROCFS_PATH", "N/A (not supported on this platform)")
logger.info("Using host procfs for stats: %s", procfs_path)
# Try to load config from drive if mounted
if is_primary_drive_mounted():
if reload_config_from_drive():