Skip to content

Commit

Permalink
[portstat, intfstat] added rates and utilization (#1750)
Browse files Browse the repository at this point in the history
#### What I did

Depends on [sonic-swss-common/pull/330](sonic-net/sonic-swss-common#330)

According to [HLD](https://github.com/Azure/SONiC/blob/master/doc/rates-and-utilization/Rates_and_utilization_HLD.md) added calculation of rates and utilization columns to the `portstat` and `intfstat` scripts output

#### How I did it

Modified the `portstat` and `intfstat` scripts

#### How to verify it

Added UT

#### Previous command output (if the output of a command-line utility has changed)

```
admin@sonic:~$ show int count
      IFACE    STATE    RX_OK    RX_BPS    RX_UTIL    RX_ERR    RX_DRP    RX_OVR    TX_OK    TX_BPS    TX_UTIL    TX_ERR    TX_DRP    TX_OVR
-----------  -------  -------  --------  ---------  --------  --------  --------  -------  --------  ---------  --------  --------  --------
  Ethernet0        X        0       N/A        N/A         0         0       N/A        0       N/A        N/A         0         0       N/A
  Ethernet2        U      287       N/A        N/A         0         0       N/A      818       N/A        N/A         0         0       N/A
  Ethernet4        U      380       N/A        N/A         0         0       N/A      858       N/A        N/A         0         0       N/A
  Ethernet6        U      286       N/A        N/A         0         0       N/A      850       N/A        N/A         0         0       N/A
```

#### New command output (if the output of a command-line utility has changed)

```
admin@sonic:~$ show int count
      IFACE    STATE    RX_OK      RX_BPS    RX_UTIL    RX_ERR    RX_DRP    RX_OVR    TX_OK      TX_BPS    TX_UTIL    TX_ERR    TX_DRP    TX_OVR
-----------  -------  -------  ----------  ---------  --------  --------  --------  -------  ----------  ---------  --------  --------  --------
  Ethernet0        X        0    0.00 B/s      0.00%         0         0       N/A        0    0.00 B/s      0.00%         0         0       N/A
  Ethernet2        U        0    0.00 B/s      0.00%         0         0       N/A        0    0.00 B/s      0.00%         0         0       N/A
  Ethernet4        U        0    0.00 B/s      0.00%         0         0       N/A        0    0.00 B/s      0.00%         0         0       N/A
  Ethernet6        U        0    0.00 B/s      0.00%         0         0       N/A        0    0.00 B/s      0.00%         0         0       N/A
```
  • Loading branch information
vadymhlushko-mlnx authored Sep 8, 2021
1 parent 26e700a commit 2b12aad
Show file tree
Hide file tree
Showing 10 changed files with 563 additions and 248 deletions.
28 changes: 28 additions & 0 deletions config/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5813,6 +5813,34 @@ def disable_link_local(ctx):
set_ipv6_link_local_only_on_interface(config_db, table_dict, table_type, key, mode)


#
# 'rate' group ('config rate ...')
#

@config.group()
def rate():
"""Set port rates configuration."""
pass


@rate.command()
@click.argument('interval', metavar='<interval>', type=click.IntRange(min=1, max=1000), required=True)
@click.argument('rates_type', type=click.Choice(['all', 'port', 'rif']), default='all')
def smoothing_interval(interval, rates_type):
"""Set rates smoothing interval """
counters_db = swsssdk.SonicV2Connector()
counters_db.connect('COUNTERS_DB')

alpha = 2.0/(interval + 1)

if rates_type in ['port', 'all']:
counters_db.set('COUNTERS_DB', 'RATES:PORT', 'PORT_SMOOTH_INTERVAL', interval)
counters_db.set('COUNTERS_DB', 'RATES:PORT', 'PORT_ALPHA', alpha)
if rates_type in ['rif', 'all']:
counters_db.set('COUNTERS_DB', 'RATES:RIF', 'RIF_SMOOTH_INTERVAL', interval)
counters_db.set('COUNTERS_DB', 'RATES:RIF', 'RIF_ALPHA', alpha)


# Load plugins and register them
helper = util_base.UtilHelper()
for plugin in helper.load_plugins(plugins):
Expand Down
136 changes: 79 additions & 57 deletions scripts/intfstat
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import argparse
import datetime
import sys
import os
import sys
import time

# mock the redis for unit test purposes #
Expand All @@ -28,15 +27,37 @@ except KeyError:
from collections import namedtuple, OrderedDict
from natsort import natsorted
from tabulate import tabulate
from utilities_common.netstat import ns_diff, ns_brate, ns_prate, table_as_json, STATUS_NA
from utilities_common.netstat import ns_diff, table_as_json, STATUS_NA, format_brate, format_prate
from swsscommon.swsscommon import SonicV2Connector

nstat_fields = (
"rx_b_ok",
"rx_p_ok",
"tx_b_ok",
"tx_p_ok",
"rx_b_err",
"rx_p_err",
"tx_b_err",
"tx_p_err"
)

NStats = namedtuple("NStats", nstat_fields)

NStats = namedtuple("NStats", "rx_b_ok, rx_p_ok, tx_b_ok, tx_p_ok,\
rx_b_err, rx_p_err, tx_b_err, tx_p_err,")
header = [
'IFACE',
'RX_OK',
'RX_BPS',
'RX_PPS',
'RX_ERR',
'TX_OK',
'TX_BPS',
'TX_PPS',
'TX_ERR'
]

header = ['IFACE', 'RX_OK', 'RX_BPS', 'RX_PPS', 'RX_ERR',
'TX_OK', 'TX_BPS', 'TX_PPS', 'TX_ERR']
rates_key_list = [ 'RX_BPS', 'RX_PPS', 'TX_BPS', 'TX_PPS']
ratestat_fields = ("rx_bps", "rx_pps", "tx_bps", "tx_pps")
RateStats = namedtuple("RateStats", ratestat_fields)

counter_names = (
'SAI_ROUTER_INTERFACE_STAT_IN_OCTETS',
Expand All @@ -49,18 +70,10 @@ counter_names = (
'SAI_ROUTER_INTERFACE_STAT_OUT_ERROR_PACKETS'
)

RATES_TABLE_PREFIX = "RATES:"

COUNTER_TABLE_PREFIX = "COUNTERS:"
COUNTERS_RIF_NAME_MAP = "COUNTERS_RIF_NAME_MAP"
COUNTERS_RIF_TYPE_MAP = "COUNTERS_RIF_TYPE_MAP"

INTERFACE_TABLE_PREFIX = "PORT_TABLE:"
INTF_STATUS_VALUE_UP = 'UP'
INTF_STATUS_VALUE_DOWN = 'DOWN'

INTF_STATE_UP = 'U'
INTF_STATE_DOWN = 'D'
INTF_STATE_DISABLED = 'X'

class Intfstat(object):
def __init__(self):
Expand All @@ -76,7 +89,7 @@ class Intfstat(object):
"""
Get the counters from specific table.
"""
fields = [STATUS_NA] * (len(header) - 1)
fields = [STATUS_NA] * len(nstat_fields)
for pos, counter_name in enumerate(counter_names):
full_table_id = COUNTER_TABLE_PREFIX + table_id
counter_data = self.db.get(self.db.COUNTERS_DB, full_table_id, counter_name)
Expand All @@ -85,13 +98,28 @@ class Intfstat(object):
cntr = NStats._make(fields)
return cntr

def get_rates(table_id):
"""
Get the rates from specific table.
"""
fields = ["0","0","0","0"]
for pos, name in enumerate(rates_key_list):
full_table_id = RATES_TABLE_PREFIX + table_id
counter_data = self.db.get(self.db.COUNTERS_DB, full_table_id, name)
if counter_data is None:
fields[pos] = STATUS_NA
elif fields[pos] != STATUS_NA:
fields[pos] = float(counter_data)
cntr = RateStats._make(fields)
return cntr

# Build a dictionary of the stats
cnstat_dict = OrderedDict()
cnstat_dict['time'] = datetime.datetime.now()
ratestat_dict = OrderedDict()

# Get the info from database
counter_rif_name_map = self.db.get_all(self.db.COUNTERS_DB, COUNTERS_RIF_NAME_MAP);

counter_rif_name_map = self.db.get_all(self.db.COUNTERS_DB, COUNTERS_RIF_NAME_MAP)

if counter_rif_name_map is None:
print("No %s in the DB!" % COUNTERS_RIF_NAME_MAP)
Expand All @@ -103,31 +131,15 @@ class Intfstat(object):

if rif:
cnstat_dict[rif] = get_counters(counter_rif_name_map[rif])
return cnstat_dict
ratestat_dict[rif] = get_rates(counter_rif_name_map[rif])
return cnstat_dict, ratestat_dict

for rif in natsorted(counter_rif_name_map):
cnstat_dict[rif] = get_counters(counter_rif_name_map[rif])
return cnstat_dict

def get_intf_state(self, port_name):
"""
Get the port state
"""
full_table_id = PORT_STATUS_TABLE_PREFIX + port_name
admin_state = self.db.get(self.db.APPL_DB, full_table_id, PORT_ADMIN_STATUS_FIELD)
oper_state = self.db.get(self.db.APPL_DB, full_table_id, PORT_OPER_STATUS_FIELD)
if admin_state is None or oper_state is None:
return STATUS_NA
elif admin_state.upper() == PORT_STATUS_VALUE_DOWN:
return PORT_STATE_DISABLED
elif admin_state.upper() == PORT_STATUS_VALUE_UP and oper_state.upper() == PORT_STATUS_VALUE_UP:
return PORT_STATE_UP
elif admin_state.upper() == PORT_STATUS_VALUE_UP and oper_state.upper() == PORT_STATUS_VALUE_DOWN:
return PORT_STATE_DOWN
else:
return STATUS_NA
ratestat_dict[rif] = get_rates(counter_rif_name_map[rif])
return cnstat_dict, ratestat_dict

def cnstat_print(self, cnstat_dict, use_json):
def cnstat_print(self, cnstat_dict, ratestat_dict, use_json):
"""
Print the cnstat.
"""
Expand All @@ -137,16 +149,25 @@ class Intfstat(object):
if key == 'time':
continue

table.append((key, data.rx_p_ok, STATUS_NA, STATUS_NA, data.rx_p_err,
data.tx_p_ok, STATUS_NA, STATUS_NA, data.tx_p_err))
rates = ratestat_dict.get(key, RateStats._make([STATUS_NA] * len(rates_key_list)))

table.append((key,
data.rx_p_ok,
format_brate(rates.rx_bps),
format_prate(rates.rx_pps),
data.rx_p_err,
data.tx_p_ok,
format_brate(rates.tx_bps),
format_prate(rates.tx_pps),
data.tx_p_err))

if use_json:
print(table_as_json(table, header))

else:
print(tabulate(table, header, tablefmt='simple', stralign='right'))

def cnstat_diff_print(self, cnstat_new_dict, cnstat_old_dict, use_json):
def cnstat_diff_print(self, cnstat_new_dict, cnstat_old_dict, ratestat_dict, use_json):
"""
Print the difference between two cnstat results.
"""
Expand All @@ -155,33 +176,34 @@ class Intfstat(object):

for key, cntr in cnstat_new_dict.items():
if key == 'time':
time_gap = cnstat_new_dict.get('time') - cnstat_old_dict.get('time')
time_gap = time_gap.total_seconds()
continue
old_cntr = None
if key in cnstat_old_dict:
old_cntr = cnstat_old_dict.get(key)

rates = ratestat_dict.get(key, RateStats._make([STATUS_NA] * len(rates_key_list)))

if old_cntr is not None:
table.append((key,
ns_diff(cntr.rx_p_ok, old_cntr.rx_p_ok),
ns_brate(cntr.rx_b_ok, old_cntr.rx_b_ok, time_gap),
ns_prate(cntr.rx_p_ok, old_cntr.rx_p_ok, time_gap),
format_brate(rates.rx_bps),
format_prate(rates.rx_pps),
ns_diff(cntr.rx_p_err, old_cntr.rx_p_err),
ns_diff(cntr.tx_p_ok, old_cntr.tx_p_ok),
ns_brate(cntr.tx_b_ok, old_cntr.tx_b_ok, time_gap),
ns_prate(cntr.tx_p_ok, old_cntr.tx_p_ok, time_gap),
format_brate(rates.tx_bps),
format_prate(rates.tx_pps),
ns_diff(cntr.tx_p_err, old_cntr.tx_p_err)))
else:
table.append((key,
cntr.rx_p_ok,
STATUS_NA,
STATUS_NA,
format_brate(rates.rx_bps),
format_prate(rates.rx_pps),
cntr.rx_p_err,
cntr.tx_p_ok,
STATUS_NA,
STATUS_NA,
format_brate(rates.tx_bps),
format_prate(rates.tx_pps),
cntr.tx_p_err))

if use_json:
print(table_as_json(table, header))
else:
Expand Down Expand Up @@ -293,7 +315,7 @@ def main():
sys.exit(0)

intfstat = Intfstat()
cnstat_dict = intfstat.get_cnstat(rif=interface_name)
cnstat_dict, ratestat_dict = intfstat.get_cnstat(rif=interface_name)

# At this point, either we'll create a file or open an existing one.
if not os.path.exists(cnstat_dir):
Expand Down Expand Up @@ -347,7 +369,7 @@ def main():
if interface_name:
intfstat.cnstat_single_interface(interface_name, cnstat_dict, cnstat_cached_dict)
else:
intfstat.cnstat_diff_print(cnstat_dict, cnstat_cached_dict, use_json)
intfstat.cnstat_diff_print(cnstat_dict, cnstat_cached_dict, ratestat_dict, use_json)
except IOError as e:
print(e.errno, e)
else:
Expand All @@ -358,16 +380,16 @@ def main():
if interface_name:
intfstat.cnstat_single_interface(interface_name, cnstat_dict, None)
else:
intfstat.cnstat_print(cnstat_dict, use_json)
intfstat.cnstat_print(cnstat_dict, ratestat_dict, use_json)
else:
#wait for the specified time and then gather the new stats and output the difference.
time.sleep(wait_time_in_seconds)
print("The rates are calculated within %s seconds period" % wait_time_in_seconds)
cnstat_new_dict = intfstat.get_cnstat(rif=interface_name)
cnstat_new_dict, ratestat_new_dict = intfstat.get_cnstat(rif=interface_name)
if interface_name:
intfstat.cnstat_single_interface(interface_name, cnstat_new_dict, cnstat_dict)
else:
intfstat.cnstat_diff_print(cnstat_new_dict, cnstat_dict, use_json)
intfstat.cnstat_diff_print(cnstat_new_dict, cnstat_dict, ratestat_new_dict, use_json)

if __name__ == "__main__":
main()
Loading

0 comments on commit 2b12aad

Please sign in to comment.