blob: 8e0f31fdf89eeacc3b694c5dd65a67fd6abf1c04 [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
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
Florian Mayerbab93652020-09-25 13:58:31 +010053data_source_stop_timeout_ms: {data_source_stop_timeout_ms}
54duration_ms: {duration_ms}
Florian Mayera8ff9032020-03-04 11:31:48 -080055'''
56
57CONTINUOUS_DUMP = """
58 continuous_dump_config {{
59 dump_phase_ms: 0
60 dump_interval_ms: {dump_interval}
61 }}
62"""
63
64PERFETTO_CMD = ('CFG=\'{cfg}\'; echo ${{CFG}} | '
65 'perfetto --txt -c - -o '
66 '/data/misc/perfetto-traces/java-profile-{user} -d')
67
Florian Mayer9a1689e2021-05-19 15:47:25 +010068SDK = {
69 'S': 31,
70}
71
72def release_or_newer(release):
73 sdk = int(subprocess.check_output(
74 ['adb', 'shell', 'getprop', 'ro.system.build.version.sdk']
75 ).decode('utf-8').strip())
76 if sdk >= SDK[release]:
77 return True
78 codename = subprocess.check_output(
79 ['adb', 'shell', 'getprop', 'ro.build.version.codename']
80 ).decode('utf-8').strip()
81 return codename == release
Florian Mayerbab93652020-09-25 13:58:31 +010082
Florian Mayera8ff9032020-03-04 11:31:48 -080083def main(argv):
84 parser = argparse.ArgumentParser()
85 parser.add_argument(
86 "-o",
87 "--output",
88 help="Filename to save profile to.",
89 metavar="FILE",
90 default=None)
91 parser.add_argument(
92 "-p",
93 "--pid",
94 help="Comma-separated list of PIDs to "
95 "profile.",
96 metavar="PIDS")
97 parser.add_argument(
98 "-n",
99 "--name",
100 help="Comma-separated list of process "
101 "names to profile.",
102 metavar="NAMES")
103 parser.add_argument(
104 "-c",
105 "--continuous-dump",
106 help="Dump interval in ms. 0 to disable continuous dump.",
107 type=int,
108 default=0)
109 parser.add_argument(
110 "--no-versions",
111 action="store_true",
112 help="Do not get version information about APKs.")
113 parser.add_argument(
Florian Mayere3eed3a2020-04-04 11:57:30 +0200114 "--dump-smaps",
115 action="store_true",
116 help="Get information about /proc/$PID/smaps of target.")
117 parser.add_argument(
Florian Mayera8ff9032020-03-04 11:31:48 -0800118 "--print-config",
119 action="store_true",
120 help="Print config instead of running. For debugging.")
Florian Mayerbab93652020-09-25 13:58:31 +0100121 parser.add_argument(
Florian Mayer70f1e982020-11-11 15:54:44 +0000122 "--stop-when-done",
Florian Mayerbab93652020-09-25 13:58:31 +0100123 action="store_true",
Florian Mayer9a1689e2021-05-19 15:47:25 +0100124 default=None,
125 help="Use a new method to stop the profile when the dump is done. "
126 "Previously, we would hardcode a duration. Available and default on S.")
127 parser.add_argument(
128 "--no-stop-when-done",
129 action="store_false",
130 dest='stop_when_done',
131 help="Do not use a new method to stop the profile when the dump is done.")
Florian Mayer70f1e982020-11-11 15:54:44 +0000132
Florian Mayera8ff9032020-03-04 11:31:48 -0800133 args = parser.parse_args()
134
Florian Mayer9a1689e2021-05-19 15:47:25 +0100135 if args.stop_when_done is None:
136 args.stop_when_done = release_or_newer('S')
137
Florian Mayera8ff9032020-03-04 11:31:48 -0800138 fail = False
139 if args.pid is None and args.name is None:
140 print("FATAL: Neither PID nor NAME given.", file=sys.stderr)
141 fail = True
142
143 target_cfg = ""
144 if args.pid:
145 for pid in args.pid.split(','):
146 try:
147 pid = int(pid)
148 except ValueError:
149 print("FATAL: invalid PID %s" % pid, file=sys.stderr)
150 fail = True
151 target_cfg += '{}pid: {}\n'.format(CFG_IDENT, pid)
152 if args.name:
153 for name in args.name.split(','):
154 target_cfg += '{}process_cmdline: "{}"\n'.format(CFG_IDENT, name)
Florian Mayere3eed3a2020-04-04 11:57:30 +0200155 if args.dump_smaps:
156 target_cfg += '{}dump_smaps: true\n'.format(CFG_IDENT)
Florian Mayera8ff9032020-03-04 11:31:48 -0800157
158 if fail:
159 parser.print_help()
160 return 1
161
162 output_file = args.output
163 if output_file is None:
164 fd, name = tempfile.mkstemp('profile')
165 os.close(fd)
166 output_file = name
167
168 continuous_dump_cfg = ""
169 if args.continuous_dump:
170 continuous_dump_cfg = CONTINUOUS_DUMP.format(
171 dump_interval=args.continuous_dump)
Florian Mayerbab93652020-09-25 13:58:31 +0100172
173 # TODO(fmayer): Once the changes have been in S for long enough, make this
174 # the default for S+.
175 if args.stop_when_done:
176 duration_ms = 1000
177 data_source_stop_timeout_ms = 100000
178 else:
179 duration_ms = 20000
180 data_source_stop_timeout_ms = 0
181
Florian Mayera8ff9032020-03-04 11:31:48 -0800182 cfg = CFG.format(
183 target_cfg=target_cfg,
Florian Mayerbab93652020-09-25 13:58:31 +0100184 continuous_dump_config=continuous_dump_cfg,
185 duration_ms=duration_ms,
186 data_source_stop_timeout_ms=data_source_stop_timeout_ms)
Florian Mayera8ff9032020-03-04 11:31:48 -0800187 if not args.no_versions:
188 cfg += PACKAGES_LIST_CFG
189
190 if args.print_config:
191 print(cfg)
192 return 0
193
Florian Mayerc8aa81c2021-04-19 15:16:15 +0100194 user = subprocess.check_output(
195 ['adb', 'shell', 'whoami']).strip().decode('utf8')
Florian Mayera8ff9032020-03-04 11:31:48 -0800196 perfetto_pid = subprocess.check_output(
197 ['adb', 'exec-out',
Florian Mayerc8aa81c2021-04-19 15:16:15 +0100198 PERFETTO_CMD.format(cfg=cfg, user=user)]).strip().decode('utf8')
Florian Mayera8ff9032020-03-04 11:31:48 -0800199 try:
200 int(perfetto_pid.strip())
201 except ValueError:
202 print("Failed to invoke perfetto: {}".format(perfetto_pid), file=sys.stderr)
203 return 1
204
205 print("Dumping Java Heap.")
206 exists = True
207
208 # Wait for perfetto cmd to return.
209 while exists:
210 exists = subprocess.call(
211 ['adb', 'shell', '[ -d /proc/{} ]'.format(perfetto_pid)]) == 0
212 time.sleep(1)
213
214 subprocess.check_call(
215 ['adb', 'pull', '/data/misc/perfetto-traces/java-profile-{}'.format(user),
216 output_file], stdout=NULL)
217
218 print("Wrote profile to {}".format(output_file))
219 print("This can be viewed using https://ui.perfetto.dev.")
220
221
222if __name__ == '__main__':
223 sys.exit(main(sys.argv))