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 platform
8import subprocess
9import sys
10
11from catapult_base import cloud_storage  # pylint: disable=import-error
12
13from telemetry.internal.util import binary_manager
14from telemetry.core import os_version
15from telemetry.core import util
16from telemetry import decorators
17from telemetry.internal.platform import linux_based_platform_backend
18from telemetry.internal.platform import posix_platform_backend
19from telemetry.internal.platform.power_monitor import msr_power_monitor
20
21
22_POSSIBLE_PERFHOST_APPLICATIONS = [
23  'perfhost_precise',
24  'perfhost_trusty',
25]
26
27
28class LinuxPlatformBackend(
29    posix_platform_backend.PosixPlatformBackend,
30    linux_based_platform_backend.LinuxBasedPlatformBackend):
31  def __init__(self):
32    super(LinuxPlatformBackend, self).__init__()
33    self._power_monitor = msr_power_monitor.MsrPowerMonitorLinux(self)
34
35  @classmethod
36  def IsPlatformBackendForHost(cls):
37    return sys.platform.startswith('linux') and not util.IsRunningOnCrosDevice()
38
39  def IsThermallyThrottled(self):
40    raise NotImplementedError()
41
42  def HasBeenThermallyThrottled(self):
43    raise NotImplementedError()
44
45  @decorators.Cache
46  def GetArchName(self):
47    return platform.machine()
48
49  def GetOSName(self):
50    return 'linux'
51
52  @decorators.Cache
53  def GetOSVersionName(self):
54    if not os.path.exists('/etc/lsb-release'):
55      raise NotImplementedError('Unknown Linux OS version')
56
57    codename = None
58    version = None
59    for line in self.GetFileContents('/etc/lsb-release').splitlines():
60      key, _, value = line.partition('=')
61      if key == 'DISTRIB_CODENAME':
62        codename = value.strip()
63      elif key == 'DISTRIB_RELEASE':
64        try:
65          version = float(value)
66        except ValueError:
67          version = 0
68      if codename and version:
69        break
70    return os_version.OSVersion(codename, version)
71
72  def CanFlushIndividualFilesFromSystemCache(self):
73    return True
74
75  def FlushEntireSystemCache(self):
76    p = subprocess.Popen(['/sbin/sysctl', '-w', 'vm.drop_caches=3'])
77    p.wait()
78    assert p.returncode == 0, 'Failed to flush system cache'
79
80  def CanLaunchApplication(self, application):
81    if application == 'ipfw' and not self._IsIpfwKernelModuleInstalled():
82      return False
83    return super(LinuxPlatformBackend, self).CanLaunchApplication(application)
84
85  def InstallApplication(self, application):
86    if application == 'ipfw':
87      self._InstallIpfw()
88    elif application == 'avconv':
89      self._InstallBinary(application, fallback_package='libav-tools')
90    elif application in _POSSIBLE_PERFHOST_APPLICATIONS:
91      self._InstallBinary(application)
92    else:
93      raise NotImplementedError(
94          'Please teach Telemetry how to install ' + application)
95
96  def CanMonitorPower(self):
97    return self._power_monitor.CanMonitorPower()
98
99  def CanMeasurePerApplicationPower(self):
100    return self._power_monitor.CanMeasurePerApplicationPower()
101
102  def StartMonitoringPower(self, browser):
103    self._power_monitor.StartMonitoringPower(browser)
104
105  def StopMonitoringPower(self):
106    return self._power_monitor.StopMonitoringPower()
107
108  def ReadMsr(self, msr_number, start=0, length=64):
109    cmd = ['rdmsr', '-d', str(msr_number)]
110    (out, err) = subprocess.Popen(cmd,
111                                  stdout=subprocess.PIPE,
112                                  stderr=subprocess.PIPE).communicate()
113    if err:
114      raise OSError(err)
115    try:
116      result = int(out)
117    except ValueError:
118      raise OSError('Cannot interpret rdmsr output: %s' % out)
119    return result >> start & ((1 << length) - 1)
120
121  def _IsIpfwKernelModuleInstalled(self):
122    return 'ipfw_mod' in subprocess.Popen(
123        ['lsmod'], stdout=subprocess.PIPE).communicate()[0]
124
125  def _InstallIpfw(self):
126    ipfw_bin = binary_manager.FindPath(
127        'ipfw', self.GetArchName(), self.GetOSName())
128    ipfw_mod = binary_manager.FindPath(
129        'ipfw_mod.ko', self.GetArchName(), self.GetOSName())
130
131    try:
132      changed = cloud_storage.GetIfChanged(
133          ipfw_bin, cloud_storage.INTERNAL_BUCKET)
134      changed |= cloud_storage.GetIfChanged(
135          ipfw_mod, cloud_storage.INTERNAL_BUCKET)
136    except cloud_storage.CloudStorageError, e:
137      logging.error(str(e))
138      logging.error('You may proceed by manually building and installing'
139                    'dummynet for your kernel. See: '
140                    'http://info.iet.unipi.it/~luigi/dummynet/')
141      sys.exit(1)
142
143    if changed or not self.CanLaunchApplication('ipfw'):
144      if not self._IsIpfwKernelModuleInstalled():
145        subprocess.check_call(['/usr/bin/sudo', 'insmod', ipfw_mod])
146      os.chmod(ipfw_bin, 0755)
147      subprocess.check_call(
148          ['/usr/bin/sudo', 'cp', ipfw_bin, '/usr/local/sbin'])
149
150    assert self.CanLaunchApplication('ipfw'), 'Failed to install ipfw. ' \
151        'ipfw provided binaries are not supported for linux kernel < 3.13. ' \
152        'You may proceed by manually building and installing dummynet for ' \
153        'your kernel. See: http://info.iet.unipi.it/~luigi/dummynet/'
154
155  def _InstallBinary(self, bin_name, fallback_package=None):
156    bin_path = binary_manager.FetchPath(
157        bin_name, self.GetArchName(), self.GetOSName())
158    if not bin_path:
159      raise Exception('Could not find the binary package %s' % bin_name)
160    os.environ['PATH'] += os.pathsep + os.path.dirname(bin_path)
161
162    try:
163      cloud_storage.GetIfChanged(bin_path, cloud_storage.INTERNAL_BUCKET)
164      os.chmod(bin_path, 0755)
165    except cloud_storage.CloudStorageError, e:
166      logging.error(str(e))
167      if fallback_package:
168        raise Exception('You may proceed by manually installing %s via:\n'
169                        'sudo apt-get install %s' %
170                        (bin_name, fallback_package))
171
172    assert self.CanLaunchApplication(bin_name), 'Failed to install ' + bin_name
173