| # |
| # Copyright (C) 2025 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| |
| import os |
| import subprocess |
| |
| from .base import ANDROID_SDK_VERSION_T, Command, ValidationError |
| from .config_builder import PREDEFINED_PERFETTO_CONFIGS |
| |
| |
| def add_config_parser(subparsers): |
| config_parser = subparsers.add_parser( |
| 'config', |
| help=('The config subcommand used' |
| ' to list and show the' |
| ' predefined perfetto configs.')) |
| config_subparsers = config_parser.add_subparsers( |
| dest='config_subcommand', help=('torq config' |
| ' subcommands')) |
| config_subparsers.add_parser( |
| 'list', help=('Command to list the predefined' |
| ' perfetto configs')) |
| config_show_parser = config_subparsers.add_parser( |
| 'show', |
| help=('Command to print' |
| ' the ' |
| ' perfetto config' |
| ' in the terminal.')) |
| config_show_parser.add_argument( |
| 'config_name', |
| choices=['lightweight', 'default', 'memory'], |
| help=('Name of the predefined perfetto' |
| ' config to print.')) |
| config_show_parser.add_argument( |
| '-d', |
| '--dur-ms', |
| type=int, |
| help=('The duration (ms) of the event. Determines when' |
| ' to stop collecting performance data.')) |
| config_show_parser.add_argument( |
| '--excluded-ftrace-events', |
| action='append', |
| help=('Excludes specified ftrace event from the perfetto' |
| ' config events.')) |
| config_show_parser.add_argument( |
| '--included-ftrace-events', |
| action='append', |
| help=('Includes specified ftrace event in the perfetto' |
| ' config events.')) |
| |
| config_pull_parser = config_subparsers.add_parser( |
| 'pull', |
| help=('Command to copy' |
| ' a predefined config' |
| ' to the specified' |
| ' file path.')) |
| config_pull_parser.add_argument( |
| 'config_name', |
| choices=['lightweight', 'default', 'memory'], |
| help='Name of the predefined config to copy') |
| config_pull_parser.add_argument( |
| 'file_path', |
| nargs='?', |
| help=('File path to copy the predefined' |
| ' config to')) |
| config_pull_parser.add_argument( |
| '-d', |
| '--dur-ms', |
| type=int, |
| help=('The duration (ms) of the event. Determines when' |
| ' to stop collecting performance data.')) |
| config_pull_parser.add_argument( |
| '--excluded-ftrace-events', |
| action='append', |
| help=('Excludes specified ftrace event from the perfetto' |
| ' config events.')) |
| config_pull_parser.add_argument( |
| '--included-ftrace-events', |
| action='append', |
| help=('Includes specified ftrace event in the perfetto' |
| ' config events.')) |
| |
| |
| def verify_config_args(args): |
| if args.config_subcommand is None: |
| return None, ValidationError( |
| ("Command is invalid because torq config cannot be called" |
| " without a subcommand."), ("Use one of the following subcommands:\n" |
| "\t torq config list\n" |
| "\t torq config show\n" |
| "\t torq config pull\n")) |
| |
| if args.config_subcommand == "pull": |
| if args.file_path is None: |
| args.file_path = "./" + args.config_name + ".pbtxt" |
| elif not os.path.isfile(args.file_path): |
| return None, ValidationError( |
| ("Command is invalid because %s is not a valid filepath." % |
| args.file_path), |
| ("A default filepath can be used if you do not specify a file-path:\n" |
| "\t torq pull default to copy to ./default.pbtxt\n" |
| "\t torq pull lightweight to copy to ./lightweight.pbtxt\n" |
| "\t torq pull memory to copy to ./memory.pbtxt")) |
| |
| return args, None |
| |
| |
| def create_config_command(args): |
| type = "config " + args.config_subcommand |
| config_name = None |
| file_path = None |
| dur_ms = None |
| excluded_ftrace_events = None |
| included_ftrace_events = None |
| if args.config_subcommand == "pull" or args.config_subcommand == "show": |
| config_name = args.config_name |
| dur_ms = args.dur_ms |
| excluded_ftrace_events = args.excluded_ftrace_events |
| included_ftrace_events = args.included_ftrace_events |
| if args.config_subcommand == "pull": |
| file_path = args.file_path |
| |
| command = ConfigCommand(type, config_name, file_path, dur_ms, |
| excluded_ftrace_events, included_ftrace_events) |
| return command |
| |
| |
| def execute_show_or_pull_command(command, device): |
| android_sdk_version = ANDROID_SDK_VERSION_T |
| error = device.check_device_connection() |
| if error is None: |
| device.root_device() |
| android_sdk_version = device.get_android_sdk_version() |
| |
| config, error = PREDEFINED_PERFETTO_CONFIGS[command.config_name]( |
| command, android_sdk_version) |
| |
| if error is not None: |
| return error |
| |
| if command.get_type() == "config pull": |
| subprocess.run(("cat > %s %s" % (command.file_path, config)), shell=True) |
| else: |
| print("\n".join(config.strip().split("\n")[2:-2])) |
| return None |
| |
| |
| def execute_config_command(args, device): |
| command = create_config_command(args) |
| match command.get_type(): |
| case "config list": |
| print("\n".join(list(PREDEFINED_PERFETTO_CONFIGS.keys()))) |
| return None |
| case "config show" | "config pull": |
| return execute_show_or_pull_command(command, device) |
| case _: |
| raise ValueError("Invalid config subcommand was used.") |
| |
| |
| class ConfigCommand(Command): |
| """ |
| Represents commands which get information about the predefined configs. |
| """ |
| |
| def __init__(self, type, config_name, file_path, dur_ms, |
| excluded_ftrace_events, included_ftrace_events): |
| super().__init__(type) |
| self.config_name = config_name |
| self.file_path = file_path |
| self.dur_ms = dur_ms |
| self.excluded_ftrace_events = excluded_ftrace_events |
| self.included_ftrace_events = included_ftrace_events |
| |
| def validate(self, device): |
| raise NotImplementedError |