blob: a1eea44001a951622eb4a41c9c63805f1f1d0850 [file] [log] [blame]
# Lint as: python2, python3
# Copyright 2022 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import time
import datetime
import threading
import logging
from server.cros.network.perf_monitor_data import PerfMonitorData
from server.cros.network.perf_monitor_command_runner import PerfMonitorCommandRunner
from server.cros.network.perf_monitor_graph import PerfMonitorGraph
from server.cros.network.perf_monitor_throughput_analytics import PerfMonitorThroughputAnalytics
class PerfMonitorService(object):
"""
Encapsulates logic to parse and represent throughput monitoring results.
To fetch network stack performance details, clients should create an
instance of this class and trigger start_monitoring_throughput() to
start performance monitoring and should call
stop_monitoring_throughput() to stop performance monitoring. Stopping
the performance monitoring will post process the collected data and
create graphs of performance results in the result directory.
start_monitoring_throughput() is executed in a separate thread and is
non-blocking.
"""
SECONDS_BETWEEN_ITERATION = 15
def __init__(self, host):
self.monitoring_throughput = False
self.perf_monitoring_data = {}
self.host = host
self.thread = threading.Thread(target=self._monitor_throughput)
def start_monitoring_throughput(self):
"""
Begin monitoring throughput in a separate thread until
stop_monitoring_throughput() is called.
"""
self.thread.start()
def _monitor_throughput(self):
"""
Monitor throughput by calling PerfMonitorCommandRunner functions until
stop_monitoring_throughput() is called.
@param host the host used to run command line commands.
"""
self.monitoring_throughput = True
timestamp = time.time()
command_runner = PerfMonitorCommandRunner()
# Gather the initial values
initial_softnet_data = command_runner.get_softnet_data(self.host)
initial_snmp_data = command_runner.get_snmp_data(self.host)
previous_snmp_data = initial_snmp_data
initial_wireless_interface_data = command_runner.get_wireless_interface_data(self.host)
ratio_labels = ['InReceives', 'InDelivers']
while self.monitoring_throughput:
#exactly SECONDS_BETWEEN_ITERATION seconds between each function call
time.sleep(max(0, self.SECONDS_BETWEEN_ITERATION -
((time.time() - timestamp) % 60.0)))
timestamp = time.time()
timestamp_date = datetime.datetime.fromtimestamp(float(timestamp))
self.perf_monitoring_data[timestamp_date] = PerfMonitorData()
self.perf_monitoring_data[timestamp_date].mpstat_data = (
command_runner.get_mpstat_data(self.host))
# Subtract the initial values from the new values so the values
# begin at 0 and increase throughout the test.
softnet_data = command_runner.get_softnet_data(self.host)
for cpu in softnet_data.keys():
for key in softnet_data[cpu].keys():
softnet_data[cpu][key] = (softnet_data[cpu][key] -
initial_softnet_data[cpu].get(key, 0))
self.perf_monitoring_data[timestamp_date].softnet_data = softnet_data
snmp_data = command_runner.get_snmp_data(self.host)
for data_id in snmp_data.keys():
for key in snmp_data[data_id].keys():
if key in ratio_labels:
previous_value = previous_snmp_data.get(key, 0)
previous_snmp_data[key] = snmp_data[data_id][key]
snmp_data[data_id][key] = (snmp_data[data_id][key] - previous_value)
else:
snmp_data[data_id][key] = (snmp_data[data_id][key] -
initial_snmp_data[data_id].get(key, 0))
self.perf_monitoring_data[timestamp_date].snmp_data = snmp_data
wireless_interface_data = command_runner.get_wireless_interface_data(self.host)
for label in wireless_interface_data.keys():
wireless_interface_data[label] = (wireless_interface_data[label] -
initial_wireless_interface_data.get(label, 0))
self.perf_monitoring_data[timestamp_date].wireless_interface_data = wireless_interface_data
def stop_monitoring_throughput(self):
"""
Stop monitoring throughput and call to graph the throughput metrics.
"""
self.monitoring_throughput = False
self.thread.join()
try:
self._throughput_perf_analytics()
self._graph_throughput_metrics()
except KeyError as k:
logging.error('KeyException observed: missing key: %s', str(k))
def _throughput_perf_analytics(self):
"""
Calculate throughput metrics.
"""
analyzer = PerfMonitorThroughputAnalytics()
analyzer.analyze_cpu_usage(self.perf_monitoring_data)
analyzer.analyze_time_squeeze(self.perf_monitoring_data)
analyzer.analyze_rx_errors(self.perf_monitoring_data)
analyzer.analyze_tx_errors(self.perf_monitoring_data)
def _graph_throughput_metrics(self):
"""
Graph throughput metrics.
"""
grapher = PerfMonitorGraph()
grapher.graph_cpu_data(self.perf_monitoring_data)
grapher.graph_softnet_data(self.perf_monitoring_data)
grapher.graph_snmp_data(self.perf_monitoring_data)
grapher.graph_wireless_interface_data(self.perf_monitoring_data)