xref: /aosp_15_r20/external/perfetto/tools/measure_tp_performance.py (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
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