diff options
Diffstat (limited to 'nfvbench/packet_stats.py')
-rw-r--r-- | nfvbench/packet_stats.py | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/nfvbench/packet_stats.py b/nfvbench/packet_stats.py new file mode 100644 index 0000000..16dc965 --- /dev/null +++ b/nfvbench/packet_stats.py @@ -0,0 +1,309 @@ +# Copyright 2018 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# +"""Manage all classes related to counting packet stats. + +InterfaceStats counts RX/TX packet counters for one interface. +PacketPathStats manages all InterfaceStats instances for a given chain. +PacketPathStatsManager manages all packet path stats for all chains. +""" + +import copy + +from traffic_gen.traffic_base import Latency + +class InterfaceStats(object): + """A class to hold the RX and TX counters for a virtual or physical interface. + + An interface stats instance can represent a real interface (e.g. traffic gen port or + vhost interface) or can represent an aggegation of multiple interfaces when packets + are faned out (e.g. one vlan subinterface can fan out to multiple vhost interfaces + in the case of multi-chaining and when the network is shared across chains). + """ + + TX = 0 + RX = 1 + + def __init__(self, name, device, shared=False): + """Create a new interface instance. + + name: interface name specific to each chain (e.g. "trex port 0 chain 0") + device: on which device this interface resides (e.g. "trex server") + fetch_tx_rx: a fetch method that takes name, chain_index and returns a (tx, rx) tuple + shared: if true this interface stats is shared across all chains + """ + self.name = name + self.device = device + self.shared = shared + # RX and TX counters for this interface + self.tx = 0 + self.rx = 0 + + def get_packet_count(self, direction): + """Get packet count for given direction. + + direction: InterfaceStats.TX or InterfaceStats.RX + """ + return self.tx if direction == InterfaceStats.TX else self.rx + + @staticmethod + def get_reverse_direction(direction): + """Get the reverse direction of a given direction. + + direction: InterfaceStats.TX or InterfaceStats.RX + return: RX if TX given, or TX is RX given + """ + return 1 - direction + + @staticmethod + def get_direction_name(direction): + """Get the rdisplay name of a given direction. + + direction: InterfaceStats.TX or InterfaceStats.RX + return: "TX" or "RX" + """ + if direction == InterfaceStats.TX: + return 'TX' + return 'RX' + + def add_if_stats(self, if_stats): + """Add another ifstats to this instance.""" + self.tx += if_stats.tx + self.rx += if_stats.rx + + def update_stats(self, tx, rx, diff): + """Update stats for this interface. + + tx: new TX packet count + rx: new RX packet count + diff: if True, perform a diff of new value with previous baselined value, + otherwise store the new value + """ + if diff: + self.tx = tx - self.tx + self.rx = rx - self.rx + else: + self.tx = tx + self.rx = rx + + def get_display_name(self, dir, name=None, aggregate=False): + """Get the name to use to display stats for this interface stats. + + dir: direction InterfaceStats.TX or InterfaceStats.RX + name: override self.name + aggregate: true if this is for an aggregate of multiple chains + """ + if name is None: + name = self.name + return self.device + '.' + InterfaceStats.get_direction_name(dir) + '.' + name + + +class PacketPathStats(object): + """Manage the packet path stats for 1 chain in both directions. + + A packet path stats instance manages an ordered list of InterfaceStats objects + that can be traversed in the forward and reverse direction to display packet + counters in each direction. + The requirement is that RX and TX counters must always alternate as we travel + along one direction. For example with 4 interfaces per chain: + [ifstat0, ifstat1, ifstat2, ifstat3] + Packet counters in the forward direction are: + [ifstat0.TX, ifstat1.RX, ifstat2.TX, ifstat3.RX] + Packet counters in the reverse direction are: + [ifstat3.TX, ifstat2.RX, ifstat1.TX, ifstat0.RX] + + A packet path stats also carries the latency data for each direction of the + chain. + """ + + def __init__(self, if_stats, aggregate=False): + """Create a packet path stats intance with the list of associated if stats. + + if_stats: a list of interface stats that compose this packet path stats + aggregate: True if this is an aggregate packet path stats + + Aggregate packet path stats are the only one that should show counters for shared + interface stats + """ + self.if_stats = if_stats + # latency for packets sent from port 0 and 1 + self.latencies = [Latency(), Latency()] + self.aggregate = aggregate + + + def add_packet_path_stats(self, pps): + """Add another packet path stat to this instance. + + pps: the other packet path stats to add to this instance + + This is used only for aggregating/collapsing multiple pps into 1 + to form a "total" pps + """ + for index, ifstats in enumerate(self.if_stats): + # shared interface stats must not be self added + if not ifstats.shared: + ifstats.add_if_stats(pps.if_stats[index]) + + @staticmethod + def get_agg_packet_path_stats(pps_list): + """Get the aggregated packet path stats from a list of packet path stats. + + Interface counters are added, latency stats are updated. + """ + agg_pps = None + for pps in pps_list: + if agg_pps is None: + # Get a clone of the first in the list + agg_pps = PacketPathStats(pps.get_cloned_if_stats(), aggregate=True) + else: + agg_pps.add_packet_path_stats(pps) + # aggregate all latencies + agg_pps.latencies = [Latency([pps.latencies[port] for pps in pps_list]) + for port in [0, 1]] + return agg_pps + + def get_if_stats(self, reverse=False): + """Get interface stats for given direction. + + reverse: if True, get the list of interface stats in the reverse direction + else (default) gets the ist in the forward direction. + return: the list of interface stats indexed by the chain index + """ + return self.if_stats[::-1] if reverse else self.if_stats + + def get_cloned_if_stats(self): + """Get a clone copy of the interface stats list.""" + return [copy.copy(ifstat) for ifstat in self.if_stats] + + + def get_header_labels(self, reverse=False, aggregate=False): + """Get the list of header labels for this packet path stats.""" + labels = [] + dir = InterfaceStats.TX + for ifstat in self.get_if_stats(reverse): + # starts at TX then RX then TX again etc... + labels.append(ifstat.get_display_name(dir, aggregate=aggregate)) + dir = InterfaceStats.get_reverse_direction(dir) + return labels + + def get_stats(self, reverse=False): + """Get the list of packet counters and latency data for this packet path stats. + + return: a dict of packet counters and latency stats + + {'packets': [2000054, 1999996, 1999996], + 'min_usec': 10, 'max_usec': 187, 'avg_usec': 45}, + """ + counters = [] + dir = InterfaceStats.TX + for ifstat in self.get_if_stats(reverse): + # starts at TX then RX then TX again etc... + if ifstat.shared and not self.aggregate: + # shared if stats countesr are only shown in aggregate pps + counters.append('') + else: + counters.append(ifstat.get_packet_count(dir)) + dir = InterfaceStats.get_reverse_direction(dir) + + # latency: use port 0 latency for forward, port 1 latency for reverse + latency = self.latencies[1] if reverse else self.latencies[0] + + if latency.available(): + results = {'lat_min_usec': latency.min_usec, + 'lat_max_usec': latency.max_usec, + 'lat_avg_usec': latency.avg_usec} + else: + results = {} + results['packets'] = counters + return results + + +class PacketPathStatsManager(object): + """Manages all the packet path stats for all chains. + + Each run will generate packet path stats for 1 or more chains. + """ + + def __init__(self, pps_list): + """Create a packet path stats intance with the list of associated if stats. + + pps_list: a list of packet path stats indexed by the chain id. + All packet path stats must have the same length. + """ + self.pps_list = pps_list + + def insert_pps_list(self, chain_index, if_stats): + """Insert a list of interface stats for given chain right after the first in the list. + + chain_index: index of chain where to insert + if_stats: list of interface stats to insert + """ + # use slicing to insert the list + self.pps_list[chain_index].if_stats[1:1] = if_stats + + def _get_if_agg_name(self, reverse): + """Get the aggegated name for all interface stats across all pps. + + return: a list of aggregated names for each position of the chain for all chains + + The agregated name is the interface stats name if there is only 1 chain. + Otherwise it is the common prefix for all interface stats names at same position in the + chain. + """ + # if there is only one chain, use the if_stats names directly + return self.pps_list[0].get_header_labels(reverse, aggregate=(len(self.pps_list) > 1)) + + def _get_results(self, reverse=False): + """Get the digested stats for the forward or reverse directions. + + return: a dict with all the labels, total and per chain counters + """ + chains = {} + # insert the aggregated row if applicable + if len(self.pps_list) > 1: + agg_pps = PacketPathStats.get_agg_packet_path_stats(self.pps_list) + chains['total'] = agg_pps.get_stats(reverse) + + for index, pps in enumerate(self.pps_list): + chains[index] = pps.get_stats(reverse) + return {'interfaces': self._get_if_agg_name(reverse), + 'chains': chains} + + def get_results(self): + """Get the digested stats for the forward and reverse directions. + + return: a dictionary of results for each direction and each chain + + Example: + + { + 'Forward': { + 'interfaces': ['Port0', 'vhost0', 'Port1'], + 'chains': { + 0: {'packets': [2000054, 1999996, 1999996], + 'min_usec': 10, + 'max_usec': 187, + 'avg_usec': 45}, + 1: {...}, + 'total': {...} + } + }, + 'Reverse': {... + } + } + + """ + results = {'Forward': self._get_results(), + 'Reverse': self._get_results(reverse=True)} + return results |