#!/usr/bin/env python3 # Copyright (C) 2021 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! # DO NOT EDIT. Auto-generated by tools/gen_amalgamated_python_tools # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! import atexit import argparse import datetime import hashlib import http.server import os import re import shutil import signal import socketserver import subprocess import sys import time import webbrowser # ----- Amalgamator: begin of python/perfetto/prebuilts/manifests/tracebox.py # This file has been generated by: tools/roll-prebuilts v48.1 TRACEBOX_MANIFEST = [{ 'arch': 'mac-amd64', 'file_name': 'tracebox', 'file_size': 1613864, 'url': 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/mac-amd64/tracebox', 'sha256': 'dfb1a3affe905d2e7d1f82bc4dda46b1fda6db054d60ae87c3215dd529b77fee', 'platform': 'darwin', 'machine': ['x86_64'] }, { 'arch': 'mac-arm64', 'file_name': 'tracebox', 'file_size': 1492184, 'url': 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/mac-arm64/tracebox', 'sha256': '4a492a629dd1f13f3146c4b8267c0b163afba8cef1d49e0c00c48bb727496066', 'platform': 'darwin', 'machine': ['arm64'] }, { 'arch': 'linux-amd64', 'file_name': 'tracebox', 'file_size': 2380040, 'url': 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-amd64/tracebox', 'sha256': 'd70b284e8c28858fd539ae61ca59764d7f9fd6232073c304926e892fe75e692a', 'platform': 'linux', 'machine': ['x86_64'] }, { 'arch': 'linux-arm', 'file_name': 'tracebox', 'file_size': 1450708, 'url': 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-arm/tracebox', 'sha256': '178fa6a1a9bc80f72d81938d40fe201c25c595ffaff7e030d59c2af09dfcc06c', 'platform': 'linux', 'machine': ['armv6l', 'armv7l', 'armv8l'] }, { 'arch': 'linux-arm64', 'file_name': 'tracebox', 'file_size': 2269816, 'url': 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/linux-arm64/tracebox', 'sha256': '42c64f9807756aaa08a2bfa13e9e4828c193a6b90ba1329408873c3ebf5adf3f', 'platform': 'linux', 'machine': ['aarch64'] }, { 'arch': 'android-arm', 'file_name': 'tracebox', 'file_size': 1333336, 'url': 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-arm/tracebox', 'sha256': '93a78d2c42e3c00f117e2f155326383f69c891281ed693a39d87b8cb54ca4e19' }, { 'arch': 'android-arm64', 'file_name': 'tracebox', 'file_size': 2115984, 'url': 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-arm64/tracebox', 'sha256': '508248a9e47ab605fd742efb700391d7267b68b586199a93e13e6ca14b72fe3d' }, { 'arch': 'android-x86', 'file_name': 'tracebox', 'file_size': 2302960, 'url': 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-x86/tracebox', 'sha256': '63d20a69c4e0c291329d7917e640fa0d4f146c344e79988e87393b1431d594b1' }, { 'arch': 'android-x64', 'file_name': 'tracebox', 'file_size': 2147880, 'url': 'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v48.1/android-x64/tracebox', 'sha256': 'c0ea1d5fd6d020e4c2b45d4d45cdd0c44ae63cd755d69260a3e5d2bacd3cbd6a' }] # ----- Amalgamator: end of python/perfetto/prebuilts/manifests/tracebox.py # ----- Amalgamator: begin of python/perfetto/prebuilts/perfetto_prebuilts.py # Copyright (C) 2021 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """ Functions to fetch pre-pinned Perfetto prebuilts. This function is used in different places: - Into the //tools/{trace_processor, traceconv} scripts, which are just plain wrappers around executables. - Into the //tools/{heap_profiler, record_android_trace} scripts, which contain some other hand-written python code. The manifest argument looks as follows: TRACECONV_MANIFEST = [ { 'arch': 'mac-amd64', 'file_name': 'traceconv', 'file_size': 7087080, 'url': https://commondatastorage.googleapis.com/.../trace_to_text', 'sha256': 7d957c005b0dc130f5bd855d6cec27e060d38841b320d04840afc569f9087490', 'platform': 'darwin', 'machine': 'x86_64' }, ... ] The intended usage is: from perfetto.prebuilts.manifests.traceconv import TRACECONV_MANIFEST bin_path = get_perfetto_prebuilt(TRACECONV_MANIFEST) subprocess.call(bin_path, ...) """ import hashlib import os import platform import random import subprocess import sys def download_or_get_cached(file_name, url, sha256): """ Downloads a prebuilt or returns a cached version The first time this is invoked, it downloads the |url| and caches it into ~/.local/share/perfetto/prebuilts/$tool_name. On subsequent invocations it just runs the cached version. """ dir = os.path.join( os.path.expanduser('~'), '.local', 'share', 'perfetto', 'prebuilts') os.makedirs(dir, exist_ok=True) bin_path = os.path.join(dir, file_name) sha256_path = os.path.join(dir, file_name + '.sha256') needs_download = True # Avoid recomputing the SHA-256 on each invocation. The SHA-256 of the last # download is cached into file_name.sha256, just check if that matches. if os.path.exists(bin_path) and os.path.exists(sha256_path): with open(sha256_path, 'rb') as f: digest = f.read().decode() if digest == sha256: needs_download = False if needs_download: # The file doesn't exist or the SHA256 doesn't match. # Use a unique random file to guard against concurrent executions. # See https://github.com/google/perfetto/issues/786 . tmp_path = '%s.%d.tmp' % (bin_path, random.randint(0, 100000)) print('Downloading ' + url) subprocess.check_call(['curl', '-f', '-L', '-#', '-o', tmp_path, url]) with open(tmp_path, 'rb') as fd: actual_sha256 = hashlib.sha256(fd.read()).hexdigest() if actual_sha256 != sha256: raise Exception('Checksum mismatch for %s (actual: %s, expected: %s)' % (url, actual_sha256, sha256)) os.chmod(tmp_path, 0o755) os.replace(tmp_path, bin_path) with open(tmp_path, 'w') as f: f.write(sha256) os.replace(tmp_path, sha256_path) return bin_path def get_perfetto_prebuilt(manifest, soft_fail=False, arch=None): """ Downloads the prebuilt, if necessary, and returns its path on disk. """ plat = sys.platform.lower() machine = platform.machine().lower() manifest_entry = None for entry in manifest: # If the caller overrides the arch, just match that (for Android prebuilts). if arch: if entry.get('arch') == arch: manifest_entry = entry break continue # Otherwise guess the local machine arch. if entry.get('platform') == plat and machine in entry.get('machine', []): manifest_entry = entry break if manifest_entry is None: if soft_fail: return None raise Exception( ('No prebuilts available for %s-%s\n' % (plat, machine)) + 'See https://perfetto.dev/docs/contributing/build-instructions') return download_or_get_cached( file_name=manifest_entry['file_name'], url=manifest_entry['url'], sha256=manifest_entry['sha256']) def run_perfetto_prebuilt(manifest): bin_path = get_perfetto_prebuilt(manifest) if sys.platform.lower() == 'win32': sys.exit(subprocess.check_call([bin_path, *sys.argv[1:]])) os.execv(bin_path, [bin_path] + sys.argv[1:]) # ----- Amalgamator: end of python/perfetto/prebuilts/perfetto_prebuilts.py # ----- Amalgamator: begin of python/perfetto/common/repo_utils.py # Copyright (C) 2021 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os def repo_root(): """ Finds the repo root by traversing up the hierarchy This is for use in scripts that get amalgamated, where _file_ can be either python/perfetto/... or tools/amalgamated_tool. """ path = os.path.dirname(os.path.abspath(__file__)) # amalgamator:nocheck last_dir = '' while path and path != last_dir: if os.path.exists(os.path.join(path, 'perfetto.rc')): return path last_dir = path path = os.path.dirname(path) return None def repo_dir(rel_path): return os.path.join(repo_root() or '', rel_path) # ----- Amalgamator: end of python/perfetto/common/repo_utils.py # This is not required. It's only used as a fallback if no adb is found on the # PATH. It's fine if it doesn't exist so this script can be copied elsewhere. HERMETIC_ADB_PATH = repo_dir('buildtools/android_sdk/platform-tools/adb') # Translates the Android ro.product.cpu.abi into the GN's target_cpu. ABI_TO_ARCH = { 'armeabi-v7a': 'arm', 'arm64-v8a': 'arm64', 'x86': 'x86', 'x86_64': 'x64', } MAX_ADB_FAILURES = 15 # 2 seconds between retries, 30 seconds total. devnull = open(os.devnull, 'rb') adb_path = None procs = [] class ANSI: END = '\033[0m' BOLD = '\033[1m' RED = '\033[91m' BLACK = '\033[30m' BLUE = '\033[94m' BG_YELLOW = '\033[43m' BG_BLUE = '\033[44m' # HTTP Server used to open the trace in the browser. class HttpHandler(http.server.SimpleHTTPRequestHandler): def end_headers(self): self.send_header('Access-Control-Allow-Origin', self.server.allow_origin) self.send_header('Cache-Control', 'no-cache') super().end_headers() def do_GET(self): if self.path != '/' + self.server.expected_fname: self.send_error(404, "File not found") return self.server.fname_get_completed = True super().do_GET() def do_POST(self): self.send_error(404, "File not found") def setup_arguments(): atexit.register(kill_all_subprocs_on_exit) default_out_dir_str = '~/traces/' default_out_dir = os.path.expanduser(default_out_dir_str) examples = '\n'.join([ ANSI.BOLD + 'Examples' + ANSI.END, ' -t 10s -b 32mb sched gfx wm -a*', ' -t 5s sched/sched_switch raw_syscalls/sys_enter raw_syscalls/sys_exit', ' -c /path/to/full-textual-trace.config', '', ANSI.BOLD + 'Long traces' + ANSI.END, 'If you want to record a hours long trace and stream it into a file ', 'you need to pass a full trace config and set write_into_file = true.', 'See https://perfetto.dev/docs/concepts/config#long-traces .' ]) parser = argparse.ArgumentParser( epilog=examples, formatter_class=argparse.RawTextHelpFormatter) help = 'Output file or directory (default: %s)' % default_out_dir_str parser.add_argument('-o', '--out', default=default_out_dir, help=help) help = 'Don\'t open or serve the trace' parser.add_argument('-n', '--no-open', action='store_true', help=help) help = 'Don\'t open in browser, but still serve trace (good for remote use)' parser.add_argument('--no-open-browser', action='store_true', help=help) help = 'The web address used to open trace files' parser.add_argument('--origin', default='https://ui.perfetto.dev', help=help) help = 'Force the use of the sideloaded binaries rather than system daemons' parser.add_argument('--sideload', action='store_true', help=help) help = ('Sideload the given binary rather than downloading it. ' + 'Implies --sideload') parser.add_argument('--sideload-path', default=None, help=help) help = 'Ignores any tracing guardrails which might be used' parser.add_argument('--no-guardrails', action='store_true', help=help) help = 'Don\'t run `adb root` run as user (only when sideloading)' parser.add_argument('-u', '--user', action='store_true', help=help) help = 'Specify the ADB device serial' parser.add_argument('--serial', '-s', default=None, help=help) grp = parser.add_argument_group( 'Short options: (only when not using -c/--config)') help = 'Trace duration N[s,m,h] (default: trace until stopped)' grp.add_argument('-t', '--time', default='0s', help=help) help = 'Ring buffer size N[mb,gb] (default: 32mb)' grp.add_argument('-b', '--buffer', default='32mb', help=help) help = ('Android (atrace) app names. Can be specified multiple times.\n-a*' + 'for all apps (without space between a and * or bash will expand it)') grp.add_argument( '-a', '--app', metavar='com.myapp', action='append', default=[], help=help) help = 'sched, gfx, am, wm (see --list)' grp.add_argument('events', metavar='Atrace events', nargs='*', help=help) help = 'sched/sched_switch kmem/kmem (see --list-ftrace)' grp.add_argument('_', metavar='Ftrace events', nargs='*', help=help) help = 'Lists all the categories available' grp.add_argument('--list', action='store_true', help=help) help = 'Lists all the ftrace events available' grp.add_argument('--list-ftrace', action='store_true', help=help) section = ('Full trace config (only when not using short options)') grp = parser.add_argument_group(section) help = 'Can be generated with https://ui.perfetto.dev/#!/record' grp.add_argument('-c', '--config', default=None, help=help) help = 'Parse input from --config as binary proto (default: parse as text)' grp.add_argument('--bin', action='store_true', help=help) help = ('Pass the trace through the trace reporter API. Only works when ' 'using the full trace config (-c) with the reporter package name ' "'android.perfetto.cts.reporter' and the reporter class name " "'android.perfetto.cts.reporter.PerfettoReportService' with the " 'reporter installed on the device (see ' 'tools/install_test_reporter_app.py).') grp.add_argument('--reporter-api', action='store_true', help=help) args = parser.parse_args() args.sideload = args.sideload or args.sideload_path is not None if args.serial: os.environ["ANDROID_SERIAL"] = args.serial find_adb() if args.list: adb('shell', 'atrace', '--list_categories').wait() sys.exit(0) if args.list_ftrace: adb('shell', 'cat /d/tracing/available_events | tr : /').wait() sys.exit(0) if args.config is not None and not os.path.exists(args.config): prt('Config file not found: %s' % args.config, ANSI.RED) sys.exit(1) if len(args.events) == 0 and args.config is None: prt('Must either pass short options (e.g. -t 10s sched) or a --config file', ANSI.RED) parser.print_help() sys.exit(1) if args.config is None and args.events and os.path.exists(args.events[0]): prt(('The passed event name "%s" is a local file. ' % args.events[0] + 'Did you mean to pass -c / --config ?'), ANSI.RED) sys.exit(1) if args.reporter_api and not args.config: prt('Must pass --config when using --reporter-api', ANSI.RED) parser.print_help() sys.exit(1) return args class SignalException(Exception): pass def signal_handler(sig, frame): raise SignalException('Received signal ' + str(sig)) signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) def start_trace(args, print_log=True): perfetto_cmd = 'perfetto' device_dir = '/data/misc/perfetto-traces/' # Check the version of android. If too old (< Q) sideload tracebox. Also use # use /data/local/tmp as /data/misc/perfetto-traces was introduced only later. probe_cmd = 'getprop ro.build.version.sdk; getprop ro.product.cpu.abi; whoami' probe = adb('shell', probe_cmd, stdout=subprocess.PIPE) lines = probe.communicate()[0].decode().strip().split('\n') lines = [x.strip() for x in lines] # To strip \r(s) on Windows. if probe.returncode != 0: prt('ADB connection failed', ANSI.RED) sys.exit(1) api_level = int(lines[0]) abi = lines[1] arch = ABI_TO_ARCH.get(abi) if arch is None: prt('Unsupported ABI: ' + abi) sys.exit(1) shell_user = lines[2] if api_level < 29 or args.sideload: # 29: Android Q. tracebox_bin = args.sideload_path if tracebox_bin is None: tracebox_bin = get_perfetto_prebuilt( TRACEBOX_MANIFEST, arch='android-' + arch) perfetto_cmd = '/data/local/tmp/tracebox' exit_code = adb('push', '--sync', tracebox_bin, perfetto_cmd).wait() exit_code |= adb('shell', 'chmod 755 ' + perfetto_cmd).wait() if exit_code != 0: prt('ADB push failed', ANSI.RED) sys.exit(1) device_dir = '/data/local/tmp/' if shell_user != 'root' and not args.user: # Run as root if possible as that will give access to more tracing # capabilities. Non-root still works, but some ftrace events might not be # available. adb('root').wait() tstamp = datetime.datetime.now().strftime('%Y-%m-%d_%H-%M') fname = '%s-%s.pftrace' % (tstamp, os.urandom(3).hex()) device_file = device_dir + fname cmd = [perfetto_cmd, '--background'] if not args.bin: cmd.append('--txt') if args.no_guardrails: cmd.append('--no-guardrails') if args.reporter_api: # Remove all old reporter files to avoid polluting the file we will extract # later. adb('shell', 'rm /sdcard/Android/data/android.perfetto.cts.reporter/files/*').wait() cmd.append('--upload') else: cmd.extend(['-o', device_file]) on_device_config = None on_host_config = None if args.config is not None: cmd += ['-c', '-'] if api_level < 24: # adb shell does not redirect stdin. Push the config on a temporary file # on the device. mktmp = adb( 'shell', 'mktemp', '--tmpdir', '/data/local/tmp', stdout=subprocess.PIPE) on_device_config = mktmp.communicate()[0].decode().strip().strip() if mktmp.returncode != 0: prt('Failed to create config on device', ANSI.RED) sys.exit(1) exit_code = adb('push', '--sync', args.config, on_device_config).wait() if exit_code != 0: prt('Failed to push config on device', ANSI.RED) sys.exit(1) cmd = ['cat', on_device_config, '|'] + cmd else: on_host_config = args.config else: cmd += ['-t', args.time, '-b', args.buffer] for app in args.app: cmd += ['--app', '\'' + app + '\''] cmd += args.events # Work out the output file or directory. if args.out.endswith('/') or os.path.isdir(args.out): host_dir = args.out host_file = os.path.join(args.out, fname) else: host_file = args.out host_dir = os.path.dirname(host_file) if host_dir == '': host_dir = '.' host_file = './' + host_file if not os.path.exists(host_dir): shutil.os.makedirs(host_dir) with open(on_host_config or os.devnull, 'rb') as f: if print_log: print('Running ' + ' '.join(cmd)) proc = adb('shell', *cmd, stdin=f, stdout=subprocess.PIPE) proc_out = proc.communicate()[0].decode().strip() if on_device_config is not None: adb('shell', 'rm', on_device_config).wait() # On older versions of Android (x86_64 emulator running API 22) the output # looks like: # WARNING: linker: /data/local/tmp/tracebox: unused DT entry: ... # WARNING: ... (other 2 WARNING: linker: lines) # 1234 <-- The actual pid we want. match = re.search(r'^(\d+)$', proc_out, re.M) if match is None: prt('Failed to read the pid from perfetto --background', ANSI.RED) prt(proc_out) sys.exit(1) bg_pid = match.group(1) exit_code = proc.wait() if exit_code != 0: prt('Perfetto invocation failed', ANSI.RED) sys.exit(1) prt('Trace started. Press CTRL+C to stop', ANSI.BLACK + ANSI.BG_BLUE) log_level = "-v" if not print_log: log_level = "-e" logcat = adb('logcat', log_level, 'brief', '-s', 'perfetto', '-b', 'main', '-T', '1') ctrl_c_count = 0 adb_failure_count = 0 while ctrl_c_count < 2: try: # On older Android devices adbd doesn't propagate the exit code. Hence # the RUN/TERM parts. poll = adb( 'shell', 'test -d /proc/%s && echo RUN || echo TERM' % bg_pid, stdout=subprocess.PIPE) poll_res = poll.communicate()[0].decode().strip() if poll_res == 'TERM': break # Process terminated if poll_res == 'RUN': # The 'perfetto' cmdline client is still running. If previously we had # an ADB error, tell the user now it's all right again. if adb_failure_count > 0: adb_failure_count = 0 prt('ADB connection re-established, the trace is still ongoing', ANSI.BLUE) time.sleep(0.5) continue # Some ADB error happened. This can happen when tracing soon after boot, # before logging in, when adb gets restarted. adb_failure_count += 1 if adb_failure_count >= MAX_ADB_FAILURES: prt('Too many unrecoverable ADB failures, bailing out', ANSI.RED) sys.exit(1) time.sleep(2) except (KeyboardInterrupt, SignalException): sig = 'TERM' if ctrl_c_count == 0 else 'KILL' ctrl_c_count += 1 if print_log: prt('Stopping the trace (SIG%s)' % sig, ANSI.BLACK + ANSI.BG_YELLOW) adb('shell', 'kill -%s %s' % (sig, bg_pid)).wait() logcat.kill() logcat.wait() if args.reporter_api: if print_log: prt('Waiting a few seconds to allow reporter to copy trace') time.sleep(5) ret = adb( 'shell', 'cp /sdcard/Android/data/android.perfetto.cts.reporter/files/* ' + device_file).wait() if ret != 0: prt('Failed to extract reporter trace', ANSI.RED) sys.exit(1) if print_log: prt('\n') prt('Pulling into %s' % host_file, ANSI.BOLD) adb('pull', device_file, host_file).wait() adb('shell', 'rm -f ' + device_file).wait() if not args.no_open: if print_log: prt('\n') prt('Opening the trace (%s) in the browser' % host_file) open_browser = not args.no_open_browser open_trace_in_browser(host_file, open_browser, args.origin) return host_file def main(): args = setup_arguments() start_trace(args) def prt(msg, colors=ANSI.END): print(colors + msg + ANSI.END) def find_adb(): """ Locate the "right" adb path If adb is in the PATH use that (likely what the user wants) otherwise use the hermetic one in our SDK copy. """ global adb_path for path in ['adb', HERMETIC_ADB_PATH]: try: subprocess.call([path, '--version'], stdout=devnull, stderr=devnull) adb_path = path break except OSError: continue if adb_path is None: sdk_url = 'https://developer.android.com/studio/releases/platform-tools' prt('Could not find a suitable adb binary in the PATH. ', ANSI.RED) prt('You can download adb from %s' % sdk_url, ANSI.RED) sys.exit(1) def open_trace_in_browser(path, open_browser, origin): # We reuse the HTTP+RPC port because it's the only one allowed by the CSP. PORT = 9001 path = os.path.abspath(path) os.chdir(os.path.dirname(path)) fname = os.path.basename(path) socketserver.TCPServer.allow_reuse_address = True with socketserver.TCPServer(('127.0.0.1', PORT), HttpHandler) as httpd: address = f'{origin}/#!/?url=http://127.0.0.1:{PORT}/{fname}&referrer=record_android_trace' if open_browser: webbrowser.open_new_tab(address) else: print(f'Open URL in browser: {address}') httpd.expected_fname = fname httpd.fname_get_completed = None httpd.allow_origin = origin while httpd.fname_get_completed is None: httpd.handle_request() def adb(*args, stdin=devnull, stdout=None, stderr=None): cmd = [adb_path, *args] setpgrp = None if os.name != 'nt': # On Linux/Mac, start a new process group so all child processes are killed # on exit. Unsupported on Windows. setpgrp = lambda: os.setpgrp() proc = subprocess.Popen( cmd, stdin=stdin, stdout=stdout, stderr=stderr, preexec_fn=setpgrp) procs.append(proc) return proc def kill_all_subprocs_on_exit(): for p in [p for p in procs if p.poll() is None]: p.kill() def check_hash(file_name, sha_value): with open(file_name, 'rb') as fd: file_hash = hashlib.sha1(fd.read()).hexdigest() return file_hash == sha_value if __name__ == '__main__': sys.exit(main())