| Index: telemetry/telemetry/internal/platform/tracing_agent/cpu_tracing_agent.py
|
| diff --git a/telemetry/telemetry/internal/platform/tracing_agent/cpu_tracing_agent.py b/telemetry/telemetry/internal/platform/tracing_agent/cpu_tracing_agent.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..faca543907eba9d0257d36d41029e332e317e22f
|
| --- /dev/null
|
| +++ b/telemetry/telemetry/internal/platform/tracing_agent/cpu_tracing_agent.py
|
| @@ -0,0 +1,197 @@
|
| +# Copyright 2016 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.
|
| +
|
| +import json
|
| +import os
|
| +try:
|
| + import psutil
|
| +except ImportError:
|
| + psutil = None
|
| +import subprocess
|
| +from threading import Timer
|
| +
|
| +from py_trace_event import trace_time
|
| +from telemetry.internal.platform import tracing_agent
|
| +from telemetry.timeline import trace_data
|
| +
|
| +DEFAULT_MIN_PCPU = 0.1
|
| +
|
| +class ProcessCollector(object):
|
| +
|
| + def __init__(self, min_pcpu):
|
| + self._min_pcpu = min_pcpu
|
| +
|
| + def GetProcesses(self):
|
| + return NotImplemented
|
| +
|
| +class UnixProcessCollector(ProcessCollector):
|
| +
|
| + _SHELL_COMMAND = NotImplemented
|
| + _START_LINE_NUMBER = 1
|
| + _TOKEN_COUNT = 4
|
| + _TOKEN_MAP = {
|
| + 'pCpu': 2,
|
| + 'pid': 0,
|
| + 'pMem': 3,
|
| + 'command': 1
|
| + }
|
| +
|
| + def __init__(self, min_pcpu, binary_output=False):
|
| + super(UnixProcessCollector, self).__init__(min_pcpu)
|
| + self._binary_output = binary_output
|
| +
|
| + def _ParseLine(self, line):
|
| + """Parses a line from top output
|
| +
|
| + Args:
|
| + line(str): a line from top output that contains the information about a
|
| + process.
|
| +
|
| + Returns:
|
| + An dictionary with useful information about the process.
|
| + """
|
| + token_list = line.strip().split()
|
| + if len(token_list) != self._TOKEN_COUNT:
|
| + return None
|
| + return {attribute_name: token_list[index]
|
| + for attribute_name, index in self._TOKEN_MAP.items()}
|
| +
|
| + def GetProcesses(self):
|
| + """Fetches the top processes returned by top command.
|
| +
|
| + Returns:
|
| + A list of dictionaries, each representing one of the top processes.
|
| + """
|
| + if self._binary_output:
|
| + processes = subprocess.check_output(self._SHELL_COMMAND).decode(
|
| + 'ascii').split('\n')
|
| + else:
|
| + processes = subprocess.check_output(self._SHELL_COMMAND).split('\n')
|
| + process_lines = processes[self._START_LINE_NUMBER:]
|
| + top_processes = []
|
| + for process_line in process_lines:
|
| + process = self._ParseLine(process_line)
|
| + if (not process) or (float(process['pCpu']) < self._min_pcpu):
|
| + continue
|
| + top_processes.append(process)
|
| + return top_processes
|
| +
|
| +
|
| +class WindowsProcessCollector(ProcessCollector):
|
| + """Class for collecting information about processes on Windows.
|
| +
|
| + Windows does not have a fast and simple command to list processes, so psutil
|
| + package is used instead."""
|
| +
|
| + def __init__(self, min_pcpu):
|
| + super(WindowsProcessCollector, self).__init__(min_pcpu)
|
| +
|
| + def GetProcesses(self):
|
| + data = []
|
| + for p in psutil.process_iter():
|
| + try:
|
| + cpu_percent = p.get_cpu_percent(interval=0)
|
| + if cpu_percent >= self._min_pcpu:
|
| + data.append({
|
| + 'pCpu': cpu_percent,
|
| + 'pMem': p.get_memory_percent(),
|
| + 'command': p.name,
|
| + 'pid': p.pid
|
| + })
|
| + except psutil.Error:
|
| + pass
|
| + data = sorted(data, key=lambda d: d['pCpu'],
|
| + reverse=True)
|
| + return data
|
| +
|
| +
|
| +class LinuxProcessCollector(UnixProcessCollector):
|
| + """Class for collecting information about processes on Linux.
|
| +
|
| + Example of Linux command output: '31887 com.app.Webkit 3.4 8.0'"""
|
| +
|
| + _SHELL_COMMAND = ["ps", "axo", "pid,cmd,pcpu,pmem", "--sort=-pcpu"]
|
| +
|
| +
|
| + def __init__(self, min_pcpu):
|
| + super(LinuxProcessCollector, self).__init__(min_pcpu)
|
| +
|
| +
|
| +class MacProcessCollector(UnixProcessCollector):
|
| + """Class for collecting information about processes on Mac.
|
| +
|
| + Example of Mac command output:
|
| + '31887 com.app.Webkit 3.4 8.0'"""
|
| +
|
| + _SHELL_COMMAND = ['ps', '-arcwwwxo', 'pid command %cpu %mem']
|
| +
|
| + def __init__(self, min_pcpu):
|
| + super(MacProcessCollector, self).__init__(min_pcpu, binary_output=True)
|
| +
|
| +
|
| +class CpuTracingAgent(tracing_agent.TracingAgent):
|
| +
|
| + SNAPSHOT_FREQUENCY = 1.0
|
| +
|
| + def __init__(self, platform_backend, min_pcpu=DEFAULT_MIN_PCPU):
|
| + super(CpuTracingAgent, self).__init__(platform_backend)
|
| + self._snapshot_ongoing = False
|
| + self._snapshots = []
|
| + os_name = platform_backend.GetOSName()
|
| + if os_name == 'win':
|
| + self._collector = WindowsProcessCollector(min_pcpu)
|
| + elif os_name == 'mac':
|
| + self._collector = MacProcessCollector(min_pcpu)
|
| + else:
|
| + self._collector = LinuxProcessCollector(min_pcpu)
|
| +
|
| + @classmethod
|
| + def IsSupported(cls, platform_backend):
|
| + os_name = platform_backend.GetOSName()
|
| + return (os_name in ['mac', 'linux']) or (os_name == 'win' and psutil)
|
| +
|
| + def StartAgentTracing(self, config, timeout):
|
| + assert not self._snapshot_ongoing, (
|
| + 'Agent is already taking snapshots when tracing is started.')
|
| + if not config.enable_cpu_trace:
|
| + return False
|
| + self._snapshot_ongoing = True
|
| + self._KeepTakingSnapshots()
|
| + return True
|
| +
|
| + def _KeepTakingSnapshots(self):
|
| + """Take CPU snapshots every SNAPSHOT_FREQUENCY seconds."""
|
| + if not self._snapshot_ongoing:
|
| + return
|
| + # Assume CpuTracingAgent shares the same clock domain as telemetry
|
| + self._snapshots.append((self._collector.GetProcesses(), trace_time.Now()))
|
| + Timer(self.SNAPSHOT_FREQUENCY, self._KeepTakingSnapshots).start()
|
| +
|
| + def StopAgentTracing(self):
|
| + assert self._snapshot_ongoing, (
|
| + 'Agent is not taking snapshots when tracing is stopped.')
|
| + self._snapshot_ongoing = False
|
| +
|
| + def CollectAgentTraceData(self, trace_data_builder, timeout=None):
|
| + assert not self._snapshot_ongoing, (
|
| + 'Agent is still taking snapshots when data is collected.')
|
| + self._snapshot_ongoing = False
|
| + data = json.dumps(self._FormatSnapshotsData())
|
| + trace_data_builder.SetTraceFor(trace_data.CPU_TRACE_DATA, data)
|
| +
|
| + def _FormatSnapshotsData(self):
|
| + """Format raw data into Object Event specified in Trace Format document."""
|
| + pid = os.getpid()
|
| + return [{
|
| + 'name': 'CPUSnapshots',
|
| + 'ph': 'O',
|
| + 'id': '0x1000',
|
| + 'local': True,
|
| + 'ts': timestamp,
|
| + 'pid': pid,
|
| + 'tid':None,
|
| + 'args': {
|
| + 'processes': snapshot
|
| + }
|
| + } for snapshot, timestamp in self._snapshots]
|
|
|