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