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