Improve Intel stats collection

This commit is contained in:
Nicolas Mowen 2026-03-28 10:18:50 -06:00
parent c35cee2d2f
commit 3835e7649c
3 changed files with 65 additions and 46 deletions

View File

@ -261,20 +261,22 @@ async def set_gpu_stats(
else: else:
stats["jetson-gpu"] = {"gpu": "", "mem": ""} stats["jetson-gpu"] = {"gpu": "", "mem": ""}
hwaccel_errors.append(args) hwaccel_errors.append(args)
elif "qsv" in args: elif "qsv" in args or ("vaapi" in args and not is_vaapi_amd_driver()):
if not config.telemetry.stats.intel_gpu_stats: if not config.telemetry.stats.intel_gpu_stats:
continue continue
# intel QSV GPU if "intel-gpu" not in stats:
intel_usage = get_intel_gpu_stats(config.telemetry.stats.intel_gpu_device) # intel GPU (QSV or VAAPI both use the same physical GPU)
intel_usage = get_intel_gpu_stats(
config.telemetry.stats.intel_gpu_device
)
if intel_usage is not None: if intel_usage is not None:
stats["intel-qsv"] = intel_usage or {"gpu": "", "mem": ""} stats["intel-gpu"] = intel_usage or {"gpu": "", "mem": ""}
else: else:
stats["intel-qsv"] = {"gpu": "", "mem": ""} stats["intel-gpu"] = {"gpu": "", "mem": ""}
hwaccel_errors.append(args) hwaccel_errors.append(args)
elif "vaapi" in args: elif "vaapi" in args:
if is_vaapi_amd_driver():
if not config.telemetry.stats.amd_gpu_stats: if not config.telemetry.stats.amd_gpu_stats:
continue continue
@ -286,20 +288,6 @@ async def set_gpu_stats(
else: else:
stats["amd-vaapi"] = {"gpu": "", "mem": ""} stats["amd-vaapi"] = {"gpu": "", "mem": ""}
hwaccel_errors.append(args) hwaccel_errors.append(args)
else:
if not config.telemetry.stats.intel_gpu_stats:
continue
# intel VAAPI GPU
intel_usage = get_intel_gpu_stats(
config.telemetry.stats.intel_gpu_device
)
if intel_usage is not None:
stats["intel-vaapi"] = intel_usage or {"gpu": "", "mem": ""}
else:
stats["intel-vaapi"] = {"gpu": "", "mem": ""}
hwaccel_errors.append(args)
elif "preset-rk" in args: elif "preset-rk" in args:
rga_usage = get_rockchip_gpu_stats() rga_usage = get_rockchip_gpu_stats()

View File

@ -39,8 +39,12 @@ class TestGpuStats(unittest.TestCase):
process.stdout = self.intel_results process.stdout = self.intel_results
sp.return_value = process sp.return_value = process
intel_stats = get_intel_gpu_stats(False) intel_stats = get_intel_gpu_stats(False)
print(f"the intel stats are {intel_stats}") # rc6 values: 47.844741 and 100.0 → avg 73.92 → gpu = 100 - 73.92 = 26.08%
# Render/3D/0: 0.0 and 0.0 → enc = 0.0%
# Video/0: 4.533124 and 0.0 → dec = 2.27%
assert intel_stats == { assert intel_stats == {
"gpu": "1.13%", "gpu": "26.08%",
"mem": "-%", "mem": "-%",
"enc": "0.0%",
"dec": "2.27%",
} }

View File

@ -265,14 +265,30 @@ def get_amd_gpu_stats() -> Optional[dict[str, str]]:
def get_intel_gpu_stats(intel_gpu_device: Optional[str]) -> Optional[dict[str, str]]: def get_intel_gpu_stats(intel_gpu_device: Optional[str]) -> Optional[dict[str, str]]:
"""Get stats using intel_gpu_top.""" """Get stats using intel_gpu_top.
Returns overall GPU usage derived from rc6 residency (idle time),
plus individual engine breakdowns:
- enc: Render/3D engine (compute/shader encoder, used by QSV)
- dec: Video engines (fixed-function codec, used by VAAPI)
"""
def get_stats_manually(output: str) -> dict[str, str]: def get_stats_manually(output: str) -> dict[str, str]:
"""Find global stats via regex when json fails to parse.""" """Find global stats via regex when json fails to parse."""
reading = "".join(output) reading = "".join(output)
results: dict[str, str] = {} results: dict[str, str] = {}
# render is used for qsv # rc6 residency for overall GPU usage
rc6_match = re.search(r'"rc6":\{"value":([\d.]+)', reading)
if rc6_match:
rc6_value = float(rc6_match.group(1))
results["gpu"] = f"{round(100.0 - rc6_value, 2)}%"
else:
results["gpu"] = "-%"
results["mem"] = "-%"
# Render/3D is the compute/encode engine
render = [] render = []
for result in re.findall(r'"Render/3D/0":{[a-z":\d.,%]+}', reading): for result in re.findall(r'"Render/3D/0":{[a-z":\d.,%]+}', reading):
packet = json.loads(result[14:]) packet = json.loads(result[14:])
@ -280,11 +296,9 @@ def get_intel_gpu_stats(intel_gpu_device: Optional[str]) -> Optional[dict[str, s
render.append(float(single)) render.append(float(single))
if render: if render:
render_avg = sum(render) / len(render) results["enc"] = f"{round(sum(render) / len(render), 2)}%"
else:
render_avg = 1
# video is used for vaapi # Video engines are the fixed-function decode engines
video = [] video = []
for result in re.findall(r'"Video/\d":{[a-z":\d.,%]+}', reading): for result in re.findall(r'"Video/\d":{[a-z":\d.,%]+}', reading):
packet = json.loads(result[10:]) packet = json.loads(result[10:])
@ -292,12 +306,8 @@ def get_intel_gpu_stats(intel_gpu_device: Optional[str]) -> Optional[dict[str, s
video.append(float(single)) video.append(float(single))
if video: if video:
video_avg = sum(video) / len(video) results["dec"] = f"{round(sum(video) / len(video), 2)}%"
else:
video_avg = 1
results["gpu"] = f"{round((video_avg + render_avg) / 2, 2)}%"
results["mem"] = "-%"
return results return results
intel_gpu_top_command = [ intel_gpu_top_command = [
@ -336,10 +346,16 @@ def get_intel_gpu_stats(intel_gpu_device: Optional[str]) -> Optional[dict[str, s
return get_stats_manually(output) return get_stats_manually(output)
results: dict[str, str] = {} results: dict[str, str] = {}
rc6_values = []
render = {"global": []} render = {"global": []}
video = {"global": []} video = {"global": []}
for block in data: for block in data:
# rc6 residency: percentage of time GPU is idle
rc6 = block.get("rc6", {}).get("value")
if rc6 is not None:
rc6_values.append(float(rc6))
global_engine = block.get("engines") global_engine = block.get("engines")
if global_engine: if global_engine:
@ -373,12 +389,23 @@ def get_intel_gpu_stats(intel_gpu_device: Optional[str]) -> Optional[dict[str, s
if video_frame is not None: if video_frame is not None:
video[key].append(float(video_frame)) video[key].append(float(video_frame))
if render["global"] and video["global"]: # Overall GPU usage from rc6 (idle) residency
results["gpu"] = ( if rc6_values:
f"{round(((sum(render['global']) / len(render['global'])) + (sum(video['global']) / len(video['global']))) / 2, 2)}%" rc6_avg = sum(rc6_values) / len(rc6_values)
) results["gpu"] = f"{round(100.0 - rc6_avg, 2)}%"
results["mem"] = "-%" results["mem"] = "-%"
# Encoder: Render/3D engine (compute/shader encode)
if render["global"]:
results["enc"] = (
f"{round(sum(render['global']) / len(render['global']), 2)}%"
)
# Decoder: Video engine (fixed-function codec)
if video["global"]:
results["dec"] = f"{round(sum(video['global']) / len(video['global']), 2)}%"
if len(render.keys()) > 1: if len(render.keys()) > 1:
results["clients"] = {} results["clients"] = {}