summaryrefslogtreecommitdiff
path: root/systrace/catapult/telemetry/telemetry/internal/platform/profiler/java_heap_profiler.py
blob: c127a19aff90ecc9f623b6aee1ec87acd76197b8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import os
import subprocess
import threading

from telemetry.core import platform
from telemetry.internal.backends.chrome import android_browser_finder
from telemetry.internal.platform import profiler
from telemetry.internal.util import binary_manager

import py_utils

try:
  from devil.android import device_errors  # pylint: disable=import-error
except ImportError:
  device_errors = None


class JavaHeapProfiler(profiler.Profiler):
  """Android-specific, trigger and fetch java heap dumps."""

  _DEFAULT_DEVICE_DIR = '/data/local/tmp/javaheap'
  # TODO(bulach): expose this as a command line option somehow.
  _DEFAULT_INTERVAL = 20
  def __init__(self, browser_backend, platform_backend, output_path, state):
    super(JavaHeapProfiler, self).__init__(
        browser_backend, platform_backend, output_path, state)
    self._run_count = 1

    self._DumpJavaHeap(False)

    self._timer = threading.Timer(self._DEFAULT_INTERVAL, self._OnTimer)
    self._timer.start()

  @classmethod
  def name(cls):
    return 'java-heap'

  @classmethod
  def is_supported(cls, browser_type):
    if browser_type == 'any':
      return android_browser_finder.CanFindAvailableBrowsers()
    return browser_type.startswith('android')

  def CollectProfile(self):
    self._timer.cancel()
    self._DumpJavaHeap(True)
    self._browser_backend.device.PullFile(
        self._DEFAULT_DEVICE_DIR, self._output_path)
    # Note: command must be passed as a string to expand wildcards.
    self._browser_backend.device.RunShellCommand(
        'rm -f ' + os.path.join(self._DEFAULT_DEVICE_DIR, '*'),
        check_return=True, shell=True)
    output_files = []
    for f in os.listdir(self._output_path):
      if os.path.splitext(f)[1] == '.aprof':
        input_file = os.path.join(self._output_path, f)
        output_file = input_file.replace('.aprof', '.hprof')
        hprof_conv = binary_manager.FetchPath(
            'hprof-conv',
            platform.GetHostPlatform().GetArchName(),
            platform.GetHostPlatform().GetOSName())
        subprocess.call([hprof_conv, input_file, output_file])
        output_files.append(output_file)
    return output_files

  def _OnTimer(self):
    self._DumpJavaHeap(False)

  def _DumpJavaHeap(self, wait_for_completion):
    if not self._browser_backend.device.FileExists(
        self._DEFAULT_DEVICE_DIR):
      self._browser_backend.device.RunShellCommand(
          ['mkdir', '-p', self._DEFAULT_DEVICE_DIR], check_return=True)
      self._browser_backend.device.RunShellCommand(
          ['chmod', '777', self._DEFAULT_DEVICE_DIR], check_return=True)

    device_dump_file = None
    for pid in self._GetProcessOutputFileMap().iterkeys():
      device_dump_file = '%s/%s.%s.aprof' % (self._DEFAULT_DEVICE_DIR, pid,
                                             self._run_count)
      self._browser_backend.device.RunShellCommand(
          ['am', 'dumpheap', str(pid), device_dump_file], check_return=True)
    if device_dump_file and wait_for_completion:
      py_utils.WaitFor(lambda: self._FileSize(device_dump_file) > 0, timeout=2)
    self._run_count += 1

  def _FileSize(self, file_name):
    try:
      return self._browser_backend.device.FileSize(file_name)
    except device_errors.CommandFailedError:
      return 0