227 lines
5.8 KiB
Python
Executable File
227 lines
5.8 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
# Copyright (C) 2020 The Android Open Source Project
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import print_function
|
|
|
|
import argparse
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import time
|
|
import uuid
|
|
|
|
NULL = open(os.devnull)
|
|
|
|
PACKAGES_LIST_CFG = '''data_sources {
|
|
config {
|
|
name: "android.packages_list"
|
|
}
|
|
}
|
|
'''
|
|
|
|
CFG_IDENT = ' '
|
|
CFG = '''buffers {{
|
|
size_kb: 100024
|
|
fill_policy: RING_BUFFER
|
|
}}
|
|
|
|
data_sources {{
|
|
config {{
|
|
name: "android.java_hprof"
|
|
java_hprof_config {{
|
|
{target_cfg}
|
|
{continuous_dump_config}
|
|
}}
|
|
}}
|
|
}}
|
|
|
|
data_source_stop_timeout_ms: {data_source_stop_timeout_ms}
|
|
duration_ms: {duration_ms}
|
|
'''
|
|
|
|
CONTINUOUS_DUMP = """
|
|
continuous_dump_config {{
|
|
dump_phase_ms: 0
|
|
dump_interval_ms: {dump_interval}
|
|
}}
|
|
"""
|
|
|
|
UUID = str(uuid.uuid4())[-6:]
|
|
PROFILE_PATH = '/data/misc/perfetto-traces/java-profile-' + UUID
|
|
|
|
PERFETTO_CMD = ('CFG=\'{cfg}\'; echo ${{CFG}} | '
|
|
'perfetto --txt -c - -o ' + PROFILE_PATH + ' -d')
|
|
|
|
SDK = {
|
|
'S': 31,
|
|
}
|
|
|
|
def release_or_newer(release):
|
|
sdk = int(subprocess.check_output(
|
|
['adb', 'shell', 'getprop', 'ro.system.build.version.sdk']
|
|
).decode('utf-8').strip())
|
|
if sdk >= SDK[release]:
|
|
return True
|
|
codename = subprocess.check_output(
|
|
['adb', 'shell', 'getprop', 'ro.build.version.codename']
|
|
).decode('utf-8').strip()
|
|
return codename == release
|
|
|
|
def main(argv):
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument(
|
|
"-o",
|
|
"--output",
|
|
help="Filename to save profile to.",
|
|
metavar="FILE",
|
|
default=None)
|
|
parser.add_argument(
|
|
"-p",
|
|
"--pid",
|
|
help="Comma-separated list of PIDs to "
|
|
"profile.",
|
|
metavar="PIDS")
|
|
parser.add_argument(
|
|
"-n",
|
|
"--name",
|
|
help="Comma-separated list of process "
|
|
"names to profile.",
|
|
metavar="NAMES")
|
|
parser.add_argument(
|
|
"-c",
|
|
"--continuous-dump",
|
|
help="Dump interval in ms. 0 to disable continuous dump.",
|
|
type=int,
|
|
default=0)
|
|
parser.add_argument(
|
|
"--no-versions",
|
|
action="store_true",
|
|
help="Do not get version information about APKs.")
|
|
parser.add_argument(
|
|
"--dump-smaps",
|
|
action="store_true",
|
|
help="Get information about /proc/$PID/smaps of target.")
|
|
parser.add_argument(
|
|
"--print-config",
|
|
action="store_true",
|
|
help="Print config instead of running. For debugging.")
|
|
parser.add_argument(
|
|
"--stop-when-done",
|
|
action="store_true",
|
|
default=None,
|
|
help="Use a new method to stop the profile when the dump is done. "
|
|
"Previously, we would hardcode a duration. Available and default on S.")
|
|
parser.add_argument(
|
|
"--no-stop-when-done",
|
|
action="store_false",
|
|
dest='stop_when_done',
|
|
help="Do not use a new method to stop the profile when the dump is done.")
|
|
|
|
args = parser.parse_args()
|
|
|
|
if args.stop_when_done is None:
|
|
args.stop_when_done = release_or_newer('S')
|
|
|
|
fail = False
|
|
if args.pid is None and args.name is None:
|
|
print("FATAL: Neither PID nor NAME given.", file=sys.stderr)
|
|
fail = True
|
|
|
|
target_cfg = ""
|
|
if args.pid:
|
|
for pid in args.pid.split(','):
|
|
try:
|
|
pid = int(pid)
|
|
except ValueError:
|
|
print("FATAL: invalid PID %s" % pid, file=sys.stderr)
|
|
fail = True
|
|
target_cfg += '{}pid: {}\n'.format(CFG_IDENT, pid)
|
|
if args.name:
|
|
for name in args.name.split(','):
|
|
target_cfg += '{}process_cmdline: "{}"\n'.format(CFG_IDENT, name)
|
|
if args.dump_smaps:
|
|
target_cfg += '{}dump_smaps: true\n'.format(CFG_IDENT)
|
|
|
|
if fail:
|
|
parser.print_help()
|
|
return 1
|
|
|
|
output_file = args.output
|
|
if output_file is None:
|
|
fd, name = tempfile.mkstemp('profile')
|
|
os.close(fd)
|
|
output_file = name
|
|
|
|
continuous_dump_cfg = ""
|
|
if args.continuous_dump:
|
|
continuous_dump_cfg = CONTINUOUS_DUMP.format(
|
|
dump_interval=args.continuous_dump)
|
|
|
|
if args.stop_when_done:
|
|
duration_ms = 1000
|
|
data_source_stop_timeout_ms = 100000
|
|
else:
|
|
duration_ms = 20000
|
|
data_source_stop_timeout_ms = 0
|
|
|
|
cfg = CFG.format(
|
|
target_cfg=target_cfg,
|
|
continuous_dump_config=continuous_dump_cfg,
|
|
duration_ms=duration_ms,
|
|
data_source_stop_timeout_ms=data_source_stop_timeout_ms)
|
|
if not args.no_versions:
|
|
cfg += PACKAGES_LIST_CFG
|
|
|
|
if args.print_config:
|
|
print(cfg)
|
|
return 0
|
|
|
|
user = subprocess.check_output(
|
|
['adb', 'shell', 'whoami']).strip().decode('utf8')
|
|
perfetto_pid = subprocess.check_output(
|
|
['adb', 'exec-out',
|
|
PERFETTO_CMD.format(cfg=cfg, user=user)]).strip().decode('utf8')
|
|
try:
|
|
int(perfetto_pid.strip())
|
|
except ValueError:
|
|
print("Failed to invoke perfetto: {}".format(perfetto_pid), file=sys.stderr)
|
|
return 1
|
|
|
|
print("Dumping Java Heap.")
|
|
exists = True
|
|
|
|
# Wait for perfetto cmd to return.
|
|
while exists:
|
|
exists = subprocess.call(
|
|
['adb', 'shell', '[ -d /proc/{} ]'.format(perfetto_pid)]) == 0
|
|
time.sleep(1)
|
|
|
|
subprocess.check_call(
|
|
['adb', 'pull', PROFILE_PATH, output_file], stdout=NULL)
|
|
|
|
subprocess.check_call(
|
|
['adb', 'shell', 'rm', PROFILE_PATH], stdout=NULL)
|
|
|
|
print("Wrote profile to {}".format(output_file))
|
|
print("This can be viewed using https://ui.perfetto.dev.")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main(sys.argv))
|