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 super().__init__(type) 53 self.event = event 54 self.profiler = profiler 55 self.out_dir = out_dir 56 self.dur_ms = dur_ms 57 self.app = app 58 self.runs = runs 59 self.simpleperf_event = simpleperf_event 60 self.perfetto_config = perfetto_config 61 self.between_dur_ms = between_dur_ms 62 self.use_ui = ui 63 self.excluded_ftrace_events = excluded_ftrace_events 64 self.included_ftrace_events = included_ftrace_events 65 self.from_user = from_user 66 self.to_user = to_user 67 match event: 68 case "custom": 69 self.command_executor = ProfilerCommandExecutor() 70 case "user-switch": 71 self.original_user = None 72 self.command_executor = UserSwitchCommandExecutor() 73 case "boot": 74 self.command_executor = BootCommandExecutor() 75 case "app-startup": 76 self.command_executor = AppStartupCommandExecutor() 77 case _: 78 raise ValueError("Invalid event name was used.") 79 80 def validate(self, device): 81 print("Further validating arguments of ProfilerCommand.") 82 if self.simpleperf_event is not None: 83 error = device.simpleperf_event_exists(self.simpleperf_event) 84 if error is not None: 85 return error 86 match self.event: 87 case "user-switch": 88 return self.validate_user_switch(device) 89 case "boot": 90 return self.validate_boot(device) 91 case "app-startup": 92 return self.validate_app_startup(device) 93 94 def validate_user_switch(self, device): 95 error = device.user_exists(self.to_user) 96 if error is not None: 97 return error 98 self.original_user = device.get_current_user() 99 if self.from_user is None: 100 self.from_user = self.original_user 101 else: 102 error = device.user_exists(self.from_user) 103 if error is not None: 104 return error 105 if self.from_user == self.to_user: 106 return ValidationError("Cannot perform user-switch to user %s because" 107 " the current user on device %s is already %s." 108 % (self.to_user, device.serial, self.from_user), 109 "Choose a --to-user ID that is different than" 110 " the --from-user ID.") 111 return None 112 113 @staticmethod 114 def validate_boot(device): 115 if device.get_android_sdk_version() < ANDROID_SDK_VERSION_T: 116 return ValidationError( 117 ("Cannot perform trace on boot because only devices with version Android 13" 118 " (T) or newer can be configured to automatically start recording traces on" 119 " boot."), ("Update your device or use a different device with" 120 " Android 13 (T) or newer.")) 121 return None 122 123 def validate_app_startup(self, device): 124 packages = device.get_packages() 125 if self.app not in packages: 126 return ValidationError(("Package %s does not exist on device with serial" 127 " %s." % (self.app, device.serial)), 128 ("Select from one of the following packages on" 129 " device with serial %s: \n\t %s" 130 % (device.serial, (",\n\t ".join(packages))))) 131 if device.is_package_running(self.app): 132 return ValidationError(("Package %s is already running on device with" 133 " serial %s." % (self.app, device.serial)), 134 ("Run 'adb -s %s shell am force-stop %s' to close" 135 " the package %s before trying to start it." 136 % (device.serial, self.app, self.app))) 137 return None 138 139 140class ConfigCommand(Command): 141 """ 142 Represents commands which get information about the predefined configs. 143 """ 144 def __init__(self, type, config_name, file_path, dur_ms, 145 excluded_ftrace_events, included_ftrace_events): 146 super().__init__(type) 147 self.config_name = config_name 148 self.file_path = file_path 149 self.dur_ms = dur_ms 150 self.excluded_ftrace_events = excluded_ftrace_events 151 self.included_ftrace_events = included_ftrace_events 152 self.command_executor = ConfigCommandExecutor() 153 154 def validate(self, device): 155 raise NotImplementedError 156 157 158class OpenCommand(Command): 159 """ 160 Represents commands which open traces. 161 """ 162 def __init__(self, file_path): 163 super().__init__(type) 164 self.file_path = file_path 165 166 def validate(self, device): 167 raise NotImplementedError 168 169 def execute(self, device): 170 open_trace(self.file_path, WEB_UI_ADDRESS) 171