Skip to content

Extend Android installer support #98

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Mar 6, 2025
Merged
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
36 changes: 24 additions & 12 deletions docs/running_android.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,29 @@ 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 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
Expand Down Expand Up @@ -70,17 +93,6 @@ If you specify the `--logcat <file>` 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 <file>` 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
Expand Down Expand Up @@ -128,4 +140,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/
[1]: https://github.com/KhronosGroup/Vulkan-ValidationLayers/releases/
7 changes: 6 additions & 1 deletion layer_gpu_timeline/README_LAYER.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <out.perfetto> --timeline-metadata <out.metadata> --auto-start
python3 lgl_android_install.py --layer layer_gpu_timeline --timeline <out> --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 `<out>.perfetto` and `<out>.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
Expand Down
62 changes: 51 additions & 11 deletions lgl_android_install.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand All @@ -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)')
Expand All @@ -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:
Expand All @@ -778,6 +814,8 @@ def main() -> int:
The process exit code.
'''
args = parse_cli()
if not args:
return 11

conn = ADBConnect()

Expand All @@ -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)
Expand Down Expand Up @@ -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')

Expand Down
12 changes: 9 additions & 3 deletions lglpy/android/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@


@contextlib.contextmanager
def NamedTempFile(suffix=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.
Expand Down Expand Up @@ -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.
Expand All @@ -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:
sargs = shlex.split(args)
conn.adb_run('am', 'start', '-n', target, *sargs, quote=True)
except sp.CalledProcessError:
return False

Expand Down