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
5try:
6  import resource  # pylint: disable=import-error
7except ImportError:
8  resource = None  # Not available on all platforms
9
10import re
11
12from telemetry.core import exceptions
13from telemetry import decorators
14from telemetry.internal.platform import platform_backend
15
16
17class LinuxBasedPlatformBackend(platform_backend.PlatformBackend):
18
19  """Abstract platform containing functionality shared by all Linux based OSes.
20
21  This includes Android and ChromeOS.
22
23  Subclasses must implement RunCommand, GetFileContents, GetPsOutput, and
24  ParseCStateSample."""
25
26  # Get the commit charge in kB.
27  def GetSystemCommitCharge(self):
28    meminfo_contents = self.GetFileContents('/proc/meminfo')
29    meminfo = self._GetProcFileDict(meminfo_contents)
30    if not meminfo:
31      return None
32    return (self._ConvertToKb(meminfo['MemTotal'])
33            - self._ConvertToKb(meminfo['MemFree'])
34            - self._ConvertToKb(meminfo['Buffers'])
35            - self._ConvertToKb(meminfo['Cached']))
36
37  @decorators.Cache
38  def GetSystemTotalPhysicalMemory(self):
39    meminfo_contents = self.GetFileContents('/proc/meminfo')
40    meminfo = self._GetProcFileDict(meminfo_contents)
41    if not meminfo:
42      return None
43    return self._ConvertToBytes(meminfo['MemTotal'])
44
45  def GetCpuStats(self, pid):
46    results = {}
47    stats = self._GetProcFileForPid(pid, 'stat')
48    if not stats:
49      return results
50    stats = stats.split()
51    utime = float(stats[13])
52    stime = float(stats[14])
53    cpu_process_jiffies = utime + stime
54    clock_ticks = self.GetClockTicks()
55    results.update({'CpuProcessTime': cpu_process_jiffies / clock_ticks})
56    return results
57
58  def GetCpuTimestamp(self):
59    total_jiffies = self._GetProcJiffies()
60    clock_ticks = self.GetClockTicks()
61    return {'TotalTime': total_jiffies / clock_ticks}
62
63  def GetMemoryStats(self, pid):
64    status_contents = self._GetProcFileForPid(pid, 'status')
65    stats = self._GetProcFileForPid(pid, 'stat').split()
66    status = self._GetProcFileDict(status_contents)
67    if not status or not stats or 'Z' in status['State']:
68      return {}
69    vm = int(stats[22])
70    vm_peak = (self._ConvertToBytes(status['VmPeak'])
71               if 'VmPeak' in status else vm)
72    wss = int(stats[23]) * resource.getpagesize()
73    wss_peak = (self._ConvertToBytes(status['VmHWM'])
74                if 'VmHWM' in status else wss)
75
76    private_dirty_bytes = 0
77    for line in self._GetProcFileForPid(pid, 'smaps').splitlines():
78      if line.startswith('Private_Dirty:'):
79        private_dirty_bytes += self._ConvertToBytes(line.split(':')[1].strip())
80
81    return {'VM': vm,
82            'VMPeak': vm_peak,
83            'PrivateDirty': private_dirty_bytes,
84            'WorkingSetSize': wss,
85            'WorkingSetSizePeak': wss_peak}
86
87  @decorators.Cache
88  def GetClockTicks(self):
89    """Returns the number of clock ticks per second.
90
91    The proper way is to call os.sysconf('SC_CLK_TCK') but that is not easy to
92    do on Android/CrOS. In practice, nearly all Linux machines have a USER_HZ
93    of 100, so just return that.
94    """
95    return 100
96
97  def GetFileContents(self, filename):
98    raise NotImplementedError()
99
100  def GetPsOutput(self, columns, pid=None):
101    raise NotImplementedError()
102
103  def RunCommand(self, cmd):
104    """Runs the specified command.
105
106    Args:
107        cmd: A list of program arguments or the path string of the program.
108    Returns:
109        A string whose content is the output of the command.
110    """
111    raise NotImplementedError()
112
113  @staticmethod
114  def ParseCStateSample(sample):
115    """Parse a single c-state residency sample.
116
117    Args:
118        sample: A sample of c-state residency times to be parsed. Organized as
119            a dictionary mapping CPU name to a string containing all c-state
120            names, the times in each state, the latency of each state, and the
121            time at which the sample was taken all separated by newlines.
122            Ex: {'cpu0': 'C0\nC1\n5000\n2000\n20\n30\n1406673171'}
123
124    Returns:
125        Dictionary associating a c-state with a time.
126    """
127    raise NotImplementedError()
128
129  def _IsPidAlive(self, pid):
130    assert pid, 'pid is required'
131    return bool(self.GetPsOutput(['pid'], pid) == str(pid))
132
133  def _GetProcFileForPid(self, pid, filename):
134    try:
135      return self.GetFileContents('/proc/%s/%s' % (pid, filename))
136    except IOError:
137      if not self._IsPidAlive(pid):
138        raise exceptions.ProcessGoneException()
139      raise
140
141  def _ConvertToKb(self, value):
142    return int(value.replace('kB', ''))
143
144  def _ConvertToBytes(self, value):
145    return self._ConvertToKb(value) * 1024
146
147  def _GetProcFileDict(self, contents):
148    retval = {}
149    for line in contents.splitlines():
150      key, value = line.split(':')
151      retval[key.strip()] = value.strip()
152    return retval
153
154  def _GetProcJiffies(self):
155    """Parse '/proc/timer_list' output and returns the first jiffies attribute.
156
157    Multi-CPU machines will have multiple 'jiffies:' lines, all of which will be
158    essentially the same.  Return the first one."""
159    jiffies_timer_lines = self.RunCommand(
160        ['grep', 'jiffies', '/proc/timer_list'])
161    if not jiffies_timer_lines:
162      raise Exception('Unable to find jiffies from /proc/timer_list')
163    jiffies_timer_list = jiffies_timer_lines.splitlines()
164    # Each line should look something like 'jiffies: 4315883489'.
165    for line in jiffies_timer_list:
166      match = re.match(r'\s*jiffies\s*:\s*(\d+)', line)
167      if match:
168        value = match.group(1)
169        return float(value)
170    raise Exception('Unable to parse jiffies attribute: %s' %
171                    repr(jiffies_timer_lines))
172