Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(270)

Unified Diff: tools/memory_inspector/memory_inspector/backends/android/android_backend.py

Issue 549313006: [Android] memory_inspector: move to libheap_profiler. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@mi3_prebuilts
Patch Set: Add prebuilts Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: tools/memory_inspector/memory_inspector/backends/android/android_backend.py
diff --git a/tools/memory_inspector/memory_inspector/backends/android/android_backend.py b/tools/memory_inspector/memory_inspector/backends/android/android_backend.py
index 50d905eb79426f6e4f0d6370827af4e2fbd8c7aa..e35e12d55fbd1fa82ecccaaa98d640b492ed5efe 100644
--- a/tools/memory_inspector/memory_inspector/backends/android/android_backend.py
+++ b/tools/memory_inspector/memory_inspector/backends/android/android_backend.py
@@ -16,7 +16,7 @@ import posixpath
from memory_inspector import constants
from memory_inspector.backends import prebuilts_fetcher
-from memory_inspector.backends.android import dumpheap_native_parser
+from memory_inspector.backends.android import native_heap_dump_parser
from memory_inspector.backends.android import memdump_parser
from memory_inspector.core import backends
from memory_inspector.core import exceptions
@@ -31,14 +31,20 @@ from pylib.device import device_utils
from pylib.symbols import elf_symbolizer
+_SUPPORTED_32BIT_ABIS = {'armeabi': 'arm', 'armeabi-v7a': 'arm'}
+_SUPPORTED_64BIT_ABIS = {'arm64-v8a': 'arm64'}
_MEMDUMP_PREBUILT_PATH = os.path.join(constants.PREBUILTS_PATH,
- 'memdump-android-arm')
+ 'memdump-android-%(arch)s')
_MEMDUMP_PATH_ON_DEVICE = '/data/local/tmp/memdump'
_PSEXT_PREBUILT_PATH = os.path.join(constants.PREBUILTS_PATH,
- 'ps_ext-android-arm')
+ 'ps_ext-android-%(arch)s')
_PSEXT_PATH_ON_DEVICE = '/data/local/tmp/ps_ext'
-_DLMALLOC_DEBUG_SYSPROP = 'libc.debug.malloc'
-_DUMPHEAP_OUT_FILE_PATH = '/data/local/tmp/heap-%d-native.dump'
+_HEAP_DUMP_PREBUILT_PATH = os.path.join(constants.PREBUILTS_PATH,
+ 'heap_dump-android-%(arch)s')
+_HEAP_DUMP_PATH_ON_DEVICE = '/data/local/tmp/heap_dump'
+_LIBHEAPPROF_PREBUILT_PATH = os.path.join(constants.PREBUILTS_PATH,
+ 'libheap_profiler-android-%(arch)s')
+_LIBHEAPPROF_FILE_NAME = 'libheap_profiler.so'
class AndroidBackend(backends.Backend):
@@ -179,6 +185,22 @@ class AndroidDevice(backends.Device):
self._processes = {} # pid (int) -> |Process|
self._initialized = False
+ # Determine the available ABIs, |_arch| will contain the primary ABI.
+ # TODO(primiano): For the moment we support only one ABI per device (i.e. we
+ # assume that all processes are 64 bit on 64 bit device, failing to profile
+ # 32 bit ones). Dealing properly with multi-ABIs requires work on ps_ext and
+ # at the moment is not an interesting use case.
+ self._arch = None
+ self._arch32 = None
+ self._arch64 = None
+ abi = adb.GetProp('ro.product.cpu.abi')
+ if abi in _SUPPORTED_64BIT_ABIS:
+ self._arch = self._arch64 = _SUPPORTED_64BIT_ABIS[abi]
+ elif abi in _SUPPORTED_32BIT_ABIS:
+ self._arch = self._arch32 = _SUPPORTED_32BIT_ABIS[abi]
+ else:
+ raise exceptions.MemoryInspectorException('ABI %s not supported' % abi)
+
def Initialize(self):
"""Starts adb root and deploys the prebuilt binaries on initialization."""
try:
@@ -190,26 +212,89 @@ class AndroidDevice(backends.Device):
'The device must be adb root-able in order to use memory_inspector')
# Download (from GCS) and deploy prebuilt helper binaries on the device.
- self._DeployPrebuiltOnDeviceIfNeeded(_MEMDUMP_PREBUILT_PATH,
- _MEMDUMP_PATH_ON_DEVICE)
- self._DeployPrebuiltOnDeviceIfNeeded(_PSEXT_PREBUILT_PATH,
- _PSEXT_PATH_ON_DEVICE)
+ self._DeployPrebuiltOnDeviceIfNeeded(
+ _MEMDUMP_PREBUILT_PATH % {'arch': self._arch}, _MEMDUMP_PATH_ON_DEVICE)
+ self._DeployPrebuiltOnDeviceIfNeeded(
+ _PSEXT_PREBUILT_PATH % {'arch': self._arch}, _PSEXT_PATH_ON_DEVICE)
+ self._DeployPrebuiltOnDeviceIfNeeded(
+ _HEAP_DUMP_PREBUILT_PATH % {'arch': self._arch},
+ _HEAP_DUMP_PATH_ON_DEVICE)
+
self._initialized = True
def IsNativeTracingEnabled(self):
- """Checks for the libc.debug.malloc system property."""
- return bool(self.adb.GetProp(_DLMALLOC_DEBUG_SYSPROP))
+ """Checks whether the libheap_profiler is preloaded in the zygote."""
+ zygote_name = 'zygote64' if self._arch64 else 'zygote'
+ zygote_process = [p for p in self.ListProcesses() if p.name == zygote_name]
+ if not zygote_process:
+ raise exceptions.MemoryInspectorException('Zygote process not found')
+ zygote_pid = zygote_process[0].pid
+ zygote_maps = self.adb.RunShellCommand('cat /proc/%d/maps' % zygote_pid)
+ return any(('libheap_profiler' in line for line in zygote_maps))
def EnableNativeTracing(self, enabled):
- """Enables libc.debug.malloc and restarts the shell."""
+ """Installs libheap_profiler in and injects it in the Zygote."""
+
+ def WrapZygote(app_process):
+ WRAPPER_SCRIPT = ('#!/system/bin/sh\n'
+ 'LD_PRELOAD="libheap_profiler.so:$LD_PRELOAD" '
+ 'exec %s.real "$@"\n' % app_process)
+ self.adb.RunShellCommand('mv %(0)s %(0)s.real' % {'0': app_process})
+ self.adb.WriteFile(app_process, WRAPPER_SCRIPT)
+ self.adb.RunShellCommand('chown root.shell ' + app_process)
+ self.adb.RunShellCommand('chmod 755 ' + app_process)
+
+ def UnwrapZygote():
+ for suffix in ('', '32', '64'):
+ # We don't really care if app_processX.real doesn't exists and mv fails.
+ # If app_processX.real doesn't exists, either app_processX is already
+ # unwrapped or it doesn't exists for the current arch.
+ app_process = '/system/bin/app_process' + suffix
+ self.adb.RunShellCommand('mv %(0)s.real %(0)s' % {'0': app_process})
+
assert(self._initialized)
- prop_value = '1' if enabled else ''
- self.adb.SetProp(_DLMALLOC_DEBUG_SYSPROP, prop_value)
- assert(self.IsNativeTracingEnabled())
- # The libc.debug property takes effect only after restarting the Zygote.
+ self.adb.old_interface.MakeSystemFolderWritable()
+
+ # Start restoring the original state in any case.
+ UnwrapZygote()
+
+ if enabled:
+ # Temporarily disable SELinux (until next reboot).
+ self.adb.RunShellCommand('setenforce 0')
+
+ # Wrap the Zygote startup binary (app_process) with a script which
+ # LD_PRELOADs libheap_profiler and invokes the original Zygote process.
+ if self._arch64:
+ app_process = '/system/bin/app_process64'
+ assert(self.adb.FileExists(app_process))
+ self._DeployPrebuiltOnDeviceIfNeeded(
+ _LIBHEAPPROF_PREBUILT_PATH % {'arch': self._arch64},
+ '/system/lib64/' + _LIBHEAPPROF_FILE_NAME)
+ WrapZygote(app_process)
+
+ if self._arch32:
+ # Path is app_process32 for Android >= L, app_process when < L.
+ app_process = '/system/bin/app_process32'
+ if not self.adb.FileExists(app_process):
+ app_process = '/system/bin/app_process'
+ assert(self.adb.FileExists(app_process))
+ self._DeployPrebuiltOnDeviceIfNeeded(
+ _LIBHEAPPROF_PREBUILT_PATH % {'arch': self._arch32},
+ '/system/lib/' + _LIBHEAPPROF_FILE_NAME)
+ WrapZygote(app_process)
+
+ # Respawn the zygote (the device will kind of reboot at this point).
self.adb.old_interface.RestartShell()
self.adb.old_interface.Adb().WaitForDevicePm(wait_time=30)
+ # Remove the wrapper. This won't have effect until the next reboot, when
+ # the profiler will be automatically disarmed.
+ UnwrapZygote()
+
+ # We can also unlink the lib files at this point. Once the Zygote has
+ # started it will keep the inodes refcounted anyways through its lifetime.
+ self.adb.RunShellCommand('rm /system/lib*/' + _LIBHEAPPROF_FILE_NAME)
+
def ListProcesses(self):
"""Returns a sequence of |AndroidProcess|."""
self._RefreshProcessesList()
@@ -324,17 +409,16 @@ class AndroidProcess(backends.Process):
return memdump_parser.Parse(dump_out)
def DumpNativeHeap(self):
- """Grabs and parses malloc traces through am dumpheap -n."""
- # TODO(primiano): grab also mmap bt (depends on pending framework change).
- dump_file_path = _DUMPHEAP_OUT_FILE_PATH % self.pid
- cmd = 'am dumpheap -n %d %s' % (self.pid, dump_file_path)
- self.device.adb.RunShellCommand(cmd)
- # TODO(primiano): Some pre-KK versions of Android might need a sleep here
- # as, IIRC, 'am dumpheap' did not wait for the dump to be completed before
- # returning. Double check this and either add a sleep or remove this TODO.
- dump_out = self.device.adb.ReadFile(dump_file_path)
- self.device.adb.RunShellCommand('rm %s' % dump_file_path)
- return dumpheap_native_parser.Parse(dump_out)
+ """Grabs and parses native heap traces using heap_dump."""
+ cmd = '%s -n -x %d' % (_HEAP_DUMP_PATH_ON_DEVICE, self.pid)
+ out_lines = self.device.adb.RunShellCommand(cmd)
+ return native_heap_dump_parser.Parse('\n'.join(out_lines))
+
+ def Freeze(self):
+ self.device.adb.RunShellCommand('kill -STOP %d' % self.pid)
+
+ def Unfreeze(self):
+ self.device.adb.RunShellCommand('kill -CONT %d' % self.pid)
def GetStats(self):
"""Calculate process CPU/VM stats (CPU stats are relative to last call)."""

Powered by Google App Engine
This is Rietveld 408576698