OLD | NEW |
(Empty) | |
| 1 # Copyright 2016 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 |
| 5 import json |
| 6 import os |
| 7 try: |
| 8 import psutil |
| 9 except ImportError: |
| 10 psutil = None |
| 11 import subprocess |
| 12 from threading import Timer |
| 13 |
| 14 from py_trace_event import trace_time |
| 15 from telemetry.internal.platform import tracing_agent |
| 16 from telemetry.timeline import trace_data |
| 17 |
| 18 DEFAULT_MIN_PCPU = 0.1 |
| 19 |
| 20 class ProcessCollector(object): |
| 21 |
| 22 def __init__(self, min_pcpu): |
| 23 self._min_pcpu = min_pcpu |
| 24 |
| 25 def GetProcesses(self): |
| 26 return NotImplemented |
| 27 |
| 28 class UnixProcessCollector(ProcessCollector): |
| 29 |
| 30 _SHELL_COMMAND = NotImplemented |
| 31 _START_LINE_NUMBER = 1 |
| 32 _TOKEN_COUNT = 4 |
| 33 _TOKEN_MAP = { |
| 34 'pCpu': 2, |
| 35 'pid': 0, |
| 36 'pMem': 3, |
| 37 'command': 1 |
| 38 } |
| 39 |
| 40 def __init__(self, min_pcpu, binary_output=False): |
| 41 super(UnixProcessCollector, self).__init__(min_pcpu) |
| 42 self._binary_output = binary_output |
| 43 |
| 44 def _ParseLine(self, line): |
| 45 """Parses a line from top output |
| 46 |
| 47 Args: |
| 48 line(str): a line from top output that contains the information about a |
| 49 process. |
| 50 |
| 51 Returns: |
| 52 An dictionary with useful information about the process. |
| 53 """ |
| 54 token_list = line.strip().split() |
| 55 if len(token_list) != self._TOKEN_COUNT: |
| 56 return None |
| 57 return {attribute_name: token_list[index] |
| 58 for attribute_name, index in self._TOKEN_MAP.items()} |
| 59 |
| 60 def GetProcesses(self): |
| 61 """Fetches the top processes returned by top command. |
| 62 |
| 63 Returns: |
| 64 A list of dictionaries, each representing one of the top processes. |
| 65 """ |
| 66 if self._binary_output: |
| 67 processes = subprocess.check_output(self._SHELL_COMMAND).decode( |
| 68 'ascii').split('\n') |
| 69 else: |
| 70 processes = subprocess.check_output(self._SHELL_COMMAND).split('\n') |
| 71 process_lines = processes[self._START_LINE_NUMBER:] |
| 72 top_processes = [] |
| 73 for process_line in process_lines: |
| 74 process = self._ParseLine(process_line) |
| 75 if (not process) or (float(process['pCpu']) < self._min_pcpu): |
| 76 continue |
| 77 top_processes.append(process) |
| 78 return top_processes |
| 79 |
| 80 |
| 81 class WindowsProcessCollector(ProcessCollector): |
| 82 """Class for collecting information about processes on Windows. |
| 83 |
| 84 Windows does not have a fast and simple command to list processes, so psutil |
| 85 package is used instead.""" |
| 86 |
| 87 def __init__(self, min_pcpu): |
| 88 super(WindowsProcessCollector, self).__init__(min_pcpu) |
| 89 |
| 90 def GetProcesses(self): |
| 91 data = [] |
| 92 for p in psutil.process_iter(): |
| 93 try: |
| 94 cpu_percent = p.get_cpu_percent(interval=0) |
| 95 if cpu_percent >= self._min_pcpu: |
| 96 data.append({ |
| 97 'pCpu': cpu_percent, |
| 98 'pMem': p.get_memory_percent(), |
| 99 'command': p.name, |
| 100 'pid': p.pid |
| 101 }) |
| 102 except psutil.Error: |
| 103 pass |
| 104 data = sorted(data, key=lambda d: d['pCpu'], |
| 105 reverse=True) |
| 106 return data |
| 107 |
| 108 |
| 109 class LinuxProcessCollector(UnixProcessCollector): |
| 110 """Class for collecting information about processes on Linux. |
| 111 |
| 112 Example of Linux command output: '31887 com.app.Webkit 3.4 8.0'""" |
| 113 |
| 114 _SHELL_COMMAND = ["ps", "axo", "pid,cmd,pcpu,pmem", "--sort=-pcpu"] |
| 115 |
| 116 |
| 117 def __init__(self, min_pcpu): |
| 118 super(LinuxProcessCollector, self).__init__(min_pcpu) |
| 119 |
| 120 |
| 121 class MacProcessCollector(UnixProcessCollector): |
| 122 """Class for collecting information about processes on Mac. |
| 123 |
| 124 Example of Mac command output: |
| 125 '31887 com.app.Webkit 3.4 8.0'""" |
| 126 |
| 127 _SHELL_COMMAND = ['ps', '-arcwwwxo', 'pid command %cpu %mem'] |
| 128 |
| 129 def __init__(self, min_pcpu): |
| 130 super(MacProcessCollector, self).__init__(min_pcpu, binary_output=True) |
| 131 |
| 132 |
| 133 class CpuTracingAgent(tracing_agent.TracingAgent): |
| 134 |
| 135 SNAPSHOT_FREQUENCY = 1.0 |
| 136 |
| 137 def __init__(self, platform_backend, min_pcpu=DEFAULT_MIN_PCPU): |
| 138 super(CpuTracingAgent, self).__init__(platform_backend) |
| 139 self._snapshot_ongoing = False |
| 140 self._snapshots = [] |
| 141 os_name = platform_backend.GetOSName() |
| 142 if os_name == 'win': |
| 143 self._collector = WindowsProcessCollector(min_pcpu) |
| 144 elif os_name == 'mac': |
| 145 self._collector = MacProcessCollector(min_pcpu) |
| 146 else: |
| 147 self._collector = LinuxProcessCollector(min_pcpu) |
| 148 |
| 149 @classmethod |
| 150 def IsSupported(cls, platform_backend): |
| 151 os_name = platform_backend.GetOSName() |
| 152 return (os_name in ['mac', 'linux']) or (os_name == 'win' and psutil) |
| 153 |
| 154 def StartAgentTracing(self, config, timeout): |
| 155 assert not self._snapshot_ongoing, ( |
| 156 'Agent is already taking snapshots when tracing is started.') |
| 157 if not config.enable_cpu_trace: |
| 158 return False |
| 159 self._snapshot_ongoing = True |
| 160 self._KeepTakingSnapshots() |
| 161 return True |
| 162 |
| 163 def _KeepTakingSnapshots(self): |
| 164 """Take CPU snapshots every SNAPSHOT_FREQUENCY seconds.""" |
| 165 if not self._snapshot_ongoing: |
| 166 return |
| 167 # Assume CpuTracingAgent shares the same clock domain as telemetry |
| 168 self._snapshots.append((self._collector.GetProcesses(), trace_time.Now())) |
| 169 Timer(self.SNAPSHOT_FREQUENCY, self._KeepTakingSnapshots).start() |
| 170 |
| 171 def StopAgentTracing(self): |
| 172 assert self._snapshot_ongoing, ( |
| 173 'Agent is not taking snapshots when tracing is stopped.') |
| 174 self._snapshot_ongoing = False |
| 175 |
| 176 def CollectAgentTraceData(self, trace_data_builder, timeout=None): |
| 177 assert not self._snapshot_ongoing, ( |
| 178 'Agent is still taking snapshots when data is collected.') |
| 179 self._snapshot_ongoing = False |
| 180 data = json.dumps(self._FormatSnapshotsData()) |
| 181 trace_data_builder.SetTraceFor(trace_data.CPU_TRACE_DATA, data) |
| 182 |
| 183 def _FormatSnapshotsData(self): |
| 184 """Format raw data into Object Event specified in Trace Format document.""" |
| 185 pid = os.getpid() |
| 186 return [{ |
| 187 'name': 'CPUSnapshots', |
| 188 'ph': 'O', |
| 189 'id': '0x1000', |
| 190 'local': True, |
| 191 'ts': timestamp, |
| 192 'pid': pid, |
| 193 'tid':None, |
| 194 'args': { |
| 195 'processes': snapshot |
| 196 } |
| 197 } for snapshot, timestamp in self._snapshots] |
OLD | NEW |