Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions benchmark-scripts/benchmark.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import json
import stream_density
from device_validation import validate_target_device, resolve_target_device_default
from windows_metrics import collector as windows_collector


def parse_args(print=False):
Expand Down Expand Up @@ -131,6 +132,8 @@ def docker_compose_containers(command, compose_files=[], compose_pre_args="",
returncode: Popen return code
'''
try:
if env_vars.get('WSL2', '').lower() == 'true' and command in ('down', 'stop'):
windows_collector.stop()
files = " -f ".join(compose_files)
compose_string = ("docker compose %s -f %s %s %s" %
(compose_pre_args, files, command,
Expand All @@ -143,6 +146,8 @@ def docker_compose_containers(command, compose_files=[], compose_pre_args="",
env=env_vars) # nosec B404, B603
stdout, stderr = p.communicate()

if env_vars.get('WSL2', '').lower() == 'true' and command == 'up' and p.returncode == 0:
windows_collector.start(env_vars)
if p.returncode and stderr:
print("Error bringing %s the compose files: %s" %
(command, stderr))
Expand Down Expand Up @@ -199,6 +204,8 @@ def main():
env_vars = os.environ.copy()
env_vars["log_dir"] = results_dir
env_vars["RESULTS_DIR"] = results_dir
if env_vars.get('WSL2', '').lower() == 'true':
env_vars["INIT_DURATION"] = str(my_args.init_duration)
env_vars["DEVICE"] = my_args.target_device
retail_use_case_root = os.path.abspath(my_args.retail_use_case_root)
env_vars["RETAIL_USE_CASE_ROOT"] = retail_use_case_root
Expand Down
151 changes: 151 additions & 0 deletions benchmark-scripts/benchmark_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,157 @@
import unittest
import benchmark
import os
import json
import tempfile
from pathlib import Path
from types import SimpleNamespace
import windows_metrics


class WindowsMetricsTesting(unittest.TestCase):
def test_configured_python_path_with_spaces(self):
executable = '/mnt/c/Program Files/Python311/python.exe'
collector = windows_metrics.WindowsMetricsCollector()
with tempfile.TemporaryDirectory() as directory, \
mock.patch.object(windows_metrics.subprocess, 'check_output',
side_effect=['C:\\helper.py', 'C:\\results']) as paths, \
mock.patch.object(windows_metrics.subprocess, 'Popen') as launch:
launch.return_value.returncode = 0
collector.start({'WSL2': 'true', 'RESULTS_DIR': directory,
'WINDOWS_PYTHON': executable})
self.assertEqual(launch.call_args.args[0][0], executable)
self.assertEqual(paths.call_count, 2)
collector.stop()

def test_configured_python_failure_does_not_try_fallback(self):
collector = windows_metrics.WindowsMetricsCollector()
with tempfile.TemporaryDirectory() as directory, \
mock.patch.object(windows_metrics.subprocess, 'check_output',
side_effect=['C:\\helper.py', 'C:\\results']) as paths, \
mock.patch.object(windows_metrics.subprocess, 'Popen',
side_effect=FileNotFoundError()) as launch:
collector.start({'WSL2': 'true', 'RESULTS_DIR': directory,
'WINDOWS_PYTHON': '/missing/python.exe'})
launch.assert_called_once()
self.assertEqual(paths.call_count, 2)
self.assertIsNone(collector.process)

def test_gpu_power_rejects_cpu_and_preserves_missing_values(self):
reader = windows_metrics.GpuPowerReader.__new__(windows_metrics.GpuPowerReader)
sensor = SimpleNamespace(SensorType='Power', Value=None,
Name='GPU Power', Identifier='/gpu/power/0')
cpu_sensor = SimpleNamespace(SensorType='Power', Value=100,
Name='CPU Package', Identifier='/cpu/power/0')
reader.computer = SimpleNamespace(Hardware=[
SimpleNamespace(HardwareType='Cpu', Sensors=[cpu_sensor]),
SimpleNamespace(HardwareType='GpuIntel', Name='GPU', Identifier='/gpu',
Sensors=[sensor], Update=lambda: None)])
self.assertIsNone(reader.read('/cpu/power/0'))
self.assertIsNone(reader.read('/gpu/power/0'))
sensor.Value = 0
self.assertEqual(reader.read('/gpu/power/0'), 0)
sensor.Value = 12.5
self.assertEqual(reader.read('/gpu/power/0'), 12.5)
sensor.Value = float('nan')
self.assertIsNone(reader.read('/gpu/power/0'))

def test_gpu_power_without_activity_counters(self):
stop = mock.MagicMock()
stop.wait.side_effect = [False, False, True]
with tempfile.TemporaryDirectory() as directory, \
mock.patch.object(windows_metrics, 'GpuPowerReader') as provider, \
mock.patch.dict('sys.modules', {'win32pdh': None}), \
mock.patch.object(windows_metrics.shutil, 'which', return_value=None):
provider.return_value.read.return_value = 12.5
windows_metrics.collect(directory, stop, lhm_dll='library.dll',
power_sensor='/gpu/power/0',
power_adapter='luid_0x0_0x1_phys_0')
metrics = json.loads((Path(directory) / 'windows_metrics.json').read_text())['metrics']
self.assertEqual(metrics['GPU_1 GPU Power (W)'], 12.5)
self.assertEqual(metrics['S0 Power Draw W'], 'NA')
provider.return_value.close.assert_called_once()

def test_gpu_engines(self):
counters = {
f'pid_{process}_luid_0x0_0x1_phys_0_eng_{engine}_engtype_{kind}': value
for process, engine, kind, value in (
(1, 0, '3D', 20), (2, 0, '3D', 30), (1, 1, '3D', 40),
(1, 2, 'Compute_0', 0), (1, 3, 'Copy', float('nan')))
}
result = windows_metrics.gpu_sample(counters)
self.assertEqual(result, {
('luid_0x0_0x1_phys_0', 'Render/3D[RCS] Utilization %'): 50,
('luid_0x0_0x1_phys_0', 'Compute[CCS] Utilization %'): 0})

def test_pcm_units(self):
with tempfile.TemporaryDirectory() as directory:
source = Path(directory) / 'windows_pcm_raw.log'
source.write_text(
'System,System,Socket 0,Socket 0,Proc Energy (Joules)\n'
'Date,Time,READ,WRITE,SKT0\n'
'2026-09-21,10:00:00,1,2,20\n'
'2026-09-21,10:00:02,2,3,40\n')
self.assertEqual(windows_metrics.read_pcm(source), {
'S0 Memory Bandwidth Usage MB/s': 5000,
'S0 Power Draw W': 20})

def test_native_noop_and_wsl_launch_failure(self):
collector = windows_metrics.WindowsMetricsCollector()
with mock.patch.object(windows_metrics.subprocess, 'Popen') as launch:
collector.start({'WSL2': 'false'})
launch.assert_not_called()
with tempfile.TemporaryDirectory() as directory:
output = Path(directory) / 'windows_metrics.json'
output.write_text('{"metrics":{"stale":100}}')
with mock.patch.object(windows_metrics.subprocess, 'check_output',
side_effect=FileNotFoundError('wslpath')):
collector.start({'WSL2': 'true', 'RESULTS_DIR': directory})
self.assertIsNone(collector.process)
self.assertEqual(json.loads(output.read_text())['metrics'], windows_metrics.blank_metrics())

def test_worker_missing_pcm_and_invalid_gpu_sample(self):
pdh = mock.MagicMock(PDH_FMT_DOUBLE=512)
pdh.GetCounterInfo.return_value = (None,) * 6 + ('localized wildcard',)
pdh.ExpandCounterPath.return_value = [
r'\GPU Engine(pid_1_luid_0x0_0x1_phys_0_eng_0_engtype_3D)\Utilization Percentage']
pdh.GetFormattedCounterValue.side_effect = [RuntimeError('invalid'), (0, 25)]
stop = mock.MagicMock()
stop.wait.side_effect = [False, False, False, False, True]
with tempfile.TemporaryDirectory() as directory, \
mock.patch.dict('sys.modules', {'win32pdh': pdh}), \
mock.patch.object(windows_metrics.shutil, 'which', return_value=None):
windows_metrics.collect(directory, stop)
metrics = json.loads((Path(directory) / 'windows_metrics.json').read_text())['metrics']
self.assertEqual(metrics['GPU_1 Render/3D[RCS] Utilization %'], 25)
self.assertEqual(metrics['GPU_1 GPU Power (W)'], 'NA')
self.assertEqual(metrics['S0 Power Draw W'], 'NA')
self.assertEqual(pdh.GetFormattedCounterValue.call_count, 2)

def test_compose_lifecycle(self):
with mock.patch.object(benchmark, 'windows_collector') as collector, \
mock.patch.object(benchmark.subprocess, 'Popen') as launch:
launch.return_value.communicate.return_value = (b'ok', b'')
launch.return_value.returncode = 0
env = {'WSL2': 'true', 'RESULTS_DIR': '/benchmark'}
benchmark.docker_compose_containers('up', env_vars=env)
collector.start.assert_called_once_with(env)
benchmark.docker_compose_containers('down', env_vars=env)
collector.stop.assert_called_once()
collector.reset_mock()
launch.return_value.returncode = 1
benchmark.docker_compose_containers('up', env_vars=env)
collector.start.assert_not_called()

def test_native_compose_never_calls_collector(self):
with mock.patch.object(benchmark, 'windows_collector') as collector, \
mock.patch.object(benchmark.subprocess, 'Popen') as launch:
launch.return_value.communicate.return_value = (b'ok', b'')
launch.return_value.returncode = 0
for env in ({}, {'WSL2': 'false'}, {'WSL': 'true'}):
benchmark.docker_compose_containers('up', env_vars=env)
benchmark.docker_compose_containers('down', env_vars=env)
collector.start.assert_not_called()
collector.stop.assert_not_called()


class Testing(unittest.TestCase):
Expand Down
31 changes: 31 additions & 0 deletions benchmark-scripts/consolidate_multiple_run_of_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -869,13 +869,18 @@ def add_parser():

root_directory = args['root_directory'][0]
output = args['output'][0]
wsl2 = os.environ.get('WSL2', '').lower() == 'true'

n = 0
df = pd.DataFrame()
full_kpi_dict = {}
all_channel_medians = [] # Collect all channel medians from all files

for kpiExtractor in KPIExtractor_OPTION:
if wsl2 and KPIExtractor_OPTION[kpiExtractor] in (
NPUUsageExtractor, PCMExtractor, MemBandwidthExtractor,
PowerUsageExtractor, QMASSAGPUUsageExtractor, XPUMUsageExtractor):
continue
fileFound = False
for dirpath, dirname, filename in os.walk(root_directory):
for file in filename:
Expand All @@ -900,6 +905,32 @@ def add_parser():

full_kpi_dict["Overall Latency (ms)"] = round(overall_median, 3)

if wsl2:
from windows_metrics import blank_metrics
full_kpi_dict.update(blank_metrics())
full_kpi_dict[AVG_NPU_USAGE_CONSTANT] = '0.00'
windows_metrics_path = pathlib.Path(root_directory) / 'windows_metrics.json'
if windows_metrics_path.is_file():
try:
with windows_metrics_path.open(encoding='utf-8-sig') as windows_file:
windows_metrics = json.load(windows_file)['metrics']
if not isinstance(windows_metrics, dict):
raise ValueError('metrics must be an object')
allowed_metric = re.compile(
r'(?:GPU_\d+ (?:Compute\[CCS\] Utilization %|'
r'Render/3D\[RCS\] Utilization %|Video\[VCS\] Utilization %|'
r'VideoEnhance\[VECS\] Utilization %|Blitter Copy Engine %|'
r'GPU Power \(W\))|S\d+ (?:Power Draw W|Memory Bandwidth Usage MB/s))')
for key, value in windows_metrics.items():
if not allowed_metric.fullmatch(key):
continue
if value == 'NA':
full_kpi_dict[key] = value
elif type(value) in (int, float) and np.isfinite(value) and value >= 0:
full_kpi_dict[key] = round(value, 2)
except (OSError, ValueError, KeyError, TypeError) as error:
print(f'WARN: Windows hardware metrics unavailable: {error}')

# Write out summary csv file from dictionary
with open(output, 'w') as csv_file:
writer = csv.writer(csv_file)
Expand Down
Loading
Loading