xref: /aosp_15_r20/external/perfetto/python/tools/cpu_profile.py (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1*6dbdd20aSAndroid Build Coastguard Worker#!/usr/bin/env python3
2*6dbdd20aSAndroid Build Coastguard Worker# Copyright (C) 2022 The Android Open Source Project
3*6dbdd20aSAndroid Build Coastguard Worker#
4*6dbdd20aSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
5*6dbdd20aSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
6*6dbdd20aSAndroid Build Coastguard Worker# You may obtain a copy of the License at
7*6dbdd20aSAndroid Build Coastguard Worker#
8*6dbdd20aSAndroid Build Coastguard Worker#      http://www.apache.org/licenses/LICENSE-2.0
9*6dbdd20aSAndroid Build Coastguard Worker#
10*6dbdd20aSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
11*6dbdd20aSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
12*6dbdd20aSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*6dbdd20aSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
14*6dbdd20aSAndroid Build Coastguard Worker# limitations under the License.
15*6dbdd20aSAndroid Build Coastguard Worker"""Runs tracing with CPU profiling enabled, and symbolizes traces if requested.
16*6dbdd20aSAndroid Build Coastguard Worker
17*6dbdd20aSAndroid Build Coastguard WorkerFor usage instructions, please see:
18*6dbdd20aSAndroid Build Coastguard Workerhttps://perfetto.dev/docs/quickstart/callstack-sampling
19*6dbdd20aSAndroid Build Coastguard Worker
20*6dbdd20aSAndroid Build Coastguard WorkerAdapted in large part from `heap_profile`.
21*6dbdd20aSAndroid Build Coastguard Worker"""
22*6dbdd20aSAndroid Build Coastguard Worker
23*6dbdd20aSAndroid Build Coastguard Workerimport argparse
24*6dbdd20aSAndroid Build Coastguard Workerimport os
25*6dbdd20aSAndroid Build Coastguard Workerimport shutil
26*6dbdd20aSAndroid Build Coastguard Workerimport signal
27*6dbdd20aSAndroid Build Coastguard Workerimport subprocess
28*6dbdd20aSAndroid Build Coastguard Workerimport sys
29*6dbdd20aSAndroid Build Coastguard Workerimport tempfile
30*6dbdd20aSAndroid Build Coastguard Workerimport textwrap
31*6dbdd20aSAndroid Build Coastguard Workerimport time
32*6dbdd20aSAndroid Build Coastguard Workerimport uuid
33*6dbdd20aSAndroid Build Coastguard Worker
34*6dbdd20aSAndroid Build Coastguard Workerfrom perfetto.prebuilts.manifests.traceconv import *
35*6dbdd20aSAndroid Build Coastguard Workerfrom perfetto.prebuilts.perfetto_prebuilts import *
36*6dbdd20aSAndroid Build Coastguard Worker
37*6dbdd20aSAndroid Build Coastguard Worker# Used for creating directories, etc.
38*6dbdd20aSAndroid Build Coastguard WorkerUUID = str(uuid.uuid4())[-6:]
39*6dbdd20aSAndroid Build Coastguard Worker
40*6dbdd20aSAndroid Build Coastguard Worker# See `sigint_handler` below.
41*6dbdd20aSAndroid Build Coastguard WorkerIS_INTERRUPTED = False
42*6dbdd20aSAndroid Build Coastguard Worker
43*6dbdd20aSAndroid Build Coastguard Worker
44*6dbdd20aSAndroid Build Coastguard Workerdef sigint_handler(signal, frame):
45*6dbdd20aSAndroid Build Coastguard Worker  """Useful for cleanly interrupting tracing."""
46*6dbdd20aSAndroid Build Coastguard Worker  global IS_INTERRUPTED
47*6dbdd20aSAndroid Build Coastguard Worker  IS_INTERRUPTED = True
48*6dbdd20aSAndroid Build Coastguard Worker
49*6dbdd20aSAndroid Build Coastguard Worker
50*6dbdd20aSAndroid Build Coastguard Workerdef exit_with_no_profile():
51*6dbdd20aSAndroid Build Coastguard Worker  sys.exit("No profiles generated.")
52*6dbdd20aSAndroid Build Coastguard Worker
53*6dbdd20aSAndroid Build Coastguard Worker
54*6dbdd20aSAndroid Build Coastguard Workerdef exit_with_bug_report(error):
55*6dbdd20aSAndroid Build Coastguard Worker  sys.exit(
56*6dbdd20aSAndroid Build Coastguard Worker      "{}\n\n If this is unexpected, please consider filing a bug at: \n"
57*6dbdd20aSAndroid Build Coastguard Worker      "https://perfetto.dev/docs/contributing/getting-started#bugs.".format(
58*6dbdd20aSAndroid Build Coastguard Worker          error))
59*6dbdd20aSAndroid Build Coastguard Worker
60*6dbdd20aSAndroid Build Coastguard Worker
61*6dbdd20aSAndroid Build Coastguard Workerdef adb_check_output(command):
62*6dbdd20aSAndroid Build Coastguard Worker  """Runs an `adb` command and returns its output."""
63*6dbdd20aSAndroid Build Coastguard Worker  try:
64*6dbdd20aSAndroid Build Coastguard Worker    return subprocess.check_output(command).decode('utf-8')
65*6dbdd20aSAndroid Build Coastguard Worker  except FileNotFoundError:
66*6dbdd20aSAndroid Build Coastguard Worker    sys.exit("`adb` not found: Is it installed or on PATH?")
67*6dbdd20aSAndroid Build Coastguard Worker  except subprocess.CalledProcessError as error:
68*6dbdd20aSAndroid Build Coastguard Worker    sys.exit("`adb` error: Are any (or multiple) devices connected?\n"
69*6dbdd20aSAndroid Build Coastguard Worker             "If multiple devices are connected, please select one by "
70*6dbdd20aSAndroid Build Coastguard Worker             "setting `ANDROID_SERIAL=device_id`.\n"
71*6dbdd20aSAndroid Build Coastguard Worker             "{}".format(error))
72*6dbdd20aSAndroid Build Coastguard Worker  except Exception as error:
73*6dbdd20aSAndroid Build Coastguard Worker    exit_with_bug_report(error)
74*6dbdd20aSAndroid Build Coastguard Worker
75*6dbdd20aSAndroid Build Coastguard Worker
76*6dbdd20aSAndroid Build Coastguard Workerdef parse_and_validate_args():
77*6dbdd20aSAndroid Build Coastguard Worker  """Parses, validates, and returns command-line arguments for this script."""
78*6dbdd20aSAndroid Build Coastguard Worker  DESCRIPTION = """Runs tracing with CPU profiling enabled, and symbolizes
79*6dbdd20aSAndroid Build Coastguard Worker  traces if requested.
80*6dbdd20aSAndroid Build Coastguard Worker
81*6dbdd20aSAndroid Build Coastguard Worker  For usage instructions, please see:
82*6dbdd20aSAndroid Build Coastguard Worker  https://perfetto.dev/docs/quickstart/callstack-sampling
83*6dbdd20aSAndroid Build Coastguard Worker  """
84*6dbdd20aSAndroid Build Coastguard Worker  parser = argparse.ArgumentParser(description=DESCRIPTION)
85*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument(
86*6dbdd20aSAndroid Build Coastguard Worker      "-f",
87*6dbdd20aSAndroid Build Coastguard Worker      "--frequency",
88*6dbdd20aSAndroid Build Coastguard Worker      help="Sampling frequency (Hz). "
89*6dbdd20aSAndroid Build Coastguard Worker      "Default: 100 Hz.",
90*6dbdd20aSAndroid Build Coastguard Worker      metavar="FREQUENCY",
91*6dbdd20aSAndroid Build Coastguard Worker      type=int,
92*6dbdd20aSAndroid Build Coastguard Worker      default=100)
93*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument(
94*6dbdd20aSAndroid Build Coastguard Worker      "-d",
95*6dbdd20aSAndroid Build Coastguard Worker      "--duration",
96*6dbdd20aSAndroid Build Coastguard Worker      help="Duration of profile (ms). 0 to run until interrupted. "
97*6dbdd20aSAndroid Build Coastguard Worker      "Default: until interrupted by user.",
98*6dbdd20aSAndroid Build Coastguard Worker      metavar="DURATION",
99*6dbdd20aSAndroid Build Coastguard Worker      type=int,
100*6dbdd20aSAndroid Build Coastguard Worker      default=0)
101*6dbdd20aSAndroid Build Coastguard Worker  # Profiling using hardware counters.
102*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument(
103*6dbdd20aSAndroid Build Coastguard Worker      "-e",
104*6dbdd20aSAndroid Build Coastguard Worker      "--event",
105*6dbdd20aSAndroid Build Coastguard Worker      help="Use the specified hardware counter event for sampling.",
106*6dbdd20aSAndroid Build Coastguard Worker      metavar="EVENT",
107*6dbdd20aSAndroid Build Coastguard Worker      action="append",
108*6dbdd20aSAndroid Build Coastguard Worker      # See: '//perfetto/protos/perfetto/trace/perfetto_trace.proto'.
109*6dbdd20aSAndroid Build Coastguard Worker      choices=['HW_CPU_CYCLES', 'HW_INSTRUCTIONS', 'HW_CACHE_REFERENCES',
110*6dbdd20aSAndroid Build Coastguard Worker               'HW_CACHE_MISSES', 'HW_BRANCH_INSTRUCTIONS', 'HW_BRANCH_MISSES',
111*6dbdd20aSAndroid Build Coastguard Worker               'HW_BUS_CYCLES', 'HW_STALLED_CYCLES_FRONTEND',
112*6dbdd20aSAndroid Build Coastguard Worker               'HW_STALLED_CYCLES_BACKEND'],
113*6dbdd20aSAndroid Build Coastguard Worker      default=[])
114*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument(
115*6dbdd20aSAndroid Build Coastguard Worker      "-k",
116*6dbdd20aSAndroid Build Coastguard Worker      "--kernel-frames",
117*6dbdd20aSAndroid Build Coastguard Worker      help="Collect kernel frames.  Default: false.",
118*6dbdd20aSAndroid Build Coastguard Worker      action="store_true",
119*6dbdd20aSAndroid Build Coastguard Worker      default=False)
120*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument(
121*6dbdd20aSAndroid Build Coastguard Worker      "-n",
122*6dbdd20aSAndroid Build Coastguard Worker      "--name",
123*6dbdd20aSAndroid Build Coastguard Worker      help="Comma-separated list of names of processes to be profiled.",
124*6dbdd20aSAndroid Build Coastguard Worker      metavar="NAMES",
125*6dbdd20aSAndroid Build Coastguard Worker      default=None)
126*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument(
127*6dbdd20aSAndroid Build Coastguard Worker      "-p",
128*6dbdd20aSAndroid Build Coastguard Worker      "--partial-matching",
129*6dbdd20aSAndroid Build Coastguard Worker      help="If set, enables \"partial matching\" on the strings in --names/-n."
130*6dbdd20aSAndroid Build Coastguard Worker      "Processes that are already running when profiling is started, and whose "
131*6dbdd20aSAndroid Build Coastguard Worker      "names include any of the values in --names/-n as substrings will be "
132*6dbdd20aSAndroid Build Coastguard Worker      "profiled.",
133*6dbdd20aSAndroid Build Coastguard Worker      action="store_true")
134*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument(
135*6dbdd20aSAndroid Build Coastguard Worker      "-c",
136*6dbdd20aSAndroid Build Coastguard Worker      "--config",
137*6dbdd20aSAndroid Build Coastguard Worker      help="A custom configuration file, if any, to be used for profiling. "
138*6dbdd20aSAndroid Build Coastguard Worker      "If provided, --frequency/-f, --duration/-d, and --name/-n are not used.",
139*6dbdd20aSAndroid Build Coastguard Worker      metavar="CONFIG",
140*6dbdd20aSAndroid Build Coastguard Worker      default=None)
141*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument(
142*6dbdd20aSAndroid Build Coastguard Worker      "--no-annotations",
143*6dbdd20aSAndroid Build Coastguard Worker      help="Do not suffix the pprof function names with Android ART mode "
144*6dbdd20aSAndroid Build Coastguard Worker      "annotations such as [jit].",
145*6dbdd20aSAndroid Build Coastguard Worker      action="store_true")
146*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument(
147*6dbdd20aSAndroid Build Coastguard Worker      "--print-config",
148*6dbdd20aSAndroid Build Coastguard Worker      action="store_true",
149*6dbdd20aSAndroid Build Coastguard Worker      help="Print config instead of running. For debugging.")
150*6dbdd20aSAndroid Build Coastguard Worker  parser.add_argument(
151*6dbdd20aSAndroid Build Coastguard Worker      "-o",
152*6dbdd20aSAndroid Build Coastguard Worker      "--output",
153*6dbdd20aSAndroid Build Coastguard Worker      help="Output directory for recorded trace.",
154*6dbdd20aSAndroid Build Coastguard Worker      metavar="DIRECTORY",
155*6dbdd20aSAndroid Build Coastguard Worker      default=None)
156*6dbdd20aSAndroid Build Coastguard Worker
157*6dbdd20aSAndroid Build Coastguard Worker  args = parser.parse_args()
158*6dbdd20aSAndroid Build Coastguard Worker  if args.config is not None:
159*6dbdd20aSAndroid Build Coastguard Worker    if args.name is not None:
160*6dbdd20aSAndroid Build Coastguard Worker      sys.exit("--name/-n should not be specified with --config/-c.")
161*6dbdd20aSAndroid Build Coastguard Worker    elif args.event:
162*6dbdd20aSAndroid Build Coastguard Worker      sys.exit("-e/--event should not be specified with --config/-c.")
163*6dbdd20aSAndroid Build Coastguard Worker  elif args.config is None and args.name is None:
164*6dbdd20aSAndroid Build Coastguard Worker    sys.exit("One of --names/-n or --config/-c is required.")
165*6dbdd20aSAndroid Build Coastguard Worker
166*6dbdd20aSAndroid Build Coastguard Worker  return args
167*6dbdd20aSAndroid Build Coastguard Worker
168*6dbdd20aSAndroid Build Coastguard Worker
169*6dbdd20aSAndroid Build Coastguard Workerdef get_matching_processes(args, names_to_match):
170*6dbdd20aSAndroid Build Coastguard Worker  """Returns a list of currently-running processes whose names match
171*6dbdd20aSAndroid Build Coastguard Worker  `names_to_match`.
172*6dbdd20aSAndroid Build Coastguard Worker
173*6dbdd20aSAndroid Build Coastguard Worker  Args:
174*6dbdd20aSAndroid Build Coastguard Worker    args: The command-line arguments provided to this script.
175*6dbdd20aSAndroid Build Coastguard Worker    names_to_match: The list of process names provided by the user.
176*6dbdd20aSAndroid Build Coastguard Worker  """
177*6dbdd20aSAndroid Build Coastguard Worker  # Returns names as they are.
178*6dbdd20aSAndroid Build Coastguard Worker  if not args.partial_matching:
179*6dbdd20aSAndroid Build Coastguard Worker    return names_to_match
180*6dbdd20aSAndroid Build Coastguard Worker
181*6dbdd20aSAndroid Build Coastguard Worker  # Attempt to match names to names of currently running processes.
182*6dbdd20aSAndroid Build Coastguard Worker  PS_PROCESS_OFFSET = 8
183*6dbdd20aSAndroid Build Coastguard Worker  matching_processes = []
184*6dbdd20aSAndroid Build Coastguard Worker  for line in adb_check_output(['adb', 'shell', 'ps', '-A']).splitlines():
185*6dbdd20aSAndroid Build Coastguard Worker    line_split = line.split()
186*6dbdd20aSAndroid Build Coastguard Worker    if len(line_split) <= PS_PROCESS_OFFSET:
187*6dbdd20aSAndroid Build Coastguard Worker      continue
188*6dbdd20aSAndroid Build Coastguard Worker    process = line_split[PS_PROCESS_OFFSET]
189*6dbdd20aSAndroid Build Coastguard Worker    for name in names_to_match:
190*6dbdd20aSAndroid Build Coastguard Worker      if name in process:
191*6dbdd20aSAndroid Build Coastguard Worker        matching_processes.append(process)
192*6dbdd20aSAndroid Build Coastguard Worker        break
193*6dbdd20aSAndroid Build Coastguard Worker
194*6dbdd20aSAndroid Build Coastguard Worker  return matching_processes
195*6dbdd20aSAndroid Build Coastguard Worker
196*6dbdd20aSAndroid Build Coastguard Worker
197*6dbdd20aSAndroid Build Coastguard Workerdef get_perfetto_config(args):
198*6dbdd20aSAndroid Build Coastguard Worker  """Returns a Perfetto config with CPU profiling enabled for the selected
199*6dbdd20aSAndroid Build Coastguard Worker  processes.
200*6dbdd20aSAndroid Build Coastguard Worker
201*6dbdd20aSAndroid Build Coastguard Worker  Args:
202*6dbdd20aSAndroid Build Coastguard Worker    args: The command-line arguments provided to this script.
203*6dbdd20aSAndroid Build Coastguard Worker  """
204*6dbdd20aSAndroid Build Coastguard Worker  if args.config is not None:
205*6dbdd20aSAndroid Build Coastguard Worker    try:
206*6dbdd20aSAndroid Build Coastguard Worker      with open(args.config, 'r') as config_file:
207*6dbdd20aSAndroid Build Coastguard Worker        return config_file.read()
208*6dbdd20aSAndroid Build Coastguard Worker    except IOError as error:
209*6dbdd20aSAndroid Build Coastguard Worker      sys.exit("Unable to read config file: {}".format(error))
210*6dbdd20aSAndroid Build Coastguard Worker
211*6dbdd20aSAndroid Build Coastguard Worker  CONFIG_INDENT = '          '
212*6dbdd20aSAndroid Build Coastguard Worker  CONFIG = textwrap.dedent('''\
213*6dbdd20aSAndroid Build Coastguard Worker  buffers {{
214*6dbdd20aSAndroid Build Coastguard Worker    size_kb: 2048
215*6dbdd20aSAndroid Build Coastguard Worker  }}
216*6dbdd20aSAndroid Build Coastguard Worker
217*6dbdd20aSAndroid Build Coastguard Worker  buffers {{
218*6dbdd20aSAndroid Build Coastguard Worker    size_kb: 63488
219*6dbdd20aSAndroid Build Coastguard Worker  }}
220*6dbdd20aSAndroid Build Coastguard Worker
221*6dbdd20aSAndroid Build Coastguard Worker  data_sources {{
222*6dbdd20aSAndroid Build Coastguard Worker    config {{
223*6dbdd20aSAndroid Build Coastguard Worker      name: "linux.process_stats"
224*6dbdd20aSAndroid Build Coastguard Worker      target_buffer: 0
225*6dbdd20aSAndroid Build Coastguard Worker      process_stats_config {{
226*6dbdd20aSAndroid Build Coastguard Worker        proc_stats_poll_ms: 100
227*6dbdd20aSAndroid Build Coastguard Worker      }}
228*6dbdd20aSAndroid Build Coastguard Worker    }}
229*6dbdd20aSAndroid Build Coastguard Worker  }}
230*6dbdd20aSAndroid Build Coastguard Worker
231*6dbdd20aSAndroid Build Coastguard Worker  duration_ms: {duration}
232*6dbdd20aSAndroid Build Coastguard Worker  write_into_file: true
233*6dbdd20aSAndroid Build Coastguard Worker  flush_timeout_ms: 30000
234*6dbdd20aSAndroid Build Coastguard Worker  flush_period_ms: 604800000
235*6dbdd20aSAndroid Build Coastguard Worker  ''')
236*6dbdd20aSAndroid Build Coastguard Worker
237*6dbdd20aSAndroid Build Coastguard Worker  matching_processes = []
238*6dbdd20aSAndroid Build Coastguard Worker  if args.name is not None:
239*6dbdd20aSAndroid Build Coastguard Worker    names_to_match = [name.strip() for name in args.name.split(',')]
240*6dbdd20aSAndroid Build Coastguard Worker    matching_processes = get_matching_processes(args, names_to_match)
241*6dbdd20aSAndroid Build Coastguard Worker
242*6dbdd20aSAndroid Build Coastguard Worker  if not matching_processes:
243*6dbdd20aSAndroid Build Coastguard Worker    sys.exit("No running processes matched for profiling.")
244*6dbdd20aSAndroid Build Coastguard Worker
245*6dbdd20aSAndroid Build Coastguard Worker  target_config = "\n".join(
246*6dbdd20aSAndroid Build Coastguard Worker      [f'{CONFIG_INDENT}target_cmdline: "{p}"' for p in matching_processes])
247*6dbdd20aSAndroid Build Coastguard Worker
248*6dbdd20aSAndroid Build Coastguard Worker  events = args.event or ['SW_CPU_CLOCK']
249*6dbdd20aSAndroid Build Coastguard Worker  for event in events:
250*6dbdd20aSAndroid Build Coastguard Worker    CONFIG += (textwrap.dedent('''
251*6dbdd20aSAndroid Build Coastguard Worker    data_sources {{
252*6dbdd20aSAndroid Build Coastguard Worker      config {{
253*6dbdd20aSAndroid Build Coastguard Worker        name: "linux.perf"
254*6dbdd20aSAndroid Build Coastguard Worker        target_buffer: 1
255*6dbdd20aSAndroid Build Coastguard Worker        perf_event_config {{
256*6dbdd20aSAndroid Build Coastguard Worker          timebase {{
257*6dbdd20aSAndroid Build Coastguard Worker            counter: %s
258*6dbdd20aSAndroid Build Coastguard Worker            frequency: {frequency}
259*6dbdd20aSAndroid Build Coastguard Worker            timestamp_clock: PERF_CLOCK_MONOTONIC
260*6dbdd20aSAndroid Build Coastguard Worker          }}
261*6dbdd20aSAndroid Build Coastguard Worker          callstack_sampling {{
262*6dbdd20aSAndroid Build Coastguard Worker            scope {{
263*6dbdd20aSAndroid Build Coastguard Worker    {target_config}
264*6dbdd20aSAndroid Build Coastguard Worker            }}
265*6dbdd20aSAndroid Build Coastguard Worker            kernel_frames: {kernel_config}
266*6dbdd20aSAndroid Build Coastguard Worker          }}
267*6dbdd20aSAndroid Build Coastguard Worker        }}
268*6dbdd20aSAndroid Build Coastguard Worker      }}
269*6dbdd20aSAndroid Build Coastguard Worker    }}
270*6dbdd20aSAndroid Build Coastguard Worker    ''') % (event))
271*6dbdd20aSAndroid Build Coastguard Worker
272*6dbdd20aSAndroid Build Coastguard Worker  if args.kernel_frames:
273*6dbdd20aSAndroid Build Coastguard Worker    kernel_config = "true"
274*6dbdd20aSAndroid Build Coastguard Worker  else:
275*6dbdd20aSAndroid Build Coastguard Worker    kernel_config = "false"
276*6dbdd20aSAndroid Build Coastguard Worker
277*6dbdd20aSAndroid Build Coastguard Worker  if not args.print_config:
278*6dbdd20aSAndroid Build Coastguard Worker    print("Configured profiling for these processes:\n")
279*6dbdd20aSAndroid Build Coastguard Worker    for matching_process in matching_processes:
280*6dbdd20aSAndroid Build Coastguard Worker      print(matching_process)
281*6dbdd20aSAndroid Build Coastguard Worker    print()
282*6dbdd20aSAndroid Build Coastguard Worker
283*6dbdd20aSAndroid Build Coastguard Worker  config = CONFIG.format(
284*6dbdd20aSAndroid Build Coastguard Worker      frequency=args.frequency,
285*6dbdd20aSAndroid Build Coastguard Worker      duration=args.duration,
286*6dbdd20aSAndroid Build Coastguard Worker      target_config=target_config,
287*6dbdd20aSAndroid Build Coastguard Worker      kernel_config=kernel_config)
288*6dbdd20aSAndroid Build Coastguard Worker
289*6dbdd20aSAndroid Build Coastguard Worker  return config
290*6dbdd20aSAndroid Build Coastguard Worker
291*6dbdd20aSAndroid Build Coastguard Worker
292*6dbdd20aSAndroid Build Coastguard Workerdef release_or_newer(release):
293*6dbdd20aSAndroid Build Coastguard Worker  """Returns whether a new enough Android release is being used."""
294*6dbdd20aSAndroid Build Coastguard Worker  SDK = {'T': 33}
295*6dbdd20aSAndroid Build Coastguard Worker  sdk = int(
296*6dbdd20aSAndroid Build Coastguard Worker      adb_check_output(
297*6dbdd20aSAndroid Build Coastguard Worker          ['adb', 'shell', 'getprop', 'ro.system.build.version.sdk']).strip())
298*6dbdd20aSAndroid Build Coastguard Worker  if sdk >= SDK[release]:
299*6dbdd20aSAndroid Build Coastguard Worker    return True
300*6dbdd20aSAndroid Build Coastguard Worker
301*6dbdd20aSAndroid Build Coastguard Worker  codename = adb_check_output(
302*6dbdd20aSAndroid Build Coastguard Worker      ['adb', 'shell', 'getprop', 'ro.build.version.codename']).strip()
303*6dbdd20aSAndroid Build Coastguard Worker  return codename == release
304*6dbdd20aSAndroid Build Coastguard Worker
305*6dbdd20aSAndroid Build Coastguard Worker
306*6dbdd20aSAndroid Build Coastguard Workerdef get_and_prepare_profile_target(args):
307*6dbdd20aSAndroid Build Coastguard Worker  """Returns the target where the trace/profile will be output.  Creates a
308*6dbdd20aSAndroid Build Coastguard Worker  new directory if necessary.
309*6dbdd20aSAndroid Build Coastguard Worker
310*6dbdd20aSAndroid Build Coastguard Worker  Args:
311*6dbdd20aSAndroid Build Coastguard Worker    args: The command-line arguments provided to this script.
312*6dbdd20aSAndroid Build Coastguard Worker  """
313*6dbdd20aSAndroid Build Coastguard Worker  profile_target = os.path.join(tempfile.gettempdir(), UUID)
314*6dbdd20aSAndroid Build Coastguard Worker  if args.output is not None:
315*6dbdd20aSAndroid Build Coastguard Worker    profile_target = args.output
316*6dbdd20aSAndroid Build Coastguard Worker  else:
317*6dbdd20aSAndroid Build Coastguard Worker    os.makedirs(profile_target, exist_ok=True)
318*6dbdd20aSAndroid Build Coastguard Worker  if not os.path.isdir(profile_target):
319*6dbdd20aSAndroid Build Coastguard Worker    sys.exit("Output directory {} not found.".format(profile_target))
320*6dbdd20aSAndroid Build Coastguard Worker  if os.listdir(profile_target):
321*6dbdd20aSAndroid Build Coastguard Worker    sys.exit("Output directory {} not empty.".format(profile_target))
322*6dbdd20aSAndroid Build Coastguard Worker
323*6dbdd20aSAndroid Build Coastguard Worker  return profile_target
324*6dbdd20aSAndroid Build Coastguard Worker
325*6dbdd20aSAndroid Build Coastguard Worker
326*6dbdd20aSAndroid Build Coastguard Workerdef record_trace(config, profile_target):
327*6dbdd20aSAndroid Build Coastguard Worker  """Runs Perfetto with the provided configuration to record a trace.
328*6dbdd20aSAndroid Build Coastguard Worker
329*6dbdd20aSAndroid Build Coastguard Worker  Args:
330*6dbdd20aSAndroid Build Coastguard Worker    config: The Perfetto config to be used for tracing/profiling.
331*6dbdd20aSAndroid Build Coastguard Worker    profile_target: The directory where the recorded trace is output.
332*6dbdd20aSAndroid Build Coastguard Worker  """
333*6dbdd20aSAndroid Build Coastguard Worker  NULL = open(os.devnull)
334*6dbdd20aSAndroid Build Coastguard Worker  NO_OUT = {
335*6dbdd20aSAndroid Build Coastguard Worker      'stdout': NULL,
336*6dbdd20aSAndroid Build Coastguard Worker      'stderr': NULL,
337*6dbdd20aSAndroid Build Coastguard Worker  }
338*6dbdd20aSAndroid Build Coastguard Worker  if not release_or_newer('T'):
339*6dbdd20aSAndroid Build Coastguard Worker    sys.exit("This tool requires Android T+ to run.")
340*6dbdd20aSAndroid Build Coastguard Worker
341*6dbdd20aSAndroid Build Coastguard Worker  # Push configuration to the device.
342*6dbdd20aSAndroid Build Coastguard Worker  tf = tempfile.NamedTemporaryFile()
343*6dbdd20aSAndroid Build Coastguard Worker  tf.file.write(config.encode('utf-8'))
344*6dbdd20aSAndroid Build Coastguard Worker  tf.file.flush()
345*6dbdd20aSAndroid Build Coastguard Worker  profile_config_path = '/data/misc/perfetto-configs/config-' + UUID
346*6dbdd20aSAndroid Build Coastguard Worker  adb_check_output(['adb', 'push', tf.name, profile_config_path])
347*6dbdd20aSAndroid Build Coastguard Worker  tf.close()
348*6dbdd20aSAndroid Build Coastguard Worker
349*6dbdd20aSAndroid Build Coastguard Worker
350*6dbdd20aSAndroid Build Coastguard Worker  profile_device_path = '/data/misc/perfetto-traces/profile-' + UUID
351*6dbdd20aSAndroid Build Coastguard Worker  perfetto_command = ('perfetto --txt -c {} -o {} -d')
352*6dbdd20aSAndroid Build Coastguard Worker  try:
353*6dbdd20aSAndroid Build Coastguard Worker    perfetto_pid = int(
354*6dbdd20aSAndroid Build Coastguard Worker        adb_check_output([
355*6dbdd20aSAndroid Build Coastguard Worker            'adb', 'exec-out',
356*6dbdd20aSAndroid Build Coastguard Worker            perfetto_command.format(profile_config_path, profile_device_path)
357*6dbdd20aSAndroid Build Coastguard Worker        ]).strip())
358*6dbdd20aSAndroid Build Coastguard Worker  except ValueError as error:
359*6dbdd20aSAndroid Build Coastguard Worker    sys.exit("Unable to start profiling: {}".format(error))
360*6dbdd20aSAndroid Build Coastguard Worker
361*6dbdd20aSAndroid Build Coastguard Worker  print("Profiling active. Press Ctrl+C to terminate.")
362*6dbdd20aSAndroid Build Coastguard Worker
363*6dbdd20aSAndroid Build Coastguard Worker  old_handler = signal.signal(signal.SIGINT, sigint_handler)
364*6dbdd20aSAndroid Build Coastguard Worker
365*6dbdd20aSAndroid Build Coastguard Worker  perfetto_alive = True
366*6dbdd20aSAndroid Build Coastguard Worker  while perfetto_alive and not IS_INTERRUPTED:
367*6dbdd20aSAndroid Build Coastguard Worker    perfetto_alive = subprocess.call(
368*6dbdd20aSAndroid Build Coastguard Worker        ['adb', 'shell', '[ -d /proc/{} ]'.format(perfetto_pid)], **NO_OUT) == 0
369*6dbdd20aSAndroid Build Coastguard Worker    time.sleep(0.25)
370*6dbdd20aSAndroid Build Coastguard Worker
371*6dbdd20aSAndroid Build Coastguard Worker  print("Finishing profiling and symbolization...")
372*6dbdd20aSAndroid Build Coastguard Worker
373*6dbdd20aSAndroid Build Coastguard Worker  if IS_INTERRUPTED:
374*6dbdd20aSAndroid Build Coastguard Worker    adb_check_output(['adb', 'shell', 'kill', '-INT', str(perfetto_pid)])
375*6dbdd20aSAndroid Build Coastguard Worker
376*6dbdd20aSAndroid Build Coastguard Worker  # Restore old handler.
377*6dbdd20aSAndroid Build Coastguard Worker  signal.signal(signal.SIGINT, old_handler)
378*6dbdd20aSAndroid Build Coastguard Worker
379*6dbdd20aSAndroid Build Coastguard Worker  while perfetto_alive:
380*6dbdd20aSAndroid Build Coastguard Worker    perfetto_alive = subprocess.call(
381*6dbdd20aSAndroid Build Coastguard Worker        ['adb', 'shell', '[ -d /proc/{} ]'.format(perfetto_pid)]) == 0
382*6dbdd20aSAndroid Build Coastguard Worker    time.sleep(0.25)
383*6dbdd20aSAndroid Build Coastguard Worker
384*6dbdd20aSAndroid Build Coastguard Worker  profile_host_path = os.path.join(profile_target, 'raw-trace')
385*6dbdd20aSAndroid Build Coastguard Worker  adb_check_output(['adb', 'pull', profile_device_path, profile_host_path])
386*6dbdd20aSAndroid Build Coastguard Worker  adb_check_output(['adb', 'shell', 'rm', profile_config_path])
387*6dbdd20aSAndroid Build Coastguard Worker  adb_check_output(['adb', 'shell', 'rm', profile_device_path])
388*6dbdd20aSAndroid Build Coastguard Worker
389*6dbdd20aSAndroid Build Coastguard Worker
390*6dbdd20aSAndroid Build Coastguard Workerdef get_traceconv():
391*6dbdd20aSAndroid Build Coastguard Worker  """Sets up and returns the path to `traceconv`."""
392*6dbdd20aSAndroid Build Coastguard Worker  try:
393*6dbdd20aSAndroid Build Coastguard Worker    traceconv = get_perfetto_prebuilt(TRACECONV_MANIFEST, soft_fail=True)
394*6dbdd20aSAndroid Build Coastguard Worker  except Exception as error:
395*6dbdd20aSAndroid Build Coastguard Worker    exit_with_bug_report(error)
396*6dbdd20aSAndroid Build Coastguard Worker  if traceconv is None:
397*6dbdd20aSAndroid Build Coastguard Worker    exit_with_bug_report(
398*6dbdd20aSAndroid Build Coastguard Worker        "Unable to download `traceconv` for symbolizing profiles.")
399*6dbdd20aSAndroid Build Coastguard Worker
400*6dbdd20aSAndroid Build Coastguard Worker  return traceconv
401*6dbdd20aSAndroid Build Coastguard Worker
402*6dbdd20aSAndroid Build Coastguard Worker
403*6dbdd20aSAndroid Build Coastguard Workerdef concatenate_files(files_to_concatenate, output_file):
404*6dbdd20aSAndroid Build Coastguard Worker  """Concatenates files.
405*6dbdd20aSAndroid Build Coastguard Worker
406*6dbdd20aSAndroid Build Coastguard Worker  Args:
407*6dbdd20aSAndroid Build Coastguard Worker    files_to_concatenate: Paths for input files to concatenate.
408*6dbdd20aSAndroid Build Coastguard Worker    output_file: Path to the resultant output file.
409*6dbdd20aSAndroid Build Coastguard Worker  """
410*6dbdd20aSAndroid Build Coastguard Worker  with open(output_file, 'wb') as output:
411*6dbdd20aSAndroid Build Coastguard Worker    for file in files_to_concatenate:
412*6dbdd20aSAndroid Build Coastguard Worker      with open(file, 'rb') as input:
413*6dbdd20aSAndroid Build Coastguard Worker        shutil.copyfileobj(input, output)
414*6dbdd20aSAndroid Build Coastguard Worker
415*6dbdd20aSAndroid Build Coastguard Worker
416*6dbdd20aSAndroid Build Coastguard Workerdef symbolize_trace(traceconv, profile_target):
417*6dbdd20aSAndroid Build Coastguard Worker  """Attempts symbolization of the recorded trace/profile, if symbols are
418*6dbdd20aSAndroid Build Coastguard Worker  available.
419*6dbdd20aSAndroid Build Coastguard Worker
420*6dbdd20aSAndroid Build Coastguard Worker  Args:
421*6dbdd20aSAndroid Build Coastguard Worker    traceconv: The path to the `traceconv` binary used for symbolization.
422*6dbdd20aSAndroid Build Coastguard Worker    profile_target: The directory where the recorded trace was output.
423*6dbdd20aSAndroid Build Coastguard Worker
424*6dbdd20aSAndroid Build Coastguard Worker  Returns:
425*6dbdd20aSAndroid Build Coastguard Worker    The path to the symbolized trace file if symbolization was completed,
426*6dbdd20aSAndroid Build Coastguard Worker    and the original trace file, if it was not.
427*6dbdd20aSAndroid Build Coastguard Worker  """
428*6dbdd20aSAndroid Build Coastguard Worker  binary_path = os.getenv('PERFETTO_BINARY_PATH')
429*6dbdd20aSAndroid Build Coastguard Worker  trace_file = os.path.join(profile_target, 'raw-trace')
430*6dbdd20aSAndroid Build Coastguard Worker  files_to_concatenate = [trace_file]
431*6dbdd20aSAndroid Build Coastguard Worker
432*6dbdd20aSAndroid Build Coastguard Worker  if binary_path is not None:
433*6dbdd20aSAndroid Build Coastguard Worker    try:
434*6dbdd20aSAndroid Build Coastguard Worker      with open(os.path.join(profile_target, 'symbols'), 'w') as symbols_file:
435*6dbdd20aSAndroid Build Coastguard Worker        return_code = subprocess.call([traceconv, 'symbolize', trace_file],
436*6dbdd20aSAndroid Build Coastguard Worker                                      env=dict(
437*6dbdd20aSAndroid Build Coastguard Worker                                          os.environ,
438*6dbdd20aSAndroid Build Coastguard Worker                                          PERFETTO_BINARY_PATH=binary_path),
439*6dbdd20aSAndroid Build Coastguard Worker                                      stdout=symbols_file)
440*6dbdd20aSAndroid Build Coastguard Worker    except IOError as error:
441*6dbdd20aSAndroid Build Coastguard Worker      sys.exit("Unable to write symbols to disk: {}".format(error))
442*6dbdd20aSAndroid Build Coastguard Worker    if return_code == 0:
443*6dbdd20aSAndroid Build Coastguard Worker      files_to_concatenate.append(os.path.join(profile_target, 'symbols'))
444*6dbdd20aSAndroid Build Coastguard Worker    else:
445*6dbdd20aSAndroid Build Coastguard Worker      print("Failed to symbolize. Continuing without symbols.", file=sys.stderr)
446*6dbdd20aSAndroid Build Coastguard Worker
447*6dbdd20aSAndroid Build Coastguard Worker  if len(files_to_concatenate) > 1:
448*6dbdd20aSAndroid Build Coastguard Worker    trace_file = os.path.join(profile_target, 'symbolized-trace')
449*6dbdd20aSAndroid Build Coastguard Worker    try:
450*6dbdd20aSAndroid Build Coastguard Worker      concatenate_files(files_to_concatenate, trace_file)
451*6dbdd20aSAndroid Build Coastguard Worker    except Exception as error:
452*6dbdd20aSAndroid Build Coastguard Worker      sys.exit("Unable to write symbolized profile to disk: {}".format(error))
453*6dbdd20aSAndroid Build Coastguard Worker
454*6dbdd20aSAndroid Build Coastguard Worker  return trace_file
455*6dbdd20aSAndroid Build Coastguard Worker
456*6dbdd20aSAndroid Build Coastguard Worker
457*6dbdd20aSAndroid Build Coastguard Workerdef generate_pprof_profiles(traceconv, trace_file, args):
458*6dbdd20aSAndroid Build Coastguard Worker  """Generates pprof profiles from the recorded trace.
459*6dbdd20aSAndroid Build Coastguard Worker
460*6dbdd20aSAndroid Build Coastguard Worker  Args:
461*6dbdd20aSAndroid Build Coastguard Worker    traceconv: The path to the `traceconv` binary used for generating profiles.
462*6dbdd20aSAndroid Build Coastguard Worker    trace_file: The oath to the recorded and potentially symbolized trace file.
463*6dbdd20aSAndroid Build Coastguard Worker
464*6dbdd20aSAndroid Build Coastguard Worker  Returns:
465*6dbdd20aSAndroid Build Coastguard Worker    The directory where pprof profiles are output.
466*6dbdd20aSAndroid Build Coastguard Worker  """
467*6dbdd20aSAndroid Build Coastguard Worker  try:
468*6dbdd20aSAndroid Build Coastguard Worker    conversion_args = [traceconv, 'profile', '--perf'] + (
469*6dbdd20aSAndroid Build Coastguard Worker        ['--no-annotations'] if args.no_annotations else []) + [trace_file]
470*6dbdd20aSAndroid Build Coastguard Worker    traceconv_output = subprocess.check_output(conversion_args)
471*6dbdd20aSAndroid Build Coastguard Worker  except Exception as error:
472*6dbdd20aSAndroid Build Coastguard Worker    exit_with_bug_report(
473*6dbdd20aSAndroid Build Coastguard Worker        "Unable to extract profiles from trace: {}".format(error))
474*6dbdd20aSAndroid Build Coastguard Worker
475*6dbdd20aSAndroid Build Coastguard Worker  profiles_output_directory = None
476*6dbdd20aSAndroid Build Coastguard Worker  for word in traceconv_output.decode('utf-8').split():
477*6dbdd20aSAndroid Build Coastguard Worker    if 'perf_profile-' in word:
478*6dbdd20aSAndroid Build Coastguard Worker      profiles_output_directory = word
479*6dbdd20aSAndroid Build Coastguard Worker  if profiles_output_directory is None:
480*6dbdd20aSAndroid Build Coastguard Worker    exit_with_no_profile()
481*6dbdd20aSAndroid Build Coastguard Worker  return profiles_output_directory
482*6dbdd20aSAndroid Build Coastguard Worker
483*6dbdd20aSAndroid Build Coastguard Worker
484*6dbdd20aSAndroid Build Coastguard Workerdef copy_profiles_to_destination(profile_target, profile_path):
485*6dbdd20aSAndroid Build Coastguard Worker  """Copies recorded profiles to `profile_target` from `profile_path`."""
486*6dbdd20aSAndroid Build Coastguard Worker  profile_files = os.listdir(profile_path)
487*6dbdd20aSAndroid Build Coastguard Worker  if not profile_files:
488*6dbdd20aSAndroid Build Coastguard Worker    exit_with_no_profile()
489*6dbdd20aSAndroid Build Coastguard Worker
490*6dbdd20aSAndroid Build Coastguard Worker  try:
491*6dbdd20aSAndroid Build Coastguard Worker    for profile_file in profile_files:
492*6dbdd20aSAndroid Build Coastguard Worker      shutil.copy(os.path.join(profile_path, profile_file), profile_target)
493*6dbdd20aSAndroid Build Coastguard Worker  except Exception as error:
494*6dbdd20aSAndroid Build Coastguard Worker    sys.exit("Unable to copy profiles to {}: {}".format(profile_target, error))
495*6dbdd20aSAndroid Build Coastguard Worker
496*6dbdd20aSAndroid Build Coastguard Worker  print("Wrote profiles to {}".format(profile_target))
497*6dbdd20aSAndroid Build Coastguard Worker
498*6dbdd20aSAndroid Build Coastguard Worker
499*6dbdd20aSAndroid Build Coastguard Workerdef main(argv):
500*6dbdd20aSAndroid Build Coastguard Worker  args = parse_and_validate_args()
501*6dbdd20aSAndroid Build Coastguard Worker  profile_target = get_and_prepare_profile_target(args)
502*6dbdd20aSAndroid Build Coastguard Worker  trace_config = get_perfetto_config(args)
503*6dbdd20aSAndroid Build Coastguard Worker  if args.print_config:
504*6dbdd20aSAndroid Build Coastguard Worker    print(trace_config)
505*6dbdd20aSAndroid Build Coastguard Worker    return 0
506*6dbdd20aSAndroid Build Coastguard Worker  record_trace(trace_config, profile_target)
507*6dbdd20aSAndroid Build Coastguard Worker  traceconv = get_traceconv()
508*6dbdd20aSAndroid Build Coastguard Worker  trace_file = symbolize_trace(traceconv, profile_target)
509*6dbdd20aSAndroid Build Coastguard Worker  copy_profiles_to_destination(
510*6dbdd20aSAndroid Build Coastguard Worker      profile_target, generate_pprof_profiles(traceconv, trace_file, args))
511*6dbdd20aSAndroid Build Coastguard Worker  return 0
512*6dbdd20aSAndroid Build Coastguard Worker
513*6dbdd20aSAndroid Build Coastguard Worker
514*6dbdd20aSAndroid Build Coastguard Workerif __name__ == '__main__':
515*6dbdd20aSAndroid Build Coastguard Worker  sys.exit(main(sys.argv))
516