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 sys
8
9from telemetry.internal.backends.chrome import android_browser_finder
10from telemetry.internal.platform import profiler
11
12# Environment variables to (android properties, default value) mapping.
13_ENV_VARIABLES = {
14  'HEAP_PROFILE_TIME_INTERVAL': ('heapprof.time_interval', 20),
15  'HEAP_PROFILE_MMAP': ('heapprof.mmap', 1),
16  'DEEP_HEAP_PROFILE': ('heapprof.deep_heap_profile', 1),
17}
18
19
20class _TCMallocHeapProfilerAndroid(object):
21  """An internal class to set android properties and fetch dumps from device."""
22
23  _DEFAULT_DEVICE_DIR = '/data/local/tmp/heap'
24
25  def __init__(self, browser_backend, output_path):
26    self._browser_backend = browser_backend
27    self._output_path = output_path
28
29    _ENV_VARIABLES['HEAPPROFILE'] = ('heapprof',
30        os.path.join(self._DEFAULT_DEVICE_DIR, 'dmprof'))
31
32    self._SetDeviceProperties(_ENV_VARIABLES)
33
34  def _SetDeviceProperties(self, properties):
35    device_configured = False
36    # This profiler requires adb root to set properties.
37    try:
38      self._browser_backend.device.EnableRoot()
39    except:
40      logging.exception('New exception caused by DeviceUtils conversion')
41      raise
42    for values in properties.itervalues():
43      device_property = self._browser_backend.device.GetProp(values[0])
44      if not device_property or not device_property.strip():
45        self._browser_backend.device.SetProp(values[0], values[1])
46        device_configured = True
47    if not self._browser_backend.device.FileExists(
48        self._DEFAULT_DEVICE_DIR):
49      self._browser_backend.device.RunShellCommand(
50          'mkdir -p ' + self._DEFAULT_DEVICE_DIR)
51      self._browser_backend.device.RunShellCommand(
52          'chmod 777 ' + self._DEFAULT_DEVICE_DIR)
53      device_configured = True
54    if device_configured:
55      raise Exception('Device required special config, run again.')
56
57  def CollectProfile(self):
58    try:
59      self._browser_backend.device.PullFile(
60          self._DEFAULT_DEVICE_DIR, self._output_path)
61    except:
62      logging.exception('New exception caused by DeviceUtils conversion')
63      raise
64    self._browser_backend.device.RunShellCommand(
65        'rm ' + os.path.join(self._DEFAULT_DEVICE_DIR, '*'))
66    if os.path.exists(self._output_path):
67      logging.info('TCMalloc dumps pulled to %s', self._output_path)
68      with file(os.path.join(self._output_path,
69                             'browser.pid'), 'w') as pid_file:
70        pid_file.write(str(self._browser_backend.pid).rjust(5, '0'))
71    return [self._output_path]
72
73
74class _TCMallocHeapProfilerLinux(object):
75  """An internal class to set environment variables and fetch dumps."""
76
77  _DEFAULT_DIR = '/tmp/tcmalloc/'
78
79  def __init__(self, browser_backend):
80    self._browser_backend = browser_backend
81    _ENV_VARIABLES['HEAPPROFILE'] = ('heapprof', self._DEFAULT_DIR + 'dmprof')
82    self._CheckEnvironmentVariables(_ENV_VARIABLES)
83
84  def _CheckEnvironmentVariables(self, env_vars):
85    msg = ''
86    for key, values in env_vars.iteritems():
87      if key not in os.environ:
88        msg += '%s=%s ' % (key, str(values[1]))
89    if msg:
90      raise Exception('Need environment variables, try again with:\n %s' % msg)
91    if not os.path.exists(os.environ['HEAPPROFILE']):
92      os.makedirs(os.environ['HEAPPROFILE'])
93    assert os.path.isdir(os.environ['HEAPPROFILE']), 'HEAPPROFILE is not a dir'
94
95  def CollectProfile(self):
96    with file(os.path.join(os.path.dirname(os.environ['HEAPPROFILE']),
97                           'browser.pid'), 'w') as pid_file:
98      pid_file.write(str(self._browser_backend.pid))
99    print 'TCMalloc dumps available ', os.environ['HEAPPROFILE']
100    return [os.environ['HEAPPROFILE']]
101
102
103class TCMallocHeapProfiler(profiler.Profiler):
104  """A Factory to instantiate the platform-specific profiler."""
105  def __init__(self, browser_backend, platform_backend, output_path, state):
106    super(TCMallocHeapProfiler, self).__init__(
107        browser_backend, platform_backend, output_path, state)
108    if platform_backend.GetOSName() == 'android':
109      self._platform_profiler = _TCMallocHeapProfilerAndroid(
110          browser_backend, output_path)
111    else:
112      self._platform_profiler = _TCMallocHeapProfilerLinux(browser_backend)
113
114  @classmethod
115  def name(cls):
116    return 'tcmalloc-heap'
117
118  @classmethod
119  def is_supported(cls, browser_type):
120    if browser_type.startswith('cros'):
121      return False
122    if sys.platform.startswith('linux'):
123      return True
124    if browser_type == 'any':
125      return android_browser_finder.CanFindAvailableBrowsers()
126    return browser_type.startswith('android')
127
128  @classmethod
129  def CustomizeBrowserOptions(cls, browser_type, options):
130    options.AppendExtraBrowserArgs('--no-sandbox')
131    options.AppendExtraBrowserArgs('--enable-memory-benchmarking')
132
133  @classmethod
134  def WillCloseBrowser(cls, browser_backend, platform_backend):
135    # The tcmalloc_heap_profiler dumps files at regular
136    # intervals (~20 secs).
137    # This is a minor optimization to ensure it'll dump the last file when
138    # the test completes.
139    for i in xrange(len(browser_backend.browser.tabs)):
140      browser_backend.browser.tabs[i].ExecuteJavaScript("""
141        if (chrome && chrome.memoryBenchmarking) {
142          chrome.memoryBenchmarking.heapProfilerDump('renderer', 'final');
143          chrome.memoryBenchmarking.heapProfilerDump('browser', 'final');
144        }
145      """)
146
147  def CollectProfile(self):
148    return self._platform_profiler.CollectProfile()
149