• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
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
27import uuid
28
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
54data_source_stop_timeout_ms: {data_source_stop_timeout_ms}
55duration_ms: {duration_ms}
56'''
57
58CONTINUOUS_DUMP = """
59      continuous_dump_config {{
60        dump_phase_ms: 0
61        dump_interval_ms: {dump_interval}
62      }}
63"""
64
65UUID = str(uuid.uuid4())[-6:]
66PROFILE_PATH = '/data/misc/perfetto-traces/java-profile-' +  UUID
67
68PERFETTO_CMD = ('CFG=\'{cfg}\'; echo ${{CFG}} | '
69                'perfetto --txt -c - -o ' + PROFILE_PATH + ' -d')
70
71SDK = {
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
85
86def 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(
117      "--dump-smaps",
118      action="store_true",
119      help="Get information about /proc/$PID/smaps of target.")
120  parser.add_argument(
121      "--print-config",
122      action="store_true",
123      help="Print config instead of running. For debugging.")
124  parser.add_argument(
125      "--stop-when-done",
126      action="store_true",
127      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.")
135
136  args = parser.parse_args()
137
138  if args.stop_when_done is None:
139    args.stop_when_done = release_or_newer('S')
140
141  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)
158  if args.dump_smaps:
159    target_cfg += '{}dump_smaps: true\n'.format(CFG_IDENT)
160
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)
175
176  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
183  cfg = CFG.format(
184      target_cfg=target_cfg,
185      continuous_dump_config=continuous_dump_cfg,
186      duration_ms=duration_ms,
187      data_source_stop_timeout_ms=data_source_stop_timeout_ms)
188  if not args.no_versions:
189    cfg += PACKAGES_LIST_CFG
190
191  if args.print_config:
192    print(cfg)
193    return 0
194
195  user = subprocess.check_output(
196    ['adb', 'shell', 'whoami']).strip().decode('utf8')
197  perfetto_pid = subprocess.check_output(
198      ['adb', 'exec-out',
199       PERFETTO_CMD.format(cfg=cfg, user=user)]).strip().decode('utf8')
200  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(
216    ['adb', 'pull', PROFILE_PATH, output_file], stdout=NULL)
217
218  subprocess.check_call(
219        ['adb', 'shell', 'rm', PROFILE_PATH], stdout=NULL)
220
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))
227