1#!/usr/bin/env python 2 3# Copyright (C) 2020 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 os 23import subprocess 24import sys 25import tempfile 26import time 27 28NULL = open(os.devnull) 29 30PACKAGES_LIST_CFG = '''data_sources { 31 config { 32 name: "android.packages_list" 33 } 34} 35''' 36 37CFG_IDENT = ' ' 38CFG = '''buffers {{ 39 size_kb: 100024 40 fill_policy: RING_BUFFER 41}} 42 43data_sources {{ 44 config {{ 45 name: "android.java_hprof" 46 java_hprof_config {{ 47{target_cfg} 48{continuous_dump_config} 49 }} 50 }} 51}} 52 53duration_ms: 20000 54''' 55 56CONTINUOUS_DUMP = """ 57 continuous_dump_config {{ 58 dump_phase_ms: 0 59 dump_interval_ms: {dump_interval} 60 }} 61""" 62 63PERFETTO_CMD = ('CFG=\'{cfg}\'; echo ${{CFG}} | ' 64 'perfetto --txt -c - -o ' 65 '/data/misc/perfetto-traces/java-profile-{user} -d') 66 67def main(argv): 68 parser = argparse.ArgumentParser() 69 parser.add_argument( 70 "-o", 71 "--output", 72 help="Filename to save profile to.", 73 metavar="FILE", 74 default=None) 75 parser.add_argument( 76 "-p", 77 "--pid", 78 help="Comma-separated list of PIDs to " 79 "profile.", 80 metavar="PIDS") 81 parser.add_argument( 82 "-n", 83 "--name", 84 help="Comma-separated list of process " 85 "names to profile.", 86 metavar="NAMES") 87 parser.add_argument( 88 "-c", 89 "--continuous-dump", 90 help="Dump interval in ms. 0 to disable continuous dump.", 91 type=int, 92 default=0) 93 parser.add_argument( 94 "--no-versions", 95 action="store_true", 96 help="Do not get version information about APKs.") 97 parser.add_argument( 98 "--dump-smaps", 99 action="store_true", 100 help="Get information about /proc/$PID/smaps of target.") 101 parser.add_argument( 102 "--print-config", 103 action="store_true", 104 help="Print config instead of running. For debugging.") 105 106 args = parser.parse_args() 107 108 fail = False 109 if args.pid is None and args.name is None: 110 print("FATAL: Neither PID nor NAME given.", file=sys.stderr) 111 fail = True 112 113 target_cfg = "" 114 if args.pid: 115 for pid in args.pid.split(','): 116 try: 117 pid = int(pid) 118 except ValueError: 119 print("FATAL: invalid PID %s" % pid, file=sys.stderr) 120 fail = True 121 target_cfg += '{}pid: {}\n'.format(CFG_IDENT, pid) 122 if args.name: 123 for name in args.name.split(','): 124 target_cfg += '{}process_cmdline: "{}"\n'.format(CFG_IDENT, name) 125 if args.dump_smaps: 126 target_cfg += '{}dump_smaps: true\n'.format(CFG_IDENT) 127 128 if fail: 129 parser.print_help() 130 return 1 131 132 output_file = args.output 133 if output_file is None: 134 fd, name = tempfile.mkstemp('profile') 135 os.close(fd) 136 output_file = name 137 138 continuous_dump_cfg = "" 139 if args.continuous_dump: 140 continuous_dump_cfg = CONTINUOUS_DUMP.format( 141 dump_interval=args.continuous_dump) 142 cfg = CFG.format( 143 target_cfg=target_cfg, 144 continuous_dump_config=continuous_dump_cfg) 145 if not args.no_versions: 146 cfg += PACKAGES_LIST_CFG 147 148 if args.print_config: 149 print(cfg) 150 return 0 151 152 user = subprocess.check_output(['adb', 'shell', 'whoami']).strip() 153 perfetto_pid = subprocess.check_output( 154 ['adb', 'exec-out', 155 PERFETTO_CMD.format(cfg=cfg, user=user)]).strip() 156 try: 157 int(perfetto_pid.strip()) 158 except ValueError: 159 print("Failed to invoke perfetto: {}".format(perfetto_pid), file=sys.stderr) 160 return 1 161 162 print("Dumping Java Heap.") 163 exists = True 164 165 # Wait for perfetto cmd to return. 166 while exists: 167 exists = subprocess.call( 168 ['adb', 'shell', '[ -d /proc/{} ]'.format(perfetto_pid)]) == 0 169 time.sleep(1) 170 171 subprocess.check_call( 172 ['adb', 'pull', '/data/misc/perfetto-traces/java-profile-{}'.format(user), 173 output_file], stdout=NULL) 174 175 print("Wrote profile to {}".format(output_file)) 176 print("This can be viewed using https://ui.perfetto.dev.") 177 178 179if __name__ == '__main__': 180 sys.exit(main(sys.argv)) 181