blob: 542e4cc7b333bdc491b8eeef0a35705adc676951 [file] [log] [blame]
#
# Copyright (C) 2024 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.
#
from abc import ABC, abstractmethod
from command_executor import ProfilerCommandExecutor, \
UserSwitchCommandExecutor, BootCommandExecutor, AppStartupCommandExecutor, \
ConfigCommandExecutor, WEB_UI_ADDRESS
from validation_error import ValidationError
from open_ui import open_trace
ANDROID_SDK_VERSION_T = 33
class Command(ABC):
"""
Abstract base class representing a command.
"""
def __init__(self, type):
self.type = type
self.command_executor = None
def get_type(self):
return self.type
def execute(self, device):
return self.command_executor.execute(self, device)
@abstractmethod
def validate(self, device):
raise NotImplementedError
class ProfilerCommand(Command):
"""
Represents commands which profile and trace the system.
"""
def __init__(self, type, event, profiler, out_dir, dur_ms, app, runs,
simpleperf_event, perfetto_config, between_dur_ms, ui,
excluded_ftrace_events, included_ftrace_events, from_user, to_user,
scripts_path, symbols):
super().__init__(type)
self.event = event
self.profiler = profiler
self.out_dir = out_dir
self.dur_ms = dur_ms
self.app = app
self.runs = runs
self.simpleperf_event = simpleperf_event
self.perfetto_config = perfetto_config
self.between_dur_ms = between_dur_ms
self.use_ui = ui
self.excluded_ftrace_events = excluded_ftrace_events
self.included_ftrace_events = included_ftrace_events
self.from_user = from_user
self.to_user = to_user
self.scripts_path = scripts_path
self.symbols = symbols
match event:
case "custom":
self.command_executor = ProfilerCommandExecutor()
case "user-switch":
self.original_user = None
self.command_executor = UserSwitchCommandExecutor()
case "boot":
self.command_executor = BootCommandExecutor()
case "app-startup":
self.command_executor = AppStartupCommandExecutor()
case _:
raise ValueError("Invalid event name was used.")
def validate(self, device):
print("Further validating arguments of ProfilerCommand.")
if self.simpleperf_event is not None:
error = device.simpleperf_event_exists(self.simpleperf_event)
if error is not None:
return error
match self.event:
case "user-switch":
return self.validate_user_switch(device)
case "boot":
return self.validate_boot(device)
case "app-startup":
return self.validate_app_startup(device)
def validate_user_switch(self, device):
error = device.user_exists(self.to_user)
if error is not None:
return error
self.original_user = device.get_current_user()
if self.from_user is None:
self.from_user = self.original_user
else:
error = device.user_exists(self.from_user)
if error is not None:
return error
if self.from_user == self.to_user:
return ValidationError("Cannot perform user-switch to user %s because"
" the current user on device %s is already %s."
% (self.to_user, device.serial, self.from_user),
"Choose a --to-user ID that is different than"
" the --from-user ID.")
return None
@staticmethod
def validate_boot(device):
if device.get_android_sdk_version() < ANDROID_SDK_VERSION_T:
return ValidationError(
("Cannot perform trace on boot because only devices with version Android 13"
" (T) or newer can be configured to automatically start recording traces on"
" boot."), ("Update your device or use a different device with"
" Android 13 (T) or newer."))
return None
def validate_app_startup(self, device):
packages = device.get_packages()
if self.app not in packages:
return ValidationError(("Package %s does not exist on device with serial"
" %s." % (self.app, device.serial)),
("Select from one of the following packages on"
" device with serial %s: \n\t %s"
% (device.serial, (",\n\t ".join(packages)))))
if device.is_package_running(self.app):
return ValidationError(("Package %s is already running on device with"
" serial %s." % (self.app, device.serial)),
("Run 'adb -s %s shell am force-stop %s' to close"
" the package %s before trying to start it."
% (device.serial, self.app, self.app)))
return None
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
self.command_executor = ConfigCommandExecutor()
def validate(self, device):
raise NotImplementedError
class OpenCommand(Command):
"""
Represents commands which open traces.
"""
def __init__(self, file_path):
super().__init__(type)
self.file_path = file_path
def validate(self, device):
raise NotImplementedError
def execute(self, device):
open_trace(self.file_path, WEB_UI_ADDRESS)