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