#! /usr/bin/env python3 # # Copyright 2023, 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. import argparse from collections import namedtuple import subprocess try: from tqdm import tqdm except: def tqdm(x): return x ProcEntry = namedtuple('ProcEntry', 'pid, ppid, cmd, name, etc_args') def get_mem_stats( zygote_pid, target_pid, target_name, imgdiag_path, boot_image, device_out_dir, host_out_dir, ): imgdiag_output_path = ( f'{device_out_dir}/imgdiag_{target_name}_{target_pid}.txt' ) cmd_collect = ( 'adb shell ' f'"{imgdiag_path} --zygote-diff-pid={zygote_pid} --image-diff-pid={target_pid} ' f'--output={imgdiag_output_path} --boot-image={boot_image} --dump-dirty-objects"' ) try: subprocess.run(cmd_collect, shell=True, check=True) except: print('imgdiag call failed on:', target_pid, target_name) return cmd_pull = f'adb pull {imgdiag_output_path} {host_out_dir}' subprocess.run(cmd_pull, shell=True, check=True, capture_output=True) def main(): parser = argparse.ArgumentParser( description=( 'Run imgdiag on selected processes and pull results from the device.' ), formatter_class=argparse.ArgumentDefaultsHelpFormatter, ) parser.add_argument( 'process_names', nargs='*', help='Process names to use. If none - dump all zygote children.', ) parser.add_argument( '--boot-image', dest='boot_image', default='/data/misc/apexdata/com.android.art/dalvik-cache/boot.art', help='Path to boot.art', ) parser.add_argument( '--zygote', default='zygote64', help='Zygote process name', ) parser.add_argument( '--imgdiag', default='/apex/com.android.art/bin/imgdiag64', help='Path to imgdiag binary.', ) parser.add_argument( '--device-out-dir', default='/data/local/tmp/imgdiag_out', help='Directory for imgdiag output files on the device.', ) parser.add_argument( '--host-out-dir', default='./', help='Directory for imgdiag output files on the host.', ) args = parser.parse_args() res = subprocess.run( args='adb shell ps -o pid:1,ppid:1,cmd:1,args:1', capture_output=True, shell=True, check=True, text=True, ) proc_entries = [] for line in res.stdout.splitlines()[1:]: # skip header pid, ppid, cmd, name, *etc_args = line.split(' ') entry = ProcEntry(int(pid), int(ppid), cmd, name, etc_args) proc_entries.append(entry) zygote_entry = next(e for e in proc_entries if e.name == args.zygote) zygote_children = [e for e in proc_entries if e.ppid == zygote_entry.pid] if args.process_names: zygote_children = [e for e in proc_entries if e.name in args.process_names] print('\n'.join(str(e.pid) + ' ' + e.name for e in zygote_children)) subprocess.run( args=f'adb shell "mkdir -p {args.device_out_dir}"', check=True, shell=True ) subprocess.run(args=f'mkdir -p {args.host_out_dir}', check=True, shell=True) for entry in tqdm(zygote_children): get_mem_stats( zygote_pid=entry.ppid, target_pid=entry.pid, target_name=entry.name, imgdiag_path=args.imgdiag, boot_image=args.boot_image, device_out_dir=args.device_out_dir, host_out_dir=args.host_out_dir, ) if __name__ == '__main__': main()