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 logging 6import os 7import subprocess 8import threading 9 10from telemetry.core import platform 11from telemetry.core import util 12from telemetry.internal.backends.chrome import android_browser_finder 13from telemetry.internal.platform import profiler 14from telemetry.internal.util import binary_manager 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 try: 52 self._browser_backend.device.PullFile( 53 self._DEFAULT_DEVICE_DIR, self._output_path) 54 except: 55 logging.exception('New exception caused by DeviceUtils conversion') 56 raise 57 self._browser_backend.device.RunShellCommand( 58 'rm ' + os.path.join(self._DEFAULT_DEVICE_DIR, '*')) 59 output_files = [] 60 for f in os.listdir(self._output_path): 61 if os.path.splitext(f)[1] == '.aprof': 62 input_file = os.path.join(self._output_path, f) 63 output_file = input_file.replace('.aprof', '.hprof') 64 hprof_conv = binary_manager.FetchPath( 65 'hprof-conv', 66 platform.GetHostPlatform().GetArchName(), 67 platform.GetHostPlatform().GetOSName()) 68 subprocess.call([hprof_conv, input_file, output_file]) 69 output_files.append(output_file) 70 return output_files 71 72 def _OnTimer(self): 73 self._DumpJavaHeap(False) 74 75 def _DumpJavaHeap(self, wait_for_completion): 76 if not self._browser_backend.device.FileExists( 77 self._DEFAULT_DEVICE_DIR): 78 self._browser_backend.device.RunShellCommand( 79 'mkdir -p ' + self._DEFAULT_DEVICE_DIR) 80 self._browser_backend.device.RunShellCommand( 81 'chmod 777 ' + self._DEFAULT_DEVICE_DIR) 82 83 device_dump_file = None 84 for pid in self._GetProcessOutputFileMap().iterkeys(): 85 device_dump_file = '%s/%s.%s.aprof' % (self._DEFAULT_DEVICE_DIR, pid, 86 self._run_count) 87 self._browser_backend.device.RunShellCommand('am dumpheap %s %s' % 88 (pid, device_dump_file)) 89 if device_dump_file and wait_for_completion: 90 util.WaitFor(lambda: self._FileSize(device_dump_file) > 0, timeout=2) 91 self._run_count += 1 92 93 def _FileSize(self, file_name): 94 try: 95 return self._browser_backend.device.Stat(file_name).st_size 96 except device_errors.CommandFailedError: 97 return 0 98