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))
 |