| # Copyright 2013 The Chromium Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| try: |
| import resource # pylint: disable=import-error |
| except ImportError: |
| resource = None # Not available on all platforms |
| |
| import re |
| |
| from telemetry.core import exceptions |
| from telemetry import decorators |
| from telemetry.internal.platform import platform_backend |
| |
| |
| class LinuxBasedPlatformBackend(platform_backend.PlatformBackend): |
| |
| """Abstract platform containing functionality shared by all Linux based OSes. |
| |
| This includes Android and ChromeOS. |
| |
| Subclasses must implement RunCommand, GetFileContents, GetPsOutput, and |
| ParseCStateSample.""" |
| |
| # Get the commit charge in kB. |
| def GetSystemCommitCharge(self): |
| meminfo_contents = self.GetFileContents('/proc/meminfo') |
| meminfo = self._GetProcFileDict(meminfo_contents) |
| if not meminfo: |
| return None |
| return (self._ConvertToKb(meminfo['MemTotal']) |
| - self._ConvertToKb(meminfo['MemFree']) |
| - self._ConvertToKb(meminfo['Buffers']) |
| - self._ConvertToKb(meminfo['Cached'])) |
| |
| @decorators.Cache |
| def GetSystemTotalPhysicalMemory(self): |
| meminfo_contents = self.GetFileContents('/proc/meminfo') |
| meminfo = self._GetProcFileDict(meminfo_contents) |
| if not meminfo: |
| return None |
| return self._ConvertToBytes(meminfo['MemTotal']) |
| |
| def GetCpuStats(self, pid): |
| results = {} |
| stats = self._GetProcFileForPid(pid, 'stat') |
| if not stats: |
| return results |
| stats = stats.split() |
| utime = float(stats[13]) |
| stime = float(stats[14]) |
| cpu_process_jiffies = utime + stime |
| clock_ticks = self.GetClockTicks() |
| results.update({'CpuProcessTime': cpu_process_jiffies / clock_ticks}) |
| return results |
| |
| def GetCpuTimestamp(self): |
| total_jiffies = self._GetProcJiffies() |
| clock_ticks = self.GetClockTicks() |
| return {'TotalTime': total_jiffies / clock_ticks} |
| |
| def GetMemoryStats(self, pid): |
| status_contents = self._GetProcFileForPid(pid, 'status') |
| stats = self._GetProcFileForPid(pid, 'stat').split() |
| status = self._GetProcFileDict(status_contents) |
| if not status or not stats or 'Z' in status['State']: |
| return {} |
| vm = int(stats[22]) |
| vm_peak = (self._ConvertToBytes(status['VmPeak']) |
| if 'VmPeak' in status else vm) |
| wss = int(stats[23]) * resource.getpagesize() |
| wss_peak = (self._ConvertToBytes(status['VmHWM']) |
| if 'VmHWM' in status else wss) |
| |
| private_dirty_bytes = 0 |
| for line in self._GetProcFileForPid(pid, 'smaps').splitlines(): |
| if line.startswith('Private_Dirty:'): |
| private_dirty_bytes += self._ConvertToBytes(line.split(':')[1].strip()) |
| |
| return {'VM': vm, |
| 'VMPeak': vm_peak, |
| 'PrivateDirty': private_dirty_bytes, |
| 'WorkingSetSize': wss, |
| 'WorkingSetSizePeak': wss_peak} |
| |
| @decorators.Cache |
| def GetClockTicks(self): |
| """Returns the number of clock ticks per second. |
| |
| The proper way is to call os.sysconf('SC_CLK_TCK') but that is not easy to |
| do on Android/CrOS. In practice, nearly all Linux machines have a USER_HZ |
| of 100, so just return that. |
| """ |
| return 100 |
| |
| def GetFileContents(self, filename): |
| raise NotImplementedError() |
| |
| def GetPsOutput(self, columns, pid=None): |
| raise NotImplementedError() |
| |
| def RunCommand(self, cmd): |
| """Runs the specified command. |
| |
| Args: |
| cmd: A list of program arguments or the path string of the program. |
| Returns: |
| A string whose content is the output of the command. |
| """ |
| raise NotImplementedError() |
| |
| @staticmethod |
| def ParseCStateSample(sample): |
| """Parse a single c-state residency sample. |
| |
| Args: |
| sample: A sample of c-state residency times to be parsed. Organized as |
| a dictionary mapping CPU name to a string containing all c-state |
| names, the times in each state, the latency of each state, and the |
| time at which the sample was taken all separated by newlines. |
| Ex: {'cpu0': 'C0\nC1\n5000\n2000\n20\n30\n1406673171'} |
| |
| Returns: |
| Dictionary associating a c-state with a time. |
| """ |
| raise NotImplementedError() |
| |
| def _IsPidAlive(self, pid): |
| assert pid, 'pid is required' |
| return bool(self.GetPsOutput(['pid'], pid) == str(pid)) |
| |
| def _GetProcFileForPid(self, pid, filename): |
| try: |
| return self.GetFileContents('/proc/%s/%s' % (pid, filename)) |
| except IOError: |
| if not self._IsPidAlive(pid): |
| raise exceptions.ProcessGoneException() |
| raise |
| |
| def _ConvertToKb(self, value): |
| return int(value.replace('kB', '')) |
| |
| def _ConvertToBytes(self, value): |
| return self._ConvertToKb(value) * 1024 |
| |
| def _GetProcFileDict(self, contents): |
| retval = {} |
| for line in contents.splitlines(): |
| key, value = line.split(':') |
| retval[key.strip()] = value.strip() |
| return retval |
| |
| def _GetProcJiffies(self): |
| """Parse '/proc/timer_list' output and returns the first jiffies attribute. |
| |
| Multi-CPU machines will have multiple 'jiffies:' lines, all of which will be |
| essentially the same. Return the first one.""" |
| jiffies_timer_lines = self.RunCommand( |
| ['grep', 'jiffies', '/proc/timer_list']) |
| if not jiffies_timer_lines: |
| raise Exception('Unable to find jiffies from /proc/timer_list') |
| jiffies_timer_list = jiffies_timer_lines.splitlines() |
| # Each line should look something like 'jiffies: 4315883489'. |
| for line in jiffies_timer_list: |
| match = re.match(r'\s*jiffies\s*:\s*(\d+)', line) |
| if match: |
| value = match.group(1) |
| return float(value) |
| raise Exception('Unable to parse jiffies attribute: %s' % |
| repr(jiffies_timer_lines)) |