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 hdrh.histogram import HdrHistogram
25 from .traffic_gen.traffic_base import Latency
27 class InterfaceStats(object):
28 """A class to hold the RX and TX counters for a virtual or physical interface.
30 An interface stats instance can represent a real interface (e.g. traffic gen port or
31 vhost interface) or can represent an aggegation of multiple interfaces when packets
32 are faned out (e.g. one vlan subinterface can fan out to multiple vhost interfaces
33 in the case of multi-chaining and when the network is shared across chains).
39 def __init__(self, name, device, shared=False):
40 """Create a new interface instance.
42 name: interface name specific to each chain (e.g. "trex port 0 chain 0")
43 device: on which device this interface resides (e.g. "trex server")
44 fetch_tx_rx: a fetch method that takes name, chain_index and returns a (tx, rx) tuple
45 shared: if true this interface stats is shared across all chains
50 # RX and TX counters for this interface
51 # A None value can be set to mean that the data is not available
54 # This is a special field to hold an optional total rx count that is only
55 # used for column aggregation to compute a total intertface stats
56 # Set to non zero to be picked by the add interface stats method for rx total
59 def get_packet_count(self, direction):
60 """Get packet count for given direction.
62 direction: InterfaceStats.TX or InterfaceStats.RX
64 return self.tx if direction == InterfaceStats.TX else self.rx
67 def get_reverse_direction(direction):
68 """Get the reverse direction of a given direction.
70 direction: InterfaceStats.TX or InterfaceStats.RX
71 return: RX if TX given, or TX is RX given
76 def get_direction_name(direction):
77 """Get the rdisplay name of a given direction.
79 direction: InterfaceStats.TX or InterfaceStats.RX
82 if direction == InterfaceStats.TX:
86 def add_if_stats(self, if_stats):
87 """Add another ifstats to this instance."""
88 def added_counter(old_value, new_value_to_add):
91 return new_value_to_add
92 return old_value + new_value_to_add
95 self.tx = added_counter(self.tx, if_stats.tx)
96 self.rx = added_counter(self.rx, if_stats.rx)
97 # Add special rx total value if set
98 self.rx = added_counter(self.rx, if_stats.rx_total)
100 def update_stats(self, tx, rx, diff):
101 """Update stats for this interface.
103 tx: new TX packet count
104 rx: new RX packet count
105 diff: if True, perform a diff of new value with previous baselined value,
106 otherwise store the new value
109 self.tx = tx - self.tx
110 self.rx = rx - self.rx
115 def get_display_name(self, dir, name=None, aggregate=False):
116 """Get the name to use to display stats for this interface stats.
118 dir: direction InterfaceStats.TX or InterfaceStats.RX
119 name: override self.name
120 aggregate: true if this is for an aggregate of multiple chains
124 return self.device + '.' + InterfaceStats.get_direction_name(dir) + '.' + name
127 class PacketPathStats(object):
128 """Manage the packet path stats for 1 chain in both directions.
130 A packet path stats instance manages an ordered list of InterfaceStats objects
131 that can be traversed in the forward and reverse direction to display packet
132 counters in each direction.
133 The requirement is that RX and TX counters must always alternate as we travel
134 along one direction. For example with 4 interfaces per chain:
135 [ifstat0, ifstat1, ifstat2, ifstat3]
136 Packet counters in the forward direction are:
137 [ifstat0.TX, ifstat1.RX, ifstat2.TX, ifstat3.RX]
138 Packet counters in the reverse direction are:
139 [ifstat3.TX, ifstat2.RX, ifstat1.TX, ifstat0.RX]
141 A packet path stats also carries the latency data for each direction of the
145 def __init__(self, config, if_stats, aggregate=False):
146 """Create a packet path stats intance with the list of associated if stats.
148 if_stats: a list of interface stats that compose this packet path stats
149 aggregate: True if this is an aggregate packet path stats
151 Aggregate packet path stats are the only one that should show counters for shared
155 self.if_stats = if_stats
156 # latency for packets sent from port 0 and 1
157 self.latencies = [Latency(), Latency()]
158 self.aggregate = aggregate
161 def add_packet_path_stats(self, pps):
162 """Add another packet path stat to this instance.
164 pps: the other packet path stats to add to this instance
166 This is used only for aggregating/collapsing multiple pps into 1
167 to form a "total" pps
169 for index, ifstats in enumerate(self.if_stats):
170 # shared interface stats must not be self added
171 if not ifstats.shared:
172 ifstats.add_if_stats(pps.if_stats[index])
175 def get_agg_packet_path_stats(config, pps_list):
176 """Get the aggregated packet path stats from a list of packet path stats.
178 Interface counters are added, latency stats are updated.
183 # Get a clone of the first in the list
184 agg_pps = PacketPathStats(config, pps.get_cloned_if_stats(), aggregate=True)
186 agg_pps.add_packet_path_stats(pps)
187 # aggregate all latencies
188 agg_pps.latencies = [Latency([pps.latencies[port] for pps in pps_list])
192 def get_if_stats(self, reverse=False):
193 """Get interface stats for given direction.
195 reverse: if True, get the list of interface stats in the reverse direction
196 else (default) gets the ist in the forward direction.
197 return: the list of interface stats indexed by the chain index
199 return self.if_stats[::-1] if reverse else self.if_stats
201 def get_cloned_if_stats(self):
202 """Get a clone copy of the interface stats list."""
203 return [copy.copy(ifstat) for ifstat in self.if_stats]
206 def get_header_labels(self, reverse=False, aggregate=False):
207 """Get the list of header labels for this packet path stats."""
209 dir = InterfaceStats.TX
210 for ifstat in self.get_if_stats(reverse):
211 # starts at TX then RX then TX again etc...
212 labels.append(ifstat.get_display_name(dir, aggregate=aggregate))
213 dir = InterfaceStats.get_reverse_direction(dir)
216 def get_stats(self, reverse=False):
217 """Get the list of packet counters and latency data for this packet path stats.
219 return: a dict of packet counters and latency stats
221 {'packets': [2000054, 1999996, 1999996],
222 'min_usec': 10, 'max_usec': 187, 'avg_usec': 45},
225 dir = InterfaceStats.TX
226 for ifstat in self.get_if_stats(reverse):
227 # starts at TX then RX then TX again etc...
228 if ifstat.shared and not self.aggregate:
229 # shared if stats countesr are only shown in aggregate pps
232 counters.append(ifstat.get_packet_count(dir))
233 dir = InterfaceStats.get_reverse_direction(dir)
235 # latency: use port 0 latency for forward, port 1 latency for reverse
236 latency = self.latencies[1] if reverse else self.latencies[0]
238 if latency.available():
239 results = {'lat_min_usec': latency.min_usec,
240 'lat_max_usec': latency.max_usec,
241 'lat_avg_usec': latency.avg_usec}
243 results['hdrh'] = latency.hdrh
244 decoded_histogram = HdrHistogram.decode(latency.hdrh)
245 # override min max and avg from hdrh
246 results['lat_min_usec'] = decoded_histogram.get_min_value()
247 results['lat_max_usec'] = decoded_histogram.get_max_value()
248 results['lat_avg_usec'] = decoded_histogram.get_mean_value()
249 results['lat_percentile'] = {}
250 for percentile in self.config.lat_percentiles:
251 results['lat_percentile'][percentile] = decoded_histogram.\
252 get_value_at_percentile(percentile)
256 results['packets'] = counters
260 class PacketPathStatsManager(object):
261 """Manages all the packet path stats for all chains.
263 Each run will generate packet path stats for 1 or more chains.
266 def __init__(self, config, pps_list):
267 """Create a packet path stats intance with the list of associated if stats.
269 pps_list: a list of packet path stats indexed by the chain id.
270 All packet path stats must have the same length.
273 self.pps_list = pps_list
275 def insert_pps_list(self, chain_index, if_stats):
276 """Insert a list of interface stats for given chain right after the first in the list.
278 chain_index: index of chain where to insert
279 if_stats: list of interface stats to insert
281 # use slicing to insert the list
282 self.pps_list[chain_index].if_stats[1:1] = if_stats
284 def _get_if_agg_name(self, reverse):
285 """Get the aggegated name for all interface stats across all pps.
287 return: a list of aggregated names for each position of the chain for all chains
289 The agregated name is the interface stats name if there is only 1 chain.
290 Otherwise it is the common prefix for all interface stats names at same position in the
293 # if there is only one chain, use the if_stats names directly
294 return self.pps_list[0].get_header_labels(reverse, aggregate=(len(self.pps_list) > 1))
296 def _get_results(self, reverse=False):
297 """Get the digested stats for the forward or reverse directions.
299 return: a dict with all the labels, total and per chain counters
302 # insert the aggregated row if applicable
303 if len(self.pps_list) > 1:
304 agg_pps = PacketPathStats.get_agg_packet_path_stats(self.config, self.pps_list)
305 chains['total'] = agg_pps.get_stats(reverse)
307 for index, pps in enumerate(self.pps_list):
308 chains[str(index)] = pps.get_stats(reverse)
309 return {'interfaces': self._get_if_agg_name(reverse),
312 def get_results(self):
313 """Get the digested stats for the forward and reverse directions.
315 return: a dictionary of results for each direction and each chain
321 'interfaces': ['Port0', 'vhost0', 'Port1'],
323 '0': {'packets': [2000054, 1999996, 1999996],
336 results = {'Forward': self._get_results(),
337 'Reverse': self._get_results(reverse=True)}