• 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
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
53data_source_stop_timeout_ms: {data_source_stop_timeout_ms}
54duration_ms: {duration_ms}
55'''
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
68
69def main(argv):
70  parser = argparse.ArgumentParser()
71  parser.add_argument(
72      "-o",
73      "--output",
74      help="Filename to save profile to.",
75      metavar="FILE",
76      default=None)
77  parser.add_argument(
78      "-p",
79      "--pid",
80      help="Comma-separated list of PIDs to "
81      "profile.",
82      metavar="PIDS")
83  parser.add_argument(
84      "-n",
85      "--name",
86      help="Comma-separated list of process "
87      "names to profile.",
88      metavar="NAMES")
89  parser.add_argument(
90      "-c",
91      "--continuous-dump",
92      help="Dump interval in ms. 0 to disable continuous dump.",
93      type=int,
94      default=0)
95  parser.add_argument(
96      "--no-versions",
97      action="store_true",
98      help="Do not get version information about APKs.")
99  parser.add_argument(
100      "--dump-smaps",
101      action="store_true",
102      help="Get information about /proc/$PID/smaps of target.")
103  parser.add_argument(
104      "--print-config",
105      action="store_true",
106      help="Print config instead of running. For debugging.")
107  parser.add_argument(
108      "--stop-when-done",
109      action="store_true",
110      help="On recent builds of S, use a new method to stop the profile when "
111           "the dump is done. Previously, we would hardcode a duration.")
112
113  args = parser.parse_args()
114
115  fail = False
116  if args.pid is None and args.name is None:
117    print("FATAL: Neither PID nor NAME given.", file=sys.stderr)
118    fail = True
119
120  target_cfg = ""
121  if args.pid:
122    for pid in args.pid.split(','):
123      try:
124        pid = int(pid)
125      except ValueError:
126        print("FATAL: invalid PID %s" % pid, file=sys.stderr)
127        fail = True
128      target_cfg += '{}pid: {}\n'.format(CFG_IDENT, pid)
129  if args.name:
130    for name in args.name.split(','):
131      target_cfg += '{}process_cmdline: "{}"\n'.format(CFG_IDENT, name)
132  if args.dump_smaps:
133    target_cfg += '{}dump_smaps: true\n'.format(CFG_IDENT)
134
135  if fail:
136    parser.print_help()
137    return 1
138
139  output_file = args.output
140  if output_file is None:
141    fd, name = tempfile.mkstemp('profile')
142    os.close(fd)
143    output_file = name
144
145  continuous_dump_cfg = ""
146  if args.continuous_dump:
147    continuous_dump_cfg = CONTINUOUS_DUMP.format(
148        dump_interval=args.continuous_dump)
149
150  # TODO(fmayer): Once the changes have been in S for long enough, make this
151  #               the default for S+.
152  if args.stop_when_done:
153    duration_ms = 1000
154    data_source_stop_timeout_ms = 100000
155  else:
156    duration_ms = 20000
157    data_source_stop_timeout_ms = 0
158
159  cfg = CFG.format(
160      target_cfg=target_cfg,
161      continuous_dump_config=continuous_dump_cfg,
162      duration_ms=duration_ms,
163      data_source_stop_timeout_ms=data_source_stop_timeout_ms)
164  if not args.no_versions:
165    cfg += PACKAGES_LIST_CFG
166
167  if args.print_config:
168    print(cfg)
169    return 0
170
171  user = subprocess.check_output(
172    ['adb', 'shell', 'whoami']).strip().decode('utf8')
173  perfetto_pid = subprocess.check_output(
174      ['adb', 'exec-out',
175       PERFETTO_CMD.format(cfg=cfg, user=user)]).strip().decode('utf8')
176  try:
177    int(perfetto_pid.strip())
178  except ValueError:
179    print("Failed to invoke perfetto: {}".format(perfetto_pid), file=sys.stderr)
180    return 1
181
182  print("Dumping Java Heap.")
183  exists = True
184
185  # Wait for perfetto cmd to return.
186  while exists:
187    exists = subprocess.call(
188        ['adb', 'shell', '[ -d /proc/{} ]'.format(perfetto_pid)]) == 0
189    time.sleep(1)
190
191  subprocess.check_call(
192    ['adb', 'pull', '/data/misc/perfetto-traces/java-profile-{}'.format(user),
193     output_file], stdout=NULL)
194
195  print("Wrote profile to {}".format(output_file))
196  print("This can be viewed using https://ui.perfetto.dev.")
197
198
199if __name__ == '__main__':
200  sys.exit(main(sys.argv))
201