Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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