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