• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#
2# Copyright (C) 2024 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#      http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16# Implementation taken from external/perfetto/tools/record_android_trace.
17#
18
19import webbrowser
20import socketserver
21import http.server
22import os
23import subprocess
24from .handle_input import HandleInput
25from .utils import path_exists, wait_for_process_or_ctrl_c, wait_for_output
26from .validation_error import ValidationError
27
28TORQ_TEMP_DIR = "/tmp/.torq"
29TRACE_PROCESSOR_BINARY = "/trace_processor"
30TORQ_TEMP_TRACE_PROCESSOR = TORQ_TEMP_DIR + TRACE_PROCESSOR_BINARY
31ANDROID_PERFETTO_TOOLS_DIR = "/external/perfetto/tools"
32ANDROID_TRACE_PROCESSOR = ANDROID_PERFETTO_TOOLS_DIR + TRACE_PROCESSOR_BINARY
33LARGE_FILE_SIZE = 1024 * 1024 * 512  # 512 MB
34WAIT_FOR_TRACE_PROCESSOR_MS = 3000
35
36
37class HttpHandler(http.server.SimpleHTTPRequestHandler):
38
39  def end_headers(self):
40    self.send_header("Access-Control-Allow-Origin", self.server.allow_origin)
41    self.send_header("Cache-Control", "no-cache")
42    super().end_headers()
43
44  def do_GET(self):
45    if self.path != "/" + self.server.expected_fname:
46      self.send_error(404, "File not found")
47      return
48    self.server.fname_get_completed = True
49    super().do_GET()
50
51  def do_POST(self):
52    self.send_error(404, "File not found")
53
54  def log_message(self, format, *args):
55    pass
56
57def download_trace_processor(path):
58  if (("ANDROID_BUILD_TOP" in os.environ and
59       path_exists(os.environ["ANDROID_BUILD_TOP"] + ANDROID_TRACE_PROCESSOR))):
60    return os.environ["ANDROID_BUILD_TOP"] + ANDROID_TRACE_PROCESSOR
61  if path_exists(TORQ_TEMP_TRACE_PROCESSOR):
62    return TORQ_TEMP_TRACE_PROCESSOR
63
64  def download_accepted_callback():
65    subprocess.run(("mkdir -p %s && wget -P %s "
66                    "https://get.perfetto.dev/trace_processor && chmod +x "
67                    "%s/trace_processor"
68                    % (TORQ_TEMP_DIR, TORQ_TEMP_DIR, TORQ_TEMP_DIR)),
69                   shell=True)
70
71    if not path_exists(TORQ_TEMP_TRACE_PROCESSOR):
72      print("Could not download perfetto scripts. Continuing.")
73      return None
74
75    return TORQ_TEMP_TRACE_PROCESSOR
76
77  def rejected_callback():
78    print("Will continue without downloading perfetto scripts.")
79    return None
80
81  return (HandleInput("You do not have $ANDROID_BUILD_TOP configured "
82                     "with the $ANDROID_BUILD_TOP%s directory.\nYour "
83                     "perfetto trace is larger than 512MB, so "
84                     "attempting to load the trace in the perfetto UI "
85                     "without the perfetto scripts might not work.\n"
86                     "torq can download the perfetto scripts to '%s'. "
87                     "Are you ok with this download? [Y/N]: "
88                     % (ANDROID_PERFETTO_TOOLS_DIR, TORQ_TEMP_DIR),
89                     "Please accept or reject the download.",
90                     {"y": download_accepted_callback,
91                      "n": rejected_callback})
92          .handle_input())
93
94def open_trace(path, origin, use_trace_processor):
95  PORT = 9001
96  path = os.path.abspath(path)
97  trace_processor_path = None
98  if os.path.getsize(path) >= LARGE_FILE_SIZE or use_trace_processor:
99    trace_processor_path = download_trace_processor(path)
100  if isinstance(trace_processor_path, ValidationError):
101    return trace_processor_path
102  if trace_processor_path is not None:
103    process = subprocess.Popen("%s --httpd %s" % (trace_processor_path, path),
104                               shell=True, stdout=subprocess.PIPE,
105                               stderr=subprocess.STDOUT)
106    print("\033[93m##### Loading trace. #####")
107    if wait_for_output("Trace loaded", process,
108                       WAIT_FOR_TRACE_PROCESSOR_MS):
109      process.kill()
110      return ValidationError("Trace took too long to load.",
111                             "Please try again.")
112    webbrowser.open_new_tab(origin)
113    print("##### Follow the directions in the Perfetto UI. Do not "
114          "exit out of torq until you are done viewing the trace. Press "
115          "CTRL+C to exit torq and close the trace_processor. #####\033[0m")
116    wait_for_process_or_ctrl_c(process)
117  else: # Open trace directly in UI
118    os.chdir(os.path.dirname(path))
119    fname = os.path.basename(path)
120    socketserver.TCPServer.allow_reuse_address = True
121    with (socketserver.TCPServer(("127.0.0.1", PORT), HttpHandler)
122          as httpd):
123      address = (f"{origin}/#!/?url=http://127.0.0.1:"
124                 f"{PORT}/{fname}&referrer=open_trace_in_ui")
125      webbrowser.open_new_tab(address)
126      httpd.expected_fname = fname
127      httpd.fname_get_completed = None
128      httpd.allow_origin = origin
129      while httpd.fname_get_completed is None:
130        httpd.handle_request()
131  return None
132