-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpacket_stats.py
309 lines (248 loc) · 11.4 KB
/
packet_stats.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
"""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_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
# A None value can be set to mean that the data is not available
self.tx = 0
self.rx = 0
# This is a special field to hold an optional total rx count that is only
# used for column aggregation to compute a total intertface stats
# Set to non zero to be picked by the add interface stats method for rx total
self.rx_total = None
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."""
def added_counter(old_value, new_value_to_add):
if new_value_to_add:
if old_value is None:
return new_value_to_add
return old_value + new_value_to_add
return old_value
self.tx = added_counter(self.tx, if_stats.tx)
self.rx = added_counter(self.rx, if_stats.rx)
# Add special rx total value if set
self.rx = added_counter(self.rx, if_stats.rx_total)
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