blob: f3ee35ae8aeea28678a4c0c4a01c5d695a7578ad [file] [log] [blame]
Florian Mayerc8aa81c2021-04-19 15:16:15 +01001#!/usr/bin/env python3
Florian Mayer801349e2018-11-29 10:15:25 +00002
3# Copyright (C) 2017 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17from __future__ import absolute_import
18from __future__ import division
19from __future__ import print_function
20
21import argparse
22import atexit
23import hashlib
24import os
Florian Mayer92c80d82019-09-25 14:00:01 +010025import shutil
Florian Mayer801349e2018-11-29 10:15:25 +000026import signal
27import subprocess
28import sys
29import tempfile
30import time
Florian Mayer15866392020-04-02 11:52:16 +020031import uuid
Hector Dearmana318af72021-04-08 16:30:02 +010032import platform
Florian Mayer801349e2018-11-29 10:15:25 +000033
Florian Mayer1e6581c2020-04-28 14:30:49 +020034
Florian Mayer801349e2018-11-29 10:15:25 +000035TRACE_TO_TEXT_SHAS = {
Hector Dearman76a3dd82021-05-12 14:51:24 +010036 'linux': '7e3e10dfb324e31723efd63ac25037856e06eba0',
37 'mac': '21f0f42dd019b4f09addd404a114fbf2322ca8a4',
Florian Mayer801349e2018-11-29 10:15:25 +000038}
39TRACE_TO_TEXT_PATH = tempfile.gettempdir()
Primiano Tucci834fdc72019-10-04 11:33:44 +010040TRACE_TO_TEXT_BASE_URL = ('https://storage.googleapis.com/perfetto/')
Florian Mayer801349e2018-11-29 10:15:25 +000041
Florian Mayerbd0a62a2019-04-10 11:09:21 +010042NULL = open(os.devnull)
43NOOUT = {
Primiano Tucci834fdc72019-10-04 11:33:44 +010044 'stdout': NULL,
45 'stderr': NULL,
Florian Mayerbd0a62a2019-04-10 11:09:21 +010046}
47
Florian Mayer2007d5c2020-09-08 15:45:44 +010048UUID = str(uuid.uuid4())[-6:]
Florian Mayerbd0a62a2019-04-10 11:09:21 +010049
Florian Mayer801349e2018-11-29 10:15:25 +000050def check_hash(file_name, sha_value):
Florian Mayer03b5c1f2020-11-23 12:12:54 +000051 file_hash = hashlib.sha1()
Florian Mayer801349e2018-11-29 10:15:25 +000052 with open(file_name, 'rb') as fd:
Florian Mayer03b5c1f2020-11-23 12:12:54 +000053 while True:
54 chunk = fd.read(4096)
55 if not chunk:
56 break
57 file_hash.update(chunk)
58 return file_hash.hexdigest() == sha_value
Florian Mayer801349e2018-11-29 10:15:25 +000059
60
Hector Dearmana318af72021-04-08 16:30:02 +010061def load_trace_to_text(os_name):
62 sha_value = TRACE_TO_TEXT_SHAS[os_name]
63 file_name = 'trace_to_text-' + os_name + '-' + sha_value
Florian Mayer801349e2018-11-29 10:15:25 +000064 local_file = os.path.join(TRACE_TO_TEXT_PATH, file_name)
65
66 if os.path.exists(local_file):
67 if not check_hash(local_file, sha_value):
68 os.remove(local_file)
69 else:
70 return local_file
71
72 url = TRACE_TO_TEXT_BASE_URL + file_name
Primiano Tucci9ff392c2020-05-15 22:35:38 +010073 subprocess.check_call(['curl', '-L', '-#', '-o', local_file, url])
Florian Mayer801349e2018-11-29 10:15:25 +000074 if not check_hash(local_file, sha_value):
75 os.remove(local_file)
76 raise ValueError("Invalid signature.")
77 os.chmod(local_file, 0o755)
78 return local_file
79
Primiano Tucci834fdc72019-10-04 11:33:44 +010080
81PACKAGES_LIST_CFG = '''data_sources {
Florian Mayerc8b28692019-05-16 17:03:21 +010082 config {
Florian Mayerfe4361d2019-05-14 11:54:00 +010083 name: "android.packages_list"
Florian Mayerc8b28692019-05-16 17:03:21 +010084 }
85}
Florian Mayerfe4361d2019-05-14 11:54:00 +010086'''
87
Florian Mayera8ff9032020-03-04 11:31:48 -080088CFG_INDENT = ' '
Primiano Tucci834fdc72019-10-04 11:33:44 +010089CFG = '''buffers {{
Florian Mayer2214c2e2021-02-03 16:54:00 +000090 size_kb: 63488
Florian Mayer801349e2018-11-29 10:15:25 +000091}}
92
93data_sources {{
94 config {{
95 name: "android.heapprofd"
96 heapprofd_config {{
Florian Mayer91b3c6d2019-04-10 13:44:37 -070097 shmem_size_bytes: {shmem_size}
Florian Mayer801349e2018-11-29 10:15:25 +000098 sampling_interval_bytes: {interval}
99{target_cfg}
Florian Mayer801349e2018-11-29 10:15:25 +0000100 }}
101 }}
102}}
103
104duration_ms: {duration}
Florian Mayer2aab3162019-05-03 16:02:30 +0100105write_into_file: true
Florian Mayer82610462019-04-16 10:26:07 +0100106flush_timeout_ms: 30000
Florian Mayer96f607b2020-01-14 12:57:41 +0000107flush_period_ms: 604800000
Florian Mayer801349e2018-11-29 10:15:25 +0000108'''
109
Florian Mayer96f607b2020-01-14 12:57:41 +0000110# flush_period_ms of 1 week to suppress trace_processor_shell warning.
111
Florian Mayera8312c72019-01-31 13:50:22 +0000112CONTINUOUS_DUMP = """
113 continuous_dump_config {{
114 dump_phase_ms: 0
115 dump_interval_ms: {dump_interval}
116 }}
117"""
118
Florian Mayer2007d5c2020-09-08 15:45:44 +0100119PROFILE_LOCAL_PATH = os.path.join(tempfile.gettempdir(), UUID)
Florian Mayer15866392020-04-02 11:52:16 +0200120
Florian Mayer801349e2018-11-29 10:15:25 +0000121IS_INTERRUPTED = False
Primiano Tucci834fdc72019-10-04 11:33:44 +0100122
Florian Mayer801349e2018-11-29 10:15:25 +0000123def sigint_handler(sig, frame):
124 global IS_INTERRUPTED
125 IS_INTERRUPTED = True
126
127
Florian Mayer96f607b2020-01-14 12:57:41 +0000128def print_no_profile_error():
129 print("No profiles generated", file=sys.stderr)
130 print(
131 "If this is unexpected, check "
Florian Mayerf3d00cf2020-06-10 17:46:13 +0200132 "https://perfetto.dev/docs/data-sources/native-heap-profiler#troubleshooting.",
Florian Mayer96f607b2020-01-14 12:57:41 +0000133 file=sys.stderr)
134
Florian Mayerc4de3912020-11-23 14:11:43 +0000135def known_issues_url(number):
136 return ('https://perfetto.dev/docs/data-sources/native-heap-profiler'
137 '#known-issues-android{}'.format(number))
138
139KNOWN_ISSUES = {
140 '10': known_issues_url(10),
141 'Q': known_issues_url(10),
142 '11': known_issues_url(11),
143 'R': known_issues_url(11),
144}
145
146def maybe_known_issues():
147 release_or_codename = subprocess.check_output(
148 ['adb', 'shell', 'getprop', 'ro.build.version.release_or_codename']
149 ).decode('utf-8').strip()
150 return KNOWN_ISSUES.get(release_or_codename, None)
151
Florian Mayer9a904742020-04-28 18:40:52 +0200152SDK = {
153 'R': 30,
154}
155
156def release_or_newer(release):
157 sdk = int(subprocess.check_output(
158 ['adb', 'shell', 'getprop', 'ro.system.build.version.sdk']
159 ).decode('utf-8').strip())
160 if sdk >= SDK[release]:
161 return True
162 codename = subprocess.check_output(
163 ['adb', 'shell', 'getprop', 'ro.build.version.codename']
164 ).decode('utf-8').strip()
165 return codename == release
166
Florian Mayerbe1e4252021-05-26 15:49:21 +0100167ORDER = ['-n', '-p', '-i', '-o']
168
169def arg_order(action):
170 result = len(ORDER)
171 for opt in action.option_strings:
172 if opt in ORDER:
173 result = min(ORDER.index(opt), result)
174 return result, action.option_strings[0].strip('-')
175
176def print_options(parser):
177 for action in sorted(parser._actions, key=arg_order):
178 if action.help is argparse.SUPPRESS:
179 continue
180 opts = ', '.join('`' + x + '`' for x in action.option_strings)
181 metavar = '' if action.metavar is None else ' _' + action.metavar + '_'
182 print('{}{}'.format(opts, metavar))
183 print(': {}'.format(action.help))
184 print()
185
Florian Mayer801349e2018-11-29 10:15:25 +0000186def main(argv):
187 parser = argparse.ArgumentParser()
Primiano Tucci834fdc72019-10-04 11:33:44 +0100188 parser.add_argument(
189 "-i",
190 "--interval",
191 help="Sampling interval. "
192 "Default 4096 (4KiB)",
193 type=int,
194 default=4096)
195 parser.add_argument(
196 "-d",
197 "--duration",
Florian Mayer2ad38612020-08-06 10:35:31 +0100198 help="Duration of profile (ms). 0 to run until interrupted. "
199 "Default: until interrupted by user.",
Primiano Tucci834fdc72019-10-04 11:33:44 +0100200 type=int,
Florian Mayer2ad38612020-08-06 10:35:31 +0100201 default=0)
Florian Mayer19ec17e2020-06-10 16:09:12 +0200202 # This flag is a no-op now. We never start heapprofd explicitly using system
203 # properties.
Primiano Tucci834fdc72019-10-04 11:33:44 +0100204 parser.add_argument(
205 "--no-start", help="Do not start heapprofd.", action='store_true')
206 parser.add_argument(
207 "-p",
208 "--pid",
209 help="Comma-separated list of PIDs to "
210 "profile.",
211 metavar="PIDS")
212 parser.add_argument(
213 "-n",
214 "--name",
215 help="Comma-separated list of process "
216 "names to profile.",
217 metavar="NAMES")
218 parser.add_argument(
219 "-c",
220 "--continuous-dump",
221 help="Dump interval in ms. 0 to disable continuous dump.",
222 type=int,
223 default=0)
224 parser.add_argument(
Florian Mayer39ddaad2020-06-25 19:58:00 +0200225 "--heaps",
226 help="Comma-separated list of heaps to collect, e.g: malloc,art. "
227 "Requires Android 12.",
228 metavar="HEAPS")
Hector Dearman091a54e2020-08-03 11:17:13 +0100229 parser.add_argument(
Florian Mayered4b1c02020-07-28 16:56:30 +0100230 "--all-heaps",
231 action="store_true",
232 help="Collect allocations from all heaps registered by target."
233 )
Florian Mayer39ddaad2020-06-25 19:58:00 +0200234 parser.add_argument(
Florian Mayercad84b72020-11-24 14:26:32 +0000235 "--no-android-tree-symbolization",
236 action="store_true",
237 help="Do not symbolize using currently lunched target in the "
238 "Android tree."
239 )
240 parser.add_argument(
Primiano Tucci834fdc72019-10-04 11:33:44 +0100241 "--disable-selinux",
242 action="store_true",
243 help="Disable SELinux enforcement for duration of "
244 "profile.")
245 parser.add_argument(
246 "--no-versions",
247 action="store_true",
248 help="Do not get version information about APKs.")
249 parser.add_argument(
250 "--no-running",
251 action="store_true",
Florian Mayer615fbc92019-12-18 16:29:07 +0000252 help="Do not target already running processes. Requires Android 11.")
Primiano Tucci834fdc72019-10-04 11:33:44 +0100253 parser.add_argument(
254 "--no-startup",
255 action="store_true",
256 help="Do not target processes that start during "
Florian Mayer615fbc92019-12-18 16:29:07 +0000257 "the profile. Requires Android 11.")
Primiano Tucci834fdc72019-10-04 11:33:44 +0100258 parser.add_argument(
259 "--shmem-size",
260 help="Size of buffer between client and "
261 "heapprofd. Default 8MiB. Needs to be a power of two "
262 "multiple of 4096, at least 8192.",
263 type=int,
264 default=8 * 1048576)
265 parser.add_argument(
266 "--block-client",
267 help="When buffer is full, block the "
268 "client to wait for buffer space. Use with caution as "
269 "this can significantly slow down the client. "
270 "This is the default",
271 action="store_true")
272 parser.add_argument(
Florian Mayere17af212019-11-13 10:04:03 +0000273 "--block-client-timeout",
274 help="If --block-client is given, do not block any allocation for "
275 "longer than this timeout (us).",
276 type=int)
277 parser.add_argument(
Primiano Tucci834fdc72019-10-04 11:33:44 +0100278 "--no-block-client",
279 help="When buffer is full, stop the "
280 "profile early.",
281 action="store_true")
282 parser.add_argument(
283 "--idle-allocations",
284 help="Keep track of how many "
285 "bytes were unused since the last dump, per "
286 "callstack",
287 action="store_true")
288 parser.add_argument(
289 "--dump-at-max",
Florian Mayer2fcb7dd2020-04-27 15:26:47 +0200290 help="Dump the maximum memory usage "
Primiano Tucci834fdc72019-10-04 11:33:44 +0100291 "rather than at the time of the dump.",
292 action="store_true")
293 parser.add_argument(
Florian Mayerf9cd8532020-04-21 12:18:10 +0200294 "--disable-fork-teardown",
295 help="Do not tear down client in forks. This can be useful for programs "
296 "that use vfork. Android 11+ only.",
297 action="store_true")
298 parser.add_argument(
Primiano Tucci834fdc72019-10-04 11:33:44 +0100299 "--simpleperf",
300 action="store_true",
301 help="Get simpleperf profile of heapprofd. This is "
302 "only for heapprofd development.")
303 parser.add_argument(
304 "--trace-to-text-binary",
305 help="Path to local trace to text. For debugging.")
306 parser.add_argument(
307 "--print-config",
308 action="store_true",
309 help="Print config instead of running. For debugging.")
Florian Mayere9a55c62020-04-14 16:39:34 +0200310 parser.add_argument(
311 "-o",
312 "--output",
313 help="Output directory.",
314 metavar="DIRECTORY",
315 default=None)
Florian Mayerbe1e4252021-05-26 15:49:21 +0100316 parser.add_argument(
317 "--print-options",
318 action="store_true",
319 help=argparse.SUPPRESS
320 )
Florian Mayer0eee91b2019-05-10 10:36:16 +0100321
Florian Mayer801349e2018-11-29 10:15:25 +0000322 args = parser.parse_args()
Florian Mayerbe1e4252021-05-26 15:49:21 +0100323 if args.print_options:
324 print_options(parser)
325 return 0
Florian Mayer801349e2018-11-29 10:15:25 +0000326 fail = False
Florian Mayerf40dedd2019-07-19 13:08:48 +0100327 if args.block_client and args.no_block_client:
Primiano Tucci834fdc72019-10-04 11:33:44 +0100328 print(
329 "FATAL: Both block-client and no-block-client given.", file=sys.stderr)
Florian Mayerf40dedd2019-07-19 13:08:48 +0100330 fail = True
Florian Mayera774cb72019-04-29 14:20:43 +0100331 if args.pid is None and args.name is None:
332 print("FATAL: Neither PID nor NAME given.", file=sys.stderr)
Florian Mayer801349e2018-11-29 10:15:25 +0000333 fail = True
334 if args.duration is None:
335 print("FATAL: No duration given.", file=sys.stderr)
336 fail = True
337 if args.interval is None:
338 print("FATAL: No interval given.", file=sys.stderr)
339 fail = True
Florian Mayer91b3c6d2019-04-10 13:44:37 -0700340 if args.shmem_size % 4096:
341 print("FATAL: shmem-size is not a multiple of 4096.", file=sys.stderr)
342 fail = True
343 if args.shmem_size < 8192:
344 print("FATAL: shmem-size is less than 8192.", file=sys.stderr)
345 fail = True
346 if args.shmem_size & (args.shmem_size - 1):
347 print("FATAL: shmem-size is not a power of two.", file=sys.stderr)
348 fail = True
Florian Mayer0eee91b2019-05-10 10:36:16 +0100349
Florian Mayer801349e2018-11-29 10:15:25 +0000350 target_cfg = ""
Florian Mayerf40dedd2019-07-19 13:08:48 +0100351 if not args.no_block_client:
Florian Mayer2ad38612020-08-06 10:35:31 +0100352 target_cfg += CFG_INDENT + "block_client: true\n"
Florian Mayere17af212019-11-13 10:04:03 +0000353 if args.block_client_timeout:
Florian Mayer2ad38612020-08-06 10:35:31 +0100354 target_cfg += (
355 CFG_INDENT + "block_client_timeout_us: %s\n" % args.block_client_timeout
356 )
Florian Mayer400e4432019-05-29 11:53:20 +0100357 if args.no_startup:
Florian Mayer2ad38612020-08-06 10:35:31 +0100358 target_cfg += CFG_INDENT + "no_startup: true\n"
Florian Mayer400e4432019-05-29 11:53:20 +0100359 if args.no_running:
Florian Mayer2ad38612020-08-06 10:35:31 +0100360 target_cfg += CFG_INDENT + "no_running: true\n"
Florian Mayer8707d4d2019-07-16 11:17:46 +0100361 if args.dump_at_max:
Florian Mayer2ad38612020-08-06 10:35:31 +0100362 target_cfg += CFG_INDENT + "dump_at_max: true\n"
Florian Mayerf9cd8532020-04-21 12:18:10 +0200363 if args.disable_fork_teardown:
Florian Mayer2ad38612020-08-06 10:35:31 +0100364 target_cfg += CFG_INDENT + "disable_fork_teardown: true\n"
Florian Mayered4b1c02020-07-28 16:56:30 +0100365 if args.all_heaps:
Florian Mayer2ad38612020-08-06 10:35:31 +0100366 target_cfg += CFG_INDENT + "all_heaps: true\n"
Florian Mayer801349e2018-11-29 10:15:25 +0000367 if args.pid:
Florian Mayer0eee91b2019-05-10 10:36:16 +0100368 for pid in args.pid.split(','):
369 try:
370 pid = int(pid)
371 except ValueError:
372 print("FATAL: invalid PID %s" % pid, file=sys.stderr)
373 fail = True
Florian Mayer2ad38612020-08-06 10:35:31 +0100374 target_cfg += CFG_INDENT + 'pid: {}\n'.format(pid)
Florian Mayer801349e2018-11-29 10:15:25 +0000375 if args.name:
Florian Mayer0eee91b2019-05-10 10:36:16 +0100376 for name in args.name.split(','):
Florian Mayer2ad38612020-08-06 10:35:31 +0100377 target_cfg += CFG_INDENT + 'process_cmdline: "{}"\n'.format(name)
Florian Mayer39ddaad2020-06-25 19:58:00 +0200378 if args.heaps:
379 for heap in args.heaps.split(','):
Florian Mayer2ad38612020-08-06 10:35:31 +0100380 target_cfg += CFG_INDENT + 'heaps: "{}"\n'.format(heap)
Florian Mayer801349e2018-11-29 10:15:25 +0000381
Florian Mayer0eee91b2019-05-10 10:36:16 +0100382 if fail:
383 parser.print_help()
384 return 1
385
Florian Mayer801349e2018-11-29 10:15:25 +0000386 trace_to_text_binary = args.trace_to_text_binary
Florian Mayer2ad38612020-08-06 10:35:31 +0100387
388 if args.continuous_dump:
389 target_cfg += CONTINUOUS_DUMP.format(dump_interval=args.continuous_dump)
390 cfg = CFG.format(
391 interval=args.interval,
392 duration=args.duration,
393 target_cfg=target_cfg,
394 shmem_size=args.shmem_size)
395 if not args.no_versions:
396 cfg += PACKAGES_LIST_CFG
397
398 if args.print_config:
399 print(cfg)
400 return 0
401
402 # Do this AFTER print_config so we do not download trace_to_text only to
403 # print out the config.
Florian Mayer2007d5c2020-09-08 15:45:44 +0100404 has_trace_to_text = True
Florian Mayer801349e2018-11-29 10:15:25 +0000405 if trace_to_text_binary is None:
Hector Dearmana318af72021-04-08 16:30:02 +0100406 os_name = None
Florian Mayer801349e2018-11-29 10:15:25 +0000407 if sys.platform.startswith('linux'):
Hector Dearmana318af72021-04-08 16:30:02 +0100408 os_name = 'linux'
Florian Mayer801349e2018-11-29 10:15:25 +0000409 elif sys.platform.startswith('darwin'):
Hector Dearmana318af72021-04-08 16:30:02 +0100410 os_name = 'mac'
Florian Mayer2007d5c2020-09-08 15:45:44 +0100411 elif sys.platform.startswith('win32'):
412 has_trace_to_text = False
Florian Mayer801349e2018-11-29 10:15:25 +0000413 else:
414 print("Invalid platform: {}".format(sys.platform), file=sys.stderr)
Florian Mayerb6279632018-11-29 13:31:49 +0000415 return 1
Florian Mayer801349e2018-11-29 10:15:25 +0000416
Hector Dearmana318af72021-04-08 16:30:02 +0100417 arch = platform.machine()
418 if arch not in ['x86_64', 'amd64']:
419 has_trace_to_text = False
420
Florian Mayer2007d5c2020-09-08 15:45:44 +0100421 if has_trace_to_text:
Hector Dearmana318af72021-04-08 16:30:02 +0100422 trace_to_text_binary = load_trace_to_text(os_name)
Florian Mayer801349e2018-11-29 10:15:25 +0000423
Florian Mayerc4de3912020-11-23 14:11:43 +0000424 known_issues = maybe_known_issues()
425 if known_issues:
426 print('If you are experiencing problems, please see the known issues for '
427 'your release: {}.'.format(known_issues))
428
Florian Mayer230b9552020-07-10 22:11:24 +0100429 # TODO(fmayer): Maybe feature detect whether we can remove traces instead of
430 # this.
431 uuid_trace = release_or_newer('R')
432 if uuid_trace:
433 profile_device_path = '/data/misc/perfetto-traces/profile-' + UUID
434 else:
435 user = subprocess.check_output(
436 ['adb', 'shell', 'whoami']).decode('utf-8').strip()
437 profile_device_path = '/data/misc/perfetto-traces/profile-' + user
438
439 perfetto_cmd = ('CFG=\'{cfg}\'; echo ${{CFG}} | '
440 'perfetto --txt -c - -o ' + profile_device_path + ' -d')
441
Florian Mayer6ae95262018-12-06 16:10:29 +0000442 if args.disable_selinux:
443 enforcing = subprocess.check_output(['adb', 'shell', 'getenforce'])
Primiano Tucci834fdc72019-10-04 11:33:44 +0100444 atexit.register(
445 subprocess.check_call,
Florian Mayer6ae95262018-12-06 16:10:29 +0000446 ['adb', 'shell', 'su root setenforce %s' % enforcing])
447 subprocess.check_call(['adb', 'shell', 'su root setenforce 0'])
Florian Mayer801349e2018-11-29 10:15:25 +0000448
Florian Mayer2b8a3b22019-05-02 18:35:38 +0100449 if args.simpleperf:
Primiano Tucci834fdc72019-10-04 11:33:44 +0100450 subprocess.check_call([
451 'adb', 'shell', 'mkdir -p /data/local/tmp/heapprofd_profile && '
452 'cd /data/local/tmp/heapprofd_profile &&'
453 '(nohup simpleperf record -g -p $(pidof heapprofd) 2>&1 &) '
454 '> /dev/null'
455 ])
Florian Mayer2b8a3b22019-05-02 18:35:38 +0100456
Florian Mayere9a55c62020-04-14 16:39:34 +0200457 profile_target = PROFILE_LOCAL_PATH
458 if args.output is not None:
459 profile_target = args.output
460 else:
461 os.mkdir(profile_target)
462
463 if not os.path.isdir(profile_target):
464 print("Output directory {} not found".format(profile_target),
465 file=sys.stderr)
466 return 1
467
468 if os.listdir(profile_target):
469 print("Output directory {} not empty".format(profile_target),
470 file=sys.stderr)
471 return 1
472
Florian Mayer801349e2018-11-29 10:15:25 +0000473 perfetto_pid = subprocess.check_output(
Primiano Tucci834fdc72019-10-04 11:33:44 +0100474 ['adb', 'exec-out',
Florian Mayer4fc59932020-04-28 17:19:55 +0200475 perfetto_cmd.format(cfg=cfg)]).strip()
Florian Mayer35647422019-03-07 16:28:10 +0000476 try:
Florian Mayer0bc32522020-04-28 16:35:55 +0200477 perfetto_pid = int(perfetto_pid.strip())
Florian Mayer35647422019-03-07 16:28:10 +0000478 except ValueError:
Primiano Tucci834fdc72019-10-04 11:33:44 +0100479 print("Failed to invoke perfetto: {}".format(perfetto_pid), file=sys.stderr)
Florian Mayer35647422019-03-07 16:28:10 +0000480 return 1
Florian Mayer801349e2018-11-29 10:15:25 +0000481
482 old_handler = signal.signal(signal.SIGINT, sigint_handler)
483 print("Profiling active. Press Ctrl+C to terminate.")
Florian Mayerbd0a62a2019-04-10 11:09:21 +0100484 print("You may disconnect your device.")
Florian Mayer96f607b2020-01-14 12:57:41 +0000485 print()
Florian Mayer801349e2018-11-29 10:15:25 +0000486 exists = True
Florian Mayerbd0a62a2019-04-10 11:09:21 +0100487 device_connected = True
488 while not device_connected or (exists and not IS_INTERRUPTED):
Florian Mayer801349e2018-11-29 10:15:25 +0000489 exists = subprocess.call(
Primiano Tucci834fdc72019-10-04 11:33:44 +0100490 ['adb', 'shell', '[ -d /proc/{} ]'.format(perfetto_pid)], **NOOUT) == 0
Florian Mayerbd0a62a2019-04-10 11:09:21 +0100491 device_connected = subprocess.call(['adb', 'shell', 'true'], **NOOUT) == 0
Florian Mayer801349e2018-11-29 10:15:25 +0000492 time.sleep(1)
Florian Mayer2007d5c2020-09-08 15:45:44 +0100493 print("Waiting for profiler shutdown...")
Florian Mayer801349e2018-11-29 10:15:25 +0000494 signal.signal(signal.SIGINT, old_handler)
495 if IS_INTERRUPTED:
496 # Not check_call because it could have existed in the meantime.
Florian Mayer0bc32522020-04-28 16:35:55 +0200497 subprocess.call(['adb', 'shell', 'kill', '-INT', str(perfetto_pid)])
Florian Mayer2b8a3b22019-05-02 18:35:38 +0100498 if args.simpleperf:
499 subprocess.check_call(['adb', 'shell', 'killall', '-INT', 'simpleperf'])
500 print("Waiting for simpleperf to exit.")
501 while subprocess.call(
Primiano Tucci834fdc72019-10-04 11:33:44 +0100502 ['adb', 'shell', '[ -f /proc/$(pidof simpleperf)/exe ]'], **NOOUT) == 0:
Florian Mayer2b8a3b22019-05-02 18:35:38 +0100503 time.sleep(1)
Primiano Tucci834fdc72019-10-04 11:33:44 +0100504 subprocess.check_call(
Florian Mayer3c57f3f2020-10-01 14:58:08 +0100505 ['adb', 'pull', '/data/local/tmp/heapprofd_profile', profile_target])
506 print(
507 "Pulled simpleperf profile to " + profile_target + "/heapprofd_profile")
Florian Mayer801349e2018-11-29 10:15:25 +0000508
Florian Mayerddbe31e2018-11-30 14:49:30 +0000509 # Wait for perfetto cmd to return.
510 while exists:
511 exists = subprocess.call(
512 ['adb', 'shell', '[ -d /proc/{} ]'.format(perfetto_pid)]) == 0
513 time.sleep(1)
514
Florian Mayer2007d5c2020-09-08 15:45:44 +0100515 profile_host_path = os.path.join(profile_target, 'raw-trace')
516 subprocess.check_call(
517 ['adb', 'pull', profile_device_path, profile_host_path], stdout=NULL)
Florian Mayer4fc59932020-04-28 17:19:55 +0200518 if uuid_trace:
519 subprocess.check_call(
520 ['adb', 'shell', 'rm', profile_device_path], stdout=NULL)
Florian Mayer213c8d42019-12-18 17:10:34 +0000521
Florian Mayer2007d5c2020-09-08 15:45:44 +0100522 if not has_trace_to_text:
523 print('Wrote profile to {}'.format(profile_host_path))
524 print('This file can be opened using the Perfetto UI, https://ui.perfetto.dev')
525 return 0
526
Florian Mayercad84b72020-11-24 14:26:32 +0000527 binary_path = os.getenv('PERFETTO_BINARY_PATH')
528 if not args.no_android_tree_symbolization:
529 product_out = os.getenv('ANDROID_PRODUCT_OUT')
530 if product_out:
531 product_out_symbols = product_out + '/symbols'
532 else:
533 product_out_symbols = None
534
535 if binary_path is None:
536 binary_path = product_out_symbols
537 elif product_out_symbols is not None:
538 binary_path += ":" + product_out_symbols
539
540 trace_file = os.path.join(profile_target, 'raw-trace')
Florian Mayeredc48102020-12-15 19:38:44 +0000541 concat_files = [trace_file]
Florian Mayercad84b72020-11-24 14:26:32 +0000542
543 if binary_path is not None:
544 with open(os.path.join(profile_target, 'symbols'), 'w') as fd:
545 ret = subprocess.call([
546 trace_to_text_binary, 'symbolize',
Florian Mayere9a55c62020-04-14 16:39:34 +0200547 os.path.join(profile_target, 'raw-trace')],
Florian Mayercad84b72020-11-24 14:26:32 +0000548 env=dict(os.environ, PERFETTO_BINARY_PATH=binary_path),
549 stdout=fd)
550 if ret == 0:
Florian Mayeredc48102020-12-15 19:38:44 +0000551 concat_files.append(os.path.join(profile_target, 'symbols'))
Florian Mayercad84b72020-11-24 14:26:32 +0000552 else:
553 print("Failed to symbolize. Continuing without symbols.",
554 file=sys.stderr)
555
Florian Mayeredc48102020-12-15 19:38:44 +0000556 proguard_map = os.getenv('PERFETTO_PROGUARD_MAP')
557 if proguard_map is not None:
558 with open(os.path.join(profile_target, 'deobfuscation-packets'), 'w') as fd:
559 ret = subprocess.call([
560 trace_to_text_binary, 'deobfuscate',
561 os.path.join(profile_target, 'raw-trace')],
562 env=dict(os.environ, PERFETTO_PROGUARD_MAP=proguard_map),
563 stdout=fd)
564 if ret == 0:
565 concat_files.append(
566 os.path.join(profile_target, 'deobfuscation-packets'))
567 else:
568 print("Failed to deobfuscate. Continuing without deobfuscated.",
569 file=sys.stderr)
570
571 if len(concat_files) > 1:
Isaac Nickaein1ca57282021-03-15 16:10:59 +0000572 with open(os.path.join(profile_target, 'symbolized-trace'), 'wb') as out:
Florian Mayeredc48102020-12-15 19:38:44 +0000573 for fn in concat_files:
Isaac Nickaein1ca57282021-03-15 16:10:59 +0000574 with open(fn, 'rb') as inp:
Florian Mayeredc48102020-12-15 19:38:44 +0000575 while True:
576 buf = inp.read(4096)
577 if not buf:
578 break
579 out.write(buf)
580 trace_file = os.path.join(profile_target, 'symbolized-trace')
581
Florian Mayercad84b72020-11-24 14:26:32 +0000582 trace_to_text_output = subprocess.check_output(
583 [trace_to_text_binary, 'profile', trace_file])
Florian Mayer801349e2018-11-29 10:15:25 +0000584 profile_path = None
Florian Mayer0bc32522020-04-28 16:35:55 +0200585 for word in trace_to_text_output.decode('utf-8').split():
Florian Mayer801349e2018-11-29 10:15:25 +0000586 if 'heap_profile-' in word:
587 profile_path = word
588 if profile_path is None:
Florian Mayer88bca762020-07-28 14:42:15 +0100589 print_no_profile_error()
Florian Mayer801349e2018-11-29 10:15:25 +0000590 return 1
591
592 profile_files = os.listdir(profile_path)
593 if not profile_files:
Florian Mayer88bca762020-07-28 14:42:15 +0100594 print_no_profile_error()
Florian Mayer801349e2018-11-29 10:15:25 +0000595 return 1
596
Florian Mayere9a55c62020-04-14 16:39:34 +0200597 for profile_file in profile_files:
598 shutil.copy(os.path.join(profile_path, profile_file), profile_target)
599
Primiano Tucci834fdc72019-10-04 11:33:44 +0100600 subprocess.check_call(
601 ['gzip'] +
Florian Mayere9a55c62020-04-14 16:39:34 +0200602 [os.path.join(profile_target, x) for x in profile_files])
Florian Mayer82f43d12019-01-17 14:37:45 +0000603
Florian Mayere9a55c62020-04-14 16:39:34 +0200604 symlink_path = None
605 if args.output is None:
Florian Mayer88bca762020-07-28 14:42:15 +0100606 symlink_path = os.path.join(
607 os.path.dirname(profile_target), "heap_profile-latest")
608 if os.path.lexists(symlink_path):
609 os.unlink(symlink_path)
610 os.symlink(profile_target, symlink_path)
Florian Mayer82f43d12019-01-17 14:37:45 +0000611
Florian Mayere9a55c62020-04-14 16:39:34 +0200612 if symlink_path is not None:
613 print("Wrote profiles to {} (symlink {})".format(
614 profile_target, symlink_path))
615 else:
616 print("Wrote profiles to {}".format(profile_target))
617
Florian Mayer801349e2018-11-29 10:15:25 +0000618 print("These can be viewed using pprof. Googlers: head to pprof/ and "
619 "upload them.")
620
621
622if __name__ == '__main__':
623 sys.exit(main(sys.argv))