| #!/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. |
| |
| import argparse |
| import os |
| import re |
| import signal |
| import sys |
| import subprocess |
| |
| import psutil |
| |
| ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
| |
| REGEX = re.compile( |
| '.*Trace loaded: ([0-9.]+) MB in ([0-9.]+)s \(([0-9.]+) MB/s\)') |
| |
| |
| def run_tp_until_ingestion(args, env): |
| tp_args = [os.path.join(args.out, 'trace_processor_shell'), args.trace_file] |
| if not args.ftrace_raw: |
| tp_args.append('--no-ftrace-raw') |
| tp_args.append('--dev') |
| tp_args.append('--dev-flag drop-after-sort=true') |
| tp = subprocess.Popen( |
| tp_args, |
| stdin=subprocess.PIPE, |
| stdout=None if args.verbose else subprocess.DEVNULL, |
| stderr=subprocess.PIPE, |
| universal_newlines=True, |
| env=env) |
| |
| lines = [] |
| while True: |
| line = tp.stderr.readline() |
| if args.verbose: |
| sys.stderr.write(line) |
| lines.append(line) |
| |
| match = REGEX.match(line) |
| if match: |
| break |
| |
| if tp.poll(): |
| break |
| |
| ret = tp.poll() |
| fail = ret is not None and ret > 0 |
| if fail: |
| print("Failed") |
| for line in lines: |
| sys.stderr.write(line) |
| return tp, fail, match[2] |
| |
| |
| def heap_profile_run(args, dump_at_max: bool): |
| profile_args = [ |
| os.path.join(ROOT_DIR, 'tools', 'heap_profile'), '-i', '1', '-n', |
| 'trace_processor_shell', '--print-config' |
| ] |
| if dump_at_max: |
| profile_args.append('--dump-at-max') |
| config = subprocess.check_output( |
| profile_args, |
| stderr=subprocess.DEVNULL, |
| ) |
| |
| out_file = os.path.join( |
| args.result, args.result_prefix + ('max' if dump_at_max else 'rest')) |
| perfetto_args = [ |
| os.path.join(args.out, 'perfetto'), '-c', '-', '--txt', '-o', out_file |
| ] |
| profile = subprocess.Popen( |
| perfetto_args, |
| stdin=subprocess.PIPE, |
| stdout=None if args.verbose else subprocess.DEVNULL, |
| stderr=None if args.verbose else subprocess.DEVNULL) |
| profile.stdin.write(config) |
| profile.stdin.close() |
| |
| env = { |
| 'LD_PRELOAD': os.path.join(args.out, 'libheapprofd_glibc_preload.so'), |
| 'TRACE_PROCESSOR_NO_MMAP': '1', |
| 'PERFETTO_HEAPPROFD_BLOCKING_INIT': '1' |
| } |
| (tp, fail, _) = run_tp_until_ingestion(args, env) |
| |
| profile.send_signal(signal.SIGINT) |
| profile.wait() |
| |
| tp.stdin.close() |
| tp.wait() |
| |
| if fail: |
| os.remove(out_file) |
| |
| |
| def regular_run(args): |
| env = {'TRACE_PROCESSOR_NO_MMAP': '1'} |
| (tp, fail, time) = run_tp_until_ingestion(args, env) |
| |
| p = psutil.Process(tp.pid) |
| mem = 0 |
| for m in p.memory_maps(): |
| mem += m.anonymous |
| |
| tp.stdin.close() |
| tp.wait() |
| |
| print(f'Time taken: {time}s, Memory: {mem / 1024.0 / 1024.0}MB') |
| |
| |
| def only_sort_run(args): |
| env = { |
| 'TRACE_PROCESSOR_NO_MMAP': '1', |
| } |
| (tp, fail, time) = run_tp_until_ingestion(args, env) |
| |
| tp.stdin.close() |
| tp.wait() |
| |
| print(f'Time taken: {time}s') |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser( |
| description="This script measures the running time of " |
| "ingesting a trace with trace processor as well as profiling " |
| "trace processor's memory usage with heapprofd") |
| parser.add_argument('--out', type=str, help='Out directory', required=True) |
| parser.add_argument( |
| '--result', type=str, help='Result directory', required=True) |
| parser.add_argument( |
| '--result-prefix', type=str, help='Result file prefix', required=True) |
| parser.add_argument( |
| '--ftrace-raw', |
| action='store_true', |
| help='Whether to ingest ftrace into raw table', |
| default=False) |
| parser.add_argument( |
| '--kill-existing', |
| action='store_true', |
| help='Kill traced, perfetto_cmd and trace processor shell if running') |
| parser.add_argument( |
| '--verbose', |
| action='store_true', |
| help='Logs all stderr and stdout from subprocesses') |
| parser.add_argument('trace_file', type=str, help='Path to trace') |
| args = parser.parse_args() |
| |
| if args.kill_existing: |
| subprocess.run(['killall', 'traced'], |
| stdout=subprocess.DEVNULL, |
| stderr=subprocess.DEVNULL) |
| subprocess.run(['killall', 'perfetto'], |
| stdout=subprocess.DEVNULL, |
| stderr=subprocess.DEVNULL) |
| subprocess.run(['killall', 'trace_processor_shell'], |
| stdout=subprocess.DEVNULL, |
| stderr=subprocess.DEVNULL) |
| |
| traced = subprocess.Popen([os.path.join(args.out, 'traced')], |
| stdout=None if args.verbose else subprocess.DEVNULL, |
| stderr=None if args.verbose else subprocess.DEVNULL) |
| print('Heap profile dump at max') |
| heap_profile_run(args, dump_at_max=True) |
| print('Heap profile dump at resting') |
| heap_profile_run(args, dump_at_max=False) |
| print('Regular run') |
| regular_run(args) |
| print('Only sort run') |
| only_sort_run(args) |
| |
| traced.send_signal(signal.SIGINT) |
| traced.wait() |
| |
| |
| if __name__ == "__main__": |
| main() |