133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck# Copyright 2013 The Chromium Authors. All rights reserved.
233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck# Use of this source code is governed by a BSD-style license that can be
333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck# found in the LICENSE file.
433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckimport logging
633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckimport os
733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckimport sys
833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckfrom telemetry.internal.backends.chrome import android_browser_finder
1033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckfrom telemetry.internal.platform import profiler
1133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
1233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck# Environment variables to (android properties, default value) mapping.
1333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck_ENV_VARIABLES = {
1433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  'HEAP_PROFILE_TIME_INTERVAL': ('heapprof.time_interval', 20),
1533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  'HEAP_PROFILE_MMAP': ('heapprof.mmap', 1),
1633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  'DEEP_HEAP_PROFILE': ('heapprof.deep_heap_profile', 1),
1733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck}
1833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
1933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
2033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckclass _TCMallocHeapProfilerAndroid(object):
2133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  """An internal class to set android properties and fetch dumps from device."""
2233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
2333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  _DEFAULT_DEVICE_DIR = '/data/local/tmp/heap'
2433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
2533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def __init__(self, browser_backend, output_path):
2633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._browser_backend = browser_backend
2733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._output_path = output_path
2833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
2933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    _ENV_VARIABLES['HEAPPROFILE'] = ('heapprof',
3033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        os.path.join(self._DEFAULT_DEVICE_DIR, 'dmprof'))
3133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
3233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._SetDeviceProperties(_ENV_VARIABLES)
3333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
3433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def _SetDeviceProperties(self, properties):
3533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    device_configured = False
3633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    # This profiler requires adb root to set properties.
37a0e5c0de428e9dea6d07dd57c5594fb1f1c17c20Chris Craik    self._browser_backend.device.EnableRoot()
3833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    for values in properties.itervalues():
3933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      device_property = self._browser_backend.device.GetProp(values[0])
4033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      if not device_property or not device_property.strip():
4133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        self._browser_backend.device.SetProp(values[0], values[1])
4233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        device_configured = True
4333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if not self._browser_backend.device.FileExists(
4433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        self._DEFAULT_DEVICE_DIR):
4533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      self._browser_backend.device.RunShellCommand(
46a0e5c0de428e9dea6d07dd57c5594fb1f1c17c20Chris Craik          ['mkdir', '-p', self._DEFAULT_DEVICE_DIR], check_return=True)
4733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      self._browser_backend.device.RunShellCommand(
48a0e5c0de428e9dea6d07dd57c5594fb1f1c17c20Chris Craik          ['chmod', '777', self._DEFAULT_DEVICE_DIR], check_return=True)
4933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      device_configured = True
5033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if device_configured:
5133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      raise Exception('Device required special config, run again.')
5233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
5333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def CollectProfile(self):
5433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    try:
5533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      self._browser_backend.device.PullFile(
5633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck          self._DEFAULT_DEVICE_DIR, self._output_path)
5733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    except:
5833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      logging.exception('New exception caused by DeviceUtils conversion')
5933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      raise
60a0e5c0de428e9dea6d07dd57c5594fb1f1c17c20Chris Craik    # Note: command must be passed as a string to expand wildcards.
6133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._browser_backend.device.RunShellCommand(
62a0e5c0de428e9dea6d07dd57c5594fb1f1c17c20Chris Craik        'rm -f ' + os.path.join(self._DEFAULT_DEVICE_DIR, '*'),
63a0e5c0de428e9dea6d07dd57c5594fb1f1c17c20Chris Craik        check_return=True, shell=True)
6433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if os.path.exists(self._output_path):
6533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      logging.info('TCMalloc dumps pulled to %s', self._output_path)
6633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      with file(os.path.join(self._output_path,
6733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck                             'browser.pid'), 'w') as pid_file:
6833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        pid_file.write(str(self._browser_backend.pid).rjust(5, '0'))
6933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return [self._output_path]
7033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
7133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
7233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckclass _TCMallocHeapProfilerLinux(object):
7333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  """An internal class to set environment variables and fetch dumps."""
7433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
7533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  _DEFAULT_DIR = '/tmp/tcmalloc/'
7633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
7733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def __init__(self, browser_backend):
7833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._browser_backend = browser_backend
7933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    _ENV_VARIABLES['HEAPPROFILE'] = ('heapprof', self._DEFAULT_DIR + 'dmprof')
8033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    self._CheckEnvironmentVariables(_ENV_VARIABLES)
8133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
8233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def _CheckEnvironmentVariables(self, env_vars):
8333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    msg = ''
8433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    for key, values in env_vars.iteritems():
8533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      if key not in os.environ:
8633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        msg += '%s=%s ' % (key, str(values[1]))
8733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if msg:
8833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      raise Exception('Need environment variables, try again with:\n %s' % msg)
8933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if not os.path.exists(os.environ['HEAPPROFILE']):
9033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      os.makedirs(os.environ['HEAPPROFILE'])
9133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    assert os.path.isdir(os.environ['HEAPPROFILE']), 'HEAPPROFILE is not a dir'
9233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
9333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def CollectProfile(self):
9433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    with file(os.path.join(os.path.dirname(os.environ['HEAPPROFILE']),
9533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck                           'browser.pid'), 'w') as pid_file:
9633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      pid_file.write(str(self._browser_backend.pid))
9733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    print 'TCMalloc dumps available ', os.environ['HEAPPROFILE']
9833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return [os.environ['HEAPPROFILE']]
9933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
10033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
10133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reckclass TCMallocHeapProfiler(profiler.Profiler):
10233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  """A Factory to instantiate the platform-specific profiler."""
10333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def __init__(self, browser_backend, platform_backend, output_path, state):
10433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    super(TCMallocHeapProfiler, self).__init__(
10533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        browser_backend, platform_backend, output_path, state)
10633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if platform_backend.GetOSName() == 'android':
10733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      self._platform_profiler = _TCMallocHeapProfilerAndroid(
10833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck          browser_backend, output_path)
10933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    else:
11033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      self._platform_profiler = _TCMallocHeapProfilerLinux(browser_backend)
11133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
11233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  @classmethod
11333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def name(cls):
11433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return 'tcmalloc-heap'
11533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
11633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  @classmethod
11733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def is_supported(cls, browser_type):
11833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if browser_type.startswith('cros'):
11933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      return False
12033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if sys.platform.startswith('linux'):
12133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      return True
12233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    if browser_type == 'any':
12333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      return android_browser_finder.CanFindAvailableBrowsers()
12433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return browser_type.startswith('android')
12533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
12633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  @classmethod
12733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def CustomizeBrowserOptions(cls, browser_type, options):
12833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    options.AppendExtraBrowserArgs('--no-sandbox')
12933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    options.AppendExtraBrowserArgs('--enable-memory-benchmarking')
13033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
13133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  @classmethod
13233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def WillCloseBrowser(cls, browser_backend, platform_backend):
13333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    # The tcmalloc_heap_profiler dumps files at regular
13433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    # intervals (~20 secs).
13533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    # This is a minor optimization to ensure it'll dump the last file when
13633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    # the test completes.
13733259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    for i in xrange(len(browser_backend.browser.tabs)):
13833259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      browser_backend.browser.tabs[i].ExecuteJavaScript("""
13933259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        if (chrome && chrome.memoryBenchmarking) {
14033259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck          chrome.memoryBenchmarking.heapProfilerDump('renderer', 'final');
14133259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck          chrome.memoryBenchmarking.heapProfilerDump('browser', 'final');
14233259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck        }
14333259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck      """)
14433259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck
14533259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck  def CollectProfile(self):
14633259e44c8229f70ffe0cf3bb5ca9375c4feb2f9John Reck    return self._platform_profiler.CollectProfile()
147