1# Copyright 2013 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 5import os 6import subprocess 7import threading 8 9from telemetry.core import platform 10from telemetry.internal.backends.chrome import android_browser_finder 11from telemetry.internal.platform import profiler 12from telemetry.internal.util import binary_manager 13 14import py_utils 15 16try: 17 from devil.android import device_errors # pylint: disable=import-error 18except ImportError: 19 device_errors = None 20 21 22class JavaHeapProfiler(profiler.Profiler): 23 """Android-specific, trigger and fetch java heap dumps.""" 24 25 _DEFAULT_DEVICE_DIR = '/data/local/tmp/javaheap' 26 # TODO(bulach): expose this as a command line option somehow. 27 _DEFAULT_INTERVAL = 20 28 def __init__(self, browser_backend, platform_backend, output_path, state): 29 super(JavaHeapProfiler, self).__init__( 30 browser_backend, platform_backend, output_path, state) 31 self._run_count = 1 32 33 self._DumpJavaHeap(False) 34 35 self._timer = threading.Timer(self._DEFAULT_INTERVAL, self._OnTimer) 36 self._timer.start() 37 38 @classmethod 39 def name(cls): 40 return 'java-heap' 41 42 @classmethod 43 def is_supported(cls, browser_type): 44 if browser_type == 'any': 45 return android_browser_finder.CanFindAvailableBrowsers() 46 return browser_type.startswith('android') 47 48 def CollectProfile(self): 49 self._timer.cancel() 50 self._DumpJavaHeap(True) 51 self._browser_backend.device.PullFile( 52 self._DEFAULT_DEVICE_DIR, self._output_path) 53 # Note: command must be passed as a string to expand wildcards. 54 self._browser_backend.device.RunShellCommand( 55 'rm -f ' + os.path.join(self._DEFAULT_DEVICE_DIR, '*'), 56 check_return=True, shell=True) 57 output_files = [] 58 for f in os.listdir(self._output_path): 59 if os.path.splitext(f)[1] == '.aprof': 60 input_file = os.path.join(self._output_path, f) 61 output_file = input_file.replace('.aprof', '.hprof') 62 hprof_conv = binary_manager.FetchPath( 63 'hprof-conv', 64 platform.GetHostPlatform().GetArchName(), 65 platform.GetHostPlatform().GetOSName()) 66 subprocess.call([hprof_conv, input_file, output_file]) 67 output_files.append(output_file) 68 return output_files 69 70 def _OnTimer(self): 71 self._DumpJavaHeap(False) 72 73 def _DumpJavaHeap(self, wait_for_completion): 74 if not self._browser_backend.device.FileExists( 75 self._DEFAULT_DEVICE_DIR): 76 self._browser_backend.device.RunShellCommand( 77 ['mkdir', '-p', self._DEFAULT_DEVICE_DIR], check_return=True) 78 self._browser_backend.device.RunShellCommand( 79 ['chmod', '777', self._DEFAULT_DEVICE_DIR], check_return=True) 80 81 device_dump_file = None 82 for pid in self._GetProcessOutputFileMap().iterkeys(): 83 device_dump_file = '%s/%s.%s.aprof' % (self._DEFAULT_DEVICE_DIR, pid, 84 self._run_count) 85 self._browser_backend.device.RunShellCommand( 86 ['am', 'dumpheap', str(pid), device_dump_file], check_return=True) 87 if device_dump_file and wait_for_completion: 88 py_utils.WaitFor(lambda: self._FileSize(device_dump_file) > 0, timeout=2) 89 self._run_count += 1 90 91 def _FileSize(self, file_name): 92 try: 93 return self._browser_backend.device.FileSize(file_name) 94 except device_errors.CommandFailedError: 95 return 0 96