xref: /aosp_15_r20/system/extras/torq/command.py (revision 288bf5226967eb3dac5cce6c939ccc2a7f2b4fe5)
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