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