1 # Copyright 2018 Cisco Systems, Inc. All rights reserved.
3 # Licensed under the Apache License, Version 2.0 (the "License"); you may
4 # not use this file except in compliance with the License. You may obtain
5 # a copy of the License at
7 # http://www.apache.org/licenses/LICENSE-2.0
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 # License for the specific language governing permissions and limitations
15 """Manage all classes related to counting packet stats.
17 InterfaceStats counts RX/TX packet counters for one interface.
18 PacketPathStats manages all InterfaceStats instances for a given chain.
19 PacketPathStatsManager manages all packet path stats for all chains.
24 from traffic_gen.traffic_base import Latency
26 class InterfaceStats(object):
27 """A class to hold the RX and TX counters for a virtual or physical interface.
29 An interface stats instance can represent a real interface (e.g. traffic gen port or
30 vhost interface) or can represent an aggegation of multiple interfaces when packets
31 are faned out (e.g. one vlan subinterface can fan out to multiple vhost interfaces
32 in the case of multi-chaining and when the network is shared across chains).
38 def __init__(self, name, device, shared=False):
39 """Create a new interface instance.
41 name: interface name specific to each chain (e.g. "trex port 0 chain 0")
42 device: on which device this interface resides (e.g. "trex server")
43 fetch_tx_rx: a fetch method that takes name, chain_index and returns a (tx, rx) tuple
44 shared: if true this interface stats is shared across all chains
49 # RX and TX counters for this interface
53 def get_packet_count(self, direction):
54 """Get packet count for given direction.
56 direction: InterfaceStats.TX or InterfaceStats.RX
58 return self.tx if direction == InterfaceStats.TX else self.rx
61 def get_reverse_direction(direction):
62 """Get the reverse direction of a given direction.
64 direction: InterfaceStats.TX or InterfaceStats.RX
65 return: RX if TX given, or TX is RX given
70 def get_direction_name(direction):
71 """Get the rdisplay name of a given direction.
73 direction: InterfaceStats.TX or InterfaceStats.RX
76 if direction == InterfaceStats.TX:
80 def add_if_stats(self, if_stats):
81 """Add another ifstats to this instance."""
82 self.tx += if_stats.tx
83 self.rx += if_stats.rx
85 def update_stats(self, tx, rx, diff):
86 """Update stats for this interface.
88 tx: new TX packet count
89 rx: new RX packet count
90 diff: if True, perform a diff of new value with previous baselined value,
91 otherwise store the new value
94 self.tx = tx - self.tx
95 self.rx = rx - self.rx
100 def get_display_name(self, dir, name=None, aggregate=False):
101 """Get the name to use to display stats for this interface stats.
103 dir: direction InterfaceStats.TX or InterfaceStats.RX
104 name: override self.name
105 aggregate: true if this is for an aggregate of multiple chains
109 return self.device + '.' + InterfaceStats.get_direction_name(dir) + '.' + name
112 class PacketPathStats(object):
113 """Manage the packet path stats for 1 chain in both directions.
115 A packet path stats instance manages an ordered list of InterfaceStats objects
116 that can be traversed in the forward and reverse direction to display packet
117 counters in each direction.
118 The requirement is that RX and TX counters must always alternate as we travel
119 along one direction. For example with 4 interfaces per chain:
120 [ifstat0, ifstat1, ifstat2, ifstat3]
121 Packet counters in the forward direction are:
122 [ifstat0.TX, ifstat1.RX, ifstat2.TX, ifstat3.RX]
123 Packet counters in the reverse direction are:
124 [ifstat3.TX, ifstat2.RX, ifstat1.TX, ifstat0.RX]
126 A packet path stats also carries the latency data for each direction of the
130 def __init__(self, if_stats, aggregate=False):
131 """Create a packet path stats intance with the list of associated if stats.
133 if_stats: a list of interface stats that compose this packet path stats
134 aggregate: True if this is an aggregate packet path stats
136 Aggregate packet path stats are the only one that should show counters for shared
139 self.if_stats = if_stats
140 # latency for packets sent from port 0 and 1
141 self.latencies = [Latency(), Latency()]
142 self.aggregate = aggregate
145 def add_packet_path_stats(self, pps):
146 """Add another packet path stat to this instance.
148 pps: the other packet path stats to add to this instance
150 This is used only for aggregating/collapsing multiple pps into 1
151 to form a "total" pps
153 for index, ifstats in enumerate(self.if_stats):
154 # shared interface stats must not be self added
155 if not ifstats.shared:
156 ifstats.add_if_stats(pps.if_stats[index])
159 def get_agg_packet_path_stats(pps_list):
160 """Get the aggregated packet path stats from a list of packet path stats.
162 Interface counters are added, latency stats are updated.
167 # Get a clone of the first in the list
168 agg_pps = PacketPathStats(pps.get_cloned_if_stats(), aggregate=True)
170 agg_pps.add_packet_path_stats(pps)
171 # aggregate all latencies
172 agg_pps.latencies = [Latency([pps.latencies[port] for pps in pps_list])
176 def get_if_stats(self, reverse=False):
177 """Get interface stats for given direction.
179 reverse: if True, get the list of interface stats in the reverse direction
180 else (default) gets the ist in the forward direction.
181 return: the list of interface stats indexed by the chain index
183 return self.if_stats[::-1] if reverse else self.if_stats
185 def get_cloned_if_stats(self):
186 """Get a clone copy of the interface stats list."""
187 return [copy.copy(ifstat) for ifstat in self.if_stats]
190 def get_header_labels(self, reverse=False, aggregate=False):
191 """Get the list of header labels for this packet path stats."""
193 dir = InterfaceStats.TX
194 for ifstat in self.get_if_stats(reverse):
195 # starts at TX then RX then TX again etc...
196 labels.append(ifstat.get_display_name(dir, aggregate=aggregate))
197 dir = InterfaceStats.get_reverse_direction(dir)
200 def get_stats(self, reverse=False):
201 """Get the list of packet counters and latency data for this packet path stats.
203 return: a dict of packet counters and latency stats
205 {'packets': [2000054, 1999996, 1999996],
206 'min_usec': 10, 'max_usec': 187, 'avg_usec': 45},
209 dir = InterfaceStats.TX
210 for ifstat in self.get_if_stats(reverse):
211 # starts at TX then RX then TX again etc...
212 if ifstat.shared and not self.aggregate:
213 # shared if stats countesr are only shown in aggregate pps
216 counters.append(ifstat.get_packet_count(dir))
217 dir = InterfaceStats.get_reverse_direction(dir)
219 # latency: use port 0 latency for forward, port 1 latency for reverse
220 latency = self.latencies[1] if reverse else self.latencies[0]
222 if latency.available():
223 results = {'lat_min_usec': latency.min_usec,
224 'lat_max_usec': latency.max_usec,
225 'lat_avg_usec': latency.avg_usec}
228 results['packets'] = counters
232 class PacketPathStatsManager(object):
233 """Manages all the packet path stats for all chains.
235 Each run will generate packet path stats for 1 or more chains.
238 def __init__(self, pps_list):
239 """Create a packet path stats intance with the list of associated if stats.
241 pps_list: a list of packet path stats indexed by the chain id.
242 All packet path stats must have the same length.
244 self.pps_list = pps_list
246 def insert_pps_list(self, chain_index, if_stats):
247 """Insert a list of interface stats for given chain right after the first in the list.
249 chain_index: index of chain where to insert
250 if_stats: list of interface stats to insert
252 # use slicing to insert the list
253 self.pps_list[chain_index].if_stats[1:1] = if_stats
255 def _get_if_agg_name(self, reverse):
256 """Get the aggegated name for all interface stats across all pps.
258 return: a list of aggregated names for each position of the chain for all chains
260 The agregated name is the interface stats name if there is only 1 chain.
261 Otherwise it is the common prefix for all interface stats names at same position in the
264 # if there is only one chain, use the if_stats names directly
265 return self.pps_list[0].get_header_labels(reverse, aggregate=(len(self.pps_list) > 1))
267 def _get_results(self, reverse=False):
268 """Get the digested stats for the forward or reverse directions.
270 return: a dict with all the labels, total and per chain counters
273 # insert the aggregated row if applicable
274 if len(self.pps_list) > 1:
275 agg_pps = PacketPathStats.get_agg_packet_path_stats(self.pps_list)
276 chains['total'] = agg_pps.get_stats(reverse)
278 for index, pps in enumerate(self.pps_list):
279 chains[index] = pps.get_stats(reverse)
280 return {'interfaces': self._get_if_agg_name(reverse),
283 def get_results(self):
284 """Get the digested stats for the forward and reverse directions.
286 return: a dictionary of results for each direction and each chain
292 'interfaces': ['Port0', 'vhost0', 'Port1'],
294 0: {'packets': [2000054, 1999996, 1999996],
307 results = {'Forward': self._get_results(),
308 'Reverse': self._get_results(reverse=True)}