blob: a0e69220ed4380d9ed6dca4fbc7062a6bdb074c9 [file] [log] [blame]
Florian Mayerc8aa81c2021-04-19 15:16:15 +01001#!/usr/bin/env python3
Florian Mayera8ff9032020-03-04 11:31:48 -08002
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
Florian Mayer0ac0e742021-05-19 15:46:59 +010027import uuid
Florian Mayera8ff9032020-03-04 11:31:48 -080028
29NULL = open(os.devnull)
30
31PACKAGES_LIST_CFG = '''data_sources {
32 config {
33 name: "android.packages_list"
34 }
35}
36'''
37
38CFG_IDENT = ' '
39CFG = '''buffers {{
40 size_kb: 100024
41 fill_policy: RING_BUFFER
42}}
43
44data_sources {{
45 config {{
46 name: "android.java_hprof"
47 java_hprof_config {{
48{target_cfg}
49{continuous_dump_config}
50 }}
51 }}
52}}
53
Florian Mayerbab93652020-09-25 13:58:31 +010054data_source_stop_timeout_ms: {data_source_stop_timeout_ms}
55duration_ms: {duration_ms}
Florian Mayera8ff9032020-03-04 11:31:48 -080056'''
57
58CONTINUOUS_DUMP = """
59 continuous_dump_config {{
60 dump_phase_ms: 0
61 dump_interval_ms: {dump_interval}
62 }}
63"""
64
Florian Mayer0ac0e742021-05-19 15:46:59 +010065UUID = str(uuid.uuid4())[-6:]
66PROFILE_PATH = '/data/misc/perfetto-traces/java-profile-' + UUID
67
Florian Mayera8ff9032020-03-04 11:31:48 -080068PERFETTO_CMD = ('CFG=\'{cfg}\'; echo ${{CFG}} | '
Florian Mayer0ac0e742021-05-19 15:46:59 +010069 'perfetto --txt -c - -o ' + PROFILE_PATH + ' -d')
Florian Mayera8ff9032020-03-04 11:31:48 -080070
Florian Mayer9a1689e2021-05-19 15:47:25 +010071SDK = {
72 'S': 31,
73}
74
75def release_or_newer(release):
76 sdk = int(subprocess.check_output(
77 ['adb', 'shell', 'getprop', 'ro.system.build.version.sdk']
78 ).decode('utf-8').strip())
79 if sdk >= SDK[release]:
80 return True
81 codename = subprocess.check_output(
82 ['adb', 'shell', 'getprop', 'ro.build.version.codename']
83 ).decode('utf-8').strip()
84 return codename == release
Florian Mayerbab93652020-09-25 13:58:31 +010085
Florian Mayera8ff9032020-03-04 11:31:48 -080086def main(argv):
87 parser = argparse.ArgumentParser()
88 parser.add_argument(
89 "-o",
90 "--output",
91 help="Filename to save profile to.",
92 metavar="FILE",
93 default=None)
94 parser.add_argument(
95 "-p",
96 "--pid",
97 help="Comma-separated list of PIDs to "
98 "profile.",
99 metavar="PIDS")
100 parser.add_argument(
101 "-n",
102 "--name",
103 help="Comma-separated list of process "
104 "names to profile.",
105 metavar="NAMES")
106 parser.add_argument(
107 "-c",
108 "--continuous-dump",
109 help="Dump interval in ms. 0 to disable continuous dump.",
110 type=int,
111 default=0)
112 parser.add_argument(
113 "--no-versions",
114 action="store_true",
115 help="Do not get version information about APKs.")
116 parser.add_argument(
Florian Mayere3eed3a2020-04-04 11:57:30 +0200117 "--dump-smaps",
118 action="store_true",
119 help="Get information about /proc/$PID/smaps of target.")
120 parser.add_argument(
Florian Mayera8ff9032020-03-04 11:31:48 -0800121 "--print-config",
122 action="store_true",
123 help="Print config instead of running. For debugging.")
Florian Mayerbab93652020-09-25 13:58:31 +0100124 parser.add_argument(
Florian Mayer70f1e982020-11-11 15:54:44 +0000125 "--stop-when-done",
Florian Mayerbab93652020-09-25 13:58:31 +0100126 action="store_true",
Florian Mayer9a1689e2021-05-19 15:47:25 +0100127 default=None,
128 help="Use a new method to stop the profile when the dump is done. "
129 "Previously, we would hardcode a duration. Available and default on S.")
130 parser.add_argument(
131 "--no-stop-when-done",
132 action="store_false",
133 dest='stop_when_done',
134 help="Do not use a new method to stop the profile when the dump is done.")
Florian Mayer70f1e982020-11-11 15:54:44 +0000135
Florian Mayera8ff9032020-03-04 11:31:48 -0800136 args = parser.parse_args()
137
Florian Mayer9a1689e2021-05-19 15:47:25 +0100138 if args.stop_when_done is None:
139 args.stop_when_done = release_or_newer('S')
140
Florian Mayera8ff9032020-03-04 11:31:48 -0800141 fail = False
142 if args.pid is None and args.name is None:
143 print("FATAL: Neither PID nor NAME given.", file=sys.stderr)
144 fail = True
145
146 target_cfg = ""
147 if args.pid:
148 for pid in args.pid.split(','):
149 try:
150 pid = int(pid)
151 except ValueError:
152 print("FATAL: invalid PID %s" % pid, file=sys.stderr)
153 fail = True
154 target_cfg += '{}pid: {}\n'.format(CFG_IDENT, pid)
155 if args.name:
156 for name in args.name.split(','):
157 target_cfg += '{}process_cmdline: "{}"\n'.format(CFG_IDENT, name)
Florian Mayere3eed3a2020-04-04 11:57:30 +0200158 if args.dump_smaps:
159 target_cfg += '{}dump_smaps: true\n'.format(CFG_IDENT)
Florian Mayera8ff9032020-03-04 11:31:48 -0800160
161 if fail:
162 parser.print_help()
163 return 1
164
165 output_file = args.output
166 if output_file is None:
167 fd, name = tempfile.mkstemp('profile')
168 os.close(fd)
169 output_file = name
170
171 continuous_dump_cfg = ""
172 if args.continuous_dump:
173 continuous_dump_cfg = CONTINUOUS_DUMP.format(
174 dump_interval=args.continuous_dump)
Florian Mayerbab93652020-09-25 13:58:31 +0100175
Florian Mayerbab93652020-09-25 13:58:31 +0100176 if args.stop_when_done:
177 duration_ms = 1000
178 data_source_stop_timeout_ms = 100000
179 else:
180 duration_ms = 20000
181 data_source_stop_timeout_ms = 0
182
Florian Mayera8ff9032020-03-04 11:31:48 -0800183 cfg = CFG.format(
184 target_cfg=target_cfg,
Florian Mayerbab93652020-09-25 13:58:31 +0100185 continuous_dump_config=continuous_dump_cfg,
186 duration_ms=duration_ms,
187 data_source_stop_timeout_ms=data_source_stop_timeout_ms)
Florian Mayera8ff9032020-03-04 11:31:48 -0800188 if not args.no_versions:
189 cfg += PACKAGES_LIST_CFG
190
191 if args.print_config:
192 print(cfg)
193 return 0
194
Florian Mayerc8aa81c2021-04-19 15:16:15 +0100195 user = subprocess.check_output(
196 ['adb', 'shell', 'whoami']).strip().decode('utf8')
Florian Mayera8ff9032020-03-04 11:31:48 -0800197 perfetto_pid = subprocess.check_output(
198 ['adb', 'exec-out',
Florian Mayerc8aa81c2021-04-19 15:16:15 +0100199 PERFETTO_CMD.format(cfg=cfg, user=user)]).strip().decode('utf8')
Florian Mayera8ff9032020-03-04 11:31:48 -0800200 try:
201 int(perfetto_pid.strip())
202 except ValueError:
203 print("Failed to invoke perfetto: {}".format(perfetto_pid), file=sys.stderr)
204 return 1
205
206 print("Dumping Java Heap.")
207 exists = True
208
209 # Wait for perfetto cmd to return.
210 while exists:
211 exists = subprocess.call(
212 ['adb', 'shell', '[ -d /proc/{} ]'.format(perfetto_pid)]) == 0
213 time.sleep(1)
214
215 subprocess.check_call(
Florian Mayer0ac0e742021-05-19 15:46:59 +0100216 ['adb', 'pull', PROFILE_PATH, output_file], stdout=NULL)
217
218 subprocess.check_call(
219 ['adb', 'shell', 'rm', PROFILE_PATH], stdout=NULL)
Florian Mayera8ff9032020-03-04 11:31:48 -0800220
221 print("Wrote profile to {}".format(output_file))
222 print("This can be viewed using https://ui.perfetto.dev.")
223
224
225if __name__ == '__main__':
226 sys.exit(main(sys.argv))