Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / tools / memory_inspector / memory_inspector / backends / android_backend.py
1 # Copyright 2014 The Chromium Authors. All rights reserved.
2 # Use of this source code is governed by a BSD-style license that can be
3 # found in the LICENSE file.
4
5 """Android-specific implementation of the core backend interfaces.
6
7 See core/backends.py for more docs.
8 """
9
10 import datetime
11 import glob
12 import hashlib
13 import json
14 import os
15 import posixpath
16
17 from memory_inspector import constants
18 from memory_inspector.backends import memdump_parser
19 from memory_inspector.backends import native_heap_dump_parser
20 from memory_inspector.backends import prebuilts_fetcher
21 from memory_inspector.core import backends
22 from memory_inspector.core import exceptions
23 from memory_inspector.core import native_heap
24 from memory_inspector.core import symbol
25
26 # The memory_inspector/__init__ module will add the <CHROME_SRC>/build/android
27 # deps to the PYTHONPATH for pylib.
28 from pylib import android_commands
29 from pylib.device import device_errors
30 from pylib.device import device_utils
31 from pylib.symbols import elf_symbolizer
32
33
34 _SUPPORTED_32BIT_ABIS = {'armeabi': 'arm', 'armeabi-v7a': 'arm'}
35 _SUPPORTED_64BIT_ABIS = {'arm64-v8a': 'arm64'}
36 _MEMDUMP_PREBUILT_PATH = os.path.join(constants.PREBUILTS_PATH,
37                                       'memdump-android-%(arch)s')
38 _MEMDUMP_PATH_ON_DEVICE = '/data/local/tmp/memdump'
39 _PSEXT_PREBUILT_PATH = os.path.join(constants.PREBUILTS_PATH,
40                                     'ps_ext-android-%(arch)s')
41 _PSEXT_PATH_ON_DEVICE = '/data/local/tmp/ps_ext'
42 _HEAP_DUMP_PREBUILT_PATH = os.path.join(constants.PREBUILTS_PATH,
43                                         'heap_dump-android-%(arch)s')
44 _HEAP_DUMP_PATH_ON_DEVICE = '/data/local/tmp/heap_dump'
45 _LIBHEAPPROF_PREBUILT_PATH = os.path.join(constants.PREBUILTS_PATH,
46                                           'libheap_profiler-android-%(arch)s')
47 _LIBHEAPPROF_FILE_NAME = 'libheap_profiler.so'
48
49
50 class AndroidBackend(backends.Backend):
51   """Android-specific implementation of the core |Backend| interface."""
52
53   _SETTINGS_KEYS = {
54       'adb_path': 'Path of directory containing the adb binary',
55       'toolchain_path': 'Path of toolchain (for addr2line)'}
56
57   def __init__(self):
58     super(AndroidBackend, self).__init__(
59         settings=backends.Settings(AndroidBackend._SETTINGS_KEYS))
60     self._devices = {}  # 'device id' -> |Device|.
61
62   def EnumerateDevices(self):
63     # If a custom adb_path has been setup through settings, prepend that to the
64     # PATH. The android_commands module will use that to locate adb.
65     if (self.settings['adb_path'] and
66         not os.environ['PATH'].startswith(self.settings['adb_path'])):
67       os.environ['PATH'] = os.pathsep.join([self.settings['adb_path'],
68                                            os.environ['PATH']])
69     for device_id in android_commands.GetAttachedDevices():
70       device = self._devices.get(device_id)
71       if not device:
72         device = AndroidDevice(
73           self, device_utils.DeviceUtils(device_id))
74         self._devices[device_id] = device
75       yield device
76
77   def ExtractSymbols(self, native_heaps, sym_paths):
78     """Performs symbolization. Returns a |symbol.Symbols| from |NativeHeap|s.
79
80     This method performs the symbolization but does NOT decorate (i.e. add
81     symbol/source info) to the stack frames of |native_heaps|. The heaps
82     can be decorated as needed using the native_heap.SymbolizeUsingSymbolDB()
83     method. Rationale: the most common use case in this application is:
84     symbolize-and-store-symbols and load-symbols-and-decorate-heaps (in two
85     different stages at two different times).
86
87     Args:
88       native_heaps: a collection of native_heap.NativeHeap instances.
89       sym_paths: either a list of or a string of semicolon-sep. symbol paths.
90     """
91     assert(all(isinstance(x, native_heap.NativeHeap) for x in native_heaps))
92     symbols = symbol.Symbols()
93
94     # Find addr2line in toolchain_path.
95     if isinstance(sym_paths, basestring):
96       sym_paths = sym_paths.split(';')
97     matches = glob.glob(os.path.join(self.settings['toolchain_path'],
98                                      '*addr2line'))
99     if not matches:
100       raise exceptions.MemoryInspectorException('Cannot find addr2line')
101     addr2line_path = matches[0]
102
103     # First group all the stack frames together by lib path.
104     frames_by_lib = {}
105     for nheap in native_heaps:
106       for stack_frame in nheap.stack_frames.itervalues():
107         frames = frames_by_lib.setdefault(stack_frame.exec_file_rel_path, set())
108         frames.add(stack_frame)
109
110     # The symbolization process is asynchronous (but yet single-threaded). This
111     # callback is invoked every time the symbol info for a stack frame is ready.
112     def SymbolizeAsyncCallback(sym_info, stack_frame):
113       if not sym_info.name:
114         return
115       sym = symbol.Symbol(name=sym_info.name,
116                           source_file_path=sym_info.source_path,
117                           line_number=sym_info.source_line)
118       symbols.Add(stack_frame.exec_file_rel_path, stack_frame.offset, sym)
119       # TODO(primiano): support inline sym info (i.e. |sym_info.inlined_by|).
120
121     # Perform the actual symbolization (ordered by lib).
122     for exec_file_rel_path, frames in frames_by_lib.iteritems():
123       # Look up the full path of the symbol in the sym paths.
124       exec_file_name = posixpath.basename(exec_file_rel_path)
125       if exec_file_rel_path.startswith('/'):
126         exec_file_rel_path = exec_file_rel_path[1:]
127       if not exec_file_rel_path:
128         continue
129       exec_file_abs_path = ''
130       for sym_path in sym_paths:
131         # First try to locate the symbol file following the full relative path
132         # e.g. /host/syms/ + /system/lib/foo.so => /host/syms/system/lib/foo.so.
133         exec_file_abs_path = os.path.join(sym_path, exec_file_rel_path)
134         if os.path.exists(exec_file_abs_path):
135           break
136
137         # If no luck, try looking just for the file name in the sym path,
138         # e.g. /host/syms/ + (/system/lib/)foo.so => /host/syms/foo.so.
139         exec_file_abs_path = os.path.join(sym_path, exec_file_name)
140         if os.path.exists(exec_file_abs_path):
141           break
142
143         # In the case of a Chrome component=shared_library build, the libs are
144         # renamed to .cr.so. Look for foo.so => foo.cr.so.
145         exec_file_abs_path = os.path.join(
146             sym_path, exec_file_name.replace('.so', '.cr.so'))
147         if os.path.exists(exec_file_abs_path):
148           break
149
150       if not os.path.isfile(exec_file_abs_path):
151         continue
152
153       symbolizer = elf_symbolizer.ELFSymbolizer(
154           elf_file_path=exec_file_abs_path,
155           addr2line_path=addr2line_path,
156           callback=SymbolizeAsyncCallback,
157           inlines=False)
158
159       # Kick off the symbolizer and then wait that all callbacks are issued.
160       for stack_frame in sorted(frames, key=lambda x: x.offset):
161         symbolizer.SymbolizeAsync(stack_frame.offset, stack_frame)
162       symbolizer.Join()
163
164     return symbols
165
166   @property
167   def name(self):
168     return 'Android'
169
170
171 class AndroidDevice(backends.Device):
172   """Android-specific implementation of the core |Device| interface."""
173
174   _SETTINGS_KEYS = {
175       'native_symbol_paths': 'Semicolon-sep. list of native libs search path'}
176
177   def __init__(self, backend, adb):
178     super(AndroidDevice, self).__init__(
179         backend=backend,
180         settings=backends.Settings(AndroidDevice._SETTINGS_KEYS))
181     self.adb = adb
182     self._name = '%s %s' % (adb.GetProp('ro.product.model'),
183                             adb.GetProp('ro.build.id'))
184     self._id = str(adb)
185     self._sys_stats = None
186     self._last_device_stats = None
187     self._sys_stats_last_update = None
188     self._processes = {}  # pid (int) -> |Process|
189     self._initialized = False
190
191     # Determine the available ABIs, |_arch| will contain the primary ABI.
192     # TODO(primiano): For the moment we support only one ABI per device (i.e. we
193     # assume that all processes are 64 bit on 64 bit device, failing to profile
194     # 32 bit ones). Dealing properly with multi-ABIs requires work on ps_ext and
195     # at the moment is not an interesting use case.
196     self._arch = None
197     self._arch32 = None
198     self._arch64 = None
199     abi = adb.GetProp('ro.product.cpu.abi')
200     if abi in _SUPPORTED_64BIT_ABIS:
201       self._arch = self._arch64 = _SUPPORTED_64BIT_ABIS[abi]
202     elif abi in _SUPPORTED_32BIT_ABIS:
203       self._arch = self._arch32 = _SUPPORTED_32BIT_ABIS[abi]
204     else:
205       raise exceptions.MemoryInspectorException('ABI %s not supported' % abi)
206
207   def Initialize(self):
208     """Starts adb root and deploys the prebuilt binaries on initialization."""
209     try:
210       self.adb.EnableRoot()
211     except device_errors.CommandFailedError:
212       # TODO(jbudorick): Handle this exception appropriately after interface
213       # conversions are finished.
214       raise exceptions.MemoryInspectorException(
215           'The device must be adb root-able in order to use memory_inspector')
216
217     # Download (from GCS) and deploy prebuilt helper binaries on the device.
218     self._DeployPrebuiltOnDeviceIfNeeded(
219         _MEMDUMP_PREBUILT_PATH % {'arch': self._arch}, _MEMDUMP_PATH_ON_DEVICE)
220     self._DeployPrebuiltOnDeviceIfNeeded(
221         _PSEXT_PREBUILT_PATH % {'arch': self._arch}, _PSEXT_PATH_ON_DEVICE)
222     self._DeployPrebuiltOnDeviceIfNeeded(
223         _HEAP_DUMP_PREBUILT_PATH % {'arch': self._arch},
224         _HEAP_DUMP_PATH_ON_DEVICE)
225
226     self._initialized = True
227
228   def IsNativeTracingEnabled(self):
229     """Checks whether the libheap_profiler is preloaded in the zygote."""
230     zygote_name = 'zygote64' if self._arch64 else 'zygote'
231     zygote_process = [p for p in self.ListProcesses() if p.name == zygote_name]
232     if not zygote_process:
233       raise exceptions.MemoryInspectorException('Zygote process not found')
234     zygote_pid = zygote_process[0].pid
235     zygote_maps = self.adb.RunShellCommand('cat /proc/%d/maps' % zygote_pid)
236     return any(('libheap_profiler' in line for line in zygote_maps))
237
238   def EnableNativeTracing(self, enabled):
239     """Installs libheap_profiler in and injects it in the Zygote."""
240
241     def WrapZygote(app_process):
242       WRAPPER_SCRIPT = ('#!/system/bin/sh\n'
243                         'LD_PRELOAD="libheap_profiler.so:$LD_PRELOAD" '
244                         'exec %s.real "$@"\n' % app_process)
245       self.adb.RunShellCommand('mv %(0)s %(0)s.real' % {'0': app_process})
246       self.adb.WriteFile(app_process, WRAPPER_SCRIPT)
247       self.adb.RunShellCommand('chown root.shell ' + app_process)
248       self.adb.RunShellCommand('chmod 755 ' + app_process)
249
250     def UnwrapZygote():
251       for suffix in ('', '32', '64'):
252         # We don't really care if app_processX.real doesn't exists and mv fails.
253         # If app_processX.real doesn't exists, either app_processX is already
254         # unwrapped or it doesn't exists for the current arch.
255         app_process = '/system/bin/app_process' + suffix
256         self.adb.RunShellCommand('mv %(0)s.real %(0)s' % {'0': app_process})
257
258     assert(self._initialized)
259     self.adb.old_interface.MakeSystemFolderWritable()
260
261     # Start restoring the original state in any case.
262     UnwrapZygote()
263
264     if enabled:
265       # Temporarily disable SELinux (until next reboot).
266       self.adb.RunShellCommand('setenforce 0')
267
268       # Wrap the Zygote startup binary (app_process) with a script which
269       # LD_PRELOADs libheap_profiler and invokes the original Zygote process.
270       if self._arch64:
271         app_process = '/system/bin/app_process64'
272         assert(self.adb.FileExists(app_process))
273         self._DeployPrebuiltOnDeviceIfNeeded(
274             _LIBHEAPPROF_PREBUILT_PATH % {'arch': self._arch64},
275             '/system/lib64/' + _LIBHEAPPROF_FILE_NAME)
276         WrapZygote(app_process)
277
278       if self._arch32:
279         # Path is app_process32 for Android >= L, app_process when < L.
280         app_process = '/system/bin/app_process32'
281         if not self.adb.FileExists(app_process):
282           app_process = '/system/bin/app_process'
283           assert(self.adb.FileExists(app_process))
284         self._DeployPrebuiltOnDeviceIfNeeded(
285             _LIBHEAPPROF_PREBUILT_PATH % {'arch': self._arch32},
286             '/system/lib/' + _LIBHEAPPROF_FILE_NAME)
287         WrapZygote(app_process)
288
289     # Respawn the zygote (the device will kind of reboot at this point).
290     self.adb.old_interface.RestartShell()
291     self.adb.old_interface.Adb().WaitForDevicePm(wait_time=30)
292
293     # Remove the wrapper. This won't have effect until the next reboot, when
294     # the profiler will be automatically disarmed.
295     UnwrapZygote()
296
297     # We can also unlink the lib files at this point. Once the Zygote has
298     # started it will keep the inodes refcounted anyways through its lifetime.
299     self.adb.RunShellCommand('rm /system/lib*/' + _LIBHEAPPROF_FILE_NAME)
300
301   def ListProcesses(self):
302     """Returns a sequence of |AndroidProcess|."""
303     self._RefreshProcessesList()
304     return self._processes.itervalues()
305
306   def GetProcess(self, pid):
307     """Returns an instance of |AndroidProcess| (None if not found)."""
308     assert(isinstance(pid, int))
309     self._RefreshProcessesList()
310     return self._processes.get(pid)
311
312   def GetStats(self):
313     """Returns an instance of |DeviceStats| with the OS CPU/Memory stats."""
314     cur = self.UpdateAndGetSystemStats()
315     old = self._last_device_stats or cur  # Handle 1st call case.
316     uptime = cur['time']['ticks'] / cur['time']['rate']
317     ticks = max(1, cur['time']['ticks'] - old['time']['ticks'])
318
319     cpu_times = []
320     for i in xrange(len(cur['cpu'])):
321       cpu_time = {
322           'usr': 100 * (cur['cpu'][i]['usr'] - old['cpu'][i]['usr']) / ticks,
323           'sys': 100 * (cur['cpu'][i]['sys'] - old['cpu'][i]['sys']) / ticks,
324           'idle': 100 * (cur['cpu'][i]['idle'] - old['cpu'][i]['idle']) / ticks}
325       # The idle tick count on many Linux kernels is frozen when the CPU is
326       # offline, and bumps up (compensating all the offline period) when it
327       # reactivates. For this reason it needs to be saturated at [0, 100].
328       cpu_time['idle'] = max(0, min(cpu_time['idle'],
329                                     100 - cpu_time['usr'] - cpu_time['sys']))
330
331       cpu_times.append(cpu_time)
332
333     memory_stats = {'Free': cur['mem']['MemFree:'],
334                     'Cache': cur['mem']['Buffers:'] + cur['mem']['Cached:'],
335                     'Swap': cur['mem']['SwapCached:'],
336                     'Anonymous': cur['mem']['AnonPages:'],
337                     'Kernel': cur['mem']['VmallocUsed:']}
338     self._last_device_stats = cur
339
340     return backends.DeviceStats(uptime=uptime,
341                                 cpu_times=cpu_times,
342                                 memory_stats=memory_stats)
343
344   def UpdateAndGetSystemStats(self):
345     """Grabs and caches system stats through ps_ext (max cache TTL = 0.5s).
346
347     Rationale of caching: avoid invoking adb too often, it is slow.
348     """
349     assert(self._initialized)
350     max_ttl = datetime.timedelta(seconds=0.5)
351     if (self._sys_stats_last_update and
352         datetime.datetime.now() - self._sys_stats_last_update <= max_ttl):
353       return self._sys_stats
354
355     dump_out = '\n'.join(
356         self.adb.RunShellCommand(_PSEXT_PATH_ON_DEVICE))
357     stats = json.loads(dump_out)
358     assert(all([x in stats for x in ['cpu', 'processes', 'time', 'mem']])), (
359         'ps_ext returned a malformed JSON dictionary.')
360     self._sys_stats = stats
361     self._sys_stats_last_update = datetime.datetime.now()
362     return self._sys_stats
363
364   def _RefreshProcessesList(self):
365     sys_stats = self.UpdateAndGetSystemStats()
366     processes_to_delete = set(self._processes.keys())
367     for pid, proc in sys_stats['processes'].iteritems():
368       pid = int(pid)
369       process = self._processes.get(pid)
370       if not process or process.name != proc['name']:
371         process = AndroidProcess(self, int(pid), proc['name'])
372         self._processes[pid] = process
373       processes_to_delete.discard(pid)
374     for pid in processes_to_delete:
375       del self._processes[pid]
376
377   def _DeployPrebuiltOnDeviceIfNeeded(self, local_path, path_on_device):
378     # TODO(primiano): check that the md5 binary is built-in also on pre-KK.
379     # Alternatively add tools/android/md5sum to prebuilts and use that one.
380     prebuilts_fetcher.GetIfChanged(local_path)
381     with open(local_path, 'rb') as f:
382       local_hash = hashlib.md5(f.read()).hexdigest()
383     device_md5_out = self.adb.RunShellCommand(
384         'md5 "%s"' % path_on_device)
385     if local_hash in device_md5_out:
386       return
387     self.adb.old_interface.Adb().Push(local_path, path_on_device)
388     self.adb.RunShellCommand('chmod 755 "%s"' % path_on_device)
389
390   @property
391   def name(self):
392     """Device name, as defined in the |backends.Device| interface."""
393     return self._name
394
395   @property
396   def id(self):
397     """Device id, as defined in the |backends.Device| interface."""
398     return self._id
399
400
401 class AndroidProcess(backends.Process):
402   """Android-specific implementation of the core |Process| interface."""
403
404   def __init__(self, device, pid, name):
405     super(AndroidProcess, self).__init__(device, pid, name)
406     self._last_sys_stats = None
407
408   def DumpMemoryMaps(self):
409     """Grabs and parses memory maps through memdump."""
410     cmd = '%s %d' % (_MEMDUMP_PATH_ON_DEVICE, self.pid)
411     dump_out = self.device.adb.RunShellCommand(cmd)
412     return memdump_parser.Parse(dump_out)
413
414   def DumpNativeHeap(self):
415     """Grabs and parses native heap traces using heap_dump."""
416     cmd = '%s -n -x %d' % (_HEAP_DUMP_PATH_ON_DEVICE, self.pid)
417     out_lines = self.device.adb.RunShellCommand(cmd)
418     return native_heap_dump_parser.Parse('\n'.join(out_lines))
419
420   def Freeze(self):
421     self.device.adb.RunShellCommand('kill -STOP %d' % self.pid)
422
423   def Unfreeze(self):
424     self.device.adb.RunShellCommand('kill -CONT %d' % self.pid)
425
426   def GetStats(self):
427     """Calculate process CPU/VM stats (CPU stats are relative to last call)."""
428     # Process must retain its own copy of _last_sys_stats because CPU times
429     # are calculated relatively to the last GetStats() call (for the process).
430     cur_sys_stats = self.device.UpdateAndGetSystemStats()
431     old_sys_stats = self._last_sys_stats or cur_sys_stats
432     cur_proc_stats = cur_sys_stats['processes'].get(str(self.pid))
433     old_proc_stats = old_sys_stats['processes'].get(str(self.pid))
434
435     # The process might have gone in the meanwhile.
436     if (not cur_proc_stats or not old_proc_stats):
437       return None
438
439     run_time = (((cur_sys_stats['time']['ticks'] -
440                 cur_proc_stats['start_time']) / cur_sys_stats['time']['rate']))
441     ticks = max(1, cur_sys_stats['time']['ticks'] -
442                 old_sys_stats['time']['ticks'])
443     cpu_usage = (100 *
444                  ((cur_proc_stats['user_time'] + cur_proc_stats['sys_time']) -
445                  (old_proc_stats['user_time'] + old_proc_stats['sys_time'])) /
446                  ticks) / len(cur_sys_stats['cpu'])
447     proc_stats = backends.ProcessStats(
448         threads=cur_proc_stats['n_threads'],
449         run_time=run_time,
450         cpu_usage=cpu_usage,
451         vm_rss=cur_proc_stats['vm_rss'],
452         page_faults=(
453             (cur_proc_stats['maj_faults'] + cur_proc_stats['min_faults']) -
454             (old_proc_stats['maj_faults'] + old_proc_stats['min_faults'])))
455     self._last_sys_stats = cur_sys_stats
456     return proc_stats