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=F0401
7except ImportError:
8  resource = None  # Not available on all platforms
9
10from telemetry import decorators
11from telemetry.core import exceptions
12from telemetry.core.platform import platform_backend
13
14
15class ProcSupportingPlatformBackend(platform_backend.PlatformBackend):
16
17  """Represents a platform that supports /proc.
18
19  Subclasses must implement _GetFileContents and _GetPsOutput."""
20
21  def GetSystemCommitCharge(self):
22    meminfo_contents = self._GetFileContents('/proc/meminfo')
23    meminfo = self._GetProcFileDict(meminfo_contents)
24    return (self._ConvertKbToByte(meminfo['MemTotal'])
25            - self._ConvertKbToByte(meminfo['MemFree'])
26            - self._ConvertKbToByte(meminfo['Buffers'])
27            - self._ConvertKbToByte(meminfo['Cached']))
28
29  @decorators.Cache
30  def GetSystemTotalPhysicalMemory(self):
31    meminfo_contents = self._GetFileContents('/proc/meminfo')
32    meminfo = self._GetProcFileDict(meminfo_contents)
33    return self._ConvertKbToByte(meminfo['MemTotal'])
34
35  def GetCpuStats(self, pid):
36    stats = self._GetProcFileForPid(pid, 'stat')
37    if not stats:
38      return {}
39    stats = stats.split()
40    utime = float(stats[13])
41    stime = float(stats[14])
42    cpu_process_jiffies = utime + stime
43    return {'CpuProcessTime': cpu_process_jiffies}
44
45  def GetCpuTimestamp(self):
46    timer_list = self._GetFileContents('/proc/timer_list')
47    total_jiffies = float(self._GetProcJiffies(timer_list))
48    return {'TotalTime': total_jiffies}
49
50  def GetMemoryStats(self, pid):
51    status_contents = self._GetProcFileForPid(pid, 'status')
52    stats = self._GetProcFileForPid(pid, 'stat').split()
53    status = self._GetProcFileDict(status_contents)
54    if not status or not stats or 'Z' in status['State']:
55      return {}
56    vm = int(stats[22])
57    vm_peak = (self._ConvertKbToByte(status['VmPeak'])
58               if 'VmPeak' in status else vm)
59    wss = int(stats[23]) * resource.getpagesize()
60    wss_peak = (self._ConvertKbToByte(status['VmHWM'])
61                if 'VmHWM' in status else wss)
62
63    private_dirty_bytes = 0
64    for line in self._GetProcFileForPid(pid, 'smaps').splitlines():
65      if line.startswith('Private_Dirty:'):
66        private_dirty_bytes += self._ConvertKbToByte(line.split(':')[1].strip())
67
68    return {'VM': vm,
69            'VMPeak': vm_peak,
70            'PrivateDirty': private_dirty_bytes,
71            'WorkingSetSize': wss,
72            'WorkingSetSizePeak': wss_peak}
73
74  def GetIOStats(self, pid):
75    io_contents = self._GetProcFileForPid(pid, 'io')
76    io = self._GetProcFileDict(io_contents)
77    return {'ReadOperationCount': int(io['syscr']),
78            'WriteOperationCount': int(io['syscw']),
79            'ReadTransferCount': int(io['rchar']),
80            'WriteTransferCount': int(io['wchar'])}
81
82  def _GetFileContents(self, filename):
83    raise NotImplementedError()
84
85  def _GetPsOutput(self, columns, pid=None):
86    raise NotImplementedError()
87
88  def _IsPidAlive(self, pid):
89    assert pid, 'pid is required'
90    return bool(self._GetPsOutput(['pid'], pid) == str(pid))
91
92  def _GetProcFileForPid(self, pid, filename):
93    try:
94      return self._GetFileContents('/proc/%s/%s' % (pid, filename))
95    except IOError:
96      if not self._IsPidAlive(pid):
97        raise exceptions.ProcessGoneException()
98      raise
99
100  def _ConvertKbToByte(self, value):
101    return int(value.replace('kB','')) * 1024
102
103  def _GetProcFileDict(self, contents):
104    retval = {}
105    for line in contents.splitlines():
106      key, value = line.split(':')
107      retval[key.strip()] = value.strip()
108    return retval
109
110  def _GetProcJiffies(self, timer_list):
111    """Parse '/proc/timer_list' output and returns the first jiffies attribute.
112
113    Multi-CPU machines will have multiple 'jiffies:' lines, all of which will be
114    essentially the same.  Return the first one."""
115    if isinstance(timer_list, str):
116      timer_list = timer_list.splitlines()
117    for line in timer_list:
118      if line.startswith('jiffies:'):
119        _, value = line.split(':')
120        return value
121    raise Exception('Unable to find jiffies from /proc/timer_list')
122