mirror of
https://github.com/blender/blender
synced 2026-09-29 04:37:17 +03:00
Benchmark: Add device name to EEVEE tests
Adding the actual GPU device name and host CPU to the test results. For Cycles the CPU isn't that important when using GPUs as it would mostly perform the scheduling, however when running EEVEE tests the CPU is more involved. This PR will extract the correct GPU device names and adds the CPU name to a separate identifier. The used CPU is then added to the report, when found in the results and different than the test device. Pull Request: https://projects.blender.org/blender/blender/pulls/155419
This commit is contained in:
parent
257696a8dd
commit
b0e2dbcc6a
5 changed files with 88 additions and 19 deletions
|
|
@ -30,6 +30,7 @@ class TestEntry:
|
|||
device_type: str = 'CPU'
|
||||
device_id: str = 'CPU'
|
||||
device_name: str = 'Unknown CPU'
|
||||
device_cpu: str = ''
|
||||
status: str = 'queued'
|
||||
# Short, single-line error.
|
||||
error_msg: str = ''
|
||||
|
|
@ -272,6 +273,9 @@ class TestConfig:
|
|||
entry.executable = executable
|
||||
entry.benchmark_type = self.benchmark_type
|
||||
entry.date = date
|
||||
entry.device_name = device.name
|
||||
if device.cpu:
|
||||
entry.device_cpu = device.cpu
|
||||
if entry.status in {'done', 'failed'}:
|
||||
entry.status = 'outdated'
|
||||
else:
|
||||
|
|
@ -287,6 +291,7 @@ class TestConfig:
|
|||
device_type=device.type,
|
||||
device_id=device.id,
|
||||
device_name=device.name,
|
||||
device_cpu=device.cpu,
|
||||
benchmark_type=self.benchmark_type)
|
||||
entries.append(entry)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@
|
|||
|
||||
import platform
|
||||
import subprocess
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_cpu_name() -> str:
|
||||
|
|
@ -22,13 +25,13 @@ def get_cpu_name() -> str:
|
|||
return "Unknown CPU"
|
||||
|
||||
|
||||
def get_gpu_device(args: None) -> list:
|
||||
def get_gpu_device_cycles(args: None) -> dict:
|
||||
# Get the list of available Cycles GPU devices.
|
||||
import bpy
|
||||
|
||||
prefs = bpy.context.preferences
|
||||
if 'cycles' not in prefs.addons.keys():
|
||||
return []
|
||||
return {'devices': []}
|
||||
cprefs = prefs.addons['cycles'].preferences
|
||||
|
||||
result = []
|
||||
|
|
@ -45,30 +48,73 @@ def get_gpu_device(args: None) -> list:
|
|||
if device.type in {"OPTIX"}:
|
||||
result.append({'type': f"{device.type}-OSL", 'name': device.name, 'index': index})
|
||||
index += 1
|
||||
return {'devices': result}
|
||||
|
||||
# Get GPU backends
|
||||
# TODO: Add support for Vulkan device selection even when backend isn't active.
|
||||
# TODO: Cannot retrieve actual GPU name as the gpu module isn't initialized when run in background mode
|
||||
# !152683 adds support to use gpu module in background mode.
|
||||
original_gpu_backend = prefs.system.gpu_backend
|
||||
for gpu_backend in prefs.system.bl_rna.properties['gpu_backend'].enum_items:
|
||||
|
||||
def get_gpu_device_backend(args: dict) -> dict:
|
||||
|
||||
import bpy
|
||||
import gpu
|
||||
|
||||
result = []
|
||||
|
||||
prefs = bpy.context.preferences
|
||||
gpu_backend = args['gpu_backend'].upper()
|
||||
|
||||
try:
|
||||
gpu.init()
|
||||
except AttributeError:
|
||||
# `gpu.init` has been introduced in March 2026, previous versions are not able to access gpu module and require a fallback.
|
||||
original_gpu_backend = prefs.system.gpu_backend
|
||||
try:
|
||||
prefs.system.gpu_backend = gpu_backend.identifier
|
||||
prefs.system.gpu_backend = gpu_backend
|
||||
except TypeError:
|
||||
# GPU backend isn't available.
|
||||
pass
|
||||
else:
|
||||
result.append({'type': gpu_backend.identifier, 'name': gpu_backend.name})
|
||||
prefs.system.gpu_backend = original_gpu_backend
|
||||
result.append({'type': gpu_backend, 'name': gpu_backend})
|
||||
prefs.system.gpu_backend = original_gpu_backend
|
||||
else:
|
||||
if gpu.platform.backend_type_get() == args['gpu_backend'].upper():
|
||||
result.append({'type': gpu.platform.backend_type_get(), 'name': gpu.platform.renderer_get()})
|
||||
|
||||
return {'devices': result}
|
||||
|
||||
|
||||
def get_gpu_devices(env) -> list:
|
||||
"""
|
||||
Return a list of devices available in default blender executable.
|
||||
"""
|
||||
from api.environment import TestFailure
|
||||
|
||||
result = []
|
||||
|
||||
try:
|
||||
cycles_devices, _ = env.run_in_blender(get_gpu_device_cycles, {})
|
||||
except TestFailure as failure:
|
||||
logger.error("Unable to receive cycles device list", exc_info=failure)
|
||||
else:
|
||||
result += cycles_devices.get('devices', [])
|
||||
|
||||
for backend in ['vulkan', 'opengl', 'metal']:
|
||||
try:
|
||||
backend_devices, _ = env.run_in_blender(
|
||||
get_gpu_device_backend, {
|
||||
'gpu_backend': backend}, gpu_backend=backend)
|
||||
except TestFailure as failure:
|
||||
logger.error("Unable to receive device list for '{backend}'", exc_info=failure)
|
||||
else:
|
||||
result += backend_devices.get('devices', [])
|
||||
|
||||
return result
|
||||
|
||||
|
||||
class TestDevice:
|
||||
def __init__(self, device_type: str, device_id: str, name: str, operating_system: str):
|
||||
def __init__(self, device_type: str, device_id: str, name: str, cpu: str, operating_system: str):
|
||||
self.type = device_type
|
||||
self.id = device_id
|
||||
self.name = name
|
||||
self.cpu = cpu
|
||||
self.operating_system = operating_system
|
||||
|
||||
|
||||
|
|
@ -76,19 +122,20 @@ class TestMachine:
|
|||
def __init__(self, env, need_gpus: bool):
|
||||
operating_system = platform.system()
|
||||
|
||||
self.devices = [TestDevice('CPU', 'CPU', get_cpu_name(), operating_system),
|
||||
TestDevice('CPU-OSL', 'CPU-OSL', get_cpu_name(), operating_system)]
|
||||
device_cpu = get_cpu_name()
|
||||
self.devices = [TestDevice('CPU', 'CPU', device_cpu, device_cpu, operating_system),
|
||||
TestDevice('CPU-OSL', 'CPU-OSL', device_cpu, device_cpu, operating_system)]
|
||||
self.has_gpus = need_gpus
|
||||
|
||||
if need_gpus and env.blender_executable:
|
||||
gpu_devices, _ = env.run_in_blender(get_gpu_device, {})
|
||||
gpu_devices = get_gpu_devices(env)
|
||||
for gpu_device in gpu_devices:
|
||||
device_type = gpu_device['type']
|
||||
device_name = gpu_device['name']
|
||||
device_id = device_type
|
||||
if 'index' in gpu_device:
|
||||
device_id += "_" + str(gpu_device['index'])
|
||||
self.devices.append(TestDevice(device_type, device_id, device_name, operating_system))
|
||||
self.devices.append(TestDevice(device_type, device_id, device_name, device_cpu, operating_system))
|
||||
|
||||
def cpu_device(self) -> TestDevice:
|
||||
return self.devices[0]
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ class TestGraph:
|
|||
|
||||
# Gather used categories.
|
||||
categories = {}
|
||||
device_cpu = ''
|
||||
for entry in device_entries:
|
||||
category = entry.category
|
||||
if category in categories.keys():
|
||||
|
|
@ -43,6 +44,9 @@ class TestGraph:
|
|||
else:
|
||||
categories[category] = [entry]
|
||||
|
||||
if device_cpu == '':
|
||||
device_cpu = entry.device_cpu
|
||||
|
||||
# Sort categories alphabetically.
|
||||
sorted_categories = sorted(categories.items(), key=lambda item: item[0])
|
||||
# Generate one graph for every device x category x result key combination.
|
||||
|
|
@ -60,11 +64,11 @@ class TestGraph:
|
|||
|
||||
for output in sorted(outputs, reverse=True):
|
||||
chart_name = f"{category} ({output})"
|
||||
data.append(self.chart(device_name, chart_name, entries, chart_type, output))
|
||||
data.append(self.chart(device_name, device_cpu, chart_name, entries, chart_type, output))
|
||||
|
||||
self.json = json.dumps(data, indent=2)
|
||||
|
||||
def chart(self, device_name: str, chart_name: str, entries: list, chart_type: str, output: str) -> dict:
|
||||
def chart(self, device_name: str, device_cpu, chart_name: str, entries: list, chart_type: str, output: str) -> dict:
|
||||
# Gather used tests.
|
||||
tests = {}
|
||||
for entry in entries:
|
||||
|
|
@ -128,7 +132,12 @@ class TestGraph:
|
|||
datasets[test_index]['data'][revision_index] = output_value
|
||||
|
||||
data = {'labels': labels, 'datasets': datasets}
|
||||
return {'device': device_name, 'name': chart_name, 'data': data, 'chart_type': chart_type}
|
||||
return {
|
||||
'device': device_name,
|
||||
'device_cpu': device_cpu,
|
||||
'name': chart_name,
|
||||
'data': data,
|
||||
'chart_type': chart_type}
|
||||
|
||||
def write(self, filepath: pathlib.Path) -> None:
|
||||
# Write HTML page with JSON graph data embedded.
|
||||
|
|
|
|||
|
|
@ -409,6 +409,12 @@
|
|||
subtitle_h4.textContent = benchmark['device'];
|
||||
section.appendChild(subtitle_h4);
|
||||
|
||||
if (benchmark['device_cpu'] && benchmark['device_cpu'] != benchmark['device']) {
|
||||
const subtitle_h4 = document.createElement('h4');
|
||||
subtitle_h4.textContent = benchmark['device_cpu'];
|
||||
section.appendChild(subtitle_h4);
|
||||
}
|
||||
|
||||
/* Create chart container. */
|
||||
const chart_container = document.createElement('div');
|
||||
chart_container.style.position = 'relative';
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import api
|
|||
import argparse
|
||||
import fnmatch
|
||||
import glob
|
||||
import logging
|
||||
import pathlib
|
||||
import shutil
|
||||
import sys
|
||||
|
|
@ -359,6 +360,7 @@ def cmd_graph(argv: list):
|
|||
|
||||
|
||||
def main():
|
||||
logging.basicConfig()
|
||||
usage = ('benchmark <command> [<args>]\n'
|
||||
'\n'
|
||||
'Commands:\n'
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue