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