chromium/tools/android/native_lib_memory/java_code_pages_pss.py

#!/usr/bin/env python3
# Copyright 2018 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Prints the total PSS attributed to another app in Chrome's mappings.

This script assumes a device with Monochrome, and requires root access.
For instance, to get the part of Chrome's memory footprint coming from GMSCore
code and bytecode pages:
$ tools/android/native_lib_memory/java_code_pages_pss.py
    --chrome-package com.android.chrome
    --app-package com.google.android.gms --verbose
"""

import argparse
import logging
import os
import re
import sys

import parse_smaps

_SRC_PATH = os.path.join(
    os.path.dirname(__file__), os.pardir, os.pardir, os.pardir)
sys.path.append(os.path.join(_SRC_PATH, 'third_party', 'catapult', 'devil'))
from devil.android import device_utils


def _GetPssInKb(mappings, app_package, verbose):
  """Returns the total PSS from mappings.

  Args:
    mappings: ([parse_smaps.Mapping]) List of mappings.
    app_package: (str) App package to look for.
    verbose: (bool) Verbose output or not.

  Returns:
    (executable_pss (int), other_pss (int)) Executable mappings and others,
                                            in kB.
  """
  executable_pss, other_pss = (0, 0)
  for mapping in mappings:
    if app_package in mapping.pathname:
      if mapping.permissions == 'r-xp':
        executable_pss += mapping.fields['Pss']
      else:
        other_pss += mapping.fields['Pss']
      if verbose:
        print(mapping.ToString())
  return (executable_pss, other_pss)


def _CreateArgumentParser():
  parser = argparse.ArgumentParser()
  parser.add_argument('--chrome-package', help='Chrome package to look for.',
                      required=True)
  parser.add_argument('--app-package', help='Application to inspect.',
                      required=True)
  parser.add_argument('--verbose', help='Verbose output.',
                      action='store_true')
  return parser


def main():
  parser = _CreateArgumentParser()
  args = parser.parse_args()
  devices = device_utils.DeviceUtils.HealthyDevices()
  if not devices:
    logging.error('No connected devices')
    return
  device = devices[0]
  device.EnableRoot()
  processes = device.ListProcesses(args.chrome_package)
  logging.basicConfig(level=logging.INFO)
  logging.info('Processes:\n\t' + '\n\t'.join(p.name for p in processes))
  total_executable_pss_kb, total_other_pss_kb = (0, 0)
  for process in processes:
    mappings = parse_smaps.ParseProcSmaps(device, process.pid)
    executable_pss_kb, other_pss_kb = _GetPssInKb(
        mappings, args.app_package, args.verbose)
    total_executable_pss_kb += executable_pss_kb
    total_other_pss_kb += other_pss_kb

  print('Total executable PSS = %dkB' % total_executable_pss_kb)
  print('Total other mappings PSS = %dkB' % total_other_pss_kb)


if __name__ == '__main__':
  main()