From f2ab56ae1fab593223526151080c83a217b23e78 Mon Sep 17 00:00:00 2001 From: Peter Harris Date: Thu, 6 Mar 2025 13:29:04 +0000 Subject: [PATCH 1/3] Add extended installer command lines --- docs/running_android.md | 35 +++++++++++------- layer_gpu_timeline/README_LAYER.md | 7 +++- lgl_android_install.py | 58 +++++++++++++++++++++++++----- lglpy/android/utils.py | 12 +++++-- 4 files changed, 87 insertions(+), 25 deletions(-) diff --git a/docs/running_android.md b/docs/running_android.md index a709808..4bcdf20 100644 --- a/docs/running_android.md +++ b/docs/running_android.md @@ -28,6 +28,28 @@ when this has been done. You can now perform your development work. When you are finished, return to the script and press a key to notify it that it can clean up the device and remove the layers. +### Package launch and configuration + +By default, the installer will configure the layer but will not launch the +Android package. You must manually restart the package after the layers are +installed to ensure that they get loaded. + +If you specify `--auto-start`, the installer will automatically start the +application after the layers are configured and stop the application when you +finish profiling. + +Auto-start will default to using the main launchable activity for the package, +but you can override this using `--package-activity` to specify the name of another activity to launch. + +You can additional pass in additional activity command line arguments by +using the `--package-arguments` option to specify the argument string to pass +to `am start`. This string will often contain spaces, so ensure that is +quoted correctly on the host shell. For example: + +```sh +... --package-arguments "-e cmd 'R.InternalRenderScaling 0.5'" +``` + ### Layer configuration Some layers require a configuration file to control their behavior. Most @@ -70,17 +92,6 @@ If you specify the `--logcat ` option the script will automatically clear the logcat log after installing the layers, and start recording logcat to the specified file. Logcat recording will end during device clean up. -### Capturing Android Perfetto traces - -The Timeline layer is designed to provide semantic metadata that can be used -to annotate an Android Perfetto render stages trace. This provides profiling -tools with API information that Perfetto alone cannot provide, making it a -much more data-rich visualization . - -If you specify the `--perfetto ` option the script will automatically -configure Perfetto to capture render stages information for the target -application and save it to the specified file. - ## Manual configuration For users with existing configuration flows, Vulkan layers can be manually @@ -128,4 +139,4 @@ adb shell settings delete global gpu_debug_layer_app _Copyright © 2024-2025, Arm Limited and contributors._ -[1]: https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/ \ No newline at end of file +[1]: https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/ diff --git a/layer_gpu_timeline/README_LAYER.md b/layer_gpu_timeline/README_LAYER.md index f9e3303..5ac7252 100644 --- a/layer_gpu_timeline/README_LAYER.md +++ b/layer_gpu_timeline/README_LAYER.md @@ -66,13 +66,18 @@ directory. Run the following script to install the layer and auto-start the application under test: ```sh -python3 lgl_android_install.py --layer layer_gpu_timeline --timeline-perfetto --timeline-metadata --auto-start +python3 lgl_android_install.py --layer layer_gpu_timeline --timeline --auto-start ``` When the test has finished, press any key in the terminal to prompt the script to remove the layer from the device and save the data files to the specified host paths. +The timeline data files will be saved as `.perfetto` and `.metadata`. +If you want to use different file names for each, you can alternatively specify +a full file path for each file using `--timeline-perfetto` and +`--timeline-metadata`. + ## Timeline visualization This project includes an experimental Python viewer which can parse and diff --git a/lgl_android_install.py b/lgl_android_install.py index 927f584..f96be10 100755 --- a/lgl_android_install.py +++ b/lgl_android_install.py @@ -739,6 +739,14 @@ def parse_cli() -> argparse.Namespace: '--package', '-P', default=None, help='target package name or regex pattern (default=auto-detected)') + parser.add_argument( + '--package-activity', default=None, + help='target package activity (optional, default=auto-detected)') + + parser.add_argument( + '--package-arguments', default=None, + help='target package arguments (optional, default=None)') + parser.add_argument( '--layer', '-L', action='append', required=True, help='layer directory of a layer to install (required, repeatable)') @@ -757,17 +765,45 @@ def parse_cli() -> argparse.Namespace: parser.add_argument( '--logcat', type=str, default=None, - help='save logcat to this file during the run') + help='save logcat to this file') + + parser.add_argument( + '--timeline', type=str, default=None, + help='save Timeline traces to files with this base name') parser.add_argument( '--timeline-metadata', type=str, default=None, - help='save Timeline metadata trace to this file during the run') + help='save Timeline metadata trace to this file') parser.add_argument( '--timeline-perfetto', type=str, default=None, - help='save Timeline Perfetto trace to this file during the run') + help='save Timeline Perfetto trace to this file') + + args = parser.parse_args() + + # Validate arguments + if args.timeline and args.timeline_metadata: + print('ERROR: Cannot use --timeline with --timeline-metadata') + return None + + if args.timeline and args.timeline_perfetto: + print('ERROR: Cannot use --timeline with --timeline-perfetto') + return None + + if args.package_activity and not args.auto_start: + print('ERROR: Cannot use --package-activity without --auto-start') + return None + + if args.package_arguments and not args.auto_start: + print('ERROR: Cannot use --package-arguments without --auto-start') + return None + + # Canonicalize variant arguments for later users + if args.timeline: + args.timeline_perfetto = f'{args.timeline}.perfetto' + args.timeline_metadata = f'{args.timeline}.metadata' - return parser.parse_args() + return args def main() -> int: @@ -778,6 +814,8 @@ def main() -> int: The process exit code. ''' args = parse_cli() + if not args: + return 11 conn = ADBConnect() @@ -801,11 +839,13 @@ def main() -> int: conn.set_package(package) - # Determine package main activity to launch - activity = AndroidUtils.get_package_main_activity(conn) + # Determine package main activity to launch, if user didn't specify one + activity = args.package_activity if not activity: - print('ERROR: Package has no identifiable main activity') - return 4 + activity = AndroidUtils.get_package_main_activity(conn) + if not activity: + print('ERROR: Package has no identifiable main activity') + return 4 # Select layers to install and their configs need_32bit = AndroidUtils.is_package_32bit(conn, package) @@ -855,7 +895,7 @@ def main() -> int: # Restart the package if requested if args.auto_start: - AndroidUtils.start_package(conn, activity) + AndroidUtils.start_package(conn, activity, args.package_arguments) input('Press any key when finished to uninstall all layers') diff --git a/lglpy/android/utils.py b/lglpy/android/utils.py index 83b31f4..5135828 100644 --- a/lglpy/android/utils.py +++ b/lglpy/android/utils.py @@ -38,7 +38,7 @@ @contextlib.contextmanager -def NamedTempFile(suffix=None): # pylint: disable=invalid-name +def NamedTempFile(suffix: str=None): # pylint: disable=invalid-name ''' Creates a context managed temporary file that can be used with external subprocess. @@ -320,13 +320,15 @@ def get_package_data_dir(conn: ADBConnect): return None @staticmethod - def start_package(conn: ADBConnect, activity: str) -> bool: + def start_package(conn: ADBConnect, activity: str, + args: Optional[str]=None) -> bool: ''' Start the package for this connection. Args: conn: The adb connection. activity: The name of the activity. + args: The optional command line arguments to pass Returns: True on success, False otherwise. @@ -336,7 +338,11 @@ def start_package(conn: ADBConnect, activity: str) -> bool: try: target = f'{conn.package}/{activity}' - conn.adb_run('am', 'start', '-n', target, quote=True) + if args is None: + conn.adb_run('am', 'start', '-n', target, quote=True) + else: + args = shlex.split(args) + conn.adb_run('am', 'start', '-n', target, *args, quote=True) except sp.CalledProcessError: return False From 60b78d8dd8a4f5eae3ef2cabd31ca16d0d3032fe Mon Sep 17 00:00:00 2001 From: Peter Harris Date: Thu, 6 Mar 2025 13:32:39 +0000 Subject: [PATCH 2/3] Typo fix --- docs/running_android.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/docs/running_android.md b/docs/running_android.md index 4bcdf20..c38647b 100644 --- a/docs/running_android.md +++ b/docs/running_android.md @@ -39,12 +39,13 @@ application after the layers are configured and stop the application when you finish profiling. Auto-start will default to using the main launchable activity for the package, -but you can override this using `--package-activity` to specify the name of another activity to launch. +but you can override this using `--package-activity` to specify the name of +another activity to launch. -You can additional pass in additional activity command line arguments by -using the `--package-arguments` option to specify the argument string to pass -to `am start`. This string will often contain spaces, so ensure that is -quoted correctly on the host shell. For example: +You can pass in additional activity command line arguments by using the +`--package-arguments` option to specify the argument string to pass to +`am start`. This string will often contain spaces, so ensure that is quoted +correctly on the host shell. For example: ```sh ... --package-arguments "-e cmd 'R.InternalRenderScaling 0.5'" From d0b9899cd0e4a5ffbfb03a9778262d180c466ac4 Mon Sep 17 00:00:00 2001 From: Peter Harris Date: Thu, 6 Mar 2025 13:37:19 +0000 Subject: [PATCH 3/3] Fix Python lint issues --- lgl_android_install.py | 4 ++-- lglpy/android/utils.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lgl_android_install.py b/lgl_android_install.py index f96be10..45e4e8f 100755 --- a/lgl_android_install.py +++ b/lgl_android_install.py @@ -722,12 +722,12 @@ def cleanup_perfetto( print('ERROR: Cannot disable Perfetto recording') -def parse_cli() -> argparse.Namespace: +def parse_cli() -> Optional[argparse.Namespace]: ''' Parse the command line. Returns: - An argparse results object. + An argparse results object, or None on error. ''' parser = argparse.ArgumentParser() diff --git a/lglpy/android/utils.py b/lglpy/android/utils.py index 5135828..2172611 100644 --- a/lglpy/android/utils.py +++ b/lglpy/android/utils.py @@ -38,7 +38,7 @@ @contextlib.contextmanager -def NamedTempFile(suffix: str=None): # pylint: disable=invalid-name +def NamedTempFile(suffix: Optional[str] = None): # pylint: disable=invalid-name ''' Creates a context managed temporary file that can be used with external subprocess. @@ -321,7 +321,7 @@ def get_package_data_dir(conn: ADBConnect): @staticmethod def start_package(conn: ADBConnect, activity: str, - args: Optional[str]=None) -> bool: + args: Optional[str] = None) -> bool: ''' Start the package for this connection. @@ -341,8 +341,8 @@ def start_package(conn: ADBConnect, activity: str, if args is None: conn.adb_run('am', 'start', '-n', target, quote=True) else: - args = shlex.split(args) - conn.adb_run('am', 'start', '-n', target, *args, quote=True) + sargs = shlex.split(args) + conn.adb_run('am', 'start', '-n', target, *sargs, quote=True) except sp.CalledProcessError: return False