1#!/usr/bin/env python3 2# Copyright (C) 2022 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 16import contextlib 17import datetime 18from datetime import timezone 19import os 20import socket 21import stat 22import subprocess 23import tempfile 24from typing import Tuple 25from urllib import request 26 27from perfetto.trace_uri_resolver.path import PathUriResolver 28from perfetto.trace_uri_resolver.registry import ResolverRegistry 29 30# URL to download script to run trace_processor 31SHELL_URL = 'https://get.perfetto.dev/trace_processor' 32 33 34class PlatformDelegate: 35 """Abstracts operations which can vary based on platform.""" 36 37 def get_resource(self, file: str) -> bytes: 38 ws = os.path.dirname(__file__) 39 with open(os.path.join(ws, file), 'rb') as x: 40 return x.read() 41 42 def get_shell_path(self, bin_path: str) -> str: 43 if bin_path is not None: 44 if not os.path.isfile(bin_path): 45 raise Exception('Path to binary is not valid') 46 return bin_path 47 48 tp_path = os.path.join(tempfile.gettempdir(), 'trace_processor_python_api') 49 if self._should_download_tp(tp_path): 50 with contextlib.ExitStack() as stack: 51 req = stack.enter_context(request.urlopen(request.Request(SHELL_URL))) 52 file = stack.enter_context(open(tp_path, 'wb')) 53 file.write(req.read()) 54 st = os.stat(tp_path) 55 os.chmod(tp_path, st.st_mode | stat.S_IEXEC) 56 return tp_path 57 58 def _should_download_tp(self, tp_path): 59 try: 60 st = os.stat(tp_path) 61 62 # If the file was empty (i.e. failed to be written properly last time), 63 # download it. 64 if st.st_size == 0: 65 return True 66 67 # Try and redownload if we last modified this file more than 7 days 68 # ago. 69 mod_time = datetime.datetime.fromtimestamp(st.st_mtime, tz=timezone.utc) 70 cutoff = datetime.datetime.now().astimezone() - datetime.timedelta(days=7) 71 return mod_time < cutoff 72 except OSError: 73 # Should happen if the file does not exist (i.e. this function has not 74 # been run before or tmp was cleared). 75 return True 76 77 def get_bind_addr(self, port: int) -> Tuple[str, int]: 78 if port: 79 return 'localhost', port 80 81 free_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 82 free_socket.bind(('', 0)) 83 free_socket.listen(5) 84 port = free_socket.getsockname()[1] 85 free_socket.close() 86 return 'localhost', port 87 88 def default_resolver_registry(self) -> ResolverRegistry: 89 return ResolverRegistry(resolvers=[PathUriResolver]) 90