1# 2# Copyright (C) 2024 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15# 16 17from abc import ABC, abstractmethod 18from .command_executor import ProfilerCommandExecutor, \ 19 UserSwitchCommandExecutor, BootCommandExecutor, AppStartupCommandExecutor, \ 20 ConfigCommandExecutor, WEB_UI_ADDRESS 21from .validation_error import ValidationError 22from .open_ui import open_trace 23 24ANDROID_SDK_VERSION_T = 33 25 26class Command(ABC): 27 """ 28 Abstract base class representing a command. 29 """ 30 def __init__(self, type): 31 self.type = type 32 self.command_executor = None 33 34 def get_type(self): 35 return self.type 36 37 def execute(self, device): 38 return self.command_executor.execute(self, device) 39 40 @abstractmethod 41 def validate(self, device): 42 raise NotImplementedError 43 44 45class ProfilerCommand(Command): 46 """ 47 Represents commands which profile and trace the system. 48 """ 49 def __init__(self, type, event, profiler, out_dir, dur_ms, app, runs, 50 simpleperf_event, perfetto_config, between_dur_ms, ui, 51 excluded_ftrace_events, included_ftrace_events, from_user, to_user, 52 scripts_path, symbols): 53 super().__init__(type) 54 self.event = event 55 self.profiler = profiler 56 self.out_dir = out_dir 57 self.dur_ms = dur_ms 58 self.app = app 59 self.runs = runs 60 self.simpleperf_event = simpleperf_event 61 self.perfetto_config = perfetto_config 62 self.between_dur_ms = between_dur_ms 63 self.use_ui = ui 64 self.excluded_ftrace_events = excluded_ftrace_events 65 self.included_ftrace_events = included_ftrace_events 66 self.from_user = from_user 67 self.to_user = to_user 68 self.scripts_path = scripts_path 69 self.symbols = symbols 70 match event: 71 case "custom": 72 self.command_executor = ProfilerCommandExecutor() 73 case "user-switch": 74 self.original_user = None 75 self.command_executor = UserSwitchCommandExecutor() 76 case "boot": 77 self.command_executor = BootCommandExecutor() 78 case "app-startup": 79 self.command_executor = AppStartupCommandExecutor() 80 case _: 81 raise ValueError("Invalid event name was used.") 82 83 def validate(self, device): 84 print("Further validating arguments of ProfilerCommand.") 85 if self.simpleperf_event is not None: 86 error = device.simpleperf_event_exists(self.simpleperf_event) 87 if error is not None: 88 return error 89 match self.event: 90 case "user-switch": 91 return self.validate_user_switch(device) 92 case "boot": 93 return self.validate_boot(device) 94 case "app-startup": 95 return self.validate_app_startup(device) 96 97 def validate_user_switch(self, device): 98 error = device.user_exists(self.to_user) 99 if error is not None: 100 return error 101 self.original_user = device.get_current_user() 102 if self.from_user is None: 103 self.from_user = self.original_user 104 else: 105 error = device.user_exists(self.from_user) 106 if error is not None: 107 return error 108 if self.from_user == self.to_user: 109 return ValidationError("Cannot perform user-switch to user %s because" 110 " the current user on device %s is already %s." 111 % (self.to_user, device.serial, self.from_user), 112 "Choose a --to-user ID that is different than" 113 " the --from-user ID.") 114 return None 115 116 @staticmethod 117 def validate_boot(device): 118 if device.get_android_sdk_version() < ANDROID_SDK_VERSION_T: 119 return ValidationError( 120 ("Cannot perform trace on boot because only devices with version Android 13" 121 " (T) or newer can be configured to automatically start recording traces on" 122 " boot."), ("Update your device or use a different device with" 123 " Android 13 (T) or newer.")) 124 return None 125 126 def validate_app_startup(self, device): 127 packages = device.get_packages() 128 if self.app not in packages: 129 return ValidationError(("Package %s does not exist on device with serial" 130 " %s." % (self.app, device.serial)), 131 ("Select from one of the following packages on" 132 " device with serial %s: \n\t %s" 133 % (device.serial, (",\n\t ".join(packages))))) 134 if device.is_package_running(self.app): 135 return ValidationError(("Package %s is already running on device with" 136 " serial %s." % (self.app, device.serial)), 137 ("Run 'adb -s %s shell am force-stop %s' to close" 138 " the package %s before trying to start it." 139 % (device.serial, self.app, self.app))) 140 return None 141 142 143class ConfigCommand(Command): 144 """ 145 Represents commands which get information about the predefined configs. 146 """ 147 def __init__(self, type, config_name, file_path, dur_ms, 148 excluded_ftrace_events, included_ftrace_events): 149 super().__init__(type) 150 self.config_name = config_name 151 self.file_path = file_path 152 self.dur_ms = dur_ms 153 self.excluded_ftrace_events = excluded_ftrace_events 154 self.included_ftrace_events = included_ftrace_events 155 self.command_executor = ConfigCommandExecutor() 156 157 def validate(self, device): 158 raise NotImplementedError 159 160 161class OpenCommand(Command): 162 """ 163 Represents commands which open traces. 164 """ 165 def __init__(self, file_path, use_trace_processor): 166 super().__init__(type) 167 self.file_path = file_path 168 self.use_trace_processor = use_trace_processor 169 170 def validate(self, device): 171 raise NotImplementedError 172 173 def execute(self, device): 174 return open_trace(self.file_path, WEB_UI_ADDRESS, self.use_trace_processor) 175