Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[portstat, intfstat] added rates and utilization #1750

Merged
merged 1 commit into from
Sep 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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