1#! /usr/bin/env python3 2# 3# Copyright 2021 The ANGLE Project Authors. All rights reserved. 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6# 7''' 8Pixel 6 (ARM based) specific script that measures the following for each restricted_trace: 9- Wall time per frame 10- GPU time per frame (if run with --vsync) 11- CPU time per frame 12- GPU power per frame 13- CPU power per frame 14- GPU memory per frame 15 16Setup: 17 18 autoninja -C out/<config> angle_trace_perf_tests angle_apks 19 20Recommended command to run: 21 22 out/<config>/restricted_trace_perf --fixedtime 10 --power --memory --output-tag android.$(date '+%Y%m%d') --loop-count 5 23 24Alternatively, you can pass the build directory and run from anywhere: 25 26 python3 restricted_trace_perf.py --fixedtime 10 --power --output-tag android.$(date '+%Y%m%d') --loop-count 5 --build-dir ../../../out/<config> 27 28- This will run through all the traces 5 times with the native driver, then 5 times with vulkan (via ANGLE) 29- 10 second run time with one warmup loop 30 31Output will be printed to the terminal as it is collected. 32 33Of the 5 runs, the high and low for each data point will be dropped, average of the remaining three will be tracked in the summary spreadsheet. 34''' 35 36import argparse 37import contextlib 38import copy 39import csv 40import fnmatch 41import json 42import logging 43import os 44import pathlib 45import posixpath 46import re 47import statistics 48import subprocess 49import sys 50import threading 51import time 52 53from collections import defaultdict, namedtuple 54from datetime import datetime 55 56PY_UTILS = str(pathlib.Path(__file__).resolve().parents[1] / 'py_utils') 57if PY_UTILS not in sys.path: 58 os.stat(PY_UTILS) and sys.path.insert(0, PY_UTILS) 59import android_helper 60import angle_path_util 61import angle_test_util 62 63DEFAULT_TEST_DIR = '.' 64DEFAULT_LOG_LEVEL = 'info' 65DEFAULT_ANGLE_PACKAGE = 'com.android.angle.test' 66 67Result = namedtuple('Result', ['stdout', 'stderr', 'time']) 68 69 70class _global(object): 71 current_user = None 72 storage_dir = None 73 cache_dir = None 74 75 76def init(): 77 _global.current_user = run_adb_shell_command('am get-current-user').strip() 78 _global.storage_dir = '/data/user/' + _global.current_user + '/com.android.angle.test/files' 79 _global.cache_dir = '/data/user_de/' + _global.current_user + '/com.android.angle.test/cache' 80 logging.debug('Running with user %s, storage %s, cache %s', _global.current_user, 81 _global.storage_dir, _global.cache_dir) 82 83 84def run_async_adb_shell_command(cmd): 85 logging.debug('Kicking off subprocess %s' % (cmd)) 86 87 try: 88 async_process = subprocess.Popen([android_helper.FindAdb(), 'shell', cmd], 89 stdout=subprocess.PIPE, 90 stderr=subprocess.STDOUT) 91 except subprocess.CalledProcessError as e: 92 raise RuntimeError("command '{}' return with error (code {}): {}".format( 93 e.cmd, e.returncode, e.output)) 94 95 logging.debug('Done launching subprocess') 96 97 return async_process 98 99 100def run_adb_command(args): 101 return android_helper._AdbRun(args).decode() 102 103 104def run_adb_shell_command(cmd): 105 return android_helper._AdbShell(cmd).decode() 106 107 108def run_adb_shell_command_with_run_as(cmd): 109 return android_helper._AdbShellWithRunAs(cmd).decode() 110 111 112def run_async_adb_command(args): 113 return run_async_command('adb ' + args) 114 115 116def cleanup(): 117 run_adb_shell_command_with_run_as('rm -f ' + _global.storage_dir + '/out.txt ' + 118 _global.storage_dir + '/gpumem.txt') 119 120 121def clear_blob_cache(): 122 run_adb_shell_command_with_run_as('rm -rf ' + _global.cache_dir) 123 124 125def select_device(device_arg): 126 # The output from 'adb devices' always includes a header and a new line at the end. 127 result_dev_out = run_adb_command(['devices']).strip() 128 129 result_header_end = result_dev_out.find('\n') 130 result_dev_out = '' if result_header_end == -1 else result_dev_out[result_header_end:] 131 result_dev_out = result_dev_out.split()[0::2] 132 133 def print_device_list(): 134 print('\nList of available devices:\n{}'.format('\n'.join(result_dev_out))) 135 136 num_connected_devices = len(result_dev_out) 137 138 # Check the device arg first. If there is none, use the ANDROID SERIAL env var. 139 android_serial_env = os.environ.get('ANDROID_SERIAL') 140 device_serial = device_arg if device_arg != '' else android_serial_env 141 142 # Check for device exceptions 143 if num_connected_devices == 0: 144 logging.error('DeviceError: No devices detected. Please connect a device and try again.') 145 exit() 146 147 if num_connected_devices > 1 and device_serial is None: 148 logging.error( 149 'DeviceError: More than one device detected. Please specify a target device\n' 150 'through either the --device option or $ANDROID_SERIAL, and try again.') 151 print_device_list() 152 exit() 153 154 if device_serial is not None and device_serial not in result_dev_out: 155 logging.error('DeviceError: Device with serial {} not detected.'.format(device_serial)) 156 if device_arg != '': 157 logging.error('Please update the --device input and try again.') 158 else: 159 logging.error('Please update $ANDROID_SERIAL and try again.') 160 print_device_list() 161 exit() 162 163 # Select device 164 if device_serial is not None: 165 logging.info('Device with serial {} selected.'.format(device_serial)) 166 os.environ['ANDROID_SERIAL'] = device_serial 167 168 else: 169 logging.info('Default device ({}) selected.'.format(result_dev_out[0])) 170 171 172def get_mode(args): 173 mode = '' 174 if args.vsync: 175 mode = 'vsync' 176 elif args.offscreen: 177 mode = 'offscreen' 178 179 return mode 180 181 182def get_trace_width(mode): 183 width = 40 184 if mode == 'vsync': 185 width += 5 186 elif mode == 'offscreen': 187 width += 10 188 189 return width 190 191 192def pull_screenshot(args, screenshot_device_dir, renderer): 193 # We don't know the name of the screenshots, since they could have a KeyFrame 194 # Rather than look that up and piece together a file name, we can pull the single screenshot in a generic fashion 195 files = run_adb_shell_command('ls -1 %s' % screenshot_device_dir).split('\n') 196 197 # There might not be a screenshot if the test was skipped 198 files = list(filter(None, (f.strip() for f in files))) # Remove empty strings and whitespace 199 if files: 200 assert len(files) == 1, 'Multiple files(%s) in %s, expected 1: %s' % ( 201 len(files), screenshot_device_dir, files) 202 203 # Grab the single screenshot 204 src_file = files[0] 205 206 # Rename the file to reflect renderer, since we force everything through the platform using "native" 207 dst_file = src_file.replace("native", renderer) 208 209 # Use CWD if no location provided 210 screenshot_dst = args.screenshot_dir if args.screenshot_dir != '' else '.' 211 212 logging.info('Pulling screenshot %s to %s' % (dst_file, screenshot_dst)) 213 run_adb_command([ 214 'pull', 215 posixpath.join(screenshot_device_dir, src_file), 216 posixpath.join(screenshot_dst, dst_file) 217 ]) 218 219 220# This function changes to the target directory, then 'yield' passes execution to the inner part of 221# the 'with' block that invoked it. The 'finally' block is executed at the end of the 'with' block, 222# including when exceptions are raised. 223@contextlib.contextmanager 224def run_from_dir(dir): 225 # If not set, just run the command and return 226 if not dir: 227 yield 228 return 229 # Otherwise, change directories 230 cwd = os.getcwd() 231 os.chdir(dir) 232 try: 233 yield 234 finally: 235 os.chdir(cwd) 236 237 238def run_trace(trace, args, screenshot_device_dir): 239 mode = get_mode(args) 240 241 # Kick off a subprocess that collects peak gpu memory periodically 242 # Note the 0.25 below is the delay (in seconds) between memory checks 243 if args.memory: 244 run_adb_command([ 245 'push', 246 os.path.join(angle_path_util.ANGLE_ROOT_DIR, 'src', 'tests', 'restricted_traces', 247 'gpumem.sh'), '/data/local/tmp' 248 ]) 249 memory_command = 'sh /data/local/tmp/gpumem.sh 0.25 ' + _global.storage_dir 250 memory_process = run_async_adb_shell_command(memory_command) 251 252 flags = [ 253 '--gtest_filter=TraceTest.' + trace, '--use-gl=native', '--verbose', '--verbose-logging' 254 ] 255 if mode != '': 256 flags.append('--' + mode) 257 if args.maxsteps != '': 258 flags += ['--max-steps-performed', args.maxsteps] 259 if args.fixedtime != '': 260 flags += ['--fixed-test-time-with-warmup', args.fixedtime] 261 if args.minimizegpuwork: 262 flags.append('--minimize-gpu-work') 263 if screenshot_device_dir != None: 264 flags += ['--screenshot-dir', screenshot_device_dir] 265 if args.screenshot_frame != '': 266 flags += ['--screenshot-frame', args.screenshot_frame] 267 if args.fps_limit != '': 268 flags += ['--fps-limit', args.fps_limit] 269 270 # Build a command that can be run directly over ADB, for example: 271 r''' 272adb shell am instrument -w \ 273 -e org.chromium.native_test.NativeTestInstrumentationTestRunner.StdoutFile /data/user/0/com.android.angle.test/files/out.txt \ 274 -e org.chromium.native_test.NativeTest.CommandLineFlags \ 275 "--gtest_filter=TraceTest.empires_and_puzzles\ --use-angle=vulkan\ --screenshot-dir\ /data/user/0/com.android.angle.test/files\ --screenshot-frame\ 2\ --max-steps-performed\ 2\ --no-warmup" \ 276 -e org.chromium.native_test.NativeTestInstrumentationTestRunner.ShardNanoTimeout "1000000000000000000" \ 277 -e org.chromium.native_test.NativeTestInstrumentationTestRunner.NativeTestActivity com.android.angle.test.AngleUnitTestActivity \ 278 com.android.angle.test/org.chromium.build.gtest_apk.NativeTestInstrumentationTestRunner 279 ''' 280 shell_command = r''' 281am instrument -w \ 282 -e org.chromium.native_test.NativeTestInstrumentationTestRunner.StdoutFile {storage}/out.txt \ 283 -e org.chromium.native_test.NativeTest.CommandLineFlags "{flags}" \ 284 -e org.chromium.native_test.NativeTestInstrumentationTestRunner.ShardNanoTimeout "1000000000000000000" \ 285 -e org.chromium.native_test.NativeTestInstrumentationTestRunner.NativeTestActivity \ 286 com.android.angle.test.AngleUnitTestActivity \ 287 com.android.angle.test/org.chromium.build.gtest_apk.NativeTestInstrumentationTestRunner 288 '''.format( 289 flags=r' '.join(flags), 290 storage=_global.storage_dir).strip() # Note: space escaped due to subprocess shell=True 291 292 start_time = time.time() 293 result = run_adb_shell_command(shell_command) 294 time_elapsed = time.time() - start_time 295 296 if args.memory: 297 logging.debug('Killing gpumem subprocess') 298 memory_process.kill() 299 300 return time_elapsed 301 302 303def get_test_time(): 304 # Pull the results from the device and parse 305 result = run_adb_shell_command_with_run_as('cat ' + _global.storage_dir + 306 '/out.txt | grep -v Error | grep -v Frame') 307 308 measured_time = None 309 310 for line in result.splitlines(): 311 logging.debug('Checking line: %s' % line) 312 313 # Look for this line and grab the second to last entry: 314 # Mean result time: 1.2793 ms 315 if "Mean result time" in line: 316 measured_time = line.split()[-2] 317 break 318 319 # Check for skipped tests 320 if "Test skipped due to missing extension" in line: 321 missing_ext = line.split()[-1] 322 logging.debug('Skipping test due to missing extension: %s' % missing_ext) 323 measured_time = missing_ext 324 break 325 326 if measured_time is None: 327 if '[ PASSED ]' in result.stdout: 328 measured_time = 'missing' 329 else: 330 measured_time = 'crashed' 331 332 return measured_time 333 334 335def get_gpu_memory(trace_duration): 336 # Pull the results from the device and parse 337 result = run_adb_shell_command_with_run_as('cat ' + _global.storage_dir + 338 '/gpumem.txt | awk "NF"') 339 340 # The gpumem script grabs snapshots of memory per process 341 # Output looks like this, repeated once per sleep_duration of the test: 342 # 343 # time_elapsed: 9 344 # com.android.angle.test:test_process 16513 345 # Memory snapshot for GPU 0: 346 # Global total: 516833280 347 # Proc 504 total: 170385408 348 # Proc 1708 total: 33767424 349 # Proc 2011 total: 17018880 350 # Proc 16513 total: 348479488 351 # Proc 27286 total: 20877312 352 # Proc 27398 total: 1028096 353 354 # Gather the memory at each snapshot 355 time_elapsed = '' 356 test_process = '' 357 gpu_mem = [] 358 gpu_mem_sustained = [] 359 for line in result.splitlines(): 360 logging.debug('Checking line: %s' % line) 361 362 if "time_elapsed" in line: 363 time_elapsed = line.split()[-1] 364 logging.debug('time_elapsed: %s' % time_elapsed) 365 continue 366 367 # Look for this line and grab the last entry: 368 # com.android.angle.test:test_process 31933 369 if "com.android.angle.test" in line: 370 test_process = line.split()[-1] 371 logging.debug('test_process: %s' % test_process) 372 continue 373 374 # If we don't know test process yet, break 375 if test_process == '': 376 continue 377 378 # If we made it this far, we have the process id 379 380 # Look for this line and grab the last entry: 381 # Proc 31933 total: 235184128 382 if test_process in line: 383 gpu_mem_entry = line.split()[-1] 384 logging.debug('Adding: %s to gpu_mem' % gpu_mem_entry) 385 gpu_mem.append(int(gpu_mem_entry)) 386 # logging.debug('gpu_mem contains: %i' % ' '.join(gpu_mem)) 387 if safe_cast_float(time_elapsed) >= (safe_cast_float(trace_duration) / 2): 388 # Start tracking sustained memory usage at the half way point 389 logging.debug('Adding: %s to gpu_mem_sustained' % gpu_mem_entry) 390 gpu_mem_sustained.append(int(gpu_mem_entry)) 391 continue 392 393 gpu_mem_max = 0 394 if len(gpu_mem) != 0: 395 gpu_mem_max = max(gpu_mem) 396 397 gpu_mem_average = 0 398 if len(gpu_mem_sustained) != 0: 399 gpu_mem_average = statistics.mean(gpu_mem_sustained) 400 401 return gpu_mem_average, gpu_mem_max 402 403 404def get_proc_memory(): 405 # Pull the results from the device and parse 406 result = run_adb_shell_command_with_run_as('cat ' + _global.storage_dir + '/out.txt') 407 memory_median = '' 408 memory_max = '' 409 410 for line in result.splitlines(): 411 # Look for "memory_max" in the line and grab the second to last entry: 412 logging.debug('Checking line: %s' % line) 413 414 if "memory_median" in line: 415 memory_median = line.split()[-2] 416 continue 417 418 if "memory_max" in line: 419 memory_max = line.split()[-2] 420 continue 421 422 return safe_cast_int(memory_max), safe_cast_int(memory_median) 423 424 425def get_gpu_time(): 426 # Pull the results from the device and parse 427 result = run_adb_shell_command_with_run_as('cat ' + _global.storage_dir + '/out.txt') 428 gpu_time = '0' 429 430 for line in result.splitlines(): 431 # Look for "gpu_time" in the line and grab the second to last entry: 432 logging.debug('Checking line: %s' % line) 433 434 if "gpu_time" in line: 435 gpu_time = line.split()[-2] 436 break 437 438 return gpu_time 439 440 441def get_cpu_time(): 442 # Pull the results from the device and parse 443 result = run_adb_shell_command_with_run_as('cat ' + _global.storage_dir + '/out.txt') 444 cpu_time = '0' 445 446 for line in result.splitlines(): 447 # Look for "cpu_time" in the line and grab the second to last entry: 448 logging.debug('Checking line: %s' % line) 449 450 if "cpu_time" in line: 451 cpu_time = line.split()[-2] 452 break 453 454 return cpu_time 455 456 457def get_frame_count(): 458 # Pull the results from the device and parse 459 result = run_adb_shell_command_with_run_as('cat ' + _global.storage_dir + 460 '/out.txt | grep -v Error | grep -v Frame') 461 462 frame_count = 0 463 464 for line in result.splitlines(): 465 logging.debug('Checking line: %s' % line) 466 if "trial_steps" in line: 467 frame_count = line.split()[-2] 468 break 469 470 logging.debug('Frame count: %s' % frame_count) 471 return frame_count 472 473 474class GPUPowerStats(): 475 476 # GPU power measured in uWs 477 478 def __init__(self): 479 self.power = {'gpu': 0, 'big_cpu': 0, 'mid_cpu': 0, 'little_cpu': 0} 480 481 def gpu_delta(self, starting): 482 return self.power['gpu'] - starting.power['gpu'] 483 484 def cpu_delta(self, starting): 485 big = self.power['big_cpu'] - starting.power['big_cpu'] 486 mid = self.power['mid_cpu'] - starting.power['mid_cpu'] 487 little = self.power['little_cpu'] - starting.power['little_cpu'] 488 return big + mid + little 489 490 def get_power_data(self): 491 energy_value_result = run_adb_shell_command( 492 'cat /sys/bus/iio/devices/iio:device*/energy_value') 493 # Output like this (ordering doesn't matter) 494 # 495 # t=251741617 496 # CH0(T=251741617)[VSYS_PWR_WLAN_BT], 192612469095 497 # CH1(T=251741617)[L2S_VDD_AOC_RET], 1393792850 498 # CH2(T=251741617)[S9S_VDD_AOC], 16816975638 499 # CH3(T=251741617)[S5S_VDDQ_MEM], 2720913855 500 # CH4(T=251741617)[S10S_VDD2L], 3656592412 501 # CH5(T=251741617)[S4S_VDD2H_MEM], 4597271837 502 # CH6(T=251741617)[S2S_VDD_G3D], 3702041607 503 # CH7(T=251741617)[L9S_GNSS_CORE], 88535064 504 # t=16086645 505 # CH0(T=16086645)[PPVAR_VSYS_PWR_DISP], 611687448 506 # CH1(T=16086645)[VSYS_PWR_MODEM], 179646995 507 # CH2(T=16086645)[VSYS_PWR_RFFE], 0 508 # CH3(T=16086645)[S2M_VDD_CPUCL2], 124265856 509 # CH4(T=16086645)[S3M_VDD_CPUCL1], 170096352 510 # CH5(T=16086645)[S4M_VDD_CPUCL0], 289995530 511 # CH6(T=16086645)[S5M_VDD_INT], 190164699 512 # CH7(T=16086645)[S1M_VDD_MIF], 196512891 513 514 id_map = { 515 r'S2S_VDD_G3D\b|S2S_VDD_GPU\b': 'gpu', 516 r'S\d+M_VDD_CPUCL2\b|S2M_VDD_CPU2\b': 'big_cpu', 517 r'S\d+M_VDD_CPUCL1\b|S3M_VDD_CPU1\b': 'mid_cpu', 518 r'S\d+M_VDD_CPUCL0\b|S4M_VDD_CPU\b': 'little_cpu', 519 } 520 521 for m in id_map.values(): 522 self.power[m] = 0 # Set to 0 to check for missing values and dupes below 523 524 for line in energy_value_result.splitlines(): 525 for mid, m in id_map.items(): 526 if re.search(mid, line): 527 value = int(line.split()[1]) 528 logging.debug('Power metric %s (%s): %d', mid, m, value) 529 assert self.power[m] == 0, 'Duplicate power metric: %s (%s)' % (mid, m) 530 self.power[m] = value 531 532 for mid, m in id_map.items(): 533 assert self.power[m] != 0, 'Power metric not found: %s (%s)' % (mid, m) 534 535 536def wait_for_test_warmup(done_event): 537 p = subprocess.Popen(['adb', 'logcat', '*:S', 'ANGLE:I'], 538 stdout=subprocess.PIPE, 539 text=True, 540 bufsize=1) # line-buffered 541 os.set_blocking(p.stdout.fileno(), False) 542 543 start_time = time.time() 544 while True: 545 line = p.stdout.readline() # non-blocking as per set_blocking above 546 547 # Look for text logged by the harness when warmup is complete and a test is starting 548 if 'running test name' in line: 549 p.kill() 550 break 551 if done_event.is_set(): 552 logging.warning('Test finished without logging to logcat') 553 p.kill() 554 break 555 556 time.sleep(0.05) 557 558 p.poll() 559 if p.returncode != None: 560 logging.warning('Logcat terminated unexpectedly') 561 return 562 563 564def collect_power(done_event, test_fixedtime, results): 565 # Starting point is post test warmup as there are spikes during warmup 566 wait_for_test_warmup(done_event) 567 568 starting_power = GPUPowerStats() 569 starting_power.get_power_data() 570 logging.debug('Starting power: %s' % starting_power.power) 571 572 duration = test_fixedtime - 1.0 # Avoid measuring through test termination 573 start_time = time.time() 574 while time.time() - start_time < duration: 575 if done_event.is_set(): 576 logging.warning('Test finished earlier than expected by collect_power') 577 break 578 time.sleep(0.05) 579 580 ending_power = GPUPowerStats() 581 ending_power.get_power_data() 582 dt = time.time() - start_time 583 logging.debug('Ending power: %s' % ending_power.power) 584 585 results.update({ 586 # 1e6 for uW -> W 587 'cpu': ending_power.cpu_delta(starting_power) / dt / 1e6, 588 'gpu': ending_power.gpu_delta(starting_power) / dt / 1e6, 589 }) 590 591 592def get_thermal_info(): 593 out = run_adb_shell_command('dumpsys android.hardware.thermal.IThermal/default') 594 result = [] 595 for line in out.splitlines(): 596 if 'ThrottlingStatus:' in line: 597 name = re.search('Name: ([^ ]*)', line).group(1) 598 if ('VIRTUAL-SKIN' in name and 599 '-CHARGE-' not in name and # only supposed to affect charging speed 600 'MODEL' not in name.split('-')): # different units and not used for throttling 601 result.append(line) 602 603 if not result: 604 logging.error('Unexpected dumpsys IThermal response:\n%s', out) 605 raise RuntimeError('Unexpected dumpsys IThermal response, logged above') 606 return result 607 608 609def set_vendor_thermal_control(disabled=None): 610 if disabled: 611 # When disabling, first wait for vendor throttling to end to reset all state 612 waiting = True 613 while waiting: 614 waiting = False 615 for line in get_thermal_info(): 616 if 'ThrottlingStatus: NONE' not in line: 617 logging.info('Waiting for vendor throttling to finish: %s', line.strip()) 618 time.sleep(10) 619 waiting = True 620 break 621 622 run_adb_shell_command('setprop persist.vendor.disable.thermal.control %d' % disabled) 623 624 625def sleep_until_temps_below(limit_temp): 626 waiting = True 627 while waiting: 628 waiting = False 629 for line in get_thermal_info(): 630 v = float(re.search('CurrentValue: ([^ ]*)', line).group(1)) 631 if v > limit_temp: 632 logging.info('Waiting for device temps below %.1f: %s', limit_temp, line.strip()) 633 time.sleep(5) 634 waiting = True 635 break 636 637 638def sleep_until_temps_below_thermalservice(limit_temp): 639 waiting = True 640 while waiting: 641 waiting = False 642 lines = run_adb_shell_command('dumpsys thermalservice').splitlines() 643 assert 'HAL Ready: true' in lines 644 for l in lines[lines.index('Current temperatures from HAL:') + 1:]: 645 if 'Temperature{' not in l: 646 break 647 v = re.search(r'mValue=([^,}]+)', l).group(1) 648 # Note: on some Pixel devices odd component temps are also reported here 649 # but some other key ones are not (e.g. CPU ODPM controlling cpu freqs) 650 if float(v) > limit_temp: 651 logging.info('Waiting for device temps below %.1f: %s', limit_temp, l.strip()) 652 time.sleep(5) 653 waiting = True 654 break 655 656 657def sleep_until_battery_level(min_battery_level): 658 while True: 659 level = int(run_adb_shell_command('dumpsys battery get level').strip()) 660 if level >= min_battery_level: 661 break 662 logging.info('Waiting for device battery level to reach %d. Current level: %d', 663 min_battery_level, level) 664 time.sleep(10) 665 666 667def drop_high_low_and_average(values): 668 if len(values) >= 3: 669 values.remove(min(values)) 670 values.remove(max(values)) 671 672 average = statistics.mean(values) 673 674 variance = 0 675 if len(values) >= 2 and average != 0: 676 variance = statistics.stdev(values) / average 677 678 print(average, variance) 679 680 return float(average), float(variance) 681 682 683def get_angle_version(): 684 angle_version = android_helper._Run('git rev-parse HEAD'.split(' ')).decode().strip() 685 origin_main_version = android_helper._Run( 686 'git rev-parse origin/main'.split(' ')).decode().strip() 687 if origin_main_version != angle_version: 688 angle_version += ' (origin/main %s)' % origin_main_version 689 return angle_version 690 691 692def safe_divide(x, y): 693 if y == 0: 694 return 0 695 return x / y 696 697 698def safe_cast_float(x): 699 if x == '': 700 return 0 701 return float(x) 702 703 704def safe_cast_int(x): 705 if x == '': 706 return 0 707 return int(x) 708 709 710def percent(x): 711 return "%.2f%%" % (x * 100) 712 713 714def main(): 715 parser = argparse.ArgumentParser() 716 parser.add_argument('-f', '--filter', help='Trace filter. Defaults to all.', default='*') 717 parser.add_argument('-l', '--log', help='Logging level.', default=DEFAULT_LOG_LEVEL) 718 parser.add_argument( 719 '--renderer', 720 help='Which renderer to use: native, vulkan (via ANGLE), or default (' + 721 'GLES driver selected by system). Providing no option will run twice, native and vulkan', 722 default='both') 723 parser.add_argument( 724 '--walltimeonly', 725 help='Limit output to just wall time', 726 action='store_true', 727 default=False) 728 parser.add_argument( 729 '--power', help='Include CPU/GPU power used per trace', action='store_true', default=False) 730 parser.add_argument( 731 '--memory', 732 help='Include CPU/GPU memory used per trace', 733 action='store_true', 734 default=False) 735 parser.add_argument('--maxsteps', help='Run for fixed set of frames', default='') 736 parser.add_argument('--fixedtime', help='Run for fixed set of time', default='') 737 parser.add_argument( 738 '--minimizegpuwork', 739 help='Whether to run with minimized GPU work', 740 action='store_true', 741 default=False) 742 parser.add_argument('--output-tag', help='Tag for output files.', required=True) 743 parser.add_argument('--angle-version', help='Specify ANGLE version string.', default='') 744 parser.add_argument( 745 '--loop-count', help='How many times to loop through the traces', default=5) 746 parser.add_argument( 747 '--device', help='Which device to run the tests on (use serial)', default='') 748 parser.add_argument( 749 '--sleep', help='Add a sleep of this many seconds between each test)', type=int, default=0) 750 parser.add_argument( 751 '--custom-throttling-temp', 752 help='Custom thermal throttling with limit set to this temperature (off by default)', 753 type=float) 754 parser.add_argument( 755 '--custom-throttling-thermalservice-temp', 756 help='Custom thermal throttling (thermalservice) with limit set to this temperature (off by default)', 757 type=float) 758 parser.add_argument( 759 '--min-battery-level', 760 help='Sleep between tests if battery level drops below this value (off by default)', 761 type=int) 762 parser.add_argument( 763 '--angle-package', 764 help='Where to load ANGLE libraries from. This will load from the test APK by default, ' + 765 'but you can point to any APK that contains ANGLE. Specify \'system\' to use libraries ' + 766 'already on the device', 767 default=DEFAULT_ANGLE_PACKAGE) 768 parser.add_argument( 769 '--build-dir', 770 help='Where to find the APK on the host, i.e. out/Android. If unset, it is assumed you ' + 771 'are running from the build dir already, or are using the wrapper script ' + 772 'out/<config>/restricted_trace_perf.', 773 default='') 774 parser.add_argument( 775 '--screenshot-dir', 776 help='Host (local) directory to store screenshots of keyframes, which is frame 1 unless ' + 777 'the trace JSON file contains \'KeyFrames\'.', 778 default='') 779 parser.add_argument( 780 '--screenshot-frame', 781 help='Specify a specific frame to screenshot. Uses --screenshot-dir if provied.', 782 default='') 783 parser.add_argument( 784 '--fps-limit', help='Limit replay framerate to specified value', default='') 785 786 group = parser.add_mutually_exclusive_group() 787 group.add_argument( 788 '--vsync', 789 help='Whether to run the trace in vsync mode', 790 action='store_true', 791 default=False) 792 group.add_argument( 793 '--offscreen', 794 help='Whether to run the trace in offscreen mode', 795 action='store_true', 796 default=False) 797 798 args = parser.parse_args() 799 800 angle_test_util.SetupLogging(args.log.upper()) 801 802 with run_from_dir(args.build_dir): 803 android_helper.Initialize("angle_trace_tests") # includes adb root 804 805 # Determine some starting parameters 806 init() 807 808 try: 809 if args.custom_throttling_temp: 810 set_vendor_thermal_control(disabled=1) 811 run_traces(args) 812 finally: 813 if args.custom_throttling_temp: 814 set_vendor_thermal_control(disabled=0) 815 # Clean up settings, including in case of exceptions (including Ctrl-C) 816 run_adb_shell_command('settings delete global angle_debug_package') 817 run_adb_shell_command('settings delete global angle_gl_driver_selection_pkgs') 818 run_adb_shell_command('settings delete global angle_gl_driver_selection_values') 819 820 return 0 821 822 823def logged_args(): 824 parser = argparse.ArgumentParser() 825 parser.add_argument('--output-tag') 826 _, extra_args = parser.parse_known_args() 827 return ' '.join(extra_args) 828 829 830def run_traces(args): 831 # Load trace names 832 test_json = os.path.join(args.build_dir, 'gen/trace_list.json') 833 with open(os.path.join(DEFAULT_TEST_DIR, test_json)) as f: 834 traces = json.loads(f.read()) 835 836 failures = [] 837 838 mode = get_mode(args) 839 trace_width = get_trace_width(mode) 840 841 # Select the target device 842 select_device(args.device) 843 844 # Add an underscore to the mode for use in loop below 845 if mode != '': 846 mode = mode + '_' 847 848 # Create output CSV 849 raw_data_filename = "raw_data." + args.output_tag + ".csv" 850 output_file = open(raw_data_filename, 'w', newline='') 851 output_writer = csv.writer(output_file) 852 853 # Set some widths that allow easily reading the values, but fit on smaller monitors. 854 column_width = { 855 'trace': trace_width, 856 'wall_time': 15, 857 'gpu_time': 15, 858 'cpu_time': 15, 859 'gpu_power': 10, 860 'cpu_power': 10, 861 'gpu_mem_sustained': 20, 862 'gpu_mem_peak': 15, 863 'proc_mem_median': 20, 864 'proc_mem_peak': 15 865 } 866 867 if args.walltimeonly: 868 print('%-*s' % (trace_width, 'wall_time_per_frame')) 869 else: 870 print('%-*s %-*s %-*s %-*s %-*s %-*s %-*s %-*s %-*s %-*s' % 871 (column_width['trace'], 'trace', column_width['wall_time'], 'wall_time', 872 column_width['gpu_time'], 'gpu_time', column_width['cpu_time'], 'cpu_time', 873 column_width['gpu_power'], 'gpu_power', column_width['cpu_power'], 'cpu_power', 874 column_width['gpu_mem_sustained'], 'gpu_mem_sustained', 875 column_width['gpu_mem_peak'], 'gpu_mem_peak', column_width['proc_mem_median'], 876 'proc_mem_median', column_width['proc_mem_peak'], 'proc_mem_peak')) 877 output_writer.writerow([ 878 'trace', 'wall_time(ms)', 'gpu_time(ms)', 'cpu_time(ms)', 'gpu_power(W)', 879 'cpu_power(W)', 'gpu_mem_sustained', 'gpu_mem_peak', 'proc_mem_median', 'proc_mem_peak' 880 ]) 881 882 if args.power: 883 starting_power = GPUPowerStats() 884 ending_power = GPUPowerStats() 885 886 renderers = [] 887 if args.renderer != "both": 888 renderers.append(args.renderer) 889 else: 890 renderers = ("native", "vulkan") 891 892 wall_times = defaultdict(dict) 893 gpu_times = defaultdict(dict) 894 cpu_times = defaultdict(dict) 895 gpu_powers = defaultdict(dict) 896 cpu_powers = defaultdict(dict) 897 gpu_mem_sustaineds = defaultdict(dict) 898 gpu_mem_peaks = defaultdict(dict) 899 proc_mem_medians = defaultdict(dict) 900 proc_mem_peaks = defaultdict(dict) 901 902 # Organize the data for writing out 903 rows = defaultdict(dict) 904 905 def populate_row(rows, name, results): 906 if len(rows[name]) == 0: 907 rows[name] = defaultdict(list) 908 for renderer, wtimes in results.items(): 909 average, variance = drop_high_low_and_average(wtimes) 910 rows[name][renderer].append(average) 911 rows[name][renderer].append(variance) 912 913 # Generate the SUMMARY output 914 summary_file = open("summary." + args.output_tag + ".csv", 'w', newline='') 915 summary_writer = csv.writer(summary_file) 916 917 android_version = run_adb_shell_command('getprop ro.build.fingerprint').strip() 918 angle_version = args.angle_version or get_angle_version() 919 # test_time = run_command('date \"+%Y%m%d\"').stdout.read().strip() 920 921 summary_writer.writerow([ 922 "\"Android: " + android_version + "\n" + "ANGLE: " + angle_version + "\n" + 923 # "Date: " + test_time + "\n" + 924 "Source: " + raw_data_filename + "\n" + "Args: " + logged_args() + "\"" 925 ]) 926 927 trace_number = 0 928 929 if (len(renderers) == 1) and (renderers[0] != "both"): 930 renderer_name = renderers[0] 931 summary_writer.writerow([ 932 "#", "\"Trace\"", f"\"{renderer_name}\nwall\ntime\nper\nframe\n(ms)\"", 933 f"\"{renderer_name}\nwall\ntime\nvariance\"", 934 f"\"{renderer_name}\nGPU\ntime\nper\nframe\n(ms)\"", 935 f"\"{renderer_name}\nGPU\ntime\nvariance\"", 936 f"\"{renderer_name}\nCPU\ntime\nper\nframe\n(ms)\"", 937 f"\"{renderer_name}\nCPU\ntime\nvariance\"", f"\"{renderer_name}\nGPU\npower\n(W)\"", 938 f"\"{renderer_name}\nGPU\npower\nvariance\"", f"\"{renderer_name}\nCPU\npower\n(W)\"", 939 f"\"{renderer_name}\nCPU\npower\nvariance\"", f"\"{renderer_name}\nGPU\nmem\n(B)\"", 940 f"\"{renderer_name}\nGPU\nmem\nvariance\"", 941 f"\"{renderer_name}\npeak\nGPU\nmem\n(B)\"", 942 f"\"{renderer_name}\npeak\nGPU\nmem\nvariance\"", 943 f"\"{renderer_name}\nprocess\nmem\n(B)\"", 944 f"\"{renderer_name}\nprocess\nmem\nvariance\"", 945 f"\"{renderer_name}\npeak\nprocess\nmem\n(B)\"", 946 f"\"{renderer_name}\npeak\nprocess\nmem\nvariance\"" 947 ]) 948 else: 949 summary_writer.writerow([ 950 "#", "\"Trace\"", "\"Native\nwall\ntime\nper\nframe\n(ms)\"", 951 "\"Native\nwall\ntime\nvariance\"", "\"ANGLE\nwall\ntime\nper\nframe\n(ms)\"", 952 "\"ANGLE\nwall\ntime\nvariance\"", "\"wall\ntime\ncompare\"", 953 "\"Native\nGPU\ntime\nper\nframe\n(ms)\"", "\"Native\nGPU\ntime\nvariance\"", 954 "\"ANGLE\nGPU\ntime\nper\nframe\n(ms)\"", "\"ANGLE\nGPU\ntime\nvariance\"", 955 "\"GPU\ntime\ncompare\"", "\"Native\nCPU\ntime\nper\nframe\n(ms)\"", 956 "\"Native\nCPU\ntime\nvariance\"", "\"ANGLE\nCPU\ntime\nper\nframe\n(ms)\"", 957 "\"ANGLE\nCPU\ntime\nvariance\"", "\"CPU\ntime\ncompare\"", 958 "\"Native\nGPU\npower\n(W)\"", "\"Native\nGPU\npower\nvariance\"", 959 "\"ANGLE\nGPU\npower\n(W)\"", "\"ANGLE\nGPU\npower\nvariance\"", 960 "\"GPU\npower\ncompare\"", "\"Native\nCPU\npower\n(W)\"", 961 "\"Native\nCPU\npower\nvariance\"", "\"ANGLE\nCPU\npower\n(W)\"", 962 "\"ANGLE\nCPU\npower\nvariance\"", "\"CPU\npower\ncompare\"", 963 "\"Native\nGPU\nmem\n(B)\"", "\"Native\nGPU\nmem\nvariance\"", 964 "\"ANGLE\nGPU\nmem\n(B)\"", "\"ANGLE\nGPU\nmem\nvariance\"", "\"GPU\nmem\ncompare\"", 965 "\"Native\npeak\nGPU\nmem\n(B)\"", "\"Native\npeak\nGPU\nmem\nvariance\"", 966 "\"ANGLE\npeak\nGPU\nmem\n(B)\"", "\"ANGLE\npeak\nGPU\nmem\nvariance\"", 967 "\"GPU\npeak\nmem\ncompare\"", "\"Native\nprocess\nmem\n(B)\"", 968 "\"Native\nprocess\nmem\nvariance\"", "\"ANGLE\nprocess\nmem\n(B)\"", 969 "\"ANGLE\nprocess\nmem\nvariance\"", "\"process\nmem\ncompare\"", 970 "\"Native\npeak\nprocess\nmem\n(B)\"", "\"Native\npeak\nprocess\nmem\nvariance\"", 971 "\"ANGLE\npeak\nprocess\nmem\n(B)\"", "\"ANGLE\npeak\nprocess\nmem\nvariance\"", 972 "\"process\npeak\nmem\ncompare\"" 973 ]) 974 975 with run_from_dir(args.build_dir): 976 android_helper.PrepareTestSuite("angle_trace_tests") 977 978 for trace in fnmatch.filter(traces, args.filter): 979 980 print( 981 "\nStarting run for %s loopcount %i with %s at %s\n" % 982 (trace, int(args.loop_count), renderers, datetime.now().strftime('%Y-%m-%d %H:%M:%S'))) 983 984 with run_from_dir(args.build_dir): 985 android_helper.PrepareRestrictedTraces([trace]) 986 987 # Start with clean data containers for each trace 988 rows.clear() 989 wall_times.clear() 990 gpu_times.clear() 991 cpu_times.clear() 992 gpu_powers.clear() 993 cpu_powers.clear() 994 gpu_mem_sustaineds.clear() 995 gpu_mem_peaks.clear() 996 proc_mem_medians.clear() 997 proc_mem_peaks.clear() 998 999 for i in range(int(args.loop_count)): 1000 1001 for renderer in renderers: 1002 1003 if renderer == "native": 1004 # Force the settings to native 1005 run_adb_shell_command( 1006 'settings put global angle_gl_driver_selection_pkgs com.android.angle.test' 1007 ) 1008 run_adb_shell_command( 1009 'settings put global angle_gl_driver_selection_values native') 1010 elif renderer == "vulkan": 1011 # Force the settings to ANGLE 1012 run_adb_shell_command( 1013 'settings put global angle_gl_driver_selection_pkgs com.android.angle.test' 1014 ) 1015 run_adb_shell_command( 1016 'settings put global angle_gl_driver_selection_values angle') 1017 elif renderer == "default": 1018 logging.info( 1019 'Deleting Android settings for forcing selection of GLES driver, ' + 1020 'allowing system to load the default') 1021 run_adb_shell_command('settings delete global angle_debug_package') 1022 run_adb_shell_command('settings delete global angle_gl_driver_all_angle') 1023 run_adb_shell_command('settings delete global angle_gl_driver_selection_pkgs') 1024 run_adb_shell_command( 1025 'settings delete global angle_gl_driver_selection_values') 1026 else: 1027 logging.error('Unsupported renderer {}'.format(renderer)) 1028 exit() 1029 1030 if args.angle_package == 'system': 1031 # Clear the debug package so ANGLE will be loaded from /system/lib64 1032 run_adb_shell_command('settings delete global angle_debug_package') 1033 else: 1034 # Otherwise, load ANGLE from the specified APK 1035 run_adb_shell_command('settings put global angle_debug_package ' + 1036 args.angle_package) 1037 1038 # Remove any previous perf results 1039 cleanup() 1040 # Clear blob cache to avoid post-warmup cache eviction b/298028816 1041 clear_blob_cache() 1042 1043 test = trace.split(' ')[0] 1044 1045 if args.power: 1046 assert args.fixedtime, '--power requires --fixedtime' 1047 done_event = threading.Event() 1048 run_adb_command(['logcat', '-c']) # needed for wait_for_test_warmup 1049 power_results = {} # output arg 1050 power_thread = threading.Thread( 1051 target=collect_power, 1052 args=(done_event, float(args.fixedtime), power_results)) 1053 power_thread.daemon = True 1054 power_thread.start() 1055 1056 # We scope the run_trace call so we can use a temp dir for screenshots that gets deleted 1057 with contextlib.ExitStack() as stack: 1058 screenshot_device_dir = None 1059 if args.screenshot_dir != '' or args.screenshot_frame != '': 1060 temp_dir = stack.enter_context(android_helper._TempDeviceDir()) 1061 screenshot_device_dir = temp_dir 1062 1063 logging.debug('Running %s' % test) 1064 test_time = run_trace(test, args, screenshot_device_dir) 1065 1066 if screenshot_device_dir: 1067 pull_screenshot(args, screenshot_device_dir, renderer) 1068 1069 gpu_power, cpu_power = 0, 0 1070 if args.power: 1071 done_event.set() 1072 power_thread.join(timeout=2) 1073 if power_thread.is_alive(): 1074 logging.warning('collect_power thread did not terminate') 1075 else: 1076 gpu_power = power_results['gpu'] 1077 cpu_power = power_results['cpu'] 1078 1079 wall_time = get_test_time() 1080 1081 gpu_time = get_gpu_time() if args.vsync else '0' 1082 1083 cpu_time = get_cpu_time() 1084 1085 gpu_mem_sustained, gpu_mem_peak = 0, 0 1086 proc_mem_peak, proc_mem_median = 0, 0 1087 if args.memory: 1088 gpu_mem_sustained, gpu_mem_peak = get_gpu_memory(test_time) 1089 logging.debug( 1090 '%s = %i, %s = %i' % 1091 ('gpu_mem_sustained', gpu_mem_sustained, 'gpu_mem_peak', gpu_mem_peak)) 1092 1093 proc_mem_peak, proc_mem_median = get_proc_memory() 1094 1095 trace_name = mode + renderer + '_' + test 1096 1097 if len(wall_times[test]) == 0: 1098 wall_times[test] = defaultdict(list) 1099 try: 1100 wt = safe_cast_float(wall_time) 1101 except ValueError: # e.g. 'crashed' 1102 wt = -1 1103 wall_times[test][renderer].append(wt) 1104 1105 if len(gpu_times[test]) == 0: 1106 gpu_times[test] = defaultdict(list) 1107 gpu_times[test][renderer].append(safe_cast_float(gpu_time)) 1108 1109 if len(cpu_times[test]) == 0: 1110 cpu_times[test] = defaultdict(list) 1111 cpu_times[test][renderer].append(safe_cast_float(cpu_time)) 1112 1113 if len(gpu_powers[test]) == 0: 1114 gpu_powers[test] = defaultdict(list) 1115 gpu_powers[test][renderer].append(safe_cast_float(gpu_power)) 1116 1117 if len(cpu_powers[test]) == 0: 1118 cpu_powers[test] = defaultdict(list) 1119 cpu_powers[test][renderer].append(safe_cast_float(cpu_power)) 1120 1121 if len(gpu_mem_sustaineds[test]) == 0: 1122 gpu_mem_sustaineds[test] = defaultdict(list) 1123 gpu_mem_sustaineds[test][renderer].append(safe_cast_int(gpu_mem_sustained)) 1124 1125 if len(gpu_mem_peaks[test]) == 0: 1126 gpu_mem_peaks[test] = defaultdict(list) 1127 gpu_mem_peaks[test][renderer].append(safe_cast_int(gpu_mem_peak)) 1128 1129 if len(proc_mem_medians[test]) == 0: 1130 proc_mem_medians[test] = defaultdict(list) 1131 proc_mem_medians[test][renderer].append(safe_cast_int(proc_mem_median)) 1132 1133 if len(proc_mem_peaks[test]) == 0: 1134 proc_mem_peaks[test] = defaultdict(list) 1135 proc_mem_peaks[test][renderer].append(safe_cast_int(proc_mem_peak)) 1136 1137 if args.walltimeonly: 1138 print('%-*s' % (trace_width, wall_time)) 1139 else: 1140 print( 1141 '%-*s %-*s %-*s %-*s %-*s %-*s %-*i %-*i %-*i %-*i' % 1142 (column_width['trace'], trace_name, column_width['wall_time'], wall_time, 1143 column_width['gpu_time'], gpu_time, column_width['cpu_time'], cpu_time, 1144 column_width['gpu_power'], '%.3f' % gpu_power, column_width['cpu_power'], 1145 '%.3f' % cpu_power, column_width['gpu_mem_sustained'], gpu_mem_sustained, 1146 column_width['gpu_mem_peak'], gpu_mem_peak, 1147 column_width['proc_mem_median'], proc_mem_median, 1148 column_width['proc_mem_peak'], proc_mem_peak)) 1149 output_writer.writerow([ 1150 mode + renderer + '_' + test, wall_time, gpu_time, cpu_time, gpu_power, 1151 cpu_power, gpu_mem_sustained, gpu_mem_peak, proc_mem_median, proc_mem_peak 1152 ]) 1153 1154 1155 # Early exit for testing 1156 #exit() 1157 1158 # Depending on workload, sleeps might be needed to dissipate heat or recharge battery 1159 if args.sleep != 0: 1160 time.sleep(args.sleep) 1161 1162 if args.custom_throttling_temp: 1163 sleep_until_temps_below(args.custom_throttling_temp) 1164 1165 if args.custom_throttling_thermalservice_temp: 1166 sleep_until_temps_below_thermalservice( 1167 args.custom_throttling_thermalservice_temp) 1168 1169 if args.min_battery_level: 1170 sleep_until_battery_level(args.min_battery_level) 1171 1172 print() # New line for readability 1173 1174 for name, results in wall_times.items(): 1175 populate_row(rows, name, results) 1176 1177 for name, results in gpu_times.items(): 1178 populate_row(rows, name, results) 1179 1180 for name, results in cpu_times.items(): 1181 populate_row(rows, name, results) 1182 1183 for name, results in gpu_powers.items(): 1184 populate_row(rows, name, results) 1185 1186 for name, results in cpu_powers.items(): 1187 populate_row(rows, name, results) 1188 1189 for name, results in gpu_mem_sustaineds.items(): 1190 populate_row(rows, name, results) 1191 1192 for name, results in gpu_mem_peaks.items(): 1193 populate_row(rows, name, results) 1194 1195 for name, results in proc_mem_medians.items(): 1196 populate_row(rows, name, results) 1197 1198 for name, results in proc_mem_peaks.items(): 1199 populate_row(rows, name, results) 1200 1201 if (len(renderers) == 1) and (renderers[0] != "both"): 1202 renderer_name = renderers[0] 1203 for name, data in rows.items(): 1204 trace_number += 1 1205 summary_writer.writerow([ 1206 trace_number, 1207 name, 1208 # wall_time 1209 "%.3f" % data[renderer_name][0], 1210 percent(data[renderer_name][1]), 1211 # GPU time 1212 "%.3f" % data[renderer_name][2], 1213 percent(data[renderer_name][3]), 1214 # CPU time 1215 "%.3f" % data[renderer_name][4], 1216 percent(data[renderer_name][5]), 1217 # GPU power 1218 "%.3f" % data[renderer_name][6], 1219 percent(data[renderer_name][7]), 1220 # CPU power 1221 "%.3f" % data[renderer_name][8], 1222 percent(data[renderer_name][9]), 1223 # GPU mem 1224 int(data[renderer_name][10]), 1225 percent(data[renderer_name][11]), 1226 # GPU peak mem 1227 int(data[renderer_name][12]), 1228 percent(data[renderer_name][13]), 1229 # process mem 1230 int(data[renderer_name][14]), 1231 percent(data[renderer_name][15]), 1232 # process peak mem 1233 int(data[renderer_name][16]), 1234 percent(data[renderer_name][17]), 1235 ]) 1236 else: 1237 for name, data in rows.items(): 1238 trace_number += 1 1239 summary_writer.writerow([ 1240 trace_number, 1241 name, 1242 # wall_time 1243 "%.3f" % data["native"][0], 1244 percent(data["native"][1]), 1245 "%.3f" % data["vulkan"][0], 1246 percent(data["vulkan"][1]), 1247 percent(safe_divide(data["native"][0], data["vulkan"][0])), 1248 # GPU time 1249 "%.3f" % data["native"][2], 1250 percent(data["native"][3]), 1251 "%.3f" % data["vulkan"][2], 1252 percent(data["vulkan"][3]), 1253 percent(safe_divide(data["native"][2], data["vulkan"][2])), 1254 # CPU time 1255 "%.3f" % data["native"][4], 1256 percent(data["native"][5]), 1257 "%.3f" % data["vulkan"][4], 1258 percent(data["vulkan"][5]), 1259 percent(safe_divide(data["native"][4], data["vulkan"][4])), 1260 # GPU power 1261 "%.3f" % data["native"][6], 1262 percent(data["native"][7]), 1263 "%.3f" % data["vulkan"][6], 1264 percent(data["vulkan"][7]), 1265 percent(safe_divide(data["native"][6], data["vulkan"][6])), 1266 # CPU power 1267 "%.3f" % data["native"][8], 1268 percent(data["native"][9]), 1269 "%.3f" % data["vulkan"][8], 1270 percent(data["vulkan"][9]), 1271 percent(safe_divide(data["native"][8], data["vulkan"][8])), 1272 # GPU mem 1273 int(data["native"][10]), 1274 percent(data["native"][11]), 1275 int(data["vulkan"][10]), 1276 percent(data["vulkan"][11]), 1277 percent(safe_divide(data["native"][10], data["vulkan"][10])), 1278 # GPU peak mem 1279 int(data["native"][12]), 1280 percent(data["native"][13]), 1281 int(data["vulkan"][12]), 1282 percent(data["vulkan"][13]), 1283 percent(safe_divide(data["native"][12], data["vulkan"][12])), 1284 # process mem 1285 int(data["native"][14]), 1286 percent(data["native"][15]), 1287 int(data["vulkan"][14]), 1288 percent(data["vulkan"][15]), 1289 percent(safe_divide(data["native"][14], data["vulkan"][14])), 1290 # process peak mem 1291 int(data["native"][16]), 1292 percent(data["native"][17]), 1293 int(data["vulkan"][16]), 1294 percent(data["vulkan"][17]), 1295 percent(safe_divide(data["native"][16], data["vulkan"][16])) 1296 ]) 1297 1298 1299if __name__ == '__main__': 1300 sys.exit(main()) 1301