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