trex_stl_stats.py revision 0fdd81a9
1#!/router/bin/python
2
3from .utils import text_tables
4from .utils.text_opts import format_text, format_threshold, format_num
5from .trex_stl_types import StatNotAvailable, is_integer
6from .trex_stl_exceptions import STLError
7
8from collections import namedtuple, OrderedDict, deque
9import sys
10import copy
11import datetime
12import time
13import re
14import math
15import threading
16import pprint
17
18GLOBAL_STATS = 'g'
19PORT_STATS = 'p'
20PORT_GRAPH = 'pg'
21PORT_STATUS = 'ps'
22STREAMS_STATS = 's'
23LATENCY_STATS = 'ls'
24LATENCY_HISTOGRAM = 'lh'
25CPU_STATS = 'c'
26MBUF_STATS = 'm'
27EXTENDED_STATS = 'x'
28EXTENDED_INC_ZERO_STATS = 'xz'
29
30ALL_STATS_OPTS = [GLOBAL_STATS, PORT_STATS, PORT_STATUS, STREAMS_STATS, LATENCY_STATS, PORT_GRAPH, LATENCY_HISTOGRAM, CPU_STATS, MBUF_STATS, EXTENDED_STATS, EXTENDED_INC_ZERO_STATS]
31COMPACT = [GLOBAL_STATS, PORT_STATS]
32GRAPH_PORT_COMPACT = [GLOBAL_STATS, PORT_GRAPH]
33SS_COMPAT = [GLOBAL_STATS, STREAMS_STATS] # stream stats
34LS_COMPAT = [GLOBAL_STATS, LATENCY_STATS] # latency stats
35LH_COMPAT = [GLOBAL_STATS, LATENCY_HISTOGRAM] # latency histogram
36UT_COMPAT = [GLOBAL_STATS, CPU_STATS, MBUF_STATS] # utilization
37
38ExportableStats = namedtuple('ExportableStats', ['raw_data', 'text_table'])
39
40def round_float (f):
41    return float("%.2f" % f) if type(f) is float else f
42
43def try_int(i):
44    try:
45        return int(i)
46    except:
47        return i
48
49# deep mrege of dicts dst = src + dst
50def deep_merge_dicts (dst, src):
51    for k, v in src.items():
52        # if not exists - deep copy it
53        if not k in dst:
54            dst[k] = copy.deepcopy(v)
55        else:
56            if isinstance(v, dict):
57                deep_merge_dicts(dst[k], v)
58
59# BPS L1 from pps and BPS L2
60def calc_bps_L1 (bps, pps):
61    if (pps == 0) or (bps == 0):
62        return 0
63
64    factor = bps / (pps * 8.0)
65    return bps * ( 1 + (20 / factor) )
66#
67
68def is_intable (value):
69    try:
70        int(value)
71        return True
72    except ValueError:
73        return False
74
75# use to calculate diffs relative to the previous values
76# for example, BW
77def calculate_diff (samples):
78    total = 0.0
79
80    weight_step = 1.0 / sum(range(0, len(samples)))
81    weight = weight_step
82
83    for i in range(0, len(samples) - 1):
84        current = samples[i] if samples[i] > 0 else 1
85        next = samples[i + 1] if samples[i + 1] > 0 else 1
86
87        s = 100 * ((float(next) / current) - 1.0)
88
89        # block change by 100%
90        total  += (min(s, 100) * weight)
91        weight += weight_step
92
93    return total
94
95
96# calculate by absolute values and not relatives (useful for CPU usage in % and etc.)
97def calculate_diff_raw (samples):
98    total = 0.0
99
100    weight_step = 1.0 / sum(range(0, len(samples)))
101    weight = weight_step
102
103    for i in range(0, len(samples) - 1):
104        current = samples[i]
105        next = samples[i + 1]
106
107        total  += ( (next - current) * weight )
108        weight += weight_step
109
110    return total
111
112get_number_of_bytes_cache = {}
113# get number of bytes: '64b'->64, '9kb'->9000 etc.
114def get_number_of_bytes(val):
115    if val not in get_number_of_bytes_cache:
116        get_number_of_bytes_cache[val] = int(val[:-1].replace('k', '000'))
117    return get_number_of_bytes_cache[val]
118
119# a simple object to keep a watch over a field
120class WatchedField(object):
121
122    def __init__ (self, name, suffix, high_th, low_th, events_handler):
123        self.name           = name
124        self.suffix         = suffix
125        self.high_th        = high_th
126        self.low_th         = low_th
127        self.events_handler = events_handler
128
129        self.hot     = False
130        self.current = None
131
132    def update (self, value):
133        if value is None:
134            return
135
136        if value > self.high_th and not self.hot:
137            self.events_handler.log_warning("{0} is high: {1}{2}".format(self.name, value, self.suffix))
138            self.hot = True
139
140        if value < self.low_th and self.hot:
141            self.hot = False
142
143        self.current = value
144
145
146
147class CTRexInfoGenerator(object):
148    """
149    This object is responsible of generating stats and information from objects maintained at
150    STLClient and the ports.
151    """
152
153    def __init__(self, global_stats_ref, ports_dict_ref, rx_stats_ref, latency_stats_ref, util_stats_ref, xstats_ref, async_monitor):
154        self._global_stats = global_stats_ref
155        self._ports_dict = ports_dict_ref
156        self._rx_stats_ref = rx_stats_ref
157        self._latency_stats_ref = latency_stats_ref
158        self._util_stats_ref = util_stats_ref
159        self._xstats_ref = xstats_ref
160        self._async_monitor = async_monitor
161
162    def generate_single_statistic(self, port_id_list, statistic_type):
163        if statistic_type == GLOBAL_STATS:
164            return self._generate_global_stats()
165
166        elif statistic_type == PORT_STATS:
167            return self._generate_port_stats(port_id_list)
168
169        elif statistic_type == PORT_GRAPH:
170            return self._generate_port_graph(port_id_list)
171
172        elif statistic_type == PORT_STATUS:
173            return self._generate_port_status(port_id_list)
174
175        elif statistic_type == STREAMS_STATS:
176            return self._generate_streams_stats()
177
178        elif statistic_type == LATENCY_STATS:
179            return self._generate_latency_stats()
180
181        elif statistic_type == LATENCY_HISTOGRAM:
182            return self._generate_latency_histogram()
183
184        elif statistic_type == CPU_STATS:
185            return self._generate_cpu_util_stats()
186
187        elif statistic_type == MBUF_STATS:
188            return self._generate_mbuf_util_stats()
189
190        elif statistic_type == EXTENDED_STATS:
191            return self._generate_xstats(port_id_list, include_zero_lines = False)
192
193        elif statistic_type == EXTENDED_INC_ZERO_STATS:
194            return self._generate_xstats(port_id_list, include_zero_lines = True)
195
196        else:
197            # ignore by returning empty object
198            return {}
199
200    def generate_streams_info(self, port_id_list, stream_id_list):
201        relevant_ports = self.__get_relevant_ports(port_id_list)
202        return_data = OrderedDict()
203
204        for port_obj in relevant_ports:
205            streams_data = self._generate_single_port_streams_info(port_obj, stream_id_list)
206            if not streams_data:
207                continue
208            hdr_key = "Port {port}:".format(port= port_obj.port_id)
209
210            # TODO: test for other ports with same stream structure, and join them
211            return_data[hdr_key] = streams_data
212
213        return return_data
214
215    def _generate_global_stats(self):
216        global_stats = self._global_stats
217
218        stats_data_left = OrderedDict([("connection", "{host}, Port {port}".format(host=global_stats.connection_info.get("server"),
219                                                                     port=global_stats.connection_info.get("sync_port"))),
220                             ("version", "{ver}".format(ver=global_stats.server_version.get("version", "N/A"))),
221
222                             ("cpu_util.", "{0}% @ {2} cores ({3} per port) {1}".format( format_threshold(round_float(global_stats.get("m_cpu_util")), [85, 100], [0, 85]),
223                                                                                         global_stats.get_trend_gui("m_cpu_util", use_raw = True),
224                                                                                         global_stats.system_info.get('dp_core_count'),
225                                                                                         global_stats.system_info.get('dp_core_count_per_port'),
226                                                                                         )),
227
228                             ("rx_cpu_util.", "{0}% {1}".format( format_threshold(round_float(global_stats.get("m_rx_cpu_util")), [85, 100], [0, 85]),
229                                                                global_stats.get_trend_gui("m_rx_cpu_util", use_raw = True))),
230
231                             ("async_util.", "{0}% / {1}".format( format_threshold(round_float(self._async_monitor.get_cpu_util()), [85, 100], [0, 85]),
232                                                                 format_num(self._async_monitor.get_bps() / 8.0, suffix = "B/sec"))),
233                            ])
234
235        stats_data_right = OrderedDict([
236                             ("total_tx_L2", "{0} {1}".format( global_stats.get("m_tx_bps", format=True, suffix="b/sec"),
237                                                                global_stats.get_trend_gui("m_tx_bps"))),
238
239                             ("total_tx_L1", "{0} {1}".format( global_stats.get("m_tx_bps_L1", format=True, suffix="b/sec"),
240                                                                global_stats.get_trend_gui("m_tx_bps_L1"))),
241
242                             ("total_rx", "{0} {1}".format( global_stats.get("m_rx_bps", format=True, suffix="b/sec"),
243                                                              global_stats.get_trend_gui("m_rx_bps"))),
244
245                             ("total_pps", "{0} {1}".format( global_stats.get("m_tx_pps", format=True, suffix="pkt/sec"),
246                                                              global_stats.get_trend_gui("m_tx_pps"))),
247
248                             ("drop_rate", "{0}".format( format_num(global_stats.get("m_rx_drop_bps"),
249                                                                    suffix = 'b/sec',
250                                                                    opts = 'green' if (global_stats.get("m_rx_drop_bps")== 0) else 'red'),
251                                                            )),
252
253                             ("queue_full", "{0}".format( format_num(global_stats.get_rel("m_total_queue_full"),
254                                                                     suffix = 'pkts',
255                                                                     compact = False,
256                                                                     opts = 'green' if (global_stats.get_rel("m_total_queue_full")== 0) else 'red'))),
257                             ])
258
259        # build table representation
260        stats_table = text_tables.TRexTextInfo()
261        stats_table.set_cols_align(["l", "l"])
262        stats_table.set_deco(0)
263        stats_table.set_cols_width([50, 45])
264        max_lines = max(len(stats_data_left), len(stats_data_right))
265        for line_num in range(max_lines):
266            row = []
267            if line_num < len(stats_data_left):
268                key = list(stats_data_left.keys())[line_num]
269                row.append('{:<12} : {}'.format(key, stats_data_left[key]))
270            else:
271                row.append('')
272            if line_num < len(stats_data_right):
273                key = list(stats_data_right.keys())[line_num]
274                row.append('{:<12} : {}'.format(key, stats_data_right[key]))
275            else:
276                row.append('')
277            stats_table.add_row(row)
278
279        return {"global_statistics": ExportableStats(None, stats_table)}
280
281    def _generate_streams_stats (self):
282        flow_stats = self._rx_stats_ref
283        # for TUI - maximum 4
284        pg_ids = list(filter(is_intable, flow_stats.latest_stats.keys()))[:4]
285        stream_count = len(pg_ids)
286
287        sstats_data = OrderedDict([ ('Tx pps',  []),
288                                        ('Tx bps L2',      []),
289                                        ('Tx bps L1',      []),
290                                        ('---', [''] * stream_count),
291                                        ('Rx pps',      []),
292                                        ('Rx bps',      []),
293                                        ('----', [''] * stream_count),
294                                        ('opackets',    []),
295                                        ('ipackets',    []),
296                                        ('obytes',      []),
297                                        ('ibytes',      []),
298                                        ('-----', [''] * stream_count),
299                                        ('tx_pkts',     []),
300                                        ('rx_pkts',     []),
301                                        ('tx_bytes',    []),
302                                        ('rx_bytes',    [])
303                                      ])
304
305
306
307        # maximum 4
308        for pg_id in pg_ids:
309
310            sstats_data['Tx pps'].append(flow_stats.get([pg_id, 'tx_pps_lpf', 'total'], format = True, suffix = "pps"))
311            sstats_data['Tx bps L2'].append(flow_stats.get([pg_id, 'tx_bps_lpf', 'total'], format = True, suffix = "bps"))
312
313            sstats_data['Tx bps L1'].append(flow_stats.get([pg_id, 'tx_bps_L1_lpf', 'total'], format = True, suffix = "bps"))
314
315            sstats_data['Rx pps'].append(flow_stats.get([pg_id, 'rx_pps_lpf', 'total'], format = True, suffix = "pps"))
316            sstats_data['Rx bps'].append(flow_stats.get([pg_id, 'rx_bps_lpf', 'total'], format = True, suffix = "bps"))
317
318            sstats_data['opackets'].append(flow_stats.get_rel([pg_id, 'tx_pkts', 'total']))
319            sstats_data['ipackets'].append(flow_stats.get_rel([pg_id, 'rx_pkts', 'total']))
320            sstats_data['obytes'].append(flow_stats.get_rel([pg_id, 'tx_bytes', 'total']))
321            sstats_data['ibytes'].append(flow_stats.get_rel([pg_id, 'rx_bytes', 'total']))
322            sstats_data['tx_bytes'].append(flow_stats.get_rel([pg_id, 'tx_bytes', 'total'], format = True, suffix = "B"))
323            sstats_data['rx_bytes'].append(flow_stats.get_rel([pg_id, 'rx_bytes', 'total'], format = True, suffix = "B"))
324            sstats_data['tx_pkts'].append(flow_stats.get_rel([pg_id, 'tx_pkts', 'total'], format = True, suffix = "pkts"))
325            sstats_data['rx_pkts'].append(flow_stats.get_rel([pg_id, 'rx_pkts', 'total'], format = True, suffix = "pkts"))
326
327
328        stats_table = text_tables.TRexTextTable()
329        stats_table.set_cols_align(["l"] + ["r"] * stream_count)
330        stats_table.set_cols_width([10] + [17]   * stream_count)
331        stats_table.set_cols_dtype(['t'] + ['t'] * stream_count)
332
333        stats_table.add_rows([[k] + v
334                              for k, v in sstats_data.items()],
335                              header=False)
336
337        header = ["PG ID"] + [key for key in pg_ids]
338        stats_table.header(header)
339
340        return {"streams_statistics": ExportableStats(sstats_data, stats_table)}
341
342    def _generate_latency_stats(self):
343        lat_stats = self._latency_stats_ref
344        latency_window_size = 14
345
346        # for TUI - maximum 5
347        pg_ids = list(filter(is_intable, lat_stats.latest_stats.keys()))[:5]
348        stream_count = len(pg_ids)
349        lstats_data = OrderedDict([('TX pkts',       []),
350                                   ('RX pkts',       []),
351                                   ('Max latency',   []),
352                                   ('Avg latency',   []),
353                                   ('-- Window --', [''] * stream_count),
354                                   ('Last (max)',     []),
355                                  ] + [('Last-%s' % i, []) for i in range(1, latency_window_size)] + [
356                                   ('---', [''] * stream_count),
357                                   ('Jitter',        []),
358                                   ('----', [''] * stream_count),
359                                   ('Errors',        []),
360                                  ])
361
362        with lat_stats.lock:
363            history = [x for x in lat_stats.history]
364        flow_stats = self._rx_stats_ref.get_stats()
365        for pg_id in pg_ids:
366            lstats_data['TX pkts'].append(flow_stats[pg_id]['tx_pkts']['total'] if pg_id in flow_stats else '')
367            lstats_data['RX pkts'].append(flow_stats[pg_id]['rx_pkts']['total'] if pg_id in flow_stats else '')
368            lstats_data['Avg latency'].append(try_int(lat_stats.get([pg_id, 'latency', 'average'])))
369            lstats_data['Max latency'].append(try_int(lat_stats.get([pg_id, 'latency', 'total_max'])))
370            lstats_data['Last (max)'].append(try_int(lat_stats.get([pg_id, 'latency', 'last_max'])))
371            for i in range(1, latency_window_size):
372                val = history[-i - 1].get(pg_id, {}).get('latency', {}).get('last_max', '') if len(history) > i else ''
373                lstats_data['Last-%s' % i].append(try_int(val))
374            lstats_data['Jitter'].append(try_int(lat_stats.get([pg_id, 'latency', 'jitter'])))
375            errors = 0
376            seq_too_low = lat_stats.get([pg_id, 'err_cntrs', 'seq_too_low'])
377            if is_integer(seq_too_low):
378                errors += seq_too_low
379            seq_too_high = lat_stats.get([pg_id, 'err_cntrs', 'seq_too_high'])
380            if is_integer(seq_too_high):
381                errors += seq_too_high
382            lstats_data['Errors'].append(format_num(errors,
383                                            opts = 'green' if errors == 0 else 'red'))
384
385
386        stats_table = text_tables.TRexTextTable()
387        stats_table.set_cols_align(["l"] + ["r"] * stream_count)
388        stats_table.set_cols_width([12] + [14]   * stream_count)
389        stats_table.set_cols_dtype(['t'] + ['t'] * stream_count)
390        stats_table.add_rows([[k] + v
391                              for k, v in lstats_data.items()],
392                              header=False)
393
394        header = ["PG ID"] + [key for key in pg_ids]
395        stats_table.header(header)
396
397        return {"latency_statistics": ExportableStats(lstats_data, stats_table)}
398
399    def _generate_latency_histogram(self):
400        lat_stats = self._latency_stats_ref.latest_stats
401        max_histogram_size = 17
402
403        # for TUI - maximum 5
404        pg_ids = list(filter(is_intable, lat_stats.keys()))[:5]
405
406        merged_histogram = {}
407        for pg_id in pg_ids:
408            merged_histogram.update(lat_stats[pg_id]['latency']['histogram'])
409        histogram_size = min(max_histogram_size, len(merged_histogram))
410
411        stream_count = len(pg_ids)
412        stats_table = text_tables.TRexTextTable()
413        stats_table.set_cols_align(["l"] + ["r"] * stream_count)
414        stats_table.set_cols_width([12] + [14]   * stream_count)
415        stats_table.set_cols_dtype(['t'] + ['t'] * stream_count)
416
417        for i in range(max_histogram_size - histogram_size):
418            if i == 0 and not merged_histogram:
419                stats_table.add_row(['  No Data'] + [' '] * stream_count)
420            else:
421                stats_table.add_row([' '] * (stream_count + 1))
422        for key in list(reversed(sorted(merged_histogram.keys())))[:histogram_size]:
423            hist_vals = []
424            for pg_id in pg_ids:
425                hist_vals.append(lat_stats[pg_id]['latency']['histogram'].get(key, ' '))
426            stats_table.add_row([key] + hist_vals)
427
428        stats_table.add_row(['- Counters -'] + [' '] * stream_count)
429        err_cntrs_dict = OrderedDict()
430        for pg_id in pg_ids:
431            for err_cntr in sorted(lat_stats[pg_id]['err_cntrs'].keys()):
432                if err_cntr not in err_cntrs_dict:
433                    err_cntrs_dict[err_cntr] = [lat_stats[pg_id]['err_cntrs'][err_cntr]]
434                else:
435                    err_cntrs_dict[err_cntr].append(lat_stats[pg_id]['err_cntrs'][err_cntr])
436        for err_cntr, val_list in err_cntrs_dict.items():
437            stats_table.add_row([err_cntr] + val_list)
438        header = ["PG ID"] + [key for key in pg_ids]
439        stats_table.header(header)
440        return {"latency_histogram": ExportableStats(None, stats_table)}
441
442    def _generate_cpu_util_stats(self):
443        util_stats = self._util_stats_ref.get_stats(use_1sec_cache = True)
444
445        stats_table = text_tables.TRexTextTable()
446        if util_stats:
447            if 'cpu' not in util_stats:
448                raise Exception("Excepting 'cpu' section in stats %s" % util_stats)
449            cpu_stats = util_stats['cpu']
450            hist_len = len(cpu_stats[0]["history"])
451            avg_len = min(5, hist_len)
452            show_len = min(15, hist_len)
453            stats_table.header(['Thread', 'Avg', 'Latest'] + list(range(-1, 0 - show_len, -1)))
454            stats_table.set_cols_align(['l'] + ['r'] * (show_len + 1))
455            stats_table.set_cols_width([10, 3, 6] + [3] * (show_len - 1))
456            stats_table.set_cols_dtype(['t'] * (show_len + 2))
457
458            for i in range(min(18, len(cpu_stats))):
459                history = cpu_stats[i]["history"]
460                ports = cpu_stats[i]["ports"]
461                avg = int(round(sum(history[:avg_len]) / avg_len))
462
463                # decode active ports for core
464                if ports == [-1, -1]:
465                    interfaces = "(IDLE)"
466                elif not -1 in ports:
467                    interfaces = "({:},{:})".format(ports[0], ports[1])
468                else:
469                    interfaces = "({:})".format(ports[0] if ports[0] != -1 else ports[1])
470
471                thread = "{:2} {:^7}".format(i, interfaces)
472                stats_table.add_row([thread, avg] + history[:show_len])
473        else:
474            stats_table.add_row(['No Data.'])
475        return {'cpu_util(%)': ExportableStats(None, stats_table)}
476
477    def _generate_mbuf_util_stats(self):
478        util_stats = self._util_stats_ref.get_stats(use_1sec_cache = True)
479        stats_table = text_tables.TRexTextTable()
480        if util_stats:
481            if 'mbuf_stats' not in util_stats:
482                raise Exception("Excepting 'mbuf_stats' section in stats %s" % util_stats)
483            mbuf_stats = util_stats['mbuf_stats']
484            for mbufs_per_socket in mbuf_stats.values():
485                first_socket_mbufs = mbufs_per_socket
486                break
487            if not self._util_stats_ref.mbuf_types_list:
488                mbuf_keys = list(first_socket_mbufs.keys())
489                mbuf_keys.sort(key = get_number_of_bytes)
490                self._util_stats_ref.mbuf_types_list = mbuf_keys
491            types_len = len(self._util_stats_ref.mbuf_types_list)
492            stats_table.set_cols_align(['l'] + ['r'] * (types_len + 1))
493            stats_table.set_cols_width([10] + [7] * (types_len + 1))
494            stats_table.set_cols_dtype(['t'] * (types_len + 2))
495            stats_table.header([''] + self._util_stats_ref.mbuf_types_list + ['RAM(MB)'])
496            total_list = []
497            sum_totals = 0
498            for mbuf_type in self._util_stats_ref.mbuf_types_list:
499                sum_totals += first_socket_mbufs[mbuf_type][1] * get_number_of_bytes(mbuf_type) + 64
500                total_list.append(first_socket_mbufs[mbuf_type][1])
501            sum_totals *= len(list(mbuf_stats.values()))
502            total_list.append(int(sum_totals/1e6))
503            stats_table.add_row(['Total:'] + total_list)
504            stats_table.add_row(['Used:'] + [''] * (types_len + 1))
505            for socket_name in sorted(list(mbuf_stats.keys())):
506                mbufs = mbuf_stats[socket_name]
507                socket_show_name = socket_name.replace('cpu-', '').replace('-', ' ').capitalize() + ':'
508                sum_used = 0
509                used_list = []
510                percentage_list = []
511                for mbuf_type in self._util_stats_ref.mbuf_types_list:
512                    used = mbufs[mbuf_type][1] - mbufs[mbuf_type][0]
513                    sum_used += used * get_number_of_bytes(mbuf_type) + 64
514                    used_list.append(used)
515                    percentage_list.append('%s%%' % int(100 * used / mbufs[mbuf_type][1]))
516                used_list.append(int(sum_used/1e6))
517                stats_table.add_row([socket_show_name] + used_list)
518                stats_table.add_row(['Percent:'] + percentage_list + [''])
519        else:
520            stats_table.add_row(['No Data.'])
521        return {'mbuf_util': ExportableStats(None, stats_table)}
522
523    def _generate_xstats(self, port_id_list, include_zero_lines = False):
524        relevant_ports = [port.port_id for port in self.__get_relevant_ports(port_id_list)]
525        # get the data on relevant ports
526        xstats_data = OrderedDict()
527        for port_id in relevant_ports:
528            for key, val in self._xstats_ref.get_stats(port_id).items():
529                if key not in xstats_data:
530                    xstats_data[key] = []
531                xstats_data[key].append(val)
532
533        # put into table
534        stats_table = text_tables.TRexTextTable()
535        stats_table.header(['Name:'] + ['Port %s:' % port_id for port_id in relevant_ports])
536        stats_table.set_cols_align(['l'] + ['r'] * len(relevant_ports))
537        stats_table.set_cols_width([30] + [15] * len(relevant_ports))
538        stats_table.set_cols_dtype(['t'] * (len(relevant_ports) + 1))
539        for key, arr in xstats_data.items():
540            if include_zero_lines or list(filter(None, arr)):
541                key = key[:28]
542                stats_table.add_row([key] + arr)
543        return {'xstats:': ExportableStats(None, stats_table)}
544
545    @staticmethod
546    def _get_rational_block_char(value, range_start, interval):
547        # in Konsole, utf-8 is sometimes printed with artifacts, return ascii for now
548        #return 'X' if value >= range_start + float(interval) / 2 else ' '
549
550        if sys.__stdout__.encoding != 'UTF-8':
551            return 'X' if value >= range_start + float(interval) / 2 else ' '
552
553        value -= range_start
554        ratio = float(value) / interval
555        if ratio <= 0.0625:
556            return u' '         # empty block
557        if ratio <= 0.1875:
558            return u'\u2581'    # 1/8
559        if ratio <= 0.3125:
560            return u'\u2582'    # 2/8
561        if ratio <= 0.4375:
562            return u'\u2583'    # 3/8
563        if ratio <= 0.5625:
564            return u'\u2584'    # 4/8
565        if ratio <= 0.6875:
566            return u'\u2585'    # 5/8
567        if ratio <= 0.8125:
568            return u'\u2586'    # 6/8
569        if ratio <= 0.9375:
570            return u'\u2587'    # 7/8
571        return u'\u2588'        # full block
572
573    def _generate_port_graph(self, port_id_list):
574        relevant_port = self.__get_relevant_ports(port_id_list)[0]
575        hist_len = len(relevant_port.port_stats.history)
576        hist_maxlen = relevant_port.port_stats.history.maxlen
577        util_tx_hist = [0] * (hist_maxlen - hist_len) + [round(relevant_port.port_stats.history[i]['tx_percentage']) for i in range(hist_len)]
578        util_rx_hist = [0] * (hist_maxlen - hist_len) + [round(relevant_port.port_stats.history[i]['rx_percentage']) for i in range(hist_len)]
579
580
581        stats_table = text_tables.TRexTextTable()
582        stats_table.header([' Util(%)', 'TX', 'RX'])
583        stats_table.set_cols_align(['c', 'c', 'c'])
584        stats_table.set_cols_width([8, hist_maxlen, hist_maxlen])
585        stats_table.set_cols_dtype(['t', 't', 't'])
586
587        for y in range(95, -1, -5):
588            stats_table.add_row([y, ''.join([self._get_rational_block_char(util_tx, y, 5) for util_tx in util_tx_hist]),
589                                    ''.join([self._get_rational_block_char(util_rx, y, 5) for util_rx in util_rx_hist])])
590
591        return {"port_graph": ExportableStats({}, stats_table)}
592
593    def _generate_port_stats(self, port_id_list):
594        relevant_ports = self.__get_relevant_ports(port_id_list)
595
596        return_stats_data = {}
597        per_field_stats = OrderedDict([("owner", []),
598                                       ('link', []),
599                                       ("state", []),
600                                       ("speed", []),
601                                       ("CPU util.", []),
602                                       ("--", []),
603                                       ("Tx bps L2", []),
604                                       ("Tx bps L1", []),
605                                       ("Tx pps", []),
606                                       ("Line Util.", []),
607
608                                       ("---", []),
609                                       ("Rx bps", []),
610                                       ("Rx pps", []),
611
612                                       ("----", []),
613                                       ("opackets", []),
614                                       ("ipackets", []),
615                                       ("obytes", []),
616                                       ("ibytes", []),
617                                       ("tx-bytes", []),
618                                       ("rx-bytes", []),
619                                       ("tx-pkts", []),
620                                       ("rx-pkts", []),
621
622                                       ("-----", []),
623                                       ("oerrors", []),
624                                       ("ierrors", []),
625
626                                      ])
627
628        total_stats = CPortStats(None)
629
630        for port_obj in relevant_ports:
631            # fetch port data
632            port_stats = port_obj.generate_port_stats()
633
634            total_stats += port_obj.port_stats
635
636            # populate to data structures
637            return_stats_data[port_obj.port_id] = port_stats
638            self.__update_per_field_dict(port_stats, per_field_stats)
639
640        total_cols = len(relevant_ports)
641        header = ["port"] + [port.port_id for port in relevant_ports]
642
643        if (total_cols > 1):
644            self.__update_per_field_dict(total_stats.generate_stats(), per_field_stats)
645            header += ['total']
646            total_cols += 1
647
648        stats_table = text_tables.TRexTextTable()
649        stats_table.set_cols_align(["l"] + ["r"] * total_cols)
650        stats_table.set_cols_width([10] + [17]   * total_cols)
651        stats_table.set_cols_dtype(['t'] + ['t'] * total_cols)
652
653        stats_table.add_rows([[k] + v
654                              for k, v in per_field_stats.items()],
655                              header=False)
656
657        stats_table.header(header)
658
659        return {"port_statistics": ExportableStats(return_stats_data, stats_table)}
660
661    def _generate_port_status(self, port_id_list):
662        relevant_ports = self.__get_relevant_ports(port_id_list)
663
664        return_stats_data = {}
665        per_field_status = OrderedDict([("driver", []),
666                                        ("description", []),
667                                        ("link status", []),
668                                        ("link speed", []),
669                                        ("port status", []),
670                                        ("promiscuous", []),
671                                        ("flow ctrl", []),
672                                        ("--", []),
673                                        ("src IPv4", []),
674                                        ("src MAC", []),
675                                        ("---", []),
676                                        ("Destination", []),
677                                        ("ARP Resolution", []),
678                                        ("----", []),
679                                        ("PCI Address", []),
680                                        ("NUMA Node", []),
681                                        ("-----", []),
682                                        ("RX Filter Mode", []),
683                                        ("RX Queueing", []),
684                                        ("RX sniffer", []),
685                                        ("Grat ARP", []),
686                                        ]
687                                       )
688
689        for port_obj in relevant_ports:
690            # fetch port data
691            # port_stats = self._async_stats.get_port_stats(port_obj.port_id)
692            port_status = port_obj.generate_port_status()
693
694            # populate to data structures
695            return_stats_data[port_obj.port_id] = port_status
696
697            self.__update_per_field_dict(port_status, per_field_status)
698
699        stats_table = text_tables.TRexTextTable()
700        stats_table.set_cols_align(["l"] + ["c"]*len(relevant_ports))
701        stats_table.set_cols_width([15] + [20] * len(relevant_ports))
702
703        stats_table.add_rows([[k] + v
704                              for k, v in per_field_status.items()],
705                             header=False)
706        stats_table.header(["port"] + [port.port_id
707                                       for port in relevant_ports])
708
709        return {"port_status": ExportableStats(return_stats_data, stats_table)}
710
711    def _generate_single_port_streams_info(self, port_obj, stream_id_list):
712
713        return_streams_data = port_obj.generate_loaded_streams_sum()
714
715        if not return_streams_data.get("streams"):
716            # we got no streams available
717            return None
718
719        # FORMAT VALUES ON DEMAND
720
721        # because we mutate this - deep copy before
722        return_streams_data = copy.deepcopy(return_streams_data)
723
724        p_type_field_len = 0
725
726        for stream_id, stream_id_sum in return_streams_data['streams'].items():
727            stream_id_sum['packet_type'] = self._trim_packet_headers(stream_id_sum['packet_type'], 30)
728            p_type_field_len = max(p_type_field_len, len(stream_id_sum['packet_type']))
729
730        info_table = text_tables.TRexTextTable()
731        info_table.set_cols_align(["c"] + ["l"] + ["r"] + ["c"] + ["r"] + ["c"])
732        info_table.set_cols_width([10]   + [p_type_field_len]  + [8]   + [16]  + [15]  + [12])
733        info_table.set_cols_dtype(["t"] + ["t"] + ["t"] + ["t"] + ["t"] + ["t"])
734
735        info_table.add_rows([v.values()
736                             for k, v in return_streams_data['streams'].items()],
737                             header=False)
738        info_table.header(["ID", "packet type", "length", "mode", "rate", "next stream"])
739
740        return ExportableStats(return_streams_data, info_table)
741
742
743    def __get_relevant_ports(self, port_id_list):
744        # fetch owned ports
745        ports = [port_obj
746                 for _, port_obj in self._ports_dict.items()
747                 if port_obj.port_id in port_id_list]
748
749        # display only the first FOUR options, by design
750        if len(ports) > 4:
751            #self.logger is not defined
752            #self.logger.log(format_text("[WARNING]: ", 'magenta', 'bold'), format_text("displaying up to 4 ports", 'magenta'))
753            ports = ports[:4]
754        return ports
755
756    def __update_per_field_dict(self, dict_src_data, dict_dest_ref):
757        for key, val in dict_src_data.items():
758            if key in dict_dest_ref:
759                dict_dest_ref[key].append(val)
760
761    @staticmethod
762    def _trim_packet_headers(headers_str, trim_limit):
763        if len(headers_str) < trim_limit:
764            # do nothing
765            return headers_str
766        else:
767            return (headers_str[:trim_limit-3] + "...")
768
769
770
771class CTRexStats(object):
772    """ This is an abstract class to represent a stats object """
773
774    def __init__(self):
775        self.reference_stats = {}
776        self.latest_stats = {}
777        self.last_update_ts = time.time()
778        self.history = deque(maxlen = 47)
779        self.lock = threading.Lock()
780        self.has_baseline = False
781
782    ######## abstract methods ##########
783
784    # get stats for user / API
785    def get_stats (self):
786        raise NotImplementedError()
787
788    # generate format stats (for TUI)
789    def generate_stats(self):
790        raise NotImplementedError()
791
792    # called when a snapshot arrives - add more fields
793    def _update (self, snapshot, baseline):
794        raise NotImplementedError()
795
796
797    ######## END abstract methods ##########
798
799    def update(self, snapshot, baseline):
800
801        # no update is valid before baseline
802        if not self.has_baseline and not baseline:
803            return
804
805        # call the underlying method
806        rc = self._update(snapshot)
807        if not rc:
808            return
809
810        # sync one time
811        if not self.has_baseline and baseline:
812            self.reference_stats = copy.deepcopy(self.latest_stats)
813            self.has_baseline = True
814
815        # save history
816        with self.lock:
817            self.history.append(self.latest_stats)
818
819
820    def clear_stats(self):
821        self.reference_stats = copy.deepcopy(self.latest_stats)
822        self.history.clear()
823
824
825    def invalidate (self):
826        self.latest_stats = {}
827
828
829    def _get (self, src, field, default = None):
830        if isinstance(field, list):
831            # deep
832            value = src
833            for level in field:
834                if not level in value:
835                    return default
836                value = value[level]
837        else:
838            # flat
839            if not field in src:
840                return default
841            value = src[field]
842
843        return value
844
845    def get(self, field, format=False, suffix="", opts = None):
846        value = self._get(self.latest_stats, field)
847        if value == None:
848            return 'N/A'
849
850        return value if not format else format_num(value, suffix = suffix, opts = opts)
851
852
853    def get_rel(self, field, format=False, suffix=""):
854        ref_value = self._get(self.reference_stats, field)
855        latest_value = self._get(self.latest_stats, field)
856
857        # latest value is an aggregation - must contain the value
858        if latest_value == None:
859            return 'N/A'
860
861        if ref_value == None:
862            ref_value = 0
863
864        value = latest_value - ref_value
865
866        return value if not format else format_num(value, suffix)
867
868
869    # get trend for a field
870    def get_trend (self, field, use_raw = False, percision = 10.0):
871        if field not in self.latest_stats:
872            return 0
873
874        # not enough history - no trend
875        if len(self.history) < 5:
876            return 0
877
878        # absolute value is too low 0 considered noise
879        if self.latest_stats[field] < percision:
880            return 0
881
882        # must lock, deque is not thread-safe for iteration
883        with self.lock:
884            field_samples = [sample[field] for sample in list(self.history)[-5:]]
885
886        if use_raw:
887            return calculate_diff_raw(field_samples)
888        else:
889            return calculate_diff(field_samples)
890
891
892    def get_trend_gui (self, field, show_value = False, use_raw = False, up_color = 'red', down_color = 'green'):
893        v = self.get_trend(field, use_raw)
894
895        value = abs(v)
896
897        # use arrows if utf-8 is supported
898        if sys.__stdout__.encoding == 'UTF-8':
899            arrow = u'\u25b2' if v > 0 else u'\u25bc'
900        else:
901            arrow = ''
902
903        if sys.version_info < (3,0):
904            arrow = arrow.encode('utf-8')
905
906        color = up_color if v > 0 else down_color
907
908        # change in 1% is not meaningful
909        if value < 1:
910            return ""
911
912        elif value > 5:
913
914            if show_value:
915                return format_text("{0}{0}{0} {1:.2f}%".format(arrow,v), color)
916            else:
917                return format_text("{0}{0}{0}".format(arrow), color)
918
919        elif value > 2:
920
921            if show_value:
922                return format_text("{0}{0} {1:.2f}%".format(arrow,v), color)
923            else:
924                return format_text("{0}{0}".format(arrow), color)
925
926        else:
927            if show_value:
928                return format_text("{0} {1:.2f}%".format(arrow,v), color)
929            else:
930                return format_text("{0}".format(arrow), color)
931
932
933
934class CGlobalStats(CTRexStats):
935
936    def __init__(self, connection_info, server_version, ports_dict_ref, events_handler):
937        super(CGlobalStats, self).__init__()
938
939        self.connection_info = connection_info
940        self.server_version  = server_version
941        self._ports_dict     = ports_dict_ref
942        self.events_handler  = events_handler
943
944        self.watched_cpu_util    = WatchedField('CPU util.', '%', 85, 60, events_handler)
945        self.watched_rx_cpu_util = WatchedField('RX core util.', '%', 85, 60, events_handler)
946
947    def get_stats (self):
948        stats = {}
949
950        # absolute
951        stats['cpu_util']    = self.get("m_cpu_util")
952        stats['rx_cpu_util'] = self.get("m_rx_cpu_util")
953        stats['bw_per_core'] = self.get("m_bw_per_core")
954
955        stats['tx_bps'] = self.get("m_tx_bps")
956        stats['tx_pps'] = self.get("m_tx_pps")
957
958        stats['rx_bps'] = self.get("m_rx_bps")
959        stats['rx_pps'] = self.get("m_rx_pps")
960        stats['rx_drop_bps'] = self.get("m_rx_drop_bps")
961
962        # relatives
963        stats['queue_full'] = self.get_rel("m_total_queue_full")
964
965        return stats
966
967
968
969    def _update(self, snapshot):
970        # L1 bps
971        bps = snapshot.get("m_tx_bps")
972        pps = snapshot.get("m_tx_pps")
973
974        snapshot['m_tx_bps_L1'] = calc_bps_L1(bps, pps)
975
976
977        # simple...
978        self.latest_stats = snapshot
979
980        self.watched_cpu_util.update(snapshot.get('m_cpu_util'))
981        self.watched_rx_cpu_util.update(snapshot.get('m_rx_cpu_util'))
982
983        return True
984
985
986class CPortStats(CTRexStats):
987
988    def __init__(self, port_obj):
989        super(CPortStats, self).__init__()
990        self._port_obj = port_obj
991
992    @staticmethod
993    def __merge_dicts (target, src):
994        for k, v in src.items():
995            if k in target:
996                target[k] += v
997            else:
998                target[k] = v
999
1000
1001    def __add__ (self, x):
1002        if not isinstance(x, CPortStats):
1003            raise TypeError("cannot add non stats object to stats")
1004
1005        # main stats
1006        if not self.latest_stats:
1007            self.latest_stats = {}
1008
1009        self.__merge_dicts(self.latest_stats, x.latest_stats)
1010
1011        # reference stats
1012        if x.reference_stats:
1013            if not self.reference_stats:
1014                self.reference_stats = x.reference_stats.copy()
1015            else:
1016                self.__merge_dicts(self.reference_stats, x.reference_stats)
1017
1018        # history - should be traverse with a lock
1019        with self.lock, x.lock:
1020            if not self.history:
1021                self.history = copy.deepcopy(x.history)
1022            else:
1023                for h1, h2 in zip(self.history, x.history):
1024                    self.__merge_dicts(h1, h2)
1025
1026        return self
1027
1028    # for port we need to do something smarter
1029    def get_stats (self):
1030        stats = {}
1031
1032        stats['opackets'] = self.get_rel("opackets")
1033        stats['ipackets'] = self.get_rel("ipackets")
1034        stats['obytes']   = self.get_rel("obytes")
1035        stats['ibytes']   = self.get_rel("ibytes")
1036        stats['oerrors']  = self.get_rel("oerrors")
1037        stats['ierrors']  = self.get_rel("ierrors")
1038
1039        stats['tx_bps']     = self.get("m_total_tx_bps")
1040        stats['tx_pps']     = self.get("m_total_tx_pps")
1041        stats['tx_bps_L1']  = self.get("m_total_tx_bps_L1")
1042        stats['tx_util']    = self.get("m_tx_util")
1043
1044        stats['rx_bps']     = self.get("m_total_rx_bps")
1045        stats['rx_pps']     = self.get("m_total_rx_pps")
1046        stats['rx_bps_L1']  = self.get("m_total_rx_bps_L1")
1047        stats['rx_util']    = self.get("m_rx_util")
1048
1049        return stats
1050
1051
1052
1053    def _update(self, snapshot):
1054        speed = self._port_obj.get_speed_bps()
1055
1056        # L1 bps
1057        tx_bps  = snapshot.get("m_total_tx_bps")
1058        tx_pps  = snapshot.get("m_total_tx_pps")
1059        rx_bps  = snapshot.get("m_total_rx_bps")
1060        rx_pps  = snapshot.get("m_total_rx_pps")
1061        ts_diff = 0.5 # TODO: change this to real ts diff from server
1062
1063        bps_tx_L1 = calc_bps_L1(tx_bps, tx_pps)
1064        bps_rx_L1 = calc_bps_L1(rx_bps, rx_pps)
1065
1066        snapshot['m_total_tx_bps_L1'] = bps_tx_L1
1067        if speed:
1068            snapshot['m_tx_util'] = (bps_tx_L1 / speed) * 100.0
1069        else:
1070            snapshot['m_tx_util'] = 0
1071
1072        snapshot['m_total_rx_bps_L1'] = bps_rx_L1
1073        if speed:
1074            snapshot['m_rx_util'] = (bps_rx_L1 / speed) * 100.0
1075        else:
1076            snapshot['m_rx_util'] = 0
1077
1078        # TX line util not smoothed
1079        diff_tx_pkts = snapshot.get('opackets', 0) - self.latest_stats.get('opackets', 0)
1080        diff_tx_bytes = snapshot.get('obytes', 0) - self.latest_stats.get('obytes', 0)
1081        tx_bps_L1 = calc_bps_L1(8.0 * diff_tx_bytes / ts_diff, float(diff_tx_pkts) / ts_diff)
1082        if speed:
1083            snapshot['tx_percentage'] = 100.0 * tx_bps_L1 / speed
1084        else:
1085            snapshot['tx_percentage'] = 0
1086
1087        # RX line util not smoothed
1088        diff_rx_pkts = snapshot.get('ipackets', 0) - self.latest_stats.get('ipackets', 0)
1089        diff_rx_bytes = snapshot.get('ibytes', 0) - self.latest_stats.get('ibytes', 0)
1090        rx_bps_L1 = calc_bps_L1(8.0 * diff_rx_bytes / ts_diff, float(diff_rx_pkts) / ts_diff)
1091        if speed:
1092            snapshot['rx_percentage'] = 100.0 * rx_bps_L1 / speed
1093        else:
1094            snapshot['rx_percentage'] = 0
1095
1096        # simple...
1097        self.latest_stats = snapshot
1098
1099        return True
1100
1101
1102    def generate_stats(self):
1103
1104        port_state = self._port_obj.get_port_state_name() if self._port_obj else ""
1105        if port_state == "TRANSMITTING":
1106            port_state = format_text(port_state, 'green', 'bold')
1107        elif port_state == "PAUSE":
1108            port_state = format_text(port_state, 'magenta', 'bold')
1109        else:
1110            port_state = format_text(port_state, 'bold')
1111
1112        if self._port_obj:
1113            link_state = 'UP' if self._port_obj.is_up() else format_text('DOWN', 'red', 'bold')
1114        else:
1115            link_state = ''
1116
1117        # default rate format modifiers
1118        rate_format = {'bpsl1': None, 'bps': None, 'pps': None, 'percentage': 'bold'}
1119
1120        # mark owned ports by color
1121        if self._port_obj:
1122            owner = self._port_obj.get_owner()
1123            rate_format[self._port_obj.last_factor_type] = ('blue', 'bold')
1124            if self._port_obj.is_acquired():
1125                owner = format_text(owner, 'green')
1126
1127        else:
1128            owner = ''
1129
1130
1131        return {"owner": owner,
1132                "state": "{0}".format(port_state),
1133                'link': link_state,
1134                "speed": "%g Gb/s" % self._port_obj.get_speed_gbps() if self._port_obj else '',
1135                "CPU util.": "{0} {1}%".format(self.get_trend_gui("m_cpu_util", use_raw = True),
1136                                               format_threshold(round_float(self.get("m_cpu_util")), [85, 100], [0, 85])) if self._port_obj else '' ,
1137                "--": " ",
1138                "---": " ",
1139                "----": " ",
1140                "-----": " ",
1141
1142                "Tx bps L1": "{0} {1}".format(self.get_trend_gui("m_total_tx_bps_L1", show_value = False),
1143                                               self.get("m_total_tx_bps_L1", format = True, suffix = "bps", opts = rate_format['bpsl1'])),
1144
1145                "Tx bps L2": "{0} {1}".format(self.get_trend_gui("m_total_tx_bps", show_value = False),
1146                                                self.get("m_total_tx_bps", format = True, suffix = "bps", opts = rate_format['bps'])),
1147
1148                "Line Util.": "{0} {1}".format(self.get_trend_gui("m_tx_util", show_value = False) if self._port_obj else "",
1149                                               self.get("m_tx_util", format = True, suffix = "%", opts = rate_format['percentage']) if self._port_obj else ""),
1150
1151                "Rx bps": "{0} {1}".format(self.get_trend_gui("m_total_rx_bps", show_value = False),
1152                                            self.get("m_total_rx_bps", format = True, suffix = "bps")),
1153
1154                "Tx pps": "{0} {1}".format(self.get_trend_gui("m_total_tx_pps", show_value = False),
1155                                            self.get("m_total_tx_pps", format = True, suffix = "pps", opts = rate_format['pps'])),
1156
1157                "Rx pps": "{0} {1}".format(self.get_trend_gui("m_total_rx_pps", show_value = False),
1158                                            self.get("m_total_rx_pps", format = True, suffix = "pps")),
1159
1160                 "opackets" : self.get_rel("opackets"),
1161                 "ipackets" : self.get_rel("ipackets"),
1162                 "obytes"   : self.get_rel("obytes"),
1163                 "ibytes"   : self.get_rel("ibytes"),
1164
1165                 "tx-bytes": self.get_rel("obytes", format = True, suffix = "B"),
1166                 "rx-bytes": self.get_rel("ibytes", format = True, suffix = "B"),
1167                 "tx-pkts": self.get_rel("opackets", format = True, suffix = "pkts"),
1168                 "rx-pkts": self.get_rel("ipackets", format = True, suffix = "pkts"),
1169
1170                 "oerrors"  : format_num(self.get_rel("oerrors"),
1171                                         compact = False,
1172                                         opts = 'green' if (self.get_rel("oerrors")== 0) else 'red'),
1173
1174                 "ierrors"  : format_num(self.get_rel("ierrors"),
1175                                         compact = False,
1176                                         opts = 'green' if (self.get_rel("ierrors")== 0) else 'red'),
1177
1178                }
1179
1180
1181class CLatencyStats(CTRexStats):
1182    def __init__(self, ports):
1183        super(CLatencyStats, self).__init__()
1184
1185
1186    # for API
1187    def get_stats (self):
1188        return copy.deepcopy(self.latest_stats)
1189
1190
1191    def _update(self, snapshot):
1192        if snapshot is None:
1193            snapshot = {}
1194        output = {}
1195
1196        output['global'] = {}
1197        for field in ['bad_hdr', 'old_flow']:
1198            if 'global' in snapshot and field in snapshot['global']:
1199                output['global'][field] = snapshot['global'][field]
1200            else:
1201                output['global'][field] = 0
1202
1203        # we care only about the current active keys
1204        pg_ids = list(filter(is_intable, snapshot.keys()))
1205
1206        for pg_id in pg_ids:
1207            current_pg = snapshot.get(pg_id)
1208            int_pg_id = int(pg_id)
1209            output[int_pg_id] = {}
1210            output[int_pg_id]['err_cntrs'] = current_pg['err_cntrs']
1211            output[int_pg_id]['latency'] = {}
1212
1213            if 'latency' in current_pg:
1214                for field in ['jitter', 'average', 'total_max', 'last_max']:
1215                    if field in current_pg['latency']:
1216                        output[int_pg_id]['latency'][field] = current_pg['latency'][field]
1217                    else:
1218                        output[int_pg_id]['latency'][field] = StatNotAvailable(field)
1219
1220                if 'histogram' in current_pg['latency']:
1221                    output[int_pg_id]['latency']['histogram'] = {int(elem): current_pg['latency']['histogram'][elem]
1222                                                                 for elem in current_pg['latency']['histogram']}
1223                    min_val = min(output[int_pg_id]['latency']['histogram'].keys())
1224                    if min_val == 0:
1225                        min_val = 2
1226                    output[int_pg_id]['latency']['total_min'] = min_val
1227                else:
1228                    output[int_pg_id]['latency']['total_min'] = StatNotAvailable('total_min')
1229                    output[int_pg_id]['latency']['histogram'] = {}
1230
1231        self.latest_stats = output
1232        return True
1233
1234
1235# RX stats objects - COMPLEX :-(
1236class CRxStats(CTRexStats):
1237    def __init__(self, ports):
1238        super(CRxStats, self).__init__()
1239        self.ports = ports
1240
1241
1242    # calculates a diff between previous snapshot
1243    # and current one
1244    def calculate_diff_sec (self, current, prev):
1245        if not 'ts' in current:
1246            raise ValueError("INTERNAL ERROR: RX stats snapshot MUST contain 'ts' field")
1247
1248        if prev:
1249            prev_ts   = prev['ts']
1250            now_ts    = current['ts']
1251            diff_sec  = (now_ts['value'] - prev_ts['value']) / float(now_ts['freq'])
1252        else:
1253            diff_sec = 0.0
1254
1255        return diff_sec
1256
1257
1258    # this is the heart of the complex
1259    def process_single_pg (self, current_pg, prev_pg):
1260
1261        # start with the previous PG
1262        output = copy.deepcopy(prev_pg)
1263
1264        for field in ['tx_pkts', 'tx_bytes', 'rx_pkts', 'rx_bytes']:
1265            # is in the first time ? (nothing in prev)
1266            if field not in output:
1267                output[field] = {}
1268
1269            # does the current snapshot has this field ?
1270            if field in current_pg:
1271                for port, pv in current_pg[field].items():
1272                    if not is_intable(port):
1273                        continue
1274
1275                    output[field][port] = pv
1276
1277            # sum up
1278            total = None
1279            for port, pv in output[field].items():
1280                if not is_intable(port):
1281                    continue
1282                if total is None:
1283                    total = 0
1284                total += pv
1285
1286            output[field]['total'] = total
1287
1288
1289        return output
1290
1291
1292    def process_snapshot (self, current, prev):
1293
1294        # final output
1295        output = {}
1296
1297        # copy timestamp field
1298        output['ts'] = current['ts']
1299
1300        # global (not per pg_id) error counters
1301        output['global'] = {}
1302        for field in ['rx_err', 'tx_err']:
1303            output['global'][field] = {}
1304            if 'global' in current and field in current['global']:
1305                for port in current['global'][field]:
1306                    output['global'][field][int(port)] = current['global'][field][port]
1307
1308        # we care only about the current active keys
1309        pg_ids = list(filter(is_intable, current.keys()))
1310
1311        for pg_id in pg_ids:
1312
1313            current_pg = current.get(pg_id, {})
1314
1315            # first time - we do not care
1316            if current_pg.get('first_time'):
1317                # new value - ignore history
1318                output[pg_id] = self.process_single_pg(current_pg, {})
1319                self.reference_stats[pg_id] = {}
1320
1321                # 'dry' B/W
1322                self.calculate_bw_for_pg(output[pg_id])
1323
1324            else:
1325                # aggregate the two values
1326                prev_pg = prev.get(pg_id, {})
1327                output[pg_id] = self.process_single_pg(current_pg, prev_pg)
1328
1329                # calculate B/W
1330                diff_sec = self.calculate_diff_sec(current, prev)
1331                self.calculate_bw_for_pg(output[pg_id], prev_pg, diff_sec)
1332
1333
1334        # cleanp old reference values - they are dead
1335        ref_pg_ids = list(filter(is_intable, self.reference_stats.keys()))
1336
1337        deleted_pg_ids = set(ref_pg_ids).difference(pg_ids)
1338        for d_pg_id in deleted_pg_ids:
1339            del self.reference_stats[d_pg_id]
1340
1341        return output
1342
1343
1344
1345    def calculate_bw_for_pg (self, pg_current, pg_prev = None, diff_sec = 0.0):
1346        # no previous values
1347        if (not pg_prev) or not (diff_sec > 0):
1348            pg_current['tx_pps']        = {}
1349            pg_current['tx_bps']        = {}
1350            pg_current['tx_bps_L1']     = {}
1351            pg_current['tx_line_util']  = {}
1352            pg_current['rx_pps']        = {}
1353            pg_current['rx_bps']        = {}
1354            pg_current['rx_bps_L1']     = {}
1355            pg_current['rx_line_util']  = {}
1356
1357            pg_current['tx_pps_lpf']    = {}
1358            pg_current['tx_bps_lpf']    = {}
1359            pg_current['tx_bps_L1_lpf'] = {}
1360            pg_current['rx_pps_lpf']    = {}
1361            pg_current['rx_bps_lpf']    = {}
1362            pg_current['rx_bps_L1_lpf'] = {}
1363            return
1364
1365        # TX
1366        for port in pg_current['tx_pkts'].keys():
1367
1368            prev_tx_pps   = pg_prev['tx_pps'].get(port)
1369            now_tx_pkts   = pg_current['tx_pkts'].get(port)
1370            prev_tx_pkts  = pg_prev['tx_pkts'].get(port)
1371            pg_current['tx_pps'][port], pg_current['tx_pps_lpf'][port] = self.calc_pps(prev_tx_pps, now_tx_pkts, prev_tx_pkts, diff_sec)
1372
1373            prev_tx_bps   = pg_prev['tx_bps'].get(port)
1374            now_tx_bytes  = pg_current['tx_bytes'].get(port)
1375            prev_tx_bytes = pg_prev['tx_bytes'].get(port)
1376
1377            pg_current['tx_bps'][port], pg_current['tx_bps_lpf'][port] = self.calc_bps(prev_tx_bps, now_tx_bytes, prev_tx_bytes, diff_sec)
1378
1379            if pg_current['tx_bps'].get(port) != None and pg_current['tx_pps'].get(port) != None:
1380                pg_current['tx_bps_L1'][port] = calc_bps_L1(pg_current['tx_bps'][port], pg_current['tx_pps'][port])
1381                pg_current['tx_bps_L1_lpf'][port] = calc_bps_L1(pg_current['tx_bps_lpf'][port], pg_current['tx_pps_lpf'][port])
1382            else:
1383                pg_current['tx_bps_L1'][port] = None
1384                pg_current['tx_bps_L1_lpf'][port] = None
1385
1386
1387        # RX
1388        for port in pg_current['rx_pkts'].keys():
1389
1390            prev_rx_pps   = pg_prev['rx_pps'].get(port)
1391            now_rx_pkts   = pg_current['rx_pkts'].get(port)
1392            prev_rx_pkts  = pg_prev['rx_pkts'].get(port)
1393            pg_current['rx_pps'][port], pg_current['rx_pps_lpf'][port] = self.calc_pps(prev_rx_pps, now_rx_pkts, prev_rx_pkts, diff_sec)
1394
1395            prev_rx_bps   = pg_prev['rx_bps'].get(port)
1396            now_rx_bytes  = pg_current['rx_bytes'].get(port)
1397            prev_rx_bytes = pg_prev['rx_bytes'].get(port)
1398            pg_current['rx_bps'][port], pg_current['rx_bps_lpf'][port] = self.calc_bps(prev_rx_bps, now_rx_bytes, prev_rx_bytes, diff_sec)
1399            if pg_current['rx_bps'].get(port) != None and pg_current['rx_pps'].get(port) != None:
1400                pg_current['rx_bps_L1'][port] = calc_bps_L1(pg_current['rx_bps'][port], pg_current['rx_pps'][port])
1401                pg_current['rx_bps_L1_lpf'][port] = calc_bps_L1(pg_current['rx_bps_lpf'][port], pg_current['rx_pps_lpf'][port])
1402            else:
1403                pg_current['rx_bps_L1'][port] = None
1404                pg_current['rx_bps_L1_lpf'][port] = None
1405
1406
1407    def calc_pps (self, prev_bw, now, prev, diff_sec):
1408        return self.calc_bw(prev_bw, now, prev, diff_sec, False)
1409
1410
1411    def calc_bps (self, prev_bw, now, prev, diff_sec):
1412        return self.calc_bw(prev_bw, now, prev, diff_sec, True)
1413
1414    # returns tuple - first value is real, second is low pass filtered
1415    def calc_bw (self, prev_bw, now, prev, diff_sec, is_bps):
1416        # B/W is not valid when the values are None
1417        if (now is None) or (prev is None):
1418            return (None, None)
1419
1420        # calculate the B/W for current snapshot
1421        current_bw = (now - prev) / diff_sec
1422        if is_bps:
1423            current_bw *= 8
1424
1425        # previous B/W is None ? ignore it
1426        if prev_bw is None:
1427            prev_bw = 0
1428
1429        return (current_bw, 0.5 * prev_bw + 0.5 * current_bw)
1430
1431
1432
1433
1434    def _update (self, snapshot):
1435        #print(snapshot)
1436        # generate a new snapshot
1437        new_snapshot = self.process_snapshot(snapshot, self.latest_stats)
1438
1439        #print new_snapshot
1440        # advance
1441        self.latest_stats = new_snapshot
1442
1443
1444        return True
1445
1446
1447
1448    # for API
1449    def get_stats (self):
1450        stats = {}
1451
1452        for pg_id, value in self.latest_stats.items():
1453            # skip non ints
1454            if not is_intable(pg_id):
1455                # 'global' stats are in the same level of the pg_ids. We do want them to go to the user
1456                if pg_id == 'global':
1457                    stats[pg_id] = value
1458                continue
1459            # bare counters
1460            stats[int(pg_id)] = {}
1461            for field in ['tx_pkts', 'tx_bytes', 'rx_pkts', 'rx_bytes']:
1462                val = self.get_rel([pg_id, field, 'total'])
1463                stats[int(pg_id)][field] = {'total': val if val != 'N/A' else StatNotAvailable(field)}
1464                for port in value[field].keys():
1465                    if is_intable(port):
1466                        val = self.get_rel([pg_id, field, port])
1467                        stats[int(pg_id)][field][int(port)] = val if val != 'N/A' else StatNotAvailable(field)
1468
1469            # BW values
1470            for field in ['tx_pps', 'tx_bps', 'tx_bps_L1', 'rx_pps', 'rx_bps', 'rx_bps_L1']:
1471                val = self.get([pg_id, field, 'total'])
1472                stats[int(pg_id)][field] = {'total': val if val != 'N/A' else StatNotAvailable(field)}
1473                for port in value[field].keys():
1474                    if is_intable(port):
1475                        val = self.get([pg_id, field, port])
1476                        stats[int(pg_id)][field][int(port)] = val if val != 'N/A' else StatNotAvailable(field)
1477
1478        return stats
1479
1480class CUtilStats(CTRexStats):
1481
1482    def __init__(self, client):
1483        super(CUtilStats, self).__init__()
1484        self.client = client
1485        self.history = deque(maxlen = 1)
1486        self.mbuf_types_list = None
1487        self.last_update_ts = -999
1488
1489    def get_stats(self, use_1sec_cache = False):
1490        time_now = time.time()
1491        if self.last_update_ts + 1 < time_now or not self.history or not use_1sec_cache:
1492            if self.client.is_connected():
1493                rc = self.client._transmit('get_utilization')
1494                if not rc:
1495                    raise STLError(rc)
1496                self.last_update_ts = time_now
1497                self.history.append(rc.data())
1498            else:
1499                self.history.append({})
1500
1501        return self.history[-1]
1502
1503class CXStats(CTRexStats):
1504
1505    def __init__(self, client):
1506        super(CXStats, self).__init__()
1507        self.client = client
1508        self.names = []
1509        self.last_update_ts = -999
1510
1511    def clear_stats(self, port_id = None):
1512        if port_id == None:
1513            ports = self.client.get_all_ports()
1514        elif type(port_id) is list:
1515            ports = port_id
1516        else:
1517            ports = [port_id]
1518
1519        for port_id in ports:
1520            self.reference_stats[port_id] = self.get_stats(port_id, relative = False)
1521
1522    def get_stats(self, port_id, use_1sec_cache = False, relative = True):
1523        time_now = time.time()
1524        if self.last_update_ts + 1 < time_now or not self.latest_stats or not use_1sec_cache:
1525            if self.client.is_connected():
1526                rc = self.client._transmit('get_port_xstats_values', params = {'port_id': port_id})
1527                if not rc:
1528                    raise STLError(rc)
1529                self.last_update_ts = time_now
1530                values = rc.data().get('xstats_values', [])
1531                if len(values) != len(self.names): # need to update names ("keys")
1532                    rc = self.client._transmit('get_port_xstats_names', params = {'port_id': port_id})
1533                    if not rc:
1534                        raise STLError(rc)
1535                    self.names = rc.data().get('xstats_names', [])
1536                if len(values) != len(self.names):
1537                    raise STLError('Length of get_xstats_names: %s and get_port_xstats_values: %s' % (len(self.names), len(values)))
1538                self.latest_stats[port_id] = OrderedDict([(key, val) for key, val in zip(self.names, values)])
1539
1540        stats = OrderedDict()
1541        for key, val in self.latest_stats[port_id].items():
1542            if relative:
1543                stats[key] = self.get_rel([port_id, key])
1544            else:
1545                stats[key] = self.get([port_id, key])
1546        return stats
1547
1548if __name__ == "__main__":
1549    pass
1550
1551