xref: /aosp_15_r20/development/tools/winscope/src/adb/winscope_proxy.py (revision 90c8c64db3049935a07c6143d7fd006e26f8ecca)
1*90c8c64dSAndroid Build Coastguard Worker#!/usr/bin/python3
2*90c8c64dSAndroid Build Coastguard Worker
3*90c8c64dSAndroid Build Coastguard Worker# Copyright (C) 2019 The Android Open Source Project
4*90c8c64dSAndroid Build Coastguard Worker#
5*90c8c64dSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
6*90c8c64dSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
7*90c8c64dSAndroid Build Coastguard Worker# You may obtain a copy of the License at
8*90c8c64dSAndroid Build Coastguard Worker#
9*90c8c64dSAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
10*90c8c64dSAndroid Build Coastguard Worker#
11*90c8c64dSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
12*90c8c64dSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
13*90c8c64dSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*90c8c64dSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
15*90c8c64dSAndroid Build Coastguard Worker# limitations under the License.
16*90c8c64dSAndroid Build Coastguard Worker
17*90c8c64dSAndroid Build Coastguard Worker#
18*90c8c64dSAndroid Build Coastguard Worker# This is an ADB proxy for Winscope.
19*90c8c64dSAndroid Build Coastguard Worker#
20*90c8c64dSAndroid Build Coastguard Worker# Requirements: python3.10 and ADB installed and in system PATH.
21*90c8c64dSAndroid Build Coastguard Worker#
22*90c8c64dSAndroid Build Coastguard Worker# Usage:
23*90c8c64dSAndroid Build Coastguard Worker#     run: python3 winscope_proxy.py
24*90c8c64dSAndroid Build Coastguard Worker#
25*90c8c64dSAndroid Build Coastguard Worker
26*90c8c64dSAndroid Build Coastguard Workerimport argparse
27*90c8c64dSAndroid Build Coastguard Workerimport base64
28*90c8c64dSAndroid Build Coastguard Workerimport gzip
29*90c8c64dSAndroid Build Coastguard Workerimport json
30*90c8c64dSAndroid Build Coastguard Workerimport logging
31*90c8c64dSAndroid Build Coastguard Workerimport os
32*90c8c64dSAndroid Build Coastguard Workerimport re
33*90c8c64dSAndroid Build Coastguard Workerimport secrets
34*90c8c64dSAndroid Build Coastguard Workerimport signal
35*90c8c64dSAndroid Build Coastguard Workerimport subprocess
36*90c8c64dSAndroid Build Coastguard Workerimport sys
37*90c8c64dSAndroid Build Coastguard Workerimport threading
38*90c8c64dSAndroid Build Coastguard Workerimport time
39*90c8c64dSAndroid Build Coastguard Workerfrom abc import abstractmethod
40*90c8c64dSAndroid Build Coastguard Workerfrom enum import Enum
41*90c8c64dSAndroid Build Coastguard Workerfrom http import HTTPStatus
42*90c8c64dSAndroid Build Coastguard Workerfrom http.server import HTTPServer, BaseHTTPRequestHandler
43*90c8c64dSAndroid Build Coastguard Workerfrom logging import DEBUG, INFO
44*90c8c64dSAndroid Build Coastguard Workerfrom tempfile import NamedTemporaryFile
45*90c8c64dSAndroid Build Coastguard Workerfrom typing import Callable
46*90c8c64dSAndroid Build Coastguard Worker
47*90c8c64dSAndroid Build Coastguard Worker# GLOBALS #
48*90c8c64dSAndroid Build Coastguard Worker
49*90c8c64dSAndroid Build Coastguard Workerlog = None
50*90c8c64dSAndroid Build Coastguard Workersecret_token = None
51*90c8c64dSAndroid Build Coastguard Worker
52*90c8c64dSAndroid Build Coastguard Worker# CONFIG #
53*90c8c64dSAndroid Build Coastguard Worker
54*90c8c64dSAndroid Build Coastguard Workerdef create_argument_parser() -> argparse.ArgumentParser:
55*90c8c64dSAndroid Build Coastguard Worker    parser = argparse.ArgumentParser(description='Proxy for go/winscope', prog='winscope_proxy')
56*90c8c64dSAndroid Build Coastguard Worker
57*90c8c64dSAndroid Build Coastguard Worker    parser.add_argument('--info', '-i', dest='loglevel', action='store_const', const=INFO)
58*90c8c64dSAndroid Build Coastguard Worker    parser.add_argument('--port', '-p', default=5544, action='store')
59*90c8c64dSAndroid Build Coastguard Worker
60*90c8c64dSAndroid Build Coastguard Worker    parser.set_defaults(loglevel=DEBUG)
61*90c8c64dSAndroid Build Coastguard Worker
62*90c8c64dSAndroid Build Coastguard Worker    return parser
63*90c8c64dSAndroid Build Coastguard Worker
64*90c8c64dSAndroid Build Coastguard Worker# Keep in sync with ProxyConnection#VERSION in Winscope
65*90c8c64dSAndroid Build Coastguard WorkerVERSION = '4.0.8'
66*90c8c64dSAndroid Build Coastguard Worker
67*90c8c64dSAndroid Build Coastguard WorkerPERFETTO_TRACE_CONFIG_FILE = '/data/misc/perfetto-configs/winscope-proxy-trace.conf'
68*90c8c64dSAndroid Build Coastguard WorkerPERFETTO_DUMP_CONFIG_FILE = '/data/misc/perfetto-configs/winscope-proxy-dump.conf'
69*90c8c64dSAndroid Build Coastguard WorkerPERFETTO_TRACE_FILE = '/data/misc/perfetto-traces/winscope-proxy-trace.perfetto-trace'
70*90c8c64dSAndroid Build Coastguard WorkerPERFETTO_DUMP_FILE = '/data/misc/perfetto-traces/winscope-proxy-dump.perfetto-trace'
71*90c8c64dSAndroid Build Coastguard WorkerPERFETTO_UNIQUE_SESSION_NAME = 'winscope proxy perfetto tracing'
72*90c8c64dSAndroid Build Coastguard WorkerPERFETTO_TRACING_SESSIONS_QUERY_START = """TRACING SESSIONS:
73*90c8c64dSAndroid Build Coastguard Worker
74*90c8c64dSAndroid Build Coastguard WorkerID      UID     STATE      BUF (#) KB   DUR (s)   #DS  STARTED  NAME
75*90c8c64dSAndroid Build Coastguard Worker===     ===     =====      ==========   =======   ===  =======  ====\n"""
76*90c8c64dSAndroid Build Coastguard WorkerPERFETTO_TRACING_SESSIONS_QUERY_END = """\nNOTE: Some tracing sessions are not reported in the list above."""
77*90c8c64dSAndroid Build Coastguard Worker
78*90c8c64dSAndroid Build Coastguard WorkerWINSCOPE_VERSION_HEADER = "Winscope-Proxy-Version"
79*90c8c64dSAndroid Build Coastguard WorkerWINSCOPE_TOKEN_HEADER = "Winscope-Token"
80*90c8c64dSAndroid Build Coastguard Worker
81*90c8c64dSAndroid Build Coastguard Worker# Location to save the proxy security token
82*90c8c64dSAndroid Build Coastguard WorkerWINSCOPE_TOKEN_LOCATION = os.path.expanduser('~/.config/winscope/.token')
83*90c8c64dSAndroid Build Coastguard Worker
84*90c8c64dSAndroid Build Coastguard Worker# Winscope traces extensions
85*90c8c64dSAndroid Build Coastguard WorkerWINSCOPE_EXT = ".winscope"
86*90c8c64dSAndroid Build Coastguard WorkerWINSCOPE_EXT_LEGACY = ".pb"
87*90c8c64dSAndroid Build Coastguard WorkerWINSCOPE_EXTS = [WINSCOPE_EXT, WINSCOPE_EXT_LEGACY]
88*90c8c64dSAndroid Build Coastguard Worker
89*90c8c64dSAndroid Build Coastguard Worker# Winscope traces directories
90*90c8c64dSAndroid Build Coastguard WorkerWINSCOPE_DIR = "/data/misc/wmtrace/"
91*90c8c64dSAndroid Build Coastguard WorkerWINSCOPE_BACKUP_DIR = "/data/local/tmp/last_winscope_tracing_session/"
92*90c8c64dSAndroid Build Coastguard Worker
93*90c8c64dSAndroid Build Coastguard Worker# Tracing handlers
94*90c8c64dSAndroid Build Coastguard WorkerSIGNAL_HANDLER_LOG = "/data/local/tmp/winscope_signal_handler.log"
95*90c8c64dSAndroid Build Coastguard WorkerWINSCOPE_STATUS = "/data/local/tmp/winscope_status"
96*90c8c64dSAndroid Build Coastguard Worker
97*90c8c64dSAndroid Build Coastguard Worker# Max interval between the client keep-alive requests in seconds
98*90c8c64dSAndroid Build Coastguard WorkerKEEP_ALIVE_INTERVAL_S = 5
99*90c8c64dSAndroid Build Coastguard Worker
100*90c8c64dSAndroid Build Coastguard Worker# Perfetto's default timeout for getting an ACK from producer processes is 5s
101*90c8c64dSAndroid Build Coastguard Worker# We need to be sure that the timeout is longer than that with a good margin.
102*90c8c64dSAndroid Build Coastguard WorkerCOMMAND_TIMEOUT_S = 15
103*90c8c64dSAndroid Build Coastguard Worker
104*90c8c64dSAndroid Build Coastguard Workerclass File:
105*90c8c64dSAndroid Build Coastguard Worker    def __init__(self, file, filetype) -> None:
106*90c8c64dSAndroid Build Coastguard Worker        self.file = file
107*90c8c64dSAndroid Build Coastguard Worker        self.type = filetype
108*90c8c64dSAndroid Build Coastguard Worker
109*90c8c64dSAndroid Build Coastguard Worker    def get_filepaths(self, device_id) -> list[str]:
110*90c8c64dSAndroid Build Coastguard Worker        return [self.file]
111*90c8c64dSAndroid Build Coastguard Worker
112*90c8c64dSAndroid Build Coastguard Worker    def get_filetype(self) -> str:
113*90c8c64dSAndroid Build Coastguard Worker        return self.type
114*90c8c64dSAndroid Build Coastguard Worker
115*90c8c64dSAndroid Build Coastguard Worker
116*90c8c64dSAndroid Build Coastguard Workerclass FileMatcher:
117*90c8c64dSAndroid Build Coastguard Worker    def __init__(self, path, matcher, filetype) -> None:
118*90c8c64dSAndroid Build Coastguard Worker        self.path = path
119*90c8c64dSAndroid Build Coastguard Worker        self.matcher = matcher
120*90c8c64dSAndroid Build Coastguard Worker        self.type = filetype
121*90c8c64dSAndroid Build Coastguard Worker
122*90c8c64dSAndroid Build Coastguard Worker    def get_filepaths(self, device_id) -> list[str]:
123*90c8c64dSAndroid Build Coastguard Worker        if len(self.matcher) > 0:
124*90c8c64dSAndroid Build Coastguard Worker            matchingFiles = call_adb(
125*90c8c64dSAndroid Build Coastguard Worker                f"shell su root find {self.path} -name {self.matcher}", device_id)
126*90c8c64dSAndroid Build Coastguard Worker        else:
127*90c8c64dSAndroid Build Coastguard Worker            matchingFiles = call_adb(
128*90c8c64dSAndroid Build Coastguard Worker                f"shell su root find {self.path}", device_id)
129*90c8c64dSAndroid Build Coastguard Worker
130*90c8c64dSAndroid Build Coastguard Worker        files = matchingFiles.split('\n')[:-1]
131*90c8c64dSAndroid Build Coastguard Worker        log.debug("Found files %s", files)
132*90c8c64dSAndroid Build Coastguard Worker        return files
133*90c8c64dSAndroid Build Coastguard Worker
134*90c8c64dSAndroid Build Coastguard Worker    def get_filetype(self) -> str:
135*90c8c64dSAndroid Build Coastguard Worker        return self.type
136*90c8c64dSAndroid Build Coastguard Worker
137*90c8c64dSAndroid Build Coastguard Worker
138*90c8c64dSAndroid Build Coastguard Workerclass WinscopeFileMatcher(FileMatcher):
139*90c8c64dSAndroid Build Coastguard Worker    def __init__(self, path, matcher, filetype) -> None:
140*90c8c64dSAndroid Build Coastguard Worker        self.path = path
141*90c8c64dSAndroid Build Coastguard Worker        self.internal_matchers = list(map(lambda ext: FileMatcher(path, f'{matcher}{ext}', filetype),
142*90c8c64dSAndroid Build Coastguard Worker            WINSCOPE_EXTS))
143*90c8c64dSAndroid Build Coastguard Worker        self.type = filetype
144*90c8c64dSAndroid Build Coastguard Worker
145*90c8c64dSAndroid Build Coastguard Worker    def get_filepaths(self, device_id) -> list[str]:
146*90c8c64dSAndroid Build Coastguard Worker        for matcher in self.internal_matchers:
147*90c8c64dSAndroid Build Coastguard Worker            files = matcher.get_filepaths(device_id)
148*90c8c64dSAndroid Build Coastguard Worker            if len(files) > 0:
149*90c8c64dSAndroid Build Coastguard Worker                return files
150*90c8c64dSAndroid Build Coastguard Worker        log.debug("No files found")
151*90c8c64dSAndroid Build Coastguard Worker        return []
152*90c8c64dSAndroid Build Coastguard Worker
153*90c8c64dSAndroid Build Coastguard Worker
154*90c8c64dSAndroid Build Coastguard Workerdef get_shell_args(device_id: str, type: str) -> list[str]:
155*90c8c64dSAndroid Build Coastguard Worker    shell = ['adb', '-s', device_id, 'shell']
156*90c8c64dSAndroid Build Coastguard Worker    log.debug(f"Starting {type} shell {' '.join(shell)}")
157*90c8c64dSAndroid Build Coastguard Worker    return shell
158*90c8c64dSAndroid Build Coastguard Worker
159*90c8c64dSAndroid Build Coastguard Workerdef is_perfetto_data_source_available(name: str, query_result: str) -> bool:
160*90c8c64dSAndroid Build Coastguard Worker    return name in query_result
161*90c8c64dSAndroid Build Coastguard Worker
162*90c8c64dSAndroid Build Coastguard Workerdef is_any_perfetto_data_source_available(query_result: str) -> bool:
163*90c8c64dSAndroid Build Coastguard Worker    return is_perfetto_data_source_available('android.inputmethod', query_result) or \
164*90c8c64dSAndroid Build Coastguard Worker       is_perfetto_data_source_available('android.protolog', query_result) or \
165*90c8c64dSAndroid Build Coastguard Worker       is_perfetto_data_source_available('android.surfaceflinger.layers', query_result) or \
166*90c8c64dSAndroid Build Coastguard Worker       is_perfetto_data_source_available('android.surfaceflinger.transactions', query_result) or \
167*90c8c64dSAndroid Build Coastguard Worker       is_perfetto_data_source_available('com.android.wm.shell.transition', query_result) or \
168*90c8c64dSAndroid Build Coastguard Worker       is_perfetto_data_source_available('android.viewcapture', query_result) or \
169*90c8c64dSAndroid Build Coastguard Worker       is_perfetto_data_source_available('android.windowmanager', query_result) or \
170*90c8c64dSAndroid Build Coastguard Worker       is_perfetto_data_source_available('android.input.inputevent', query_result)
171*90c8c64dSAndroid Build Coastguard Worker
172*90c8c64dSAndroid Build Coastguard Worker
173*90c8c64dSAndroid Build Coastguard Workerclass TraceConfig:
174*90c8c64dSAndroid Build Coastguard Worker    def __init__(self, is_perfetto: bool) -> None:
175*90c8c64dSAndroid Build Coastguard Worker        self.is_perfetto = is_perfetto
176*90c8c64dSAndroid Build Coastguard Worker
177*90c8c64dSAndroid Build Coastguard Worker    @abstractmethod
178*90c8c64dSAndroid Build Coastguard Worker    def add(self, config: str, value: str | list[str] | None) -> None:
179*90c8c64dSAndroid Build Coastguard Worker        pass
180*90c8c64dSAndroid Build Coastguard Worker
181*90c8c64dSAndroid Build Coastguard Worker    @abstractmethod
182*90c8c64dSAndroid Build Coastguard Worker    def is_valid(self, config: str) -> bool:
183*90c8c64dSAndroid Build Coastguard Worker        pass
184*90c8c64dSAndroid Build Coastguard Worker
185*90c8c64dSAndroid Build Coastguard Worker    @abstractmethod
186*90c8c64dSAndroid Build Coastguard Worker    def execute_command(self, server, device_id):
187*90c8c64dSAndroid Build Coastguard Worker        pass
188*90c8c64dSAndroid Build Coastguard Worker
189*90c8c64dSAndroid Build Coastguard Worker    def get_trace_identifiers(self)-> list[str]:
190*90c8c64dSAndroid Build Coastguard Worker        return [""]
191*90c8c64dSAndroid Build Coastguard Worker
192*90c8c64dSAndroid Build Coastguard Worker    def get_optional_start_args(self, identifier)-> str:
193*90c8c64dSAndroid Build Coastguard Worker        return ""
194*90c8c64dSAndroid Build Coastguard Worker
195*90c8c64dSAndroid Build Coastguard Worker    def execute_optional_config_command(self, server, device_id, shell, command, config_key, config_value):
196*90c8c64dSAndroid Build Coastguard Worker        process = subprocess.Popen(shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
197*90c8c64dSAndroid Build Coastguard Worker                                    stdin=subprocess.PIPE, start_new_session=True)
198*90c8c64dSAndroid Build Coastguard Worker        out, err = process.communicate(command.encode('utf-8'))
199*90c8c64dSAndroid Build Coastguard Worker        if process.returncode != 0:
200*90c8c64dSAndroid Build Coastguard Worker            raise AdbError(
201*90c8c64dSAndroid Build Coastguard Worker                f"Error executing command:\n {command}\n\n### OUTPUT ###{out.decode('utf-8')}\n{err.decode('utf-8')}")
202*90c8c64dSAndroid Build Coastguard Worker        log.debug(f"Optional trace config changed on device {device_id} {config_key}:{config_value}")
203*90c8c64dSAndroid Build Coastguard Worker
204*90c8c64dSAndroid Build Coastguard Worker    def execute_perfetto_config_command(self, server, shell, command, trace_name):
205*90c8c64dSAndroid Build Coastguard Worker        process = subprocess.Popen(shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
206*90c8c64dSAndroid Build Coastguard Worker                                    stdin=subprocess.PIPE, start_new_session=True)
207*90c8c64dSAndroid Build Coastguard Worker        out, err = process.communicate(command.encode('utf-8'))
208*90c8c64dSAndroid Build Coastguard Worker        if process.returncode != 0:
209*90c8c64dSAndroid Build Coastguard Worker            raise AdbError(
210*90c8c64dSAndroid Build Coastguard Worker                f"Error executing command:\n {command}\n\n### OUTPUT ###{out.decode('utf-8')}\n{err.decode('utf-8')}")
211*90c8c64dSAndroid Build Coastguard Worker        log.info(f'{trace_name} (perfetto) configured to start along the other perfetto traces.')
212*90c8c64dSAndroid Build Coastguard Worker
213*90c8c64dSAndroid Build Coastguard Worker
214*90c8c64dSAndroid Build Coastguard Workerclass SurfaceFlingerTraceConfig(TraceConfig):
215*90c8c64dSAndroid Build Coastguard Worker    """Handles optional configuration for Surface Flinger traces.
216*90c8c64dSAndroid Build Coastguard Worker    """
217*90c8c64dSAndroid Build Coastguard Worker    LEGACY_FLAGS_MAP = {
218*90c8c64dSAndroid Build Coastguard Worker        "input": 1 << 1,
219*90c8c64dSAndroid Build Coastguard Worker        "composition": 1 << 2,
220*90c8c64dSAndroid Build Coastguard Worker        "metadata": 1 << 3,
221*90c8c64dSAndroid Build Coastguard Worker        "hwc": 1 << 4,
222*90c8c64dSAndroid Build Coastguard Worker        "tracebuffers": 1 << 5,
223*90c8c64dSAndroid Build Coastguard Worker        "virtualdisplays": 1 << 6
224*90c8c64dSAndroid Build Coastguard Worker    }
225*90c8c64dSAndroid Build Coastguard Worker
226*90c8c64dSAndroid Build Coastguard Worker    PERFETTO_FLAGS_MAP = {
227*90c8c64dSAndroid Build Coastguard Worker        "input": "TRACE_FLAG_INPUT",
228*90c8c64dSAndroid Build Coastguard Worker        "composition": "TRACE_FLAG_COMPOSITION",
229*90c8c64dSAndroid Build Coastguard Worker        "metadata": "TRACE_FLAG_EXTRA",
230*90c8c64dSAndroid Build Coastguard Worker        "hwc": "TRACE_FLAG_HWC",
231*90c8c64dSAndroid Build Coastguard Worker        "tracebuffers": "TRACE_FLAG_BUFFERS",
232*90c8c64dSAndroid Build Coastguard Worker        "virtualdisplays": "TRACE_FLAG_VIRTUAL_DISPLAYS",
233*90c8c64dSAndroid Build Coastguard Worker    }
234*90c8c64dSAndroid Build Coastguard Worker
235*90c8c64dSAndroid Build Coastguard Worker    def __init__(self, is_perfetto: bool) -> None:
236*90c8c64dSAndroid Build Coastguard Worker        super().__init__(is_perfetto)
237*90c8c64dSAndroid Build Coastguard Worker        self.flags = []
238*90c8c64dSAndroid Build Coastguard Worker        self.perfetto_flags = []
239*90c8c64dSAndroid Build Coastguard Worker
240*90c8c64dSAndroid Build Coastguard Worker        # defaults set for all configs
241*90c8c64dSAndroid Build Coastguard Worker        self.selected_configs = {
242*90c8c64dSAndroid Build Coastguard Worker            "sfbuffersize": "16000"
243*90c8c64dSAndroid Build Coastguard Worker        }
244*90c8c64dSAndroid Build Coastguard Worker
245*90c8c64dSAndroid Build Coastguard Worker    def add(self, config: str, value: str | None) -> None:
246*90c8c64dSAndroid Build Coastguard Worker        if config in SurfaceFlingerTraceConfig.LEGACY_FLAGS_MAP:
247*90c8c64dSAndroid Build Coastguard Worker            self.flags.append(config)
248*90c8c64dSAndroid Build Coastguard Worker        elif config in self.selected_configs:
249*90c8c64dSAndroid Build Coastguard Worker            self.selected_configs[config] = value
250*90c8c64dSAndroid Build Coastguard Worker
251*90c8c64dSAndroid Build Coastguard Worker    def is_valid(self, config: str) -> bool:
252*90c8c64dSAndroid Build Coastguard Worker        return config in SurfaceFlingerTraceConfig.LEGACY_FLAGS_MAP or config in self.selected_configs
253*90c8c64dSAndroid Build Coastguard Worker
254*90c8c64dSAndroid Build Coastguard Worker    def execute_command(self, server, device_id):
255*90c8c64dSAndroid Build Coastguard Worker        shell = get_shell_args(device_id, "sf config")
256*90c8c64dSAndroid Build Coastguard Worker
257*90c8c64dSAndroid Build Coastguard Worker        if self.is_perfetto:
258*90c8c64dSAndroid Build Coastguard Worker            self.execute_perfetto_config_command(server, shell, self._perfetto_config_command(), "SurfaceFlinger")
259*90c8c64dSAndroid Build Coastguard Worker        else:
260*90c8c64dSAndroid Build Coastguard Worker            self.execute_optional_config_command(server, device_id, shell, self._legacy_flags_command(), "sf flags", self.flags)
261*90c8c64dSAndroid Build Coastguard Worker            self.execute_optional_config_command(server, device_id, shell, self._legacy_buffer_size_command(), "sf buffer size", self.selected_configs["sfbuffersize"])
262*90c8c64dSAndroid Build Coastguard Worker
263*90c8c64dSAndroid Build Coastguard Worker    def _perfetto_config_command(self) -> str:
264*90c8c64dSAndroid Build Coastguard Worker        flags = "\n".join([f"""trace_flags: {SurfaceFlingerTraceConfig.PERFETTO_FLAGS_MAP[flag]}""" for flag in self.flags])
265*90c8c64dSAndroid Build Coastguard Worker
266*90c8c64dSAndroid Build Coastguard Worker        return f"""
267*90c8c64dSAndroid Build Coastguard Workercat << EOF >> {PERFETTO_TRACE_CONFIG_FILE}
268*90c8c64dSAndroid Build Coastguard Workerdata_sources: {{
269*90c8c64dSAndroid Build Coastguard Worker    config {{
270*90c8c64dSAndroid Build Coastguard Worker        name: "android.surfaceflinger.layers"
271*90c8c64dSAndroid Build Coastguard Worker        surfaceflinger_layers_config: {{
272*90c8c64dSAndroid Build Coastguard Worker            mode: MODE_ACTIVE
273*90c8c64dSAndroid Build Coastguard Worker            {flags}
274*90c8c64dSAndroid Build Coastguard Worker        }}
275*90c8c64dSAndroid Build Coastguard Worker    }}
276*90c8c64dSAndroid Build Coastguard Worker}}
277*90c8c64dSAndroid Build Coastguard WorkerEOF
278*90c8c64dSAndroid Build Coastguard Worker"""
279*90c8c64dSAndroid Build Coastguard Worker
280*90c8c64dSAndroid Build Coastguard Worker    def _legacy_buffer_size_command(self) -> str:
281*90c8c64dSAndroid Build Coastguard Worker        return f'su root service call SurfaceFlinger 1029 i32 {self.selected_configs["sfbuffersize"]}'
282*90c8c64dSAndroid Build Coastguard Worker
283*90c8c64dSAndroid Build Coastguard Worker    def _legacy_flags_command(self) -> str:
284*90c8c64dSAndroid Build Coastguard Worker        flags = 0
285*90c8c64dSAndroid Build Coastguard Worker        for flag in self.flags:
286*90c8c64dSAndroid Build Coastguard Worker            flags |= SurfaceFlingerTraceConfig.LEGACY_FLAGS_MAP[flag]
287*90c8c64dSAndroid Build Coastguard Worker
288*90c8c64dSAndroid Build Coastguard Worker        return f"su root service call SurfaceFlinger 1033 i32 {flags}"
289*90c8c64dSAndroid Build Coastguard Worker
290*90c8c64dSAndroid Build Coastguard Worker
291*90c8c64dSAndroid Build Coastguard Workerclass WindowManagerTraceConfig(TraceConfig):
292*90c8c64dSAndroid Build Coastguard Worker    PERFETTO_LOG_LEVEL_MAP = {
293*90c8c64dSAndroid Build Coastguard Worker       "verbose": "LOG_LEVEL_VERBOSE",
294*90c8c64dSAndroid Build Coastguard Worker       "debug": "LOG_LEVEL_DEBUG",
295*90c8c64dSAndroid Build Coastguard Worker       "critical": "LOG_LEVEL_CRITICAL",
296*90c8c64dSAndroid Build Coastguard Worker    }
297*90c8c64dSAndroid Build Coastguard Worker
298*90c8c64dSAndroid Build Coastguard Worker    PERFETTO_LOG_FREQUENCY_MAP = {
299*90c8c64dSAndroid Build Coastguard Worker       "frame": "LOG_FREQUENCY_FRAME",
300*90c8c64dSAndroid Build Coastguard Worker       "transaction": "LOG_FREQUENCY_TRANSACTION",
301*90c8c64dSAndroid Build Coastguard Worker    }
302*90c8c64dSAndroid Build Coastguard Worker
303*90c8c64dSAndroid Build Coastguard Worker    """Handles optional selected configuration for Window Manager traces.
304*90c8c64dSAndroid Build Coastguard Worker    """
305*90c8c64dSAndroid Build Coastguard Worker
306*90c8c64dSAndroid Build Coastguard Worker    def __init__(self, is_perfetto: bool) -> None:
307*90c8c64dSAndroid Build Coastguard Worker        super().__init__(is_perfetto)
308*90c8c64dSAndroid Build Coastguard Worker        # defaults set for all configs
309*90c8c64dSAndroid Build Coastguard Worker        self.selected_configs = {
310*90c8c64dSAndroid Build Coastguard Worker            "wmbuffersize": "16000",
311*90c8c64dSAndroid Build Coastguard Worker            "tracinglevel": "debug",
312*90c8c64dSAndroid Build Coastguard Worker            "tracingtype": "frame",
313*90c8c64dSAndroid Build Coastguard Worker        }
314*90c8c64dSAndroid Build Coastguard Worker
315*90c8c64dSAndroid Build Coastguard Worker    def add(self, config_type: str, config_value: str | None) -> None:
316*90c8c64dSAndroid Build Coastguard Worker        self.selected_configs[config_type] = config_value
317*90c8c64dSAndroid Build Coastguard Worker
318*90c8c64dSAndroid Build Coastguard Worker    def is_valid(self, config_type: str) -> bool:
319*90c8c64dSAndroid Build Coastguard Worker        return config_type in self.selected_configs
320*90c8c64dSAndroid Build Coastguard Worker
321*90c8c64dSAndroid Build Coastguard Worker    def execute_command(self, server, device_id):
322*90c8c64dSAndroid Build Coastguard Worker        shell = get_shell_args(device_id, "wm config")
323*90c8c64dSAndroid Build Coastguard Worker
324*90c8c64dSAndroid Build Coastguard Worker        if self.is_perfetto:
325*90c8c64dSAndroid Build Coastguard Worker            self.execute_perfetto_config_command(server, shell, self._perfetto_config_command(), "WM")
326*90c8c64dSAndroid Build Coastguard Worker        else:
327*90c8c64dSAndroid Build Coastguard Worker            self.execute_optional_config_command(server, device_id, shell, self._legacy_tracing_type_command(), "tracing type", self.selected_configs["tracingtype"])
328*90c8c64dSAndroid Build Coastguard Worker            self.execute_optional_config_command(server, device_id, shell, self._legacy_tracing_level_command(), "tracing level", self.selected_configs["tracinglevel"])
329*90c8c64dSAndroid Build Coastguard Worker            # /!\ buffer size must be configured last
330*90c8c64dSAndroid Build Coastguard Worker            # otherwise the other configurations might override it
331*90c8c64dSAndroid Build Coastguard Worker            self.execute_optional_config_command(server, device_id, shell, self._legacy_buffer_size_command(), "wm buffer size", self.selected_configs["wmbuffersize"])
332*90c8c64dSAndroid Build Coastguard Worker
333*90c8c64dSAndroid Build Coastguard Worker    def _perfetto_config_command(self) -> str:
334*90c8c64dSAndroid Build Coastguard Worker        log_level = WindowManagerTraceConfig.PERFETTO_LOG_LEVEL_MAP[self.selected_configs["tracinglevel"]]
335*90c8c64dSAndroid Build Coastguard Worker        log_frequency = WindowManagerTraceConfig.PERFETTO_LOG_FREQUENCY_MAP[self.selected_configs["tracingtype"]]
336*90c8c64dSAndroid Build Coastguard Worker        return f"""
337*90c8c64dSAndroid Build Coastguard Workercat << EOF >> {PERFETTO_TRACE_CONFIG_FILE}
338*90c8c64dSAndroid Build Coastguard Workerdata_sources: {{
339*90c8c64dSAndroid Build Coastguard Worker    config {{
340*90c8c64dSAndroid Build Coastguard Worker        name: "android.windowmanager"
341*90c8c64dSAndroid Build Coastguard Worker        windowmanager_config: {{
342*90c8c64dSAndroid Build Coastguard Worker            log_level: {log_level}
343*90c8c64dSAndroid Build Coastguard Worker            log_frequency: {log_frequency}
344*90c8c64dSAndroid Build Coastguard Worker        }}
345*90c8c64dSAndroid Build Coastguard Worker    }}
346*90c8c64dSAndroid Build Coastguard Worker}}
347*90c8c64dSAndroid Build Coastguard WorkerEOF
348*90c8c64dSAndroid Build Coastguard Worker"""
349*90c8c64dSAndroid Build Coastguard Worker
350*90c8c64dSAndroid Build Coastguard Worker    def _legacy_tracing_type_command(self) -> str:
351*90c8c64dSAndroid Build Coastguard Worker        return f'su root cmd window tracing {self.selected_configs["tracingtype"]}'
352*90c8c64dSAndroid Build Coastguard Worker
353*90c8c64dSAndroid Build Coastguard Worker    def _legacy_tracing_level_command(self) -> str:
354*90c8c64dSAndroid Build Coastguard Worker        return f'su root cmd window tracing level {self.selected_configs["tracinglevel"]}'
355*90c8c64dSAndroid Build Coastguard Worker
356*90c8c64dSAndroid Build Coastguard Worker    def _legacy_buffer_size_command(self) -> str:
357*90c8c64dSAndroid Build Coastguard Worker        return f'su root cmd window tracing size {self.selected_configs["wmbuffersize"]}'
358*90c8c64dSAndroid Build Coastguard Worker
359*90c8c64dSAndroid Build Coastguard Worker
360*90c8c64dSAndroid Build Coastguard Workerclass ViewCaptureTraceConfig(TraceConfig):
361*90c8c64dSAndroid Build Coastguard Worker    """Handles perfetto config for View Capture traces."""
362*90c8c64dSAndroid Build Coastguard Worker
363*90c8c64dSAndroid Build Coastguard Worker    COMMAND = f"""
364*90c8c64dSAndroid Build Coastguard Workercat << EOF >> {PERFETTO_TRACE_CONFIG_FILE}
365*90c8c64dSAndroid Build Coastguard Workerdata_sources: {{
366*90c8c64dSAndroid Build Coastguard Worker    config {{
367*90c8c64dSAndroid Build Coastguard Worker        name: "android.viewcapture"
368*90c8c64dSAndroid Build Coastguard Worker    }}
369*90c8c64dSAndroid Build Coastguard Worker}}
370*90c8c64dSAndroid Build Coastguard WorkerEOF
371*90c8c64dSAndroid Build Coastguard Worker    """
372*90c8c64dSAndroid Build Coastguard Worker
373*90c8c64dSAndroid Build Coastguard Worker    def execute_command(self, server, device_id):
374*90c8c64dSAndroid Build Coastguard Worker        if (self.is_perfetto):
375*90c8c64dSAndroid Build Coastguard Worker            shell = get_shell_args(device_id, "perfetto config view capture")
376*90c8c64dSAndroid Build Coastguard Worker            self.execute_perfetto_config_command(server, shell, ViewCaptureTraceConfig.COMMAND, "View Capture")
377*90c8c64dSAndroid Build Coastguard Worker
378*90c8c64dSAndroid Build Coastguard Workerclass TransactionsConfig(TraceConfig):
379*90c8c64dSAndroid Build Coastguard Worker    """Handles perfetto config for Transactions traces."""
380*90c8c64dSAndroid Build Coastguard Worker
381*90c8c64dSAndroid Build Coastguard Worker    COMMAND = f"""
382*90c8c64dSAndroid Build Coastguard Workercat << EOF >> {PERFETTO_TRACE_CONFIG_FILE}
383*90c8c64dSAndroid Build Coastguard Workerdata_sources: {{
384*90c8c64dSAndroid Build Coastguard Worker    config {{
385*90c8c64dSAndroid Build Coastguard Worker        name: "android.surfaceflinger.transactions"
386*90c8c64dSAndroid Build Coastguard Worker        surfaceflinger_transactions_config: {{
387*90c8c64dSAndroid Build Coastguard Worker            mode: MODE_ACTIVE
388*90c8c64dSAndroid Build Coastguard Worker        }}
389*90c8c64dSAndroid Build Coastguard Worker    }}
390*90c8c64dSAndroid Build Coastguard Worker}}
391*90c8c64dSAndroid Build Coastguard WorkerEOF
392*90c8c64dSAndroid Build Coastguard Worker    """
393*90c8c64dSAndroid Build Coastguard Worker
394*90c8c64dSAndroid Build Coastguard Worker    def execute_command(self, server, device_id):
395*90c8c64dSAndroid Build Coastguard Worker        if (self.is_perfetto):
396*90c8c64dSAndroid Build Coastguard Worker            shell = get_shell_args(device_id, "perfetto config transactions")
397*90c8c64dSAndroid Build Coastguard Worker            self.execute_perfetto_config_command(server, shell, TransactionsConfig.COMMAND, "SF transactions")
398*90c8c64dSAndroid Build Coastguard Worker
399*90c8c64dSAndroid Build Coastguard Worker
400*90c8c64dSAndroid Build Coastguard Workerclass ProtoLogConfig(TraceConfig):
401*90c8c64dSAndroid Build Coastguard Worker    """Handles perfetto config for ProtoLog traces."""
402*90c8c64dSAndroid Build Coastguard Worker
403*90c8c64dSAndroid Build Coastguard Worker    COMMAND = f"""
404*90c8c64dSAndroid Build Coastguard Workercat << EOF >> {PERFETTO_TRACE_CONFIG_FILE}
405*90c8c64dSAndroid Build Coastguard Workerdata_sources: {{
406*90c8c64dSAndroid Build Coastguard Worker    config {{
407*90c8c64dSAndroid Build Coastguard Worker        name: "android.protolog"
408*90c8c64dSAndroid Build Coastguard Worker        protolog_config: {{
409*90c8c64dSAndroid Build Coastguard Worker            tracing_mode: ENABLE_ALL
410*90c8c64dSAndroid Build Coastguard Worker        }}
411*90c8c64dSAndroid Build Coastguard Worker    }}
412*90c8c64dSAndroid Build Coastguard Worker}}
413*90c8c64dSAndroid Build Coastguard WorkerEOF
414*90c8c64dSAndroid Build Coastguard Worker    """
415*90c8c64dSAndroid Build Coastguard Worker
416*90c8c64dSAndroid Build Coastguard Worker    def execute_command(self, server, device_id):
417*90c8c64dSAndroid Build Coastguard Worker        if (self.is_perfetto):
418*90c8c64dSAndroid Build Coastguard Worker            shell = get_shell_args(device_id, "perfetto config protolog")
419*90c8c64dSAndroid Build Coastguard Worker            self.execute_perfetto_config_command(server, shell, ProtoLogConfig.COMMAND, "ProtoLog")
420*90c8c64dSAndroid Build Coastguard Worker
421*90c8c64dSAndroid Build Coastguard Worker
422*90c8c64dSAndroid Build Coastguard Workerclass ImeConfig(TraceConfig):
423*90c8c64dSAndroid Build Coastguard Worker    """Handles perfetto config for IME traces."""
424*90c8c64dSAndroid Build Coastguard Worker
425*90c8c64dSAndroid Build Coastguard Worker    COMMAND = f"""
426*90c8c64dSAndroid Build Coastguard Workercat << EOF >> {PERFETTO_TRACE_CONFIG_FILE}
427*90c8c64dSAndroid Build Coastguard Workerdata_sources: {{
428*90c8c64dSAndroid Build Coastguard Worker    config {{
429*90c8c64dSAndroid Build Coastguard Worker        name: "android.inputmethod"
430*90c8c64dSAndroid Build Coastguard Worker    }}
431*90c8c64dSAndroid Build Coastguard Worker}}
432*90c8c64dSAndroid Build Coastguard WorkerEOF
433*90c8c64dSAndroid Build Coastguard Worker    """
434*90c8c64dSAndroid Build Coastguard Worker
435*90c8c64dSAndroid Build Coastguard Worker    def execute_command(self, server, device_id):
436*90c8c64dSAndroid Build Coastguard Worker        if (self.is_perfetto):
437*90c8c64dSAndroid Build Coastguard Worker            shell = get_shell_args(device_id, "perfetto config ime")
438*90c8c64dSAndroid Build Coastguard Worker            self.execute_perfetto_config_command(server, shell, ImeConfig.COMMAND, "IME tracing")
439*90c8c64dSAndroid Build Coastguard Worker
440*90c8c64dSAndroid Build Coastguard Worker
441*90c8c64dSAndroid Build Coastguard Workerclass TransitionTracesConfig(TraceConfig):
442*90c8c64dSAndroid Build Coastguard Worker    """Handles perfetto config for Transition traces."""
443*90c8c64dSAndroid Build Coastguard Worker
444*90c8c64dSAndroid Build Coastguard Worker    COMMAND = f"""
445*90c8c64dSAndroid Build Coastguard Workercat << EOF >> {PERFETTO_TRACE_CONFIG_FILE}
446*90c8c64dSAndroid Build Coastguard Workerdata_sources: {{
447*90c8c64dSAndroid Build Coastguard Worker    config {{
448*90c8c64dSAndroid Build Coastguard Worker        name: "com.android.wm.shell.transition"
449*90c8c64dSAndroid Build Coastguard Worker    }}
450*90c8c64dSAndroid Build Coastguard Worker}}
451*90c8c64dSAndroid Build Coastguard WorkerEOF
452*90c8c64dSAndroid Build Coastguard Worker    """
453*90c8c64dSAndroid Build Coastguard Worker
454*90c8c64dSAndroid Build Coastguard Worker    def execute_command(self, server, device_id):
455*90c8c64dSAndroid Build Coastguard Worker        if (self.is_perfetto):
456*90c8c64dSAndroid Build Coastguard Worker            shell = get_shell_args(device_id, "perfetto config transitions")
457*90c8c64dSAndroid Build Coastguard Worker            self.execute_perfetto_config_command(server, shell, TransitionTracesConfig.COMMAND, "Transitions")
458*90c8c64dSAndroid Build Coastguard Worker
459*90c8c64dSAndroid Build Coastguard Worker
460*90c8c64dSAndroid Build Coastguard Workerclass InputConfig(TraceConfig):
461*90c8c64dSAndroid Build Coastguard Worker    """Handles perfetto config for Input traces."""
462*90c8c64dSAndroid Build Coastguard Worker
463*90c8c64dSAndroid Build Coastguard Worker    COMMAND = f"""
464*90c8c64dSAndroid Build Coastguard Workercat << EOF >> {PERFETTO_TRACE_CONFIG_FILE}
465*90c8c64dSAndroid Build Coastguard Workerdata_sources: {{
466*90c8c64dSAndroid Build Coastguard Worker    config {{
467*90c8c64dSAndroid Build Coastguard Worker        name: "android.input.inputevent"
468*90c8c64dSAndroid Build Coastguard Worker        android_input_event_config {{
469*90c8c64dSAndroid Build Coastguard Worker            mode: TRACE_MODE_TRACE_ALL
470*90c8c64dSAndroid Build Coastguard Worker        }}
471*90c8c64dSAndroid Build Coastguard Worker    }}
472*90c8c64dSAndroid Build Coastguard Worker}}
473*90c8c64dSAndroid Build Coastguard WorkerEOF
474*90c8c64dSAndroid Build Coastguard Worker    """
475*90c8c64dSAndroid Build Coastguard Worker
476*90c8c64dSAndroid Build Coastguard Worker    def execute_command(self, server, device_id):
477*90c8c64dSAndroid Build Coastguard Worker        if (self.is_perfetto):
478*90c8c64dSAndroid Build Coastguard Worker            shell = get_shell_args(device_id, "perfetto config input")
479*90c8c64dSAndroid Build Coastguard Worker            self.execute_perfetto_config_command(server, shell, InputConfig.COMMAND, "Input trace")
480*90c8c64dSAndroid Build Coastguard Worker
481*90c8c64dSAndroid Build Coastguard Worker
482*90c8c64dSAndroid Build Coastguard Workerclass MediaBasedConfig(TraceConfig):
483*90c8c64dSAndroid Build Coastguard Worker    """Creates trace identifiers for Screen Recording traces and Screenshots."""
484*90c8c64dSAndroid Build Coastguard Worker    trace_identifiers = ["active"]
485*90c8c64dSAndroid Build Coastguard Worker
486*90c8c64dSAndroid Build Coastguard Worker    def get_trace_identifiers(self):
487*90c8c64dSAndroid Build Coastguard Worker        return self.trace_identifiers
488*90c8c64dSAndroid Build Coastguard Worker
489*90c8c64dSAndroid Build Coastguard Worker    def is_valid(self, config_type: str) -> bool:
490*90c8c64dSAndroid Build Coastguard Worker        return config_type == "displays"
491*90c8c64dSAndroid Build Coastguard Worker
492*90c8c64dSAndroid Build Coastguard Worker    def add(self, config_type: str, config_value: str | list[str] | None):
493*90c8c64dSAndroid Build Coastguard Worker        if config_type != "displays":
494*90c8c64dSAndroid Build Coastguard Worker           return
495*90c8c64dSAndroid Build Coastguard Worker        if config_value and len(config_value) > 0:
496*90c8c64dSAndroid Build Coastguard Worker            if type(config_value) == str:
497*90c8c64dSAndroid Build Coastguard Worker                self.trace_identifiers = [config_value.split(" ")[0]]
498*90c8c64dSAndroid Build Coastguard Worker            else:
499*90c8c64dSAndroid Build Coastguard Worker                self.trace_identifiers = list(map(lambda d: d.split(" ")[0], config_value))
500*90c8c64dSAndroid Build Coastguard Worker
501*90c8c64dSAndroid Build Coastguard Worker    def execute_command(self, server, device_id):
502*90c8c64dSAndroid Build Coastguard Worker        pass
503*90c8c64dSAndroid Build Coastguard Worker
504*90c8c64dSAndroid Build Coastguard Workerclass ScreenRecordingConfig(MediaBasedConfig):
505*90c8c64dSAndroid Build Coastguard Worker    """Creates display args for Screen Recording traces."""
506*90c8c64dSAndroid Build Coastguard Worker    def get_optional_start_args(self, identifier):
507*90c8c64dSAndroid Build Coastguard Worker        if identifier == "active":
508*90c8c64dSAndroid Build Coastguard Worker            return ""
509*90c8c64dSAndroid Build Coastguard Worker        return f"--display-id {identifier}"
510*90c8c64dSAndroid Build Coastguard Worker
511*90c8c64dSAndroid Build Coastguard Workerclass ScreenshotConfig(MediaBasedConfig):
512*90c8c64dSAndroid Build Coastguard Worker    """Creates display args for Screenshots."""
513*90c8c64dSAndroid Build Coastguard Worker    def get_optional_start_args(self, identifier):
514*90c8c64dSAndroid Build Coastguard Worker        if identifier == "active":
515*90c8c64dSAndroid Build Coastguard Worker            return ""
516*90c8c64dSAndroid Build Coastguard Worker        return f"-d {identifier}"
517*90c8c64dSAndroid Build Coastguard Worker
518*90c8c64dSAndroid Build Coastguard Worker
519*90c8c64dSAndroid Build Coastguard Workerclass SurfaceFlingerDumpConfig(TraceConfig):
520*90c8c64dSAndroid Build Coastguard Worker    """Handles perfetto config for SurfaceFlinger dumps."""
521*90c8c64dSAndroid Build Coastguard Worker    def __init__(self, is_perfetto: bool) -> None:
522*90c8c64dSAndroid Build Coastguard Worker        super().__init__(is_perfetto)
523*90c8c64dSAndroid Build Coastguard Worker
524*90c8c64dSAndroid Build Coastguard Worker    def add(self, config: str, value: str | None) -> None:
525*90c8c64dSAndroid Build Coastguard Worker        pass
526*90c8c64dSAndroid Build Coastguard Worker
527*90c8c64dSAndroid Build Coastguard Worker    def is_valid(self, config: str) -> bool:
528*90c8c64dSAndroid Build Coastguard Worker        return False
529*90c8c64dSAndroid Build Coastguard Worker
530*90c8c64dSAndroid Build Coastguard Worker    def execute_command(self, server, device_id):
531*90c8c64dSAndroid Build Coastguard Worker        shell = get_shell_args(device_id, "sf config")
532*90c8c64dSAndroid Build Coastguard Worker
533*90c8c64dSAndroid Build Coastguard Worker        if self.is_perfetto:
534*90c8c64dSAndroid Build Coastguard Worker            self.execute_perfetto_config_command(server, shell, self._perfetto_config_command(), "SurfaceFlinger")
535*90c8c64dSAndroid Build Coastguard Worker
536*90c8c64dSAndroid Build Coastguard Worker    def _perfetto_config_command(self) -> str:
537*90c8c64dSAndroid Build Coastguard Worker        return f"""
538*90c8c64dSAndroid Build Coastguard Workercat << EOF >> {PERFETTO_DUMP_CONFIG_FILE}
539*90c8c64dSAndroid Build Coastguard Workerdata_sources: {{
540*90c8c64dSAndroid Build Coastguard Worker    config {{
541*90c8c64dSAndroid Build Coastguard Worker        name: "android.surfaceflinger.layers"
542*90c8c64dSAndroid Build Coastguard Worker        surfaceflinger_layers_config: {{
543*90c8c64dSAndroid Build Coastguard Worker            mode: MODE_DUMP
544*90c8c64dSAndroid Build Coastguard Worker            trace_flags: TRACE_FLAG_INPUT
545*90c8c64dSAndroid Build Coastguard Worker            trace_flags: TRACE_FLAG_COMPOSITION
546*90c8c64dSAndroid Build Coastguard Worker            trace_flags: TRACE_FLAG_HWC
547*90c8c64dSAndroid Build Coastguard Worker            trace_flags: TRACE_FLAG_BUFFERS
548*90c8c64dSAndroid Build Coastguard Worker            trace_flags: TRACE_FLAG_VIRTUAL_DISPLAYS
549*90c8c64dSAndroid Build Coastguard Worker        }}
550*90c8c64dSAndroid Build Coastguard Worker    }}
551*90c8c64dSAndroid Build Coastguard Worker}}
552*90c8c64dSAndroid Build Coastguard WorkerEOF
553*90c8c64dSAndroid Build Coastguard Worker"""
554*90c8c64dSAndroid Build Coastguard Worker
555*90c8c64dSAndroid Build Coastguard Worker    def _legacy_buffer_size_command(self) -> str:
556*90c8c64dSAndroid Build Coastguard Worker        return f'su root service call SurfaceFlinger 1029 i32 {self.selected_configs["sfbuffersize"]}'
557*90c8c64dSAndroid Build Coastguard Worker
558*90c8c64dSAndroid Build Coastguard Worker    def _legacy_flags_command(self) -> str:
559*90c8c64dSAndroid Build Coastguard Worker        flags = 0
560*90c8c64dSAndroid Build Coastguard Worker        for flag in self.flags:
561*90c8c64dSAndroid Build Coastguard Worker            flags |= SurfaceFlingerTraceConfig.LEGACY_FLAGS_MAP[flag]
562*90c8c64dSAndroid Build Coastguard Worker
563*90c8c64dSAndroid Build Coastguard Worker        return f"su root service call SurfaceFlinger 1033 i32 {flags}"
564*90c8c64dSAndroid Build Coastguard Worker
565*90c8c64dSAndroid Build Coastguard Worker
566*90c8c64dSAndroid Build Coastguard Workerclass TraceTarget:
567*90c8c64dSAndroid Build Coastguard Worker    """Defines a single parameter to trace.
568*90c8c64dSAndroid Build Coastguard Worker
569*90c8c64dSAndroid Build Coastguard Worker    Attributes:
570*90c8c64dSAndroid Build Coastguard Worker        trace_name: used as a key to access config and log statements during Start/End endpoints.
571*90c8c64dSAndroid Build Coastguard Worker        files: the matchers used to identify the paths on the device the trace results are saved to.
572*90c8c64dSAndroid Build Coastguard Worker        is_perfetto_available: callback to determine if perfetto tracing is available for target.
573*90c8c64dSAndroid Build Coastguard Worker        trace_start: command to start the trace from adb shell, must not block.
574*90c8c64dSAndroid Build Coastguard Worker        trace_stop: command to stop the trace, should block until the trace is stopped.
575*90c8c64dSAndroid Build Coastguard Worker        get_trace_config: getter for optional setup to execute pre-tracing adb commands and define start command arguments.
576*90c8c64dSAndroid Build Coastguard Worker    """
577*90c8c64dSAndroid Build Coastguard Worker
578*90c8c64dSAndroid Build Coastguard Worker    def __init__(
579*90c8c64dSAndroid Build Coastguard Worker            self,
580*90c8c64dSAndroid Build Coastguard Worker            trace_name: str,
581*90c8c64dSAndroid Build Coastguard Worker            files: list[File | FileMatcher],
582*90c8c64dSAndroid Build Coastguard Worker            is_perfetto_available: Callable[[str], bool],
583*90c8c64dSAndroid Build Coastguard Worker            trace_start: str,
584*90c8c64dSAndroid Build Coastguard Worker            trace_stop: str,
585*90c8c64dSAndroid Build Coastguard Worker            get_trace_config: Callable[[bool], TraceConfig] | None = None
586*90c8c64dSAndroid Build Coastguard Worker        ) -> None:
587*90c8c64dSAndroid Build Coastguard Worker        self.trace_name = trace_name
588*90c8c64dSAndroid Build Coastguard Worker        if type(files) is not list:
589*90c8c64dSAndroid Build Coastguard Worker            files = [files]
590*90c8c64dSAndroid Build Coastguard Worker        self.files = files
591*90c8c64dSAndroid Build Coastguard Worker        self.is_perfetto_available = is_perfetto_available
592*90c8c64dSAndroid Build Coastguard Worker        self.trace_start = trace_start
593*90c8c64dSAndroid Build Coastguard Worker        self.trace_stop = trace_stop
594*90c8c64dSAndroid Build Coastguard Worker        self.get_trace_config = get_trace_config
595*90c8c64dSAndroid Build Coastguard Worker        self.status_filename = WINSCOPE_STATUS + "_" + trace_name
596*90c8c64dSAndroid Build Coastguard Worker
597*90c8c64dSAndroid Build Coastguard Worker
598*90c8c64dSAndroid Build Coastguard Worker# Order of files matters as they will be expected in that order and decoded in that order
599*90c8c64dSAndroid Build Coastguard WorkerTRACE_TARGETS = {
600*90c8c64dSAndroid Build Coastguard Worker    "view_capture_trace": TraceTarget(
601*90c8c64dSAndroid Build Coastguard Worker        "view_capture_trace",
602*90c8c64dSAndroid Build Coastguard Worker        File('/data/misc/wmtrace/view_capture_trace.zip', "view_capture_trace.zip"),
603*90c8c64dSAndroid Build Coastguard Worker        lambda res: is_perfetto_data_source_available("android.viewcapture", res),
604*90c8c64dSAndroid Build Coastguard Worker        """
605*90c8c64dSAndroid Build Coastguard Workersu root settings put global view_capture_enabled 1
606*90c8c64dSAndroid Build Coastguard Workerecho 'ViewCapture tracing (legacy) started.'
607*90c8c64dSAndroid Build Coastguard Worker        """,
608*90c8c64dSAndroid Build Coastguard Worker        """
609*90c8c64dSAndroid Build Coastguard Workersu root sh -c 'cmd launcherapps dump-view-hierarchies >/data/misc/wmtrace/view_capture_trace.zip'
610*90c8c64dSAndroid Build Coastguard Workersu root settings put global view_capture_enabled 0
611*90c8c64dSAndroid Build Coastguard Workerecho 'ViewCapture tracing (legacy) stopped.'
612*90c8c64dSAndroid Build Coastguard Worker        """,
613*90c8c64dSAndroid Build Coastguard Worker        lambda is_perfetto: ViewCaptureTraceConfig(is_perfetto)
614*90c8c64dSAndroid Build Coastguard Worker    ),
615*90c8c64dSAndroid Build Coastguard Worker    "window_trace": TraceTarget(
616*90c8c64dSAndroid Build Coastguard Worker        "window_trace",
617*90c8c64dSAndroid Build Coastguard Worker        WinscopeFileMatcher(WINSCOPE_DIR, "wm_trace", "window_trace"),
618*90c8c64dSAndroid Build Coastguard Worker        lambda res: is_perfetto_data_source_available('android.windowmanager', res),
619*90c8c64dSAndroid Build Coastguard Worker        """
620*90c8c64dSAndroid Build Coastguard Workersu root cmd window tracing start
621*90c8c64dSAndroid Build Coastguard Workerecho 'WM trace (legacy) started.'
622*90c8c64dSAndroid Build Coastguard Worker        """,
623*90c8c64dSAndroid Build Coastguard Worker        """
624*90c8c64dSAndroid Build Coastguard Workersu root cmd window tracing stop >/dev/null 2>&1
625*90c8c64dSAndroid Build Coastguard Workerecho 'WM trace (legacy) stopped.'
626*90c8c64dSAndroid Build Coastguard Worker        """,
627*90c8c64dSAndroid Build Coastguard Worker        lambda is_perfetto: WindowManagerTraceConfig(is_perfetto)
628*90c8c64dSAndroid Build Coastguard Worker    ),
629*90c8c64dSAndroid Build Coastguard Worker    "layers_trace": TraceTarget(
630*90c8c64dSAndroid Build Coastguard Worker        "layers_trace",
631*90c8c64dSAndroid Build Coastguard Worker        WinscopeFileMatcher(WINSCOPE_DIR, "layers_trace", "layers_trace"),
632*90c8c64dSAndroid Build Coastguard Worker        lambda res: is_perfetto_data_source_available('android.surfaceflinger.layers', res),
633*90c8c64dSAndroid Build Coastguard Worker        """
634*90c8c64dSAndroid Build Coastguard Workersu root service call SurfaceFlinger 1025 i32 1
635*90c8c64dSAndroid Build Coastguard Workerecho 'SF layers trace (legacy) started.'
636*90c8c64dSAndroid Build Coastguard Worker        """,
637*90c8c64dSAndroid Build Coastguard Worker        """
638*90c8c64dSAndroid Build Coastguard Workersu root service call SurfaceFlinger 1025 i32 0 >/dev/null 2>&1
639*90c8c64dSAndroid Build Coastguard Workerecho 'SF layers trace (legacy) stopped.'
640*90c8c64dSAndroid Build Coastguard Worker        """,
641*90c8c64dSAndroid Build Coastguard Worker        lambda is_perfetto: SurfaceFlingerTraceConfig(is_perfetto)
642*90c8c64dSAndroid Build Coastguard Worker    ),
643*90c8c64dSAndroid Build Coastguard Worker    "screen_recording": TraceTarget(
644*90c8c64dSAndroid Build Coastguard Worker        "screen_recording",
645*90c8c64dSAndroid Build Coastguard Worker        File(
646*90c8c64dSAndroid Build Coastguard Worker            '/data/local/tmp/screen_{trace_identifier}.mp4',
647*90c8c64dSAndroid Build Coastguard Worker            "screen_recording_{trace_identifier}"),
648*90c8c64dSAndroid Build Coastguard Worker        lambda res: False,
649*90c8c64dSAndroid Build Coastguard Worker        '''
650*90c8c64dSAndroid Build Coastguard Worker        settings put system show_touches 1 && \
651*90c8c64dSAndroid Build Coastguard Worker        settings put system pointer_location 1 && \
652*90c8c64dSAndroid Build Coastguard Worker        screenrecord --bugreport --bit-rate 8M {options} /data/local/tmp/screen_{trace_identifier}.mp4 & \
653*90c8c64dSAndroid Build Coastguard Worker        echo "ScreenRecorder started."
654*90c8c64dSAndroid Build Coastguard Worker        ''',
655*90c8c64dSAndroid Build Coastguard Worker        '''settings put system pointer_location 0 && \
656*90c8c64dSAndroid Build Coastguard Worker        settings put system show_touches 0 && \
657*90c8c64dSAndroid Build Coastguard Worker        pkill -l SIGINT screenrecord >/dev/null 2>&1
658*90c8c64dSAndroid Build Coastguard Worker        '''.strip(),
659*90c8c64dSAndroid Build Coastguard Worker        lambda is_perfetto: ScreenRecordingConfig(is_perfetto)
660*90c8c64dSAndroid Build Coastguard Worker    ),
661*90c8c64dSAndroid Build Coastguard Worker    "transactions": TraceTarget(
662*90c8c64dSAndroid Build Coastguard Worker        "transactions",
663*90c8c64dSAndroid Build Coastguard Worker        WinscopeFileMatcher(WINSCOPE_DIR, "transactions_trace", "transactions"),
664*90c8c64dSAndroid Build Coastguard Worker        lambda res: is_perfetto_data_source_available('android.surfaceflinger.transactions', res),
665*90c8c64dSAndroid Build Coastguard Worker        """
666*90c8c64dSAndroid Build Coastguard Workersu root service call SurfaceFlinger 1041 i32 1
667*90c8c64dSAndroid Build Coastguard Workerecho 'SF transactions trace (legacy) started.'
668*90c8c64dSAndroid Build Coastguard Worker        """,
669*90c8c64dSAndroid Build Coastguard Worker        "su root service call SurfaceFlinger 1041 i32 0 >/dev/null 2>&1",
670*90c8c64dSAndroid Build Coastguard Worker        lambda is_perfetto: TransactionsConfig(is_perfetto)
671*90c8c64dSAndroid Build Coastguard Worker    ),
672*90c8c64dSAndroid Build Coastguard Worker    "transactions_legacy": TraceTarget(
673*90c8c64dSAndroid Build Coastguard Worker        "transactions_legacy",
674*90c8c64dSAndroid Build Coastguard Worker        [
675*90c8c64dSAndroid Build Coastguard Worker            WinscopeFileMatcher(WINSCOPE_DIR, "transaction_trace", "transactions_legacy"),
676*90c8c64dSAndroid Build Coastguard Worker            FileMatcher(WINSCOPE_DIR, f'transaction_merges_*', "transaction_merges"),
677*90c8c64dSAndroid Build Coastguard Worker        ],
678*90c8c64dSAndroid Build Coastguard Worker        lambda res: False,
679*90c8c64dSAndroid Build Coastguard Worker        'su root service call SurfaceFlinger 1020 i32 1\necho "SF transactions recording started."',
680*90c8c64dSAndroid Build Coastguard Worker        'su root service call SurfaceFlinger 1020 i32 0 >/dev/null 2>&1',
681*90c8c64dSAndroid Build Coastguard Worker    ),
682*90c8c64dSAndroid Build Coastguard Worker    "proto_log": TraceTarget(
683*90c8c64dSAndroid Build Coastguard Worker         "proto_log",
684*90c8c64dSAndroid Build Coastguard Worker        WinscopeFileMatcher(WINSCOPE_DIR, "wm_log", "proto_log"),
685*90c8c64dSAndroid Build Coastguard Worker        lambda res: is_perfetto_data_source_available('android.protolog', res),
686*90c8c64dSAndroid Build Coastguard Worker        """
687*90c8c64dSAndroid Build Coastguard Workersu root cmd window logging start
688*90c8c64dSAndroid Build Coastguard Workerecho "ProtoLog (legacy) started."
689*90c8c64dSAndroid Build Coastguard Worker        """,
690*90c8c64dSAndroid Build Coastguard Worker        """
691*90c8c64dSAndroid Build Coastguard Workersu root cmd window logging stop >/dev/null 2>&1
692*90c8c64dSAndroid Build Coastguard Workerecho "ProtoLog (legacy) stopped."
693*90c8c64dSAndroid Build Coastguard Worker        """,
694*90c8c64dSAndroid Build Coastguard Worker        lambda is_perfetto: ProtoLogConfig(is_perfetto)
695*90c8c64dSAndroid Build Coastguard Worker    ),
696*90c8c64dSAndroid Build Coastguard Worker    "ime": TraceTarget(
697*90c8c64dSAndroid Build Coastguard Worker        "ime",
698*90c8c64dSAndroid Build Coastguard Worker        [WinscopeFileMatcher(WINSCOPE_DIR, "ime_trace_clients", "ime_trace_clients"),
699*90c8c64dSAndroid Build Coastguard Worker         WinscopeFileMatcher(WINSCOPE_DIR, "ime_trace_service", "ime_trace_service"),
700*90c8c64dSAndroid Build Coastguard Worker         WinscopeFileMatcher(WINSCOPE_DIR, "ime_trace_managerservice", "ime_trace_managerservice")],
701*90c8c64dSAndroid Build Coastguard Worker        lambda res: is_perfetto_data_source_available('android.inputmethod', res),
702*90c8c64dSAndroid Build Coastguard Worker        """
703*90c8c64dSAndroid Build Coastguard Workersu root ime tracing start
704*90c8c64dSAndroid Build Coastguard Workerecho "IME tracing (legacy) started."
705*90c8c64dSAndroid Build Coastguard Worker        """,
706*90c8c64dSAndroid Build Coastguard Worker        """
707*90c8c64dSAndroid Build Coastguard Workersu root ime tracing stop >/dev/null 2>&1
708*90c8c64dSAndroid Build Coastguard Workerecho "IME tracing (legacy) stopped."
709*90c8c64dSAndroid Build Coastguard Worker        """,
710*90c8c64dSAndroid Build Coastguard Worker        lambda is_perfetto: ImeConfig(is_perfetto)
711*90c8c64dSAndroid Build Coastguard Worker    ),
712*90c8c64dSAndroid Build Coastguard Worker    "wayland_trace": TraceTarget(
713*90c8c64dSAndroid Build Coastguard Worker        "wayland_trace",
714*90c8c64dSAndroid Build Coastguard Worker        WinscopeFileMatcher("/data/misc/wltrace", "wl_trace", "wl_trace"),
715*90c8c64dSAndroid Build Coastguard Worker        lambda res: False,
716*90c8c64dSAndroid Build Coastguard Worker        'su root service call Wayland 26 i32 1 >/dev/null\necho "Wayland trace started."',
717*90c8c64dSAndroid Build Coastguard Worker        'su root service call Wayland 26 i32 0 >/dev/null'
718*90c8c64dSAndroid Build Coastguard Worker    ),
719*90c8c64dSAndroid Build Coastguard Worker    "eventlog": TraceTarget(
720*90c8c64dSAndroid Build Coastguard Worker        "eventlog",
721*90c8c64dSAndroid Build Coastguard Worker        WinscopeFileMatcher("/data/local/tmp", "eventlog", "eventlog"),
722*90c8c64dSAndroid Build Coastguard Worker        lambda res: False,
723*90c8c64dSAndroid Build Coastguard Worker        'rm -f /data/local/tmp/eventlog.winscope && EVENT_LOG_TRACING_START_TIME=$EPOCHREALTIME\necho "Event Log trace started."',
724*90c8c64dSAndroid Build Coastguard Worker        'echo "EventLog\\n" > /data/local/tmp/eventlog.winscope && su root logcat -b events -v threadtime -v printable -v uid -v nsec -v epoch -b events -t $EVENT_LOG_TRACING_START_TIME >> /data/local/tmp/eventlog.winscope',
725*90c8c64dSAndroid Build Coastguard Worker    ),
726*90c8c64dSAndroid Build Coastguard Worker    "transition_traces": TraceTarget(
727*90c8c64dSAndroid Build Coastguard Worker        "transition_traces",
728*90c8c64dSAndroid Build Coastguard Worker        [WinscopeFileMatcher(WINSCOPE_DIR, "wm_transition_trace", "wm_transition_trace"),
729*90c8c64dSAndroid Build Coastguard Worker         WinscopeFileMatcher(WINSCOPE_DIR, "shell_transition_trace", "shell_transition_trace")],
730*90c8c64dSAndroid Build Coastguard Worker        lambda res: is_perfetto_data_source_available('com.android.wm.shell.transition', res),
731*90c8c64dSAndroid Build Coastguard Worker        """
732*90c8c64dSAndroid Build Coastguard Workersu root cmd window shell tracing start && su root dumpsys activity service SystemUIService WMShell transitions tracing start
733*90c8c64dSAndroid Build Coastguard Workerecho "Transition traces (legacy) started."
734*90c8c64dSAndroid Build Coastguard Worker        """,
735*90c8c64dSAndroid Build Coastguard Worker        """
736*90c8c64dSAndroid Build Coastguard Workersu root cmd window shell tracing stop && su root dumpsys activity service SystemUIService WMShell transitions tracing stop >/dev/null 2>&1
737*90c8c64dSAndroid Build Coastguard Workerecho 'Transition traces (legacy) stopped.'
738*90c8c64dSAndroid Build Coastguard Worker        """,
739*90c8c64dSAndroid Build Coastguard Worker        lambda is_perfetto: TransitionTracesConfig(is_perfetto)
740*90c8c64dSAndroid Build Coastguard Worker    ),
741*90c8c64dSAndroid Build Coastguard Worker    "input": TraceTarget(
742*90c8c64dSAndroid Build Coastguard Worker        "input",
743*90c8c64dSAndroid Build Coastguard Worker        [WinscopeFileMatcher(WINSCOPE_DIR, "input_trace", "input_trace")],
744*90c8c64dSAndroid Build Coastguard Worker        lambda res: is_perfetto_data_source_available('android.input.inputevent', res),
745*90c8c64dSAndroid Build Coastguard Worker        "",
746*90c8c64dSAndroid Build Coastguard Worker        "",
747*90c8c64dSAndroid Build Coastguard Worker        lambda is_perfetto: InputConfig(is_perfetto)
748*90c8c64dSAndroid Build Coastguard Worker    ),
749*90c8c64dSAndroid Build Coastguard Worker    "perfetto_trace": TraceTarget(
750*90c8c64dSAndroid Build Coastguard Worker         "perfetto_trace",
751*90c8c64dSAndroid Build Coastguard Worker        File(PERFETTO_TRACE_FILE, "trace.perfetto-trace"),
752*90c8c64dSAndroid Build Coastguard Worker        lambda res: is_any_perfetto_data_source_available(res),
753*90c8c64dSAndroid Build Coastguard Worker        f"""
754*90c8c64dSAndroid Build Coastguard Workercat << EOF >> {PERFETTO_TRACE_CONFIG_FILE}
755*90c8c64dSAndroid Build Coastguard Workerbuffers: {{
756*90c8c64dSAndroid Build Coastguard Worker    size_kb: 500000
757*90c8c64dSAndroid Build Coastguard Worker    fill_policy: RING_BUFFER
758*90c8c64dSAndroid Build Coastguard Worker}}
759*90c8c64dSAndroid Build Coastguard Workerduration_ms: 0
760*90c8c64dSAndroid Build Coastguard Workerfile_write_period_ms: 999999999
761*90c8c64dSAndroid Build Coastguard Workerwrite_into_file: true
762*90c8c64dSAndroid Build Coastguard Workerunique_session_name: "{PERFETTO_UNIQUE_SESSION_NAME}"
763*90c8c64dSAndroid Build Coastguard WorkerEOF
764*90c8c64dSAndroid Build Coastguard Worker
765*90c8c64dSAndroid Build Coastguard Workerrm -f {PERFETTO_TRACE_FILE}
766*90c8c64dSAndroid Build Coastguard Workerperfetto --out {PERFETTO_TRACE_FILE} --txt --config {PERFETTO_TRACE_CONFIG_FILE} --detach=WINSCOPE-PROXY-TRACING-SESSION
767*90c8c64dSAndroid Build Coastguard Workerecho 'Started perfetto trace.'
768*90c8c64dSAndroid Build Coastguard Worker""",
769*90c8c64dSAndroid Build Coastguard Worker        """
770*90c8c64dSAndroid Build Coastguard Workerperfetto --attach=WINSCOPE-PROXY-TRACING-SESSION --stop
771*90c8c64dSAndroid Build Coastguard Workerecho 'Stopped perfetto trace.'
772*90c8c64dSAndroid Build Coastguard Worker""",
773*90c8c64dSAndroid Build Coastguard Worker    ),
774*90c8c64dSAndroid Build Coastguard Worker}
775*90c8c64dSAndroid Build Coastguard Worker
776*90c8c64dSAndroid Build Coastguard Worker
777*90c8c64dSAndroid Build Coastguard Workerclass DumpTarget:
778*90c8c64dSAndroid Build Coastguard Worker    """Defines a single parameter to dump.
779*90c8c64dSAndroid Build Coastguard Worker
780*90c8c64dSAndroid Build Coastguard Worker    Attributes:
781*90c8c64dSAndroid Build Coastguard Worker        trace_name: used as a key to access config and log statements during Dump endpoint.
782*90c8c64dSAndroid Build Coastguard Worker        files: the matchers used to identify the paths on the device the dump results are saved to.
783*90c8c64dSAndroid Build Coastguard Worker        dump_command: command to dump state to file.
784*90c8c64dSAndroid Build Coastguard Worker        get_trace_config: getter for optional setup to execute pre-tracing adb commands and define start command arguments.
785*90c8c64dSAndroid Build Coastguard Worker    """
786*90c8c64dSAndroid Build Coastguard Worker
787*90c8c64dSAndroid Build Coastguard Worker    def __init__(
788*90c8c64dSAndroid Build Coastguard Worker            self,
789*90c8c64dSAndroid Build Coastguard Worker            trace_name: str,
790*90c8c64dSAndroid Build Coastguard Worker            files: list[File | FileMatcher],
791*90c8c64dSAndroid Build Coastguard Worker            is_perfetto_available: Callable[[str], bool],
792*90c8c64dSAndroid Build Coastguard Worker            dump_command: str,
793*90c8c64dSAndroid Build Coastguard Worker            get_trace_config: Callable[[bool], TraceConfig] | None = None
794*90c8c64dSAndroid Build Coastguard Worker        ) -> None:
795*90c8c64dSAndroid Build Coastguard Worker        self.trace_name = trace_name
796*90c8c64dSAndroid Build Coastguard Worker        if type(files) is not list:
797*90c8c64dSAndroid Build Coastguard Worker            files = [files]
798*90c8c64dSAndroid Build Coastguard Worker        self.files = files
799*90c8c64dSAndroid Build Coastguard Worker        self.is_perfetto_available = is_perfetto_available
800*90c8c64dSAndroid Build Coastguard Worker        self.dump_command = dump_command
801*90c8c64dSAndroid Build Coastguard Worker        self.get_trace_config = get_trace_config
802*90c8c64dSAndroid Build Coastguard Worker
803*90c8c64dSAndroid Build Coastguard Worker
804*90c8c64dSAndroid Build Coastguard WorkerDUMP_TARGETS = {
805*90c8c64dSAndroid Build Coastguard Worker    "window_dump": DumpTarget(
806*90c8c64dSAndroid Build Coastguard Worker        "window_dump",
807*90c8c64dSAndroid Build Coastguard Worker        File(f'/data/local/tmp/wm_dump{WINSCOPE_EXT}', "window_dump"),
808*90c8c64dSAndroid Build Coastguard Worker        lambda res: False,
809*90c8c64dSAndroid Build Coastguard Worker        f'su root dumpsys window --proto > /data/local/tmp/wm_dump{WINSCOPE_EXT}'
810*90c8c64dSAndroid Build Coastguard Worker    ),
811*90c8c64dSAndroid Build Coastguard Worker
812*90c8c64dSAndroid Build Coastguard Worker    "layers_dump": DumpTarget(
813*90c8c64dSAndroid Build Coastguard Worker        "layers_dump",
814*90c8c64dSAndroid Build Coastguard Worker        File(f'/data/local/tmp/sf_dump{WINSCOPE_EXT}', "layers_dump"),
815*90c8c64dSAndroid Build Coastguard Worker        lambda res: is_perfetto_data_source_available('android.surfaceflinger.layers', res),
816*90c8c64dSAndroid Build Coastguard Worker        f"""
817*90c8c64dSAndroid Build Coastguard Workersu root dumpsys SurfaceFlinger --proto > /data/local/tmp/sf_dump{WINSCOPE_EXT}
818*90c8c64dSAndroid Build Coastguard Worker        """,
819*90c8c64dSAndroid Build Coastguard Worker        lambda is_perfetto: SurfaceFlingerDumpConfig(is_perfetto)
820*90c8c64dSAndroid Build Coastguard Worker    ),
821*90c8c64dSAndroid Build Coastguard Worker
822*90c8c64dSAndroid Build Coastguard Worker    "screenshot": DumpTarget(
823*90c8c64dSAndroid Build Coastguard Worker        "screenshot",
824*90c8c64dSAndroid Build Coastguard Worker        File("/data/local/tmp/screenshot_{trace_identifier}.png", "screenshot_{trace_identifier}.png"),
825*90c8c64dSAndroid Build Coastguard Worker        lambda res: False,
826*90c8c64dSAndroid Build Coastguard Worker        "screencap -p {options}> /data/local/tmp/screenshot_{trace_identifier}.png",
827*90c8c64dSAndroid Build Coastguard Worker        lambda is_perfetto: ScreenshotConfig(is_perfetto)
828*90c8c64dSAndroid Build Coastguard Worker    ),
829*90c8c64dSAndroid Build Coastguard Worker
830*90c8c64dSAndroid Build Coastguard Worker    "perfetto_dump": DumpTarget(
831*90c8c64dSAndroid Build Coastguard Worker        "perfetto_dump",
832*90c8c64dSAndroid Build Coastguard Worker        File(PERFETTO_DUMP_FILE, "dump.perfetto-trace"),
833*90c8c64dSAndroid Build Coastguard Worker        lambda res: is_any_perfetto_data_source_available(res),
834*90c8c64dSAndroid Build Coastguard Worker        f"""
835*90c8c64dSAndroid Build Coastguard Workercat << EOF >> {PERFETTO_DUMP_CONFIG_FILE}
836*90c8c64dSAndroid Build Coastguard Workerbuffers: {{
837*90c8c64dSAndroid Build Coastguard Worker    size_kb: 500000
838*90c8c64dSAndroid Build Coastguard Worker    fill_policy: RING_BUFFER
839*90c8c64dSAndroid Build Coastguard Worker}}
840*90c8c64dSAndroid Build Coastguard Workerduration_ms: 1
841*90c8c64dSAndroid Build Coastguard WorkerEOF
842*90c8c64dSAndroid Build Coastguard Worker
843*90c8c64dSAndroid Build Coastguard Workerrm -f {PERFETTO_DUMP_FILE}
844*90c8c64dSAndroid Build Coastguard Workerperfetto --out {PERFETTO_DUMP_FILE} --txt --config {PERFETTO_DUMP_CONFIG_FILE}
845*90c8c64dSAndroid Build Coastguard Workerecho 'Recorded perfetto dump.'
846*90c8c64dSAndroid Build Coastguard Worker        """
847*90c8c64dSAndroid Build Coastguard Worker    )
848*90c8c64dSAndroid Build Coastguard Worker}
849*90c8c64dSAndroid Build Coastguard Worker
850*90c8c64dSAndroid Build Coastguard Worker
851*90c8c64dSAndroid Build Coastguard Worker# END OF CONFIG #
852*90c8c64dSAndroid Build Coastguard Worker
853*90c8c64dSAndroid Build Coastguard Worker
854*90c8c64dSAndroid Build Coastguard Workerdef get_token() -> str:
855*90c8c64dSAndroid Build Coastguard Worker    """Returns saved proxy security token or creates new one"""
856*90c8c64dSAndroid Build Coastguard Worker    try:
857*90c8c64dSAndroid Build Coastguard Worker        with open(WINSCOPE_TOKEN_LOCATION, 'r') as token_file:
858*90c8c64dSAndroid Build Coastguard Worker            token = token_file.readline()
859*90c8c64dSAndroid Build Coastguard Worker            log.debug("Loaded token {} from {}".format(
860*90c8c64dSAndroid Build Coastguard Worker                token, WINSCOPE_TOKEN_LOCATION))
861*90c8c64dSAndroid Build Coastguard Worker            return token
862*90c8c64dSAndroid Build Coastguard Worker    except IOError:
863*90c8c64dSAndroid Build Coastguard Worker        token = secrets.token_hex(32)
864*90c8c64dSAndroid Build Coastguard Worker        os.makedirs(os.path.dirname(WINSCOPE_TOKEN_LOCATION), exist_ok=True)
865*90c8c64dSAndroid Build Coastguard Worker        try:
866*90c8c64dSAndroid Build Coastguard Worker            with open(WINSCOPE_TOKEN_LOCATION, 'w') as token_file:
867*90c8c64dSAndroid Build Coastguard Worker                log.debug("Created and saved token {} to {}".format(
868*90c8c64dSAndroid Build Coastguard Worker                    token, WINSCOPE_TOKEN_LOCATION))
869*90c8c64dSAndroid Build Coastguard Worker                token_file.write(token)
870*90c8c64dSAndroid Build Coastguard Worker            os.chmod(WINSCOPE_TOKEN_LOCATION, 0o600)
871*90c8c64dSAndroid Build Coastguard Worker        except IOError:
872*90c8c64dSAndroid Build Coastguard Worker            log.error("Unable to save persistent token {} to {}".format(
873*90c8c64dSAndroid Build Coastguard Worker                token, WINSCOPE_TOKEN_LOCATION))
874*90c8c64dSAndroid Build Coastguard Worker        return token
875*90c8c64dSAndroid Build Coastguard Worker
876*90c8c64dSAndroid Build Coastguard Worker
877*90c8c64dSAndroid Build Coastguard Workerclass RequestType(Enum):
878*90c8c64dSAndroid Build Coastguard Worker    GET = 1
879*90c8c64dSAndroid Build Coastguard Worker    POST = 2
880*90c8c64dSAndroid Build Coastguard Worker    HEAD = 3
881*90c8c64dSAndroid Build Coastguard Worker
882*90c8c64dSAndroid Build Coastguard Worker
883*90c8c64dSAndroid Build Coastguard Workerdef add_standard_headers(server):
884*90c8c64dSAndroid Build Coastguard Worker    server.send_header('Cache-Control', 'no-cache, no-store, must-revalidate')
885*90c8c64dSAndroid Build Coastguard Worker    server.send_header('Access-Control-Allow-Origin', '*')
886*90c8c64dSAndroid Build Coastguard Worker    server.send_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS')
887*90c8c64dSAndroid Build Coastguard Worker    server.send_header('Access-Control-Allow-Headers',
888*90c8c64dSAndroid Build Coastguard Worker                       WINSCOPE_TOKEN_HEADER + ', Content-Type, Content-Length')
889*90c8c64dSAndroid Build Coastguard Worker    server.send_header('Access-Control-Expose-Headers',
890*90c8c64dSAndroid Build Coastguard Worker                       'Winscope-Proxy-Version')
891*90c8c64dSAndroid Build Coastguard Worker    server.send_header(WINSCOPE_VERSION_HEADER, VERSION)
892*90c8c64dSAndroid Build Coastguard Worker    server.end_headers()
893*90c8c64dSAndroid Build Coastguard Worker
894*90c8c64dSAndroid Build Coastguard Worker
895*90c8c64dSAndroid Build Coastguard Workerclass RequestEndpoint:
896*90c8c64dSAndroid Build Coastguard Worker    """Request endpoint to use with the RequestRouter."""
897*90c8c64dSAndroid Build Coastguard Worker
898*90c8c64dSAndroid Build Coastguard Worker    @abstractmethod
899*90c8c64dSAndroid Build Coastguard Worker    def process(self, server, path):
900*90c8c64dSAndroid Build Coastguard Worker        pass
901*90c8c64dSAndroid Build Coastguard Worker
902*90c8c64dSAndroid Build Coastguard Worker
903*90c8c64dSAndroid Build Coastguard Workerclass AdbError(Exception):
904*90c8c64dSAndroid Build Coastguard Worker    """Unsuccessful ADB operation"""
905*90c8c64dSAndroid Build Coastguard Worker    pass
906*90c8c64dSAndroid Build Coastguard Worker
907*90c8c64dSAndroid Build Coastguard Worker
908*90c8c64dSAndroid Build Coastguard Workerclass BadRequest(Exception):
909*90c8c64dSAndroid Build Coastguard Worker    """Invalid client request"""
910*90c8c64dSAndroid Build Coastguard Worker    pass
911*90c8c64dSAndroid Build Coastguard Worker
912*90c8c64dSAndroid Build Coastguard Worker
913*90c8c64dSAndroid Build Coastguard Workerclass RequestRouter:
914*90c8c64dSAndroid Build Coastguard Worker    """Handles HTTP request authentication and routing"""
915*90c8c64dSAndroid Build Coastguard Worker
916*90c8c64dSAndroid Build Coastguard Worker    def __init__(self, handler):
917*90c8c64dSAndroid Build Coastguard Worker        self.request = handler
918*90c8c64dSAndroid Build Coastguard Worker        self.endpoints = {}
919*90c8c64dSAndroid Build Coastguard Worker
920*90c8c64dSAndroid Build Coastguard Worker    def register_endpoint(self, method: RequestType, name: str, endpoint: RequestEndpoint):
921*90c8c64dSAndroid Build Coastguard Worker        self.endpoints[(method, name)] = endpoint
922*90c8c64dSAndroid Build Coastguard Worker
923*90c8c64dSAndroid Build Coastguard Worker    def _bad_request(self, error: str):
924*90c8c64dSAndroid Build Coastguard Worker        log.warning("Bad request: " + error)
925*90c8c64dSAndroid Build Coastguard Worker        self.request.respond(HTTPStatus.BAD_REQUEST, b"Bad request!\nThis is Winscope ADB proxy.\n\n"
926*90c8c64dSAndroid Build Coastguard Worker                             + error.encode("utf-8"), 'text/txt')
927*90c8c64dSAndroid Build Coastguard Worker
928*90c8c64dSAndroid Build Coastguard Worker    def _internal_error(self, error: str):
929*90c8c64dSAndroid Build Coastguard Worker        log.error("Internal error: " + error)
930*90c8c64dSAndroid Build Coastguard Worker        self.request.respond(HTTPStatus.INTERNAL_SERVER_ERROR,
931*90c8c64dSAndroid Build Coastguard Worker                             error.encode("utf-8"), 'text/txt')
932*90c8c64dSAndroid Build Coastguard Worker
933*90c8c64dSAndroid Build Coastguard Worker    def _bad_token(self):
934*90c8c64dSAndroid Build Coastguard Worker        log.warning("Bad token")
935*90c8c64dSAndroid Build Coastguard Worker        self.request.respond(HTTPStatus.FORBIDDEN, b"Bad Winscope authorization token!\nThis is Winscope ADB proxy.\n",
936*90c8c64dSAndroid Build Coastguard Worker                             'text/txt')
937*90c8c64dSAndroid Build Coastguard Worker
938*90c8c64dSAndroid Build Coastguard Worker    def process(self, method: RequestType):
939*90c8c64dSAndroid Build Coastguard Worker        token = self.request.headers[WINSCOPE_TOKEN_HEADER]
940*90c8c64dSAndroid Build Coastguard Worker        if not token or token != secret_token:
941*90c8c64dSAndroid Build Coastguard Worker            return self._bad_token()
942*90c8c64dSAndroid Build Coastguard Worker        path = self.request.path.strip('/').split('/')
943*90c8c64dSAndroid Build Coastguard Worker        if path and len(path) > 0:
944*90c8c64dSAndroid Build Coastguard Worker            endpoint_name = path[0]
945*90c8c64dSAndroid Build Coastguard Worker            try:
946*90c8c64dSAndroid Build Coastguard Worker                return self.endpoints[(method, endpoint_name)].process(self.request, path[1:])
947*90c8c64dSAndroid Build Coastguard Worker            except KeyError as ex:
948*90c8c64dSAndroid Build Coastguard Worker                if "RequestType" in repr(ex):
949*90c8c64dSAndroid Build Coastguard Worker                    return self._bad_request("Unknown endpoint /{}/".format(endpoint_name))
950*90c8c64dSAndroid Build Coastguard Worker                return self._internal_error(repr(ex))
951*90c8c64dSAndroid Build Coastguard Worker            except AdbError as ex:
952*90c8c64dSAndroid Build Coastguard Worker                return self._internal_error(str(ex))
953*90c8c64dSAndroid Build Coastguard Worker            except BadRequest as ex:
954*90c8c64dSAndroid Build Coastguard Worker                return self._bad_request(str(ex))
955*90c8c64dSAndroid Build Coastguard Worker            except Exception as ex:
956*90c8c64dSAndroid Build Coastguard Worker                return self._internal_error(repr(ex))
957*90c8c64dSAndroid Build Coastguard Worker        self._bad_request("No endpoint specified")
958*90c8c64dSAndroid Build Coastguard Worker
959*90c8c64dSAndroid Build Coastguard Worker
960*90c8c64dSAndroid Build Coastguard Workerdef call_adb(params: str, device: str = None, stdin: bytes = None):
961*90c8c64dSAndroid Build Coastguard Worker    command = ['adb'] + (['-s', device] if device else []) + params.split(' ')
962*90c8c64dSAndroid Build Coastguard Worker    try:
963*90c8c64dSAndroid Build Coastguard Worker        log.debug("Call: " + ' '.join(command))
964*90c8c64dSAndroid Build Coastguard Worker        return subprocess.check_output(command, stderr=subprocess.STDOUT, input=stdin).decode('utf-8')
965*90c8c64dSAndroid Build Coastguard Worker    except OSError as ex:
966*90c8c64dSAndroid Build Coastguard Worker        raise AdbError('Error executing adb command: {}\n{}'.format(
967*90c8c64dSAndroid Build Coastguard Worker            ' '.join(command), repr(ex)))
968*90c8c64dSAndroid Build Coastguard Worker    except subprocess.CalledProcessError as ex:
969*90c8c64dSAndroid Build Coastguard Worker        raise AdbError('Error executing adb command: adb {}\n{}'.format(
970*90c8c64dSAndroid Build Coastguard Worker            params, ex.output.decode("utf-8")))
971*90c8c64dSAndroid Build Coastguard Worker
972*90c8c64dSAndroid Build Coastguard Worker
973*90c8c64dSAndroid Build Coastguard Workerdef call_adb_outfile(params: str, outfile, device: str = None, stdin: bytes = None):
974*90c8c64dSAndroid Build Coastguard Worker    try:
975*90c8c64dSAndroid Build Coastguard Worker        process = subprocess.Popen(['adb'] + (['-s', device] if device else []) + params.split(' '), stdout=outfile,
976*90c8c64dSAndroid Build Coastguard Worker                                   stderr=subprocess.PIPE)
977*90c8c64dSAndroid Build Coastguard Worker        _, err = process.communicate(stdin)
978*90c8c64dSAndroid Build Coastguard Worker        outfile.seek(0)
979*90c8c64dSAndroid Build Coastguard Worker        if process.returncode != 0:
980*90c8c64dSAndroid Build Coastguard Worker            raise AdbError('Error executing adb command: adb {}\n'.format(params) + err.decode(
981*90c8c64dSAndroid Build Coastguard Worker                'utf-8') + '\n' + outfile.read().decode('utf-8'))
982*90c8c64dSAndroid Build Coastguard Worker    except OSError as ex:
983*90c8c64dSAndroid Build Coastguard Worker        raise AdbError(
984*90c8c64dSAndroid Build Coastguard Worker            'Error executing adb command: adb {}\n{}'.format(params, repr(ex)))
985*90c8c64dSAndroid Build Coastguard Worker
986*90c8c64dSAndroid Build Coastguard Worker
987*90c8c64dSAndroid Build Coastguard Workerclass ListDevicesEndpoint(RequestEndpoint):
988*90c8c64dSAndroid Build Coastguard Worker    ADB_INFO_RE = re.compile("^([A-Za-z0-9._:\\-]+)\\s+(\\w+)(.*model:(\\w+))?")
989*90c8c64dSAndroid Build Coastguard Worker    foundDevices: dict[str | int, dict[str, bool | str]] = {}
990*90c8c64dSAndroid Build Coastguard Worker
991*90c8c64dSAndroid Build Coastguard Worker    def process(self, server, path):
992*90c8c64dSAndroid Build Coastguard Worker        lines = list(filter(None, call_adb('devices -l').split('\n')))
993*90c8c64dSAndroid Build Coastguard Worker        devices = {}
994*90c8c64dSAndroid Build Coastguard Worker        for m in [ListDevicesEndpoint.ADB_INFO_RE.match(d) for d in lines[1:]]:
995*90c8c64dSAndroid Build Coastguard Worker            if m:
996*90c8c64dSAndroid Build Coastguard Worker                authorized = str(m.group(2)) != 'unauthorized'
997*90c8c64dSAndroid Build Coastguard Worker                try:
998*90c8c64dSAndroid Build Coastguard Worker                    screen_record_version = call_adb('shell screenrecord --version', m.group(1)) if authorized else '0'
999*90c8c64dSAndroid Build Coastguard Worker                except AdbError:
1000*90c8c64dSAndroid Build Coastguard Worker                    try:
1001*90c8c64dSAndroid Build Coastguard Worker                        help_text = call_adb('shell screenrecord --help', m.group(1))
1002*90c8c64dSAndroid Build Coastguard Worker                        version_start_index = help_text.find('v') + 1
1003*90c8c64dSAndroid Build Coastguard Worker                        screen_record_version = help_text[version_start_index:version_start_index + 3]
1004*90c8c64dSAndroid Build Coastguard Worker                    except AdbError:
1005*90c8c64dSAndroid Build Coastguard Worker                        screen_record_version = '0'
1006*90c8c64dSAndroid Build Coastguard Worker
1007*90c8c64dSAndroid Build Coastguard Worker                try:
1008*90c8c64dSAndroid Build Coastguard Worker                    displays = list(filter(None, call_adb('shell su root dumpsys SurfaceFlinger --display-id',
1009*90c8c64dSAndroid Build Coastguard Worker                                                          m.group(1)).split('\n'))) if authorized else []
1010*90c8c64dSAndroid Build Coastguard Worker                except AdbError:
1011*90c8c64dSAndroid Build Coastguard Worker                    displays = []
1012*90c8c64dSAndroid Build Coastguard Worker
1013*90c8c64dSAndroid Build Coastguard Worker                devices[m.group(1)] = {
1014*90c8c64dSAndroid Build Coastguard Worker                    'authorized': authorized,
1015*90c8c64dSAndroid Build Coastguard Worker                    'model': m.group(4).replace('_', ' ') if m.group(4) else '',
1016*90c8c64dSAndroid Build Coastguard Worker                    'displays': displays,
1017*90c8c64dSAndroid Build Coastguard Worker                    'screenrecord_version': screen_record_version,
1018*90c8c64dSAndroid Build Coastguard Worker                }
1019*90c8c64dSAndroid Build Coastguard Worker        self.foundDevices = devices
1020*90c8c64dSAndroid Build Coastguard Worker        j = json.dumps(devices)
1021*90c8c64dSAndroid Build Coastguard Worker        log.info("Detected devices: " + j)
1022*90c8c64dSAndroid Build Coastguard Worker        server.respond(HTTPStatus.OK, j.encode("utf-8"), "text/json")
1023*90c8c64dSAndroid Build Coastguard Worker
1024*90c8c64dSAndroid Build Coastguard Worker
1025*90c8c64dSAndroid Build Coastguard Worker
1026*90c8c64dSAndroid Build Coastguard Workerclass CheckWaylandServiceEndpoint(RequestEndpoint):
1027*90c8c64dSAndroid Build Coastguard Worker    def __init__(self, listDevicesEndpoint: ListDevicesEndpoint):
1028*90c8c64dSAndroid Build Coastguard Worker      self._listDevicesEndpoint = listDevicesEndpoint
1029*90c8c64dSAndroid Build Coastguard Worker
1030*90c8c64dSAndroid Build Coastguard Worker    def process(self, server, path):
1031*90c8c64dSAndroid Build Coastguard Worker        self._listDevicesEndpoint.process(server, path)
1032*90c8c64dSAndroid Build Coastguard Worker        foundDevices = self._listDevicesEndpoint.foundDevices
1033*90c8c64dSAndroid Build Coastguard Worker
1034*90c8c64dSAndroid Build Coastguard Worker        if len(foundDevices) != 1:
1035*90c8c64dSAndroid Build Coastguard Worker            res = 'false'
1036*90c8c64dSAndroid Build Coastguard Worker        else:
1037*90c8c64dSAndroid Build Coastguard Worker            device = list(foundDevices.values())[0]
1038*90c8c64dSAndroid Build Coastguard Worker            if not device.get('authorized') or not device.get('model'):
1039*90c8c64dSAndroid Build Coastguard Worker                res = 'false'
1040*90c8c64dSAndroid Build Coastguard Worker            else:
1041*90c8c64dSAndroid Build Coastguard Worker                raw_res = call_adb('shell service check Wayland')
1042*90c8c64dSAndroid Build Coastguard Worker                res = 'false' if 'not found' in raw_res else 'true'
1043*90c8c64dSAndroid Build Coastguard Worker        server.respond(HTTPStatus.OK, res.encode("utf-8"), "text/json")
1044*90c8c64dSAndroid Build Coastguard Worker
1045*90c8c64dSAndroid Build Coastguard Worker
1046*90c8c64dSAndroid Build Coastguard Worker
1047*90c8c64dSAndroid Build Coastguard Workerclass DeviceRequestEndpoint(RequestEndpoint):
1048*90c8c64dSAndroid Build Coastguard Worker    def process(self, server, path):
1049*90c8c64dSAndroid Build Coastguard Worker        if len(path) > 0 and re.fullmatch("[A-Za-z0-9._:\\-]+", path[0]):
1050*90c8c64dSAndroid Build Coastguard Worker            self.process_with_device(server, path[1:], path[0])
1051*90c8c64dSAndroid Build Coastguard Worker        else:
1052*90c8c64dSAndroid Build Coastguard Worker            raise BadRequest("Device id not specified")
1053*90c8c64dSAndroid Build Coastguard Worker
1054*90c8c64dSAndroid Build Coastguard Worker    @abstractmethod
1055*90c8c64dSAndroid Build Coastguard Worker    def process_with_device(self, server, path, device_id):
1056*90c8c64dSAndroid Build Coastguard Worker        pass
1057*90c8c64dSAndroid Build Coastguard Worker
1058*90c8c64dSAndroid Build Coastguard Worker    def get_request(self, server) -> str:
1059*90c8c64dSAndroid Build Coastguard Worker        try:
1060*90c8c64dSAndroid Build Coastguard Worker            length = int(server.headers["Content-Length"])
1061*90c8c64dSAndroid Build Coastguard Worker        except KeyError as err:
1062*90c8c64dSAndroid Build Coastguard Worker            raise BadRequest("Missing Content-Length header\n" + str(err))
1063*90c8c64dSAndroid Build Coastguard Worker        except ValueError as err:
1064*90c8c64dSAndroid Build Coastguard Worker            raise BadRequest("Content length unreadable\n" + str(err))
1065*90c8c64dSAndroid Build Coastguard Worker        return json.loads(server.rfile.read(length).decode("utf-8"))
1066*90c8c64dSAndroid Build Coastguard Worker
1067*90c8c64dSAndroid Build Coastguard Worker    def get_targets_and_prepare_for_tracing(self, server, device_id, perfetto_config_file, targets_map: dict[str, TraceTarget | DumpTarget], perfetto_name):
1068*90c8c64dSAndroid Build Coastguard Worker        warnings: list[str] = []
1069*90c8c64dSAndroid Build Coastguard Worker
1070*90c8c64dSAndroid Build Coastguard Worker        call_adb(f"shell su root rm -f {perfetto_config_file}", device_id)
1071*90c8c64dSAndroid Build Coastguard Worker        log.debug("Cleared perfetto config file for previous tracing session")
1072*90c8c64dSAndroid Build Coastguard Worker
1073*90c8c64dSAndroid Build Coastguard Worker        trace_requests: list[dict] = self.get_request(server)
1074*90c8c64dSAndroid Build Coastguard Worker        trace_types = [t.get("name") for t in trace_requests]
1075*90c8c64dSAndroid Build Coastguard Worker        log.debug(f"Received client request of {trace_types} for {device_id}")
1076*90c8c64dSAndroid Build Coastguard Worker
1077*90c8c64dSAndroid Build Coastguard Worker        perfetto_query_result = call_adb("shell perfetto --query", device_id)
1078*90c8c64dSAndroid Build Coastguard Worker
1079*90c8c64dSAndroid Build Coastguard Worker        too_many_perfetto_sessions = self.too_many_perfetto_sessions(perfetto_query_result)
1080*90c8c64dSAndroid Build Coastguard Worker        if (too_many_perfetto_sessions):
1081*90c8c64dSAndroid Build Coastguard Worker            warnings.append("Limit of 5 Perfetto sessions reached on device. Will attempt to collect legacy traces.")
1082*90c8c64dSAndroid Build Coastguard Worker            log.warning(warnings[0])
1083*90c8c64dSAndroid Build Coastguard Worker
1084*90c8c64dSAndroid Build Coastguard Worker        trace_targets: list[tuple[DumpTarget | TraceTarget, TraceConfig | None]] = []
1085*90c8c64dSAndroid Build Coastguard Worker        for t in trace_requests:
1086*90c8c64dSAndroid Build Coastguard Worker            try:
1087*90c8c64dSAndroid Build Coastguard Worker                trace_name = t.get("name")
1088*90c8c64dSAndroid Build Coastguard Worker                target = targets_map[trace_name]
1089*90c8c64dSAndroid Build Coastguard Worker                is_perfetto = (not too_many_perfetto_sessions) and target.is_perfetto_available(perfetto_query_result)
1090*90c8c64dSAndroid Build Coastguard Worker                config = None
1091*90c8c64dSAndroid Build Coastguard Worker                if target.get_trace_config is not None:
1092*90c8c64dSAndroid Build Coastguard Worker                    config = target.get_trace_config(is_perfetto)
1093*90c8c64dSAndroid Build Coastguard Worker                    self.apply_config(config, t.get("config"), server, device_id)
1094*90c8c64dSAndroid Build Coastguard Worker                is_valid_perfetto_target = trace_name == perfetto_name and not too_many_perfetto_sessions
1095*90c8c64dSAndroid Build Coastguard Worker                is_valid_trace_target = trace_name != perfetto_name and not is_perfetto
1096*90c8c64dSAndroid Build Coastguard Worker                if is_valid_perfetto_target or is_valid_trace_target:
1097*90c8c64dSAndroid Build Coastguard Worker                    trace_targets.append((target, config))
1098*90c8c64dSAndroid Build Coastguard Worker
1099*90c8c64dSAndroid Build Coastguard Worker            except KeyError as err:
1100*90c8c64dSAndroid Build Coastguard Worker                log.warning("Unsupported trace target\n" + str(err))
1101*90c8c64dSAndroid Build Coastguard Worker        trace_targets = self.move_perfetto_target_to_end_of_list(trace_targets)
1102*90c8c64dSAndroid Build Coastguard Worker
1103*90c8c64dSAndroid Build Coastguard Worker        self.check_device_and_permissions(server, device_id)
1104*90c8c64dSAndroid Build Coastguard Worker        self.clear_last_tracing_session(device_id)
1105*90c8c64dSAndroid Build Coastguard Worker
1106*90c8c64dSAndroid Build Coastguard Worker        log.debug("Trace requested for {} with targets {}".format(
1107*90c8c64dSAndroid Build Coastguard Worker            device_id, ','.join([target.trace_name for target, config in trace_targets])))
1108*90c8c64dSAndroid Build Coastguard Worker
1109*90c8c64dSAndroid Build Coastguard Worker        return trace_targets, warnings
1110*90c8c64dSAndroid Build Coastguard Worker
1111*90c8c64dSAndroid Build Coastguard Worker    def too_many_perfetto_sessions(self, perfetto_query_result: str):
1112*90c8c64dSAndroid Build Coastguard Worker        concurrent_sessions_start = perfetto_query_result.find(PERFETTO_TRACING_SESSIONS_QUERY_START)
1113*90c8c64dSAndroid Build Coastguard Worker        if concurrent_sessions_start != -1:
1114*90c8c64dSAndroid Build Coastguard Worker            concurrent_sessions = perfetto_query_result[concurrent_sessions_start + len(PERFETTO_TRACING_SESSIONS_QUERY_START):]
1115*90c8c64dSAndroid Build Coastguard Worker            log.info(f"Concurrent sessions:\n{concurrent_sessions}\n")
1116*90c8c64dSAndroid Build Coastguard Worker            concurrent_sessions_end = concurrent_sessions.find(PERFETTO_TRACING_SESSIONS_QUERY_END)
1117*90c8c64dSAndroid Build Coastguard Worker            if concurrent_sessions_end > 0:
1118*90c8c64dSAndroid Build Coastguard Worker                concurrent_sessions_end -= 1
1119*90c8c64dSAndroid Build Coastguard Worker            concurrent_sessions = concurrent_sessions[:concurrent_sessions_end]
1120*90c8c64dSAndroid Build Coastguard Worker            number_of_concurrent_sessions = len(concurrent_sessions.split("\n")) if len(concurrent_sessions) > 0 else 0
1121*90c8c64dSAndroid Build Coastguard Worker
1122*90c8c64dSAndroid Build Coastguard Worker            if PERFETTO_UNIQUE_SESSION_NAME in concurrent_sessions:
1123*90c8c64dSAndroid Build Coastguard Worker                call_adb("shell perfetto --attach=WINSCOPE-PROXY-TRACING-SESSION --stop")
1124*90c8c64dSAndroid Build Coastguard Worker                log.debug("Stopped already-running winscope perfetto session.")
1125*90c8c64dSAndroid Build Coastguard Worker                number_of_concurrent_sessions -= 1
1126*90c8c64dSAndroid Build Coastguard Worker            return number_of_concurrent_sessions >= 5
1127*90c8c64dSAndroid Build Coastguard Worker        return False
1128*90c8c64dSAndroid Build Coastguard Worker
1129*90c8c64dSAndroid Build Coastguard Worker    def apply_config(self, trace_config: TraceConfig, requested_configs: list[dict], server, device_id):
1130*90c8c64dSAndroid Build Coastguard Worker        for requested_config in requested_configs:
1131*90c8c64dSAndroid Build Coastguard Worker            config_key = requested_config.get("key")
1132*90c8c64dSAndroid Build Coastguard Worker            if not trace_config.is_valid(config_key):
1133*90c8c64dSAndroid Build Coastguard Worker                raise BadRequest(
1134*90c8c64dSAndroid Build Coastguard Worker                    f"Unsupported config {config_key}\n")
1135*90c8c64dSAndroid Build Coastguard Worker            trace_config.add(config_key, requested_config.get("value"))
1136*90c8c64dSAndroid Build Coastguard Worker
1137*90c8c64dSAndroid Build Coastguard Worker        if device_id in TRACE_THREADS:
1138*90c8c64dSAndroid Build Coastguard Worker            BadRequest(f"Trace in progress for {device_id}")
1139*90c8c64dSAndroid Build Coastguard Worker        if not self.check_root(device_id):
1140*90c8c64dSAndroid Build Coastguard Worker            raise AdbError(
1141*90c8c64dSAndroid Build Coastguard Worker                f"Unable to acquire root privileges on the device - check the output of 'adb -s {device_id} shell su root id'")
1142*90c8c64dSAndroid Build Coastguard Worker        trace_config.execute_command(server, device_id)
1143*90c8c64dSAndroid Build Coastguard Worker
1144*90c8c64dSAndroid Build Coastguard Worker    def check_root(self, device_id):
1145*90c8c64dSAndroid Build Coastguard Worker        log.debug("Checking root access on {}".format(device_id))
1146*90c8c64dSAndroid Build Coastguard Worker        return int(call_adb('shell su root id -u', device_id)) == 0
1147*90c8c64dSAndroid Build Coastguard Worker
1148*90c8c64dSAndroid Build Coastguard Worker    def move_perfetto_target_to_end_of_list(self, targets: list[tuple[TraceTarget, TraceConfig | None]]) -> list[tuple[TraceTarget, TraceConfig | None]]:
1149*90c8c64dSAndroid Build Coastguard Worker        # Make sure a perfetto target (if present) comes last in the list of targets, i.e. will
1150*90c8c64dSAndroid Build Coastguard Worker        # be processed last.
1151*90c8c64dSAndroid Build Coastguard Worker        # A perfetto target must be processed last, so that perfetto tracing is started only after
1152*90c8c64dSAndroid Build Coastguard Worker        # the other targets have been processed and have configured the perfetto config file.
1153*90c8c64dSAndroid Build Coastguard Worker        def is_perfetto_target(target: TraceTarget):
1154*90c8c64dSAndroid Build Coastguard Worker            return target == TRACE_TARGETS["perfetto_trace"] or target == DUMP_TARGETS["perfetto_dump"]
1155*90c8c64dSAndroid Build Coastguard Worker        non_perfetto_targets = [t for t in targets if not is_perfetto_target(t[0])]
1156*90c8c64dSAndroid Build Coastguard Worker        perfetto_targets = [t for t in targets if is_perfetto_target(t[0])]
1157*90c8c64dSAndroid Build Coastguard Worker        return non_perfetto_targets + perfetto_targets
1158*90c8c64dSAndroid Build Coastguard Worker
1159*90c8c64dSAndroid Build Coastguard Worker    def check_device_and_permissions(self, server, device_id):
1160*90c8c64dSAndroid Build Coastguard Worker        if device_id in TRACE_THREADS:
1161*90c8c64dSAndroid Build Coastguard Worker            log.warning("Trace already in progress for {}", device_id)
1162*90c8c64dSAndroid Build Coastguard Worker            server.respond(HTTPStatus.OK, b'', "text/plain")
1163*90c8c64dSAndroid Build Coastguard Worker        if not self.check_root(device_id):
1164*90c8c64dSAndroid Build Coastguard Worker            raise AdbError(
1165*90c8c64dSAndroid Build Coastguard Worker                "Unable to acquire root privileges on the device - check the output of 'adb -s {} shell su root id'"
1166*90c8c64dSAndroid Build Coastguard Worker                .format( device_id))
1167*90c8c64dSAndroid Build Coastguard Worker
1168*90c8c64dSAndroid Build Coastguard Worker    def clear_last_tracing_session(self, device_id):
1169*90c8c64dSAndroid Build Coastguard Worker        call_adb(f"shell su root rm -rf {WINSCOPE_BACKUP_DIR}", device_id)
1170*90c8c64dSAndroid Build Coastguard Worker        call_adb(f"shell su root mkdir {WINSCOPE_BACKUP_DIR}", device_id)
1171*90c8c64dSAndroid Build Coastguard Worker        log.debug("Cleared previous tracing session files from device")
1172*90c8c64dSAndroid Build Coastguard Worker
1173*90c8c64dSAndroid Build Coastguard Worker
1174*90c8c64dSAndroid Build Coastguard Workerclass FetchFilesEndpoint(DeviceRequestEndpoint):
1175*90c8c64dSAndroid Build Coastguard Worker    def process_with_device(self, server, path, device_id):
1176*90c8c64dSAndroid Build Coastguard Worker        file_buffers = self.fetch_existing_files(device_id)
1177*90c8c64dSAndroid Build Coastguard Worker
1178*90c8c64dSAndroid Build Coastguard Worker        j = json.dumps(file_buffers)
1179*90c8c64dSAndroid Build Coastguard Worker        server.respond(HTTPStatus.OK, j.encode("utf-8"), "text/json")
1180*90c8c64dSAndroid Build Coastguard Worker
1181*90c8c64dSAndroid Build Coastguard Worker    def fetch_existing_files(self, device_id):
1182*90c8c64dSAndroid Build Coastguard Worker        file_buffers = dict()
1183*90c8c64dSAndroid Build Coastguard Worker        file = FileMatcher(f"{WINSCOPE_BACKUP_DIR}*", "", "")
1184*90c8c64dSAndroid Build Coastguard Worker        try:
1185*90c8c64dSAndroid Build Coastguard Worker            file_paths = file.get_filepaths(device_id)
1186*90c8c64dSAndroid Build Coastguard Worker            for file_path in file_paths:
1187*90c8c64dSAndroid Build Coastguard Worker                with NamedTemporaryFile() as tmp:
1188*90c8c64dSAndroid Build Coastguard Worker                    file_name = file_path.split('/')[-1] + ".gz"
1189*90c8c64dSAndroid Build Coastguard Worker                    log.debug(
1190*90c8c64dSAndroid Build Coastguard Worker                        f"Fetching file {file_path} from device to {tmp.name}")
1191*90c8c64dSAndroid Build Coastguard Worker                    try:
1192*90c8c64dSAndroid Build Coastguard Worker                        call_adb_outfile('exec-out su root cat ' +
1193*90c8c64dSAndroid Build Coastguard Worker                                            file_path, tmp, device_id)
1194*90c8c64dSAndroid Build Coastguard Worker                    except AdbError as ex:
1195*90c8c64dSAndroid Build Coastguard Worker                        log.warning(f"Unable to fetch file {file_path} - {repr(ex)}")
1196*90c8c64dSAndroid Build Coastguard Worker                        return
1197*90c8c64dSAndroid Build Coastguard Worker                    log.debug(f"Uploading file {tmp.name}")
1198*90c8c64dSAndroid Build Coastguard Worker                    if file_name not in file_buffers:
1199*90c8c64dSAndroid Build Coastguard Worker                        file_buffers[file_name] = []
1200*90c8c64dSAndroid Build Coastguard Worker                    buf = base64.encodebytes(gzip.compress(tmp.read())).decode("utf-8")
1201*90c8c64dSAndroid Build Coastguard Worker                    file_buffers[file_name].append(buf)
1202*90c8c64dSAndroid Build Coastguard Worker        except:
1203*90c8c64dSAndroid Build Coastguard Worker            self.log_no_files_warning()
1204*90c8c64dSAndroid Build Coastguard Worker        return file_buffers
1205*90c8c64dSAndroid Build Coastguard Worker
1206*90c8c64dSAndroid Build Coastguard Worker    def log_no_files_warning(self):
1207*90c8c64dSAndroid Build Coastguard Worker        log.warning("Proxy didn't find any file to fetch")
1208*90c8c64dSAndroid Build Coastguard Worker
1209*90c8c64dSAndroid Build Coastguard Worker
1210*90c8c64dSAndroid Build Coastguard WorkerTRACE_THREADS = {}
1211*90c8c64dSAndroid Build Coastguard Worker
1212*90c8c64dSAndroid Build Coastguard Workerclass TraceThread(threading.Thread):
1213*90c8c64dSAndroid Build Coastguard Worker    def __init__(self, trace_name: str, device_id: str, command: str, trace_identifier: str, status_filename: str):
1214*90c8c64dSAndroid Build Coastguard Worker        self.trace_command = command
1215*90c8c64dSAndroid Build Coastguard Worker        self.trace_name = trace_name
1216*90c8c64dSAndroid Build Coastguard Worker        self.trace_identifier = trace_identifier
1217*90c8c64dSAndroid Build Coastguard Worker        self.status_filename = status_filename
1218*90c8c64dSAndroid Build Coastguard Worker        self._device_id = device_id
1219*90c8c64dSAndroid Build Coastguard Worker        self._keep_alive_timer = None
1220*90c8c64dSAndroid Build Coastguard Worker        self.out = None,
1221*90c8c64dSAndroid Build Coastguard Worker        self.err = None,
1222*90c8c64dSAndroid Build Coastguard Worker        self._command_timed_out = False
1223*90c8c64dSAndroid Build Coastguard Worker        self._success = False
1224*90c8c64dSAndroid Build Coastguard Worker        try:
1225*90c8c64dSAndroid Build Coastguard Worker            shell = get_shell_args(self._device_id, "trace")
1226*90c8c64dSAndroid Build Coastguard Worker            self.process = subprocess.Popen(shell, stdout=subprocess.PIPE,
1227*90c8c64dSAndroid Build Coastguard Worker                                            stderr=subprocess.PIPE, stdin=subprocess.PIPE, start_new_session=True)
1228*90c8c64dSAndroid Build Coastguard Worker        except OSError as ex:
1229*90c8c64dSAndroid Build Coastguard Worker            raise AdbError(
1230*90c8c64dSAndroid Build Coastguard Worker                'Error executing adb command for trace {}: adb shell\n{}'.format(trace_name, repr(ex)))
1231*90c8c64dSAndroid Build Coastguard Worker
1232*90c8c64dSAndroid Build Coastguard Worker        super().__init__()
1233*90c8c64dSAndroid Build Coastguard Worker
1234*90c8c64dSAndroid Build Coastguard Worker    def timeout(self):
1235*90c8c64dSAndroid Build Coastguard Worker        if self.is_alive():
1236*90c8c64dSAndroid Build Coastguard Worker            log.warning("Keep-alive timeout for {} trace on {}".format(self.trace_name, self._device_id))
1237*90c8c64dSAndroid Build Coastguard Worker            self.end_trace()
1238*90c8c64dSAndroid Build Coastguard Worker
1239*90c8c64dSAndroid Build Coastguard Worker    def reset_timer(self):
1240*90c8c64dSAndroid Build Coastguard Worker        log.info(
1241*90c8c64dSAndroid Build Coastguard Worker            "Resetting keep-alive clock for {} trace on {}".format(self.trace_name, self._device_id))
1242*90c8c64dSAndroid Build Coastguard Worker        if self._keep_alive_timer:
1243*90c8c64dSAndroid Build Coastguard Worker            self._keep_alive_timer.cancel()
1244*90c8c64dSAndroid Build Coastguard Worker        self._keep_alive_timer = threading.Timer(
1245*90c8c64dSAndroid Build Coastguard Worker            KEEP_ALIVE_INTERVAL_S, self.timeout)
1246*90c8c64dSAndroid Build Coastguard Worker        self._keep_alive_timer.start()
1247*90c8c64dSAndroid Build Coastguard Worker
1248*90c8c64dSAndroid Build Coastguard Worker    def end_trace(self):
1249*90c8c64dSAndroid Build Coastguard Worker        if self._keep_alive_timer:
1250*90c8c64dSAndroid Build Coastguard Worker            self._keep_alive_timer.cancel()
1251*90c8c64dSAndroid Build Coastguard Worker        log.info("Sending SIGINT to the {} process on {}".format(
1252*90c8c64dSAndroid Build Coastguard Worker            self.trace_name,
1253*90c8c64dSAndroid Build Coastguard Worker            self._device_id))
1254*90c8c64dSAndroid Build Coastguard Worker        self.process.send_signal(signal.SIGINT)
1255*90c8c64dSAndroid Build Coastguard Worker        try:
1256*90c8c64dSAndroid Build Coastguard Worker            log.debug("Waiting for {} trace shell to exit for {}".format(
1257*90c8c64dSAndroid Build Coastguard Worker                self.trace_name,
1258*90c8c64dSAndroid Build Coastguard Worker                self._device_id))
1259*90c8c64dSAndroid Build Coastguard Worker            self.process.wait(timeout=COMMAND_TIMEOUT_S)
1260*90c8c64dSAndroid Build Coastguard Worker        except TimeoutError:
1261*90c8c64dSAndroid Build Coastguard Worker            log.error(
1262*90c8c64dSAndroid Build Coastguard Worker                "TIMEOUT - sending SIGKILL to the {} trace process on {}".format(self.trace_name, self._device_id))
1263*90c8c64dSAndroid Build Coastguard Worker            self.process.kill()
1264*90c8c64dSAndroid Build Coastguard Worker        self.join()
1265*90c8c64dSAndroid Build Coastguard Worker
1266*90c8c64dSAndroid Build Coastguard Worker    def run(self):
1267*90c8c64dSAndroid Build Coastguard Worker        retry_interval = 0.1
1268*90c8c64dSAndroid Build Coastguard Worker        log.info("Trace {} started on {}".format(self.trace_name, self._device_id))
1269*90c8c64dSAndroid Build Coastguard Worker        self.reset_timer()
1270*90c8c64dSAndroid Build Coastguard Worker        self.out, self.err = self.process.communicate(self.trace_command)
1271*90c8c64dSAndroid Build Coastguard Worker        log.info("Trace {} ended on {}, waiting for cleanup".format(self.trace_name, self._device_id))
1272*90c8c64dSAndroid Build Coastguard Worker        time.sleep(0.2)
1273*90c8c64dSAndroid Build Coastguard Worker        for i in range(int(COMMAND_TIMEOUT_S / retry_interval)):
1274*90c8c64dSAndroid Build Coastguard Worker            if call_adb(f"shell su root cat {self.status_filename}", device=self._device_id) == 'TRACE_OK\n':
1275*90c8c64dSAndroid Build Coastguard Worker                log.info("Trace {} finished on {}".format(
1276*90c8c64dSAndroid Build Coastguard Worker                    self.trace_name,
1277*90c8c64dSAndroid Build Coastguard Worker                    self._device_id))
1278*90c8c64dSAndroid Build Coastguard Worker                if self.trace_name == "perfetto_trace":
1279*90c8c64dSAndroid Build Coastguard Worker                    self._success = True
1280*90c8c64dSAndroid Build Coastguard Worker                else:
1281*90c8c64dSAndroid Build Coastguard Worker                    self._success = len(self.err) == 0
1282*90c8c64dSAndroid Build Coastguard Worker                return
1283*90c8c64dSAndroid Build Coastguard Worker            log.debug("Still waiting for cleanup on {} for {}".format(self._device_id, self.trace_name))
1284*90c8c64dSAndroid Build Coastguard Worker            time.sleep(retry_interval)
1285*90c8c64dSAndroid Build Coastguard Worker
1286*90c8c64dSAndroid Build Coastguard Worker        self._command_timed_out = True
1287*90c8c64dSAndroid Build Coastguard Worker
1288*90c8c64dSAndroid Build Coastguard Worker    def success(self):
1289*90c8c64dSAndroid Build Coastguard Worker        return self._success
1290*90c8c64dSAndroid Build Coastguard Worker
1291*90c8c64dSAndroid Build Coastguard Worker    def timed_out(self):
1292*90c8c64dSAndroid Build Coastguard Worker        return self._command_timed_out
1293*90c8c64dSAndroid Build Coastguard Worker
1294*90c8c64dSAndroid Build Coastguard Workerclass StartTraceEndpoint(DeviceRequestEndpoint):
1295*90c8c64dSAndroid Build Coastguard Worker    TRACE_COMMAND = """
1296*90c8c64dSAndroid Build Coastguard Workerset -e
1297*90c8c64dSAndroid Build Coastguard Worker
1298*90c8c64dSAndroid Build Coastguard Workerecho "Starting trace..."
1299*90c8c64dSAndroid Build Coastguard Workerecho "TRACE_START" > {winscope_status}
1300*90c8c64dSAndroid Build Coastguard Worker
1301*90c8c64dSAndroid Build Coastguard Worker# Do not print anything to stdout/stderr in the handler
1302*90c8c64dSAndroid Build Coastguard Workerfunction stop_trace() {{
1303*90c8c64dSAndroid Build Coastguard Worker  echo "start" >{signal_handler_log}
1304*90c8c64dSAndroid Build Coastguard Worker
1305*90c8c64dSAndroid Build Coastguard Worker  # redirect stdout/stderr to log file
1306*90c8c64dSAndroid Build Coastguard Worker  exec 1>>{signal_handler_log}
1307*90c8c64dSAndroid Build Coastguard Worker  exec 2>>{signal_handler_log}
1308*90c8c64dSAndroid Build Coastguard Worker
1309*90c8c64dSAndroid Build Coastguard Worker  set -x
1310*90c8c64dSAndroid Build Coastguard Worker  trap - EXIT HUP INT
1311*90c8c64dSAndroid Build Coastguard Worker  {stop_commands}
1312*90c8c64dSAndroid Build Coastguard Worker  echo "TRACE_OK" > {winscope_status}
1313*90c8c64dSAndroid Build Coastguard Worker}}
1314*90c8c64dSAndroid Build Coastguard Worker
1315*90c8c64dSAndroid Build Coastguard Workertrap stop_trace EXIT HUP INT
1316*90c8c64dSAndroid Build Coastguard Workerecho "Signal handler registered."
1317*90c8c64dSAndroid Build Coastguard Worker
1318*90c8c64dSAndroid Build Coastguard Worker{start_commands}
1319*90c8c64dSAndroid Build Coastguard Worker
1320*90c8c64dSAndroid Build Coastguard Worker# ADB shell does not handle hung up well and does not call HUP handler when a child is active in foreground,
1321*90c8c64dSAndroid Build Coastguard Worker# as a workaround we sleep for short intervals in a loop so the handler is called after a sleep interval.
1322*90c8c64dSAndroid Build Coastguard Workerwhile true; do sleep 0.1; done
1323*90c8c64dSAndroid Build Coastguard Worker"""
1324*90c8c64dSAndroid Build Coastguard Worker
1325*90c8c64dSAndroid Build Coastguard Worker    def process_with_device(self, server, path, device_id):
1326*90c8c64dSAndroid Build Coastguard Worker        trace_targets, warnings = self.get_targets_and_prepare_for_tracing(
1327*90c8c64dSAndroid Build Coastguard Worker            server, device_id, PERFETTO_TRACE_CONFIG_FILE, TRACE_TARGETS, "perfetto_trace")
1328*90c8c64dSAndroid Build Coastguard Worker
1329*90c8c64dSAndroid Build Coastguard Worker        for t, config in trace_targets:
1330*90c8c64dSAndroid Build Coastguard Worker            if config:
1331*90c8c64dSAndroid Build Coastguard Worker                trace_identifiers = config.get_trace_identifiers()
1332*90c8c64dSAndroid Build Coastguard Worker            else:
1333*90c8c64dSAndroid Build Coastguard Worker                trace_identifiers = [""]
1334*90c8c64dSAndroid Build Coastguard Worker
1335*90c8c64dSAndroid Build Coastguard Worker            for trace_identifier in trace_identifiers:
1336*90c8c64dSAndroid Build Coastguard Worker                if trace_identifier:
1337*90c8c64dSAndroid Build Coastguard Worker                    if config:
1338*90c8c64dSAndroid Build Coastguard Worker                        options = config.get_optional_start_args(trace_identifier)
1339*90c8c64dSAndroid Build Coastguard Worker                    else:
1340*90c8c64dSAndroid Build Coastguard Worker                        options = ""
1341*90c8c64dSAndroid Build Coastguard Worker                    start_cmd = t.trace_start.format(trace_identifier=trace_identifier, options=options)
1342*90c8c64dSAndroid Build Coastguard Worker                else:
1343*90c8c64dSAndroid Build Coastguard Worker                    start_cmd = t.trace_start
1344*90c8c64dSAndroid Build Coastguard Worker
1345*90c8c64dSAndroid Build Coastguard Worker                command = StartTraceEndpoint.TRACE_COMMAND.format(
1346*90c8c64dSAndroid Build Coastguard Worker                    winscope_status=t.status_filename,
1347*90c8c64dSAndroid Build Coastguard Worker                    signal_handler_log=SIGNAL_HANDLER_LOG,
1348*90c8c64dSAndroid Build Coastguard Worker                    stop_commands=t.trace_stop,
1349*90c8c64dSAndroid Build Coastguard Worker                    perfetto_config_file=PERFETTO_TRACE_CONFIG_FILE,
1350*90c8c64dSAndroid Build Coastguard Worker                    start_commands=start_cmd,
1351*90c8c64dSAndroid Build Coastguard Worker                )
1352*90c8c64dSAndroid Build Coastguard Worker                log.debug(f"Executing start command for {t.trace_name} on {device_id}...")
1353*90c8c64dSAndroid Build Coastguard Worker                thread = TraceThread(t.trace_name, device_id, command.encode('utf-8'), trace_identifier, t.status_filename)
1354*90c8c64dSAndroid Build Coastguard Worker                if device_id not in TRACE_THREADS:
1355*90c8c64dSAndroid Build Coastguard Worker                    TRACE_THREADS[device_id] = [thread]
1356*90c8c64dSAndroid Build Coastguard Worker                else:
1357*90c8c64dSAndroid Build Coastguard Worker                    TRACE_THREADS[device_id].append(thread)
1358*90c8c64dSAndroid Build Coastguard Worker                thread.start()
1359*90c8c64dSAndroid Build Coastguard Worker
1360*90c8c64dSAndroid Build Coastguard Worker        server.respond(HTTPStatus.OK, json.dumps(warnings).encode("utf-8"), "text/json")
1361*90c8c64dSAndroid Build Coastguard Worker
1362*90c8c64dSAndroid Build Coastguard Worker
1363*90c8c64dSAndroid Build Coastguard Workerdef move_collected_files(files: list[File | FileMatcher], device_id, trace_identifier):
1364*90c8c64dSAndroid Build Coastguard Worker    for f in files:
1365*90c8c64dSAndroid Build Coastguard Worker        file_paths = f.get_filepaths(device_id)
1366*90c8c64dSAndroid Build Coastguard Worker        file_type = f.get_filetype().format(trace_identifier=trace_identifier)
1367*90c8c64dSAndroid Build Coastguard Worker
1368*90c8c64dSAndroid Build Coastguard Worker        for file_path in file_paths:
1369*90c8c64dSAndroid Build Coastguard Worker            formatted_path = file_path.format(trace_identifier=trace_identifier)
1370*90c8c64dSAndroid Build Coastguard Worker            log.debug(f"Moving file {formatted_path} to {WINSCOPE_BACKUP_DIR}{file_type} on device")
1371*90c8c64dSAndroid Build Coastguard Worker            try:
1372*90c8c64dSAndroid Build Coastguard Worker                call_adb(
1373*90c8c64dSAndroid Build Coastguard Worker                    f"shell su root [ ! -f {formatted_path} ] || su root mv {formatted_path} {WINSCOPE_BACKUP_DIR}{file_type}",
1374*90c8c64dSAndroid Build Coastguard Worker                    device_id)
1375*90c8c64dSAndroid Build Coastguard Worker            except AdbError as ex:
1376*90c8c64dSAndroid Build Coastguard Worker                log.warning(f"Unable to move file {formatted_path} - {repr(ex)}")
1377*90c8c64dSAndroid Build Coastguard Worker
1378*90c8c64dSAndroid Build Coastguard Worker
1379*90c8c64dSAndroid Build Coastguard Workerclass EndTraceEndpoint(DeviceRequestEndpoint):
1380*90c8c64dSAndroid Build Coastguard Worker    def process_with_device(self, server, path, device_id):
1381*90c8c64dSAndroid Build Coastguard Worker        if device_id not in TRACE_THREADS:
1382*90c8c64dSAndroid Build Coastguard Worker            raise BadRequest("No trace in progress for {}".format(device_id))
1383*90c8c64dSAndroid Build Coastguard Worker
1384*90c8c64dSAndroid Build Coastguard Worker        errors: list[str] = []
1385*90c8c64dSAndroid Build Coastguard Worker
1386*90c8c64dSAndroid Build Coastguard Worker        for thread in TRACE_THREADS[device_id]:
1387*90c8c64dSAndroid Build Coastguard Worker            if thread.is_alive():
1388*90c8c64dSAndroid Build Coastguard Worker                thread.end_trace()
1389*90c8c64dSAndroid Build Coastguard Worker            success = thread.success()
1390*90c8c64dSAndroid Build Coastguard Worker            signal_handler_log = call_adb(f"shell su root cat {SIGNAL_HANDLER_LOG}", device=device_id).encode('utf-8')
1391*90c8c64dSAndroid Build Coastguard Worker
1392*90c8c64dSAndroid Build Coastguard Worker            if (thread.timed_out()):
1393*90c8c64dSAndroid Build Coastguard Worker                timeout_message = "Trace {} timed out during cleanup".format(thread.trace_name)
1394*90c8c64dSAndroid Build Coastguard Worker                errors.append(timeout_message)
1395*90c8c64dSAndroid Build Coastguard Worker                log.error(timeout_message)
1396*90c8c64dSAndroid Build Coastguard Worker
1397*90c8c64dSAndroid Build Coastguard Worker            if not success:
1398*90c8c64dSAndroid Build Coastguard Worker                log.error("Error ending trace {} on the device".format(thread.trace_name))
1399*90c8c64dSAndroid Build Coastguard Worker                errors.append("Error ending trace {} on the device: {}".format(thread.trace_name, thread.err))
1400*90c8c64dSAndroid Build Coastguard Worker
1401*90c8c64dSAndroid Build Coastguard Worker            out = b"### Shell script's stdout ###\n" + \
1402*90c8c64dSAndroid Build Coastguard Worker                (thread.out if thread.out else b'<no stdout>') + \
1403*90c8c64dSAndroid Build Coastguard Worker                b"\n### Shell script's stderr ###\n" + \
1404*90c8c64dSAndroid Build Coastguard Worker                (thread.err if thread.err else b'<no stderr>') + \
1405*90c8c64dSAndroid Build Coastguard Worker                b"\n### Signal handler log ###\n" + \
1406*90c8c64dSAndroid Build Coastguard Worker                (signal_handler_log if signal_handler_log else b'<no signal handler logs>') + \
1407*90c8c64dSAndroid Build Coastguard Worker                b"\n"
1408*90c8c64dSAndroid Build Coastguard Worker            log.debug("### Output ###\n".format(thread.trace_name) + out.decode("utf-8"))
1409*90c8c64dSAndroid Build Coastguard Worker            if thread.trace_name in TRACE_TARGETS:
1410*90c8c64dSAndroid Build Coastguard Worker                files = TRACE_TARGETS[thread.trace_name].files
1411*90c8c64dSAndroid Build Coastguard Worker                move_collected_files(files, device_id, thread.trace_identifier)
1412*90c8c64dSAndroid Build Coastguard Worker            else:
1413*90c8c64dSAndroid Build Coastguard Worker                errors.append(f"File location unknown for {thread.trace_name}")
1414*90c8c64dSAndroid Build Coastguard Worker
1415*90c8c64dSAndroid Build Coastguard Worker        call_adb(f"shell su root rm {thread.status_filename}", device=device_id)
1416*90c8c64dSAndroid Build Coastguard Worker        TRACE_THREADS.pop(device_id)
1417*90c8c64dSAndroid Build Coastguard Worker        server.respond(HTTPStatus.OK, json.dumps(errors).encode("utf-8"), "text/plain")
1418*90c8c64dSAndroid Build Coastguard Worker
1419*90c8c64dSAndroid Build Coastguard Worker
1420*90c8c64dSAndroid Build Coastguard Workerclass StatusEndpoint(DeviceRequestEndpoint):
1421*90c8c64dSAndroid Build Coastguard Worker    def process_with_device(self, server, path, device_id):
1422*90c8c64dSAndroid Build Coastguard Worker        if device_id not in TRACE_THREADS:
1423*90c8c64dSAndroid Build Coastguard Worker            raise BadRequest("No trace in progress for {}".format(device_id))
1424*90c8c64dSAndroid Build Coastguard Worker        for thread in TRACE_THREADS[device_id]:
1425*90c8c64dSAndroid Build Coastguard Worker            thread.reset_timer()
1426*90c8c64dSAndroid Build Coastguard Worker        server.respond(HTTPStatus.OK, str(TRACE_THREADS[device_id][0].is_alive()).encode("utf-8"), "text/plain")
1427*90c8c64dSAndroid Build Coastguard Worker
1428*90c8c64dSAndroid Build Coastguard Worker
1429*90c8c64dSAndroid Build Coastguard Workerclass DumpEndpoint(DeviceRequestEndpoint):
1430*90c8c64dSAndroid Build Coastguard Worker    def process_with_device(self, server, path, device_id):
1431*90c8c64dSAndroid Build Coastguard Worker        dump_targets, warnings = self.get_targets_and_prepare_for_tracing(
1432*90c8c64dSAndroid Build Coastguard Worker            server, device_id, PERFETTO_DUMP_CONFIG_FILE, DUMP_TARGETS, "perfetto_dump")
1433*90c8c64dSAndroid Build Coastguard Worker
1434*90c8c64dSAndroid Build Coastguard Worker        dump_commands = []
1435*90c8c64dSAndroid Build Coastguard Worker        for t, config in dump_targets:
1436*90c8c64dSAndroid Build Coastguard Worker            if config:
1437*90c8c64dSAndroid Build Coastguard Worker                for trace_identifier in config.get_trace_identifiers():
1438*90c8c64dSAndroid Build Coastguard Worker                    dump_commands.append(
1439*90c8c64dSAndroid Build Coastguard Worker                        t.dump_command.format(trace_identifier=trace_identifier, options=config.get_optional_start_args(trace_identifier)))
1440*90c8c64dSAndroid Build Coastguard Worker            else:
1441*90c8c64dSAndroid Build Coastguard Worker                dump_commands.append(t.dump_command)
1442*90c8c64dSAndroid Build Coastguard Worker
1443*90c8c64dSAndroid Build Coastguard Worker        dump_commands = '\n'.join(dump_commands)
1444*90c8c64dSAndroid Build Coastguard Worker
1445*90c8c64dSAndroid Build Coastguard Worker        shell = get_shell_args(device_id, "dump")
1446*90c8c64dSAndroid Build Coastguard Worker        process = subprocess.Popen(shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
1447*90c8c64dSAndroid Build Coastguard Worker                                   stdin=subprocess.PIPE, start_new_session=True)
1448*90c8c64dSAndroid Build Coastguard Worker        log.info("Starting dump on device {}".format(device_id))
1449*90c8c64dSAndroid Build Coastguard Worker        out, err = process.communicate(dump_commands.encode('utf-8'))
1450*90c8c64dSAndroid Build Coastguard Worker        if process.returncode != 0:
1451*90c8c64dSAndroid Build Coastguard Worker            raise AdbError("Error executing dump command." + "\n\n### OUTPUT ###" + out.decode('utf-8') + "\n"
1452*90c8c64dSAndroid Build Coastguard Worker                           + err.decode('utf-8'))
1453*90c8c64dSAndroid Build Coastguard Worker        log.info("Dump finished on device {}".format(device_id))
1454*90c8c64dSAndroid Build Coastguard Worker
1455*90c8c64dSAndroid Build Coastguard Worker        for target, config in dump_targets:
1456*90c8c64dSAndroid Build Coastguard Worker            if config:
1457*90c8c64dSAndroid Build Coastguard Worker                trace_identifiers = config.get_trace_identifiers()
1458*90c8c64dSAndroid Build Coastguard Worker                for trace_identifier in trace_identifiers:
1459*90c8c64dSAndroid Build Coastguard Worker                    move_collected_files(target.files, device_id, trace_identifier)
1460*90c8c64dSAndroid Build Coastguard Worker            else:
1461*90c8c64dSAndroid Build Coastguard Worker                move_collected_files(target.files, device_id, "")
1462*90c8c64dSAndroid Build Coastguard Worker
1463*90c8c64dSAndroid Build Coastguard Worker        server.respond(HTTPStatus.OK, json.dumps(warnings).encode("utf-8"), "text/plain")
1464*90c8c64dSAndroid Build Coastguard Worker
1465*90c8c64dSAndroid Build Coastguard Worker
1466*90c8c64dSAndroid Build Coastguard Workerclass ADBWinscopeProxy(BaseHTTPRequestHandler):
1467*90c8c64dSAndroid Build Coastguard Worker    def __init__(self, request, client_address, server):
1468*90c8c64dSAndroid Build Coastguard Worker        self.router = RequestRouter(self)
1469*90c8c64dSAndroid Build Coastguard Worker        listDevicesEndpoint = ListDevicesEndpoint()
1470*90c8c64dSAndroid Build Coastguard Worker        self.router.register_endpoint(
1471*90c8c64dSAndroid Build Coastguard Worker            RequestType.GET, "devices", listDevicesEndpoint)
1472*90c8c64dSAndroid Build Coastguard Worker        self.router.register_endpoint(
1473*90c8c64dSAndroid Build Coastguard Worker            RequestType.GET, "status", StatusEndpoint())
1474*90c8c64dSAndroid Build Coastguard Worker        self.router.register_endpoint(
1475*90c8c64dSAndroid Build Coastguard Worker            RequestType.GET, "fetch", FetchFilesEndpoint())
1476*90c8c64dSAndroid Build Coastguard Worker        self.router.register_endpoint(RequestType.POST, "start", StartTraceEndpoint())
1477*90c8c64dSAndroid Build Coastguard Worker        self.router.register_endpoint(RequestType.POST, "end", EndTraceEndpoint())
1478*90c8c64dSAndroid Build Coastguard Worker        self.router.register_endpoint(RequestType.POST, "dump", DumpEndpoint())
1479*90c8c64dSAndroid Build Coastguard Worker        self.router.register_endpoint(
1480*90c8c64dSAndroid Build Coastguard Worker            RequestType.GET, "checkwayland", CheckWaylandServiceEndpoint(listDevicesEndpoint))
1481*90c8c64dSAndroid Build Coastguard Worker        super().__init__(request, client_address, server)
1482*90c8c64dSAndroid Build Coastguard Worker
1483*90c8c64dSAndroid Build Coastguard Worker    def respond(self, code: int, data: bytes, mime: str) -> None:
1484*90c8c64dSAndroid Build Coastguard Worker        self.send_response(code)
1485*90c8c64dSAndroid Build Coastguard Worker        self.send_header('Content-type', mime)
1486*90c8c64dSAndroid Build Coastguard Worker        add_standard_headers(self)
1487*90c8c64dSAndroid Build Coastguard Worker        self.wfile.write(data)
1488*90c8c64dSAndroid Build Coastguard Worker
1489*90c8c64dSAndroid Build Coastguard Worker    def do_GET(self):
1490*90c8c64dSAndroid Build Coastguard Worker        self.router.process(RequestType.GET)
1491*90c8c64dSAndroid Build Coastguard Worker
1492*90c8c64dSAndroid Build Coastguard Worker    def do_POST(self):
1493*90c8c64dSAndroid Build Coastguard Worker        self.router.process(RequestType.POST)
1494*90c8c64dSAndroid Build Coastguard Worker
1495*90c8c64dSAndroid Build Coastguard Worker    def do_OPTIONS(self):
1496*90c8c64dSAndroid Build Coastguard Worker        self.send_response(HTTPStatus.OK)
1497*90c8c64dSAndroid Build Coastguard Worker        self.send_header('Allow', 'GET,POST')
1498*90c8c64dSAndroid Build Coastguard Worker        add_standard_headers(self)
1499*90c8c64dSAndroid Build Coastguard Worker        self.end_headers()
1500*90c8c64dSAndroid Build Coastguard Worker        self.wfile.write(b'GET,POST')
1501*90c8c64dSAndroid Build Coastguard Worker
1502*90c8c64dSAndroid Build Coastguard Worker    def log_request(self, code='-', size='-'):
1503*90c8c64dSAndroid Build Coastguard Worker        log.info('{} {} {}'.format(self.requestline, str(code), str(size)))
1504*90c8c64dSAndroid Build Coastguard Worker
1505*90c8c64dSAndroid Build Coastguard Worker
1506*90c8c64dSAndroid Build Coastguard Workerif __name__ == '__main__':
1507*90c8c64dSAndroid Build Coastguard Worker    args = create_argument_parser().parse_args()
1508*90c8c64dSAndroid Build Coastguard Worker
1509*90c8c64dSAndroid Build Coastguard Worker    logging.basicConfig(stream=sys.stderr, level=args.loglevel,
1510*90c8c64dSAndroid Build Coastguard Worker                        format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
1511*90c8c64dSAndroid Build Coastguard Worker
1512*90c8c64dSAndroid Build Coastguard Worker    log = logging.getLogger("ADBProxy")
1513*90c8c64dSAndroid Build Coastguard Worker    secret_token = get_token()
1514*90c8c64dSAndroid Build Coastguard Worker
1515*90c8c64dSAndroid Build Coastguard Worker    print("Winscope ADB Connect proxy version: " + VERSION)
1516*90c8c64dSAndroid Build Coastguard Worker    print('Winscope token: ' + secret_token)
1517*90c8c64dSAndroid Build Coastguard Worker
1518*90c8c64dSAndroid Build Coastguard Worker    httpd = HTTPServer(('localhost', args.port), ADBWinscopeProxy)
1519*90c8c64dSAndroid Build Coastguard Worker    try:
1520*90c8c64dSAndroid Build Coastguard Worker        httpd.serve_forever()
1521*90c8c64dSAndroid Build Coastguard Worker    except KeyboardInterrupt:
1522*90c8c64dSAndroid Build Coastguard Worker        log.info("Shutting down")
1523