1#!/usr/bin/env python3 2# Copyright (C) 2021 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 argparse 17import os 18import re 19import signal 20import sys 21import subprocess 22 23import psutil 24 25ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 26 27REGEX = re.compile( 28 '.*Trace loaded: ([0-9.]+) MB in ([0-9.]+)s \(([0-9.]+) MB/s\)') 29 30 31def run_tp_until_ingestion(args, env): 32 tp_args = [os.path.join(args.out, 'trace_processor_shell'), args.trace_file] 33 if not args.ftrace_raw: 34 tp_args.append('--no-ftrace-raw') 35 tp = subprocess.Popen( 36 tp_args, 37 stdin=subprocess.PIPE, 38 stdout=None if args.verbose else subprocess.DEVNULL, 39 stderr=subprocess.PIPE, 40 universal_newlines=True, 41 env=env) 42 43 lines = [] 44 while True: 45 line = tp.stderr.readline() 46 if args.verbose: 47 sys.stderr.write(line) 48 lines.append(line) 49 50 match = REGEX.match(line) 51 if match: 52 break 53 54 if tp.poll(): 55 break 56 57 ret = tp.poll() 58 fail = ret is not None and ret > 0 59 if fail: 60 print("Failed") 61 for line in lines: 62 sys.stderr.write(line) 63 return tp, fail, match[2] 64 65 66def heap_profile_run(args, dump_at_max: bool): 67 profile_args = [ 68 os.path.join(ROOT_DIR, 'tools', 'heap_profile'), '-i', '1', '-n', 69 'trace_processor_shell', '--print-config' 70 ] 71 if dump_at_max: 72 profile_args.append('--dump-at-max') 73 config = subprocess.check_output( 74 profile_args, 75 stderr=subprocess.DEVNULL, 76 ) 77 78 out_file = os.path.join( 79 args.result, args.result_prefix + ('max' if dump_at_max else 'rest')) 80 perfetto_args = [ 81 os.path.join(args.out, 'perfetto'), '-c', '-', '--txt', '-o', out_file 82 ] 83 profile = subprocess.Popen( 84 perfetto_args, 85 stdin=subprocess.PIPE, 86 stdout=None if args.verbose else subprocess.DEVNULL, 87 stderr=None if args.verbose else subprocess.DEVNULL) 88 profile.stdin.write(config) 89 profile.stdin.close() 90 91 env = { 92 'LD_PRELOAD': os.path.join(args.out, 'libheapprofd_glibc_preload.so'), 93 'TRACE_PROCESSOR_NO_MMAP': '1', 94 'PERFETTO_HEAPPROFD_BLOCKING_INIT': '1' 95 } 96 (tp, fail, _) = run_tp_until_ingestion(args, env) 97 98 profile.send_signal(signal.SIGINT) 99 profile.wait() 100 101 tp.stdin.close() 102 tp.wait() 103 104 if fail: 105 os.remove(out_file) 106 107 108def regular_run(args): 109 env = {'TRACE_PROCESSOR_NO_MMAP': '1'} 110 (tp, fail, time) = run_tp_until_ingestion(args, env) 111 112 p = psutil.Process(tp.pid) 113 mem = 0 114 for m in p.memory_maps(): 115 mem += m.anonymous 116 117 tp.stdin.close() 118 tp.wait() 119 120 print(f'Time taken: {time}s, Memory: {mem / 1024.0 / 1024.0}MB') 121 122 123def only_sort_run(args): 124 env = { 125 'TRACE_PROCESSOR_NO_MMAP': '1', 126 'TRACE_PROCESSOR_SORT_ONLY': '1', 127 } 128 (tp, fail, time) = run_tp_until_ingestion(args, env) 129 130 tp.stdin.close() 131 tp.wait() 132 133 print(f'Time taken: {time}s') 134 135 136def main(): 137 parser = argparse.ArgumentParser( 138 description="This script measures the running time of " 139 "ingesting a trace with trace processor as well as profiling " 140 "trace processor's memory usage with heapprofd") 141 parser.add_argument('--out', type=str, help='Out directory', required=True) 142 parser.add_argument( 143 '--result', type=str, help='Result directory', required=True) 144 parser.add_argument( 145 '--result-prefix', type=str, help='Result file prefix', required=True) 146 parser.add_argument( 147 '--ftrace-raw', 148 action='store_true', 149 help='Whether to ingest ftrace into raw table', 150 default=False) 151 parser.add_argument( 152 '--kill-existing', 153 action='store_true', 154 help='Kill traced, perfetto_cmd and trace processor shell if running') 155 parser.add_argument( 156 '--verbose', 157 action='store_true', 158 help='Logs all stderr and stdout from subprocesses') 159 parser.add_argument('trace_file', type=str, help='Path to trace') 160 args = parser.parse_args() 161 162 if args.kill_existing: 163 subprocess.run(['killall', 'traced'], 164 stdout=subprocess.DEVNULL, 165 stderr=subprocess.DEVNULL) 166 subprocess.run(['killall', 'perfetto'], 167 stdout=subprocess.DEVNULL, 168 stderr=subprocess.DEVNULL) 169 subprocess.run(['killall', 'trace_processor_shell'], 170 stdout=subprocess.DEVNULL, 171 stderr=subprocess.DEVNULL) 172 173 traced = subprocess.Popen([os.path.join(args.out, 'traced')], 174 stdout=None if args.verbose else subprocess.DEVNULL, 175 stderr=None if args.verbose else subprocess.DEVNULL) 176 print('Heap profile dump at max') 177 heap_profile_run(args, dump_at_max=True) 178 print('Heap profile dump at resting') 179 heap_profile_run(args, dump_at_max=False) 180 print('Regular run') 181 regular_run(args) 182 print('Only sort run') 183 only_sort_run(args) 184 185 traced.send_signal(signal.SIGINT) 186 traced.wait() 187 188 189if __name__ == "__main__": 190 main() 191