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
50 # A None value can be set to mean that the data is not available
53 # This is a special field to hold an optional total rx count that is only
54 # used for column aggregation to compute a total intertface stats
55 # Set to non zero to be picked by the add interface stats method for rx total
58 def get_packet_count(self, direction):
59 """Get packet count for given direction.
61 direction: InterfaceStats.TX or InterfaceStats.RX
63 return self.tx if direction == InterfaceStats.TX else self.rx
66 def get_reverse_direction(direction):
67 """Get the reverse direction of a given direction.
69 direction: InterfaceStats.TX or InterfaceStats.RX
70 return: RX if TX given, or TX is RX given
75 def get_direction_name(direction):
76 """Get the rdisplay name of a given direction.
78 direction: InterfaceStats.TX or InterfaceStats.RX
81 if direction == InterfaceStats.TX:
85 def add_if_stats(self, if_stats):
86 """Add another ifstats to this instance."""
87 def added_counter(old_value, new_value_to_add):
90 return new_value_to_add
91 return old_value + new_value_to_add
94 self.tx = added_counter(self.tx, if_stats.tx)
95 self.rx = added_counter(self.rx, if_stats.rx)
96 # Add special rx total value if set
97 self.rx = added_counter(self.rx, if_stats.rx_total)
99 def update_stats(self, tx, rx, diff):
100 """Update stats for this interface.
102 tx: new TX packet count
103 rx: new RX packet count
104 diff: if True, perform a diff of new value with previous baselined value,
105 otherwise store the new value
108 self.tx = tx - self.tx
109 self.rx = rx - self.rx
114 def get_display_name(self, dir, name=None, aggregate=False):
115 """Get the name to use to display stats for this interface stats.
117 dir: direction InterfaceStats.TX or InterfaceStats.RX
118 name: override self.name
119 aggregate: true if this is for an aggregate of multiple chains
123 return self.device + '.' + InterfaceStats.get_direction_name(dir) + '.' + name
126 class PacketPathStats(object):
127 """Manage the packet path stats for 1 chain in both directions.
129 A packet path stats instance manages an ordered list of InterfaceStats objects
130 that can be traversed in the forward and reverse direction to display packet
131 counters in each direction.
132 The requirement is that RX and TX counters must always alternate as we travel
133 along one direction. For example with 4 interfaces per chain:
134 [ifstat0, ifstat1, ifstat2, ifstat3]
135 Packet counters in the forward direction are:
136 [ifstat0.TX, ifstat1.RX, ifstat2.TX, ifstat3.RX]
137 Packet counters in the reverse direction are:
138 [ifstat3.TX, ifstat2.RX, ifstat1.TX, ifstat0.RX]
140 A packet path stats also carries the latency data for each direction of the
144 def __init__(self, if_stats, aggregate=False):
145 """Create a packet path stats intance with the list of associated if stats.
147 if_stats: a list of interface stats that compose this packet path stats
148 aggregate: True if this is an aggregate packet path stats
150 Aggregate packet path stats are the only one that should show counters for shared
153 self.if_stats = if_stats
154 # latency for packets sent from port 0 and 1
155 self.latencies = [Latency(), Latency()]
156 self.aggregate = aggregate
159 def add_packet_path_stats(self, pps):
160 """Add another packet path stat to this instance.
162 pps: the other packet path stats to add to this instance
164 This is used only for aggregating/collapsing multiple pps into 1
165 to form a "total" pps
167 for index, ifstats in enumerate(self.if_stats):
168 # shared interface stats must not be self added
169 if not ifstats.shared:
170 ifstats.add_if_stats(pps.if_stats[index])
173 def get_agg_packet_path_stats(pps_list):
174 """Get the aggregated packet path stats from a list of packet path stats.
176 Interface counters are added, latency stats are updated.
181 # Get a clone of the first in the list
182 agg_pps = PacketPathStats(pps.get_cloned_if_stats(), aggregate=True)
184 agg_pps.add_packet_path_stats(pps)
185 # aggregate all latencies
186 agg_pps.latencies = [Latency([pps.latencies[port] for pps in pps_list])
190 def get_if_stats(self, reverse=False):
191 """Get interface stats for given direction.
193 reverse: if True, get the list of interface stats in the reverse direction
194 else (default) gets the ist in the forward direction.
195 return: the list of interface stats indexed by the chain index
197 return self.if_stats[::-1] if reverse else self.if_stats
199 def get_cloned_if_stats(self):
200 """Get a clone copy of the interface stats list."""
201 return [copy.copy(ifstat) for ifstat in self.if_stats]
204 def get_header_labels(self, reverse=False, aggregate=False):
205 """Get the list of header labels for this packet path stats."""
207 dir = InterfaceStats.TX
208 for ifstat in self.get_if_stats(reverse):
209 # starts at TX then RX then TX again etc...
210 labels.append(ifstat.get_display_name(dir, aggregate=aggregate))
211 dir = InterfaceStats.get_reverse_direction(dir)
214 def get_stats(self, reverse=False):
215 """Get the list of packet counters and latency data for this packet path stats.
217 return: a dict of packet counters and latency stats
219 {'packets': [2000054, 1999996, 1999996],
220 'min_usec': 10, 'max_usec': 187, 'avg_usec': 45},
223 dir = InterfaceStats.TX
224 for ifstat in self.get_if_stats(reverse):
225 # starts at TX then RX then TX again etc...
226 if ifstat.shared and not self.aggregate:
227 # shared if stats countesr are only shown in aggregate pps
230 counters.append(ifstat.get_packet_count(dir))
231 dir = InterfaceStats.get_reverse_direction(dir)
233 # latency: use port 0 latency for forward, port 1 latency for reverse
234 latency = self.latencies[1] if reverse else self.latencies[0]
236 if latency.available():
237 results = {'lat_min_usec': latency.min_usec,
238 'lat_max_usec': latency.max_usec,
239 'lat_avg_usec': latency.avg_usec}
241 results['hdrh'] = latency.hdrh
244 results['packets'] = counters
248 class PacketPathStatsManager(object):
249 """Manages all the packet path stats for all chains.
251 Each run will generate packet path stats for 1 or more chains.
254 def __init__(self, pps_list):
255 """Create a packet path stats intance with the list of associated if stats.
257 pps_list: a list of packet path stats indexed by the chain id.
258 All packet path stats must have the same length.
260 self.pps_list = pps_list
262 def insert_pps_list(self, chain_index, if_stats):
263 """Insert a list of interface stats for given chain right after the first in the list.
265 chain_index: index of chain where to insert
266 if_stats: list of interface stats to insert
268 # use slicing to insert the list
269 self.pps_list[chain_index].if_stats[1:1] = if_stats
271 def _get_if_agg_name(self, reverse):
272 """Get the aggegated name for all interface stats across all pps.
274 return: a list of aggregated names for each position of the chain for all chains
276 The agregated name is the interface stats name if there is only 1 chain.
277 Otherwise it is the common prefix for all interface stats names at same position in the
280 # if there is only one chain, use the if_stats names directly
281 return self.pps_list[0].get_header_labels(reverse, aggregate=(len(self.pps_list) > 1))
283 def _get_results(self, reverse=False):
284 """Get the digested stats for the forward or reverse directions.
286 return: a dict with all the labels, total and per chain counters
289 # insert the aggregated row if applicable
290 if len(self.pps_list) > 1:
291 agg_pps = PacketPathStats.get_agg_packet_path_stats(self.pps_list)
292 chains['total'] = agg_pps.get_stats(reverse)
294 for index, pps in enumerate(self.pps_list):
295 chains[str(index)] = pps.get_stats(reverse)
296 return {'interfaces': self._get_if_agg_name(reverse),
299 def get_results(self):
300 """Get the digested stats for the forward and reverse directions.
302 return: a dictionary of results for each direction and each chain
308 'interfaces': ['Port0', 'vhost0', 'Port1'],
310 '0': {'packets': [2000054, 1999996, 1999996],
323 results = {'Forward': self._get_results(),
324 'Reverse': self._get_results(reverse=True)}