• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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