Skip to content

Commit

Permalink
[portstat]: Add packet rate and port state in portstat (sonic-net#37)
Browse files Browse the repository at this point in the history
  • Loading branch information
sihuihan88 authored and Shuotian Cheng committed Apr 26, 2017
1 parent 11db861 commit 9ea7ae4
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 67 deletions.
231 changes: 165 additions & 66 deletions scripts/portstat
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@ PORT_RATE = 40

NStats = namedtuple("NStats", "rx_ok, rx_err, rx_drop, rx_ovr, tx_ok,\
tx_err, tx_drop, tx_ovr, rx_byt, tx_byt")
header = ['Iface', 'RX_OK', 'RX_RATE', 'RX_UTIL', 'RX_ERR', 'RX_DRP', 'RX_OVR',
'TX_OK', 'TX_RATE', 'TX_UTIL', 'TX_ERR', 'TX_DRP', 'TX_OVR']
header_all = ['Iface', 'STATE', 'RX_OK', 'RX_BPS', 'RX_PPS', 'RX_UTIL', 'RX_ERR', 'RX_DRP', 'RX_OVR',
'TX_OK', 'TX_BPS', 'Tx_PPS', 'TX_UTIL', 'TX_ERR', 'TX_DRP', 'TX_OVR']

header = ['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']

counter_bucket_dict = {
'SAI_PORT_STAT_IF_IN_UCAST_PKTS': 0,
Expand All @@ -47,11 +50,14 @@ counter_bucket_dict = {

COUNTER_TABLE_PREFIX = "COUNTERS:"
COUNTERS_PORT_NAME_MAP = "COUNTERS_PORT_NAME_MAP"
PORT_STATUS_TABLE_PREFIX = "PORT_TABLE:"
PORT_STATUS_FIELD = "oper_status"

class Portstat(object):
def __init__(self):
self.db = swsssdk.SonicV2Connector()
self.db.connect(self.db.COUNTERS_DB)
self.db.connect(self.db.APPL_DB)

def get_cnstat(self):
"""
Expand All @@ -68,7 +74,7 @@ class Portstat(object):
if counter_data is None:
fields[pos] = "N/A"
elif fields[pos] != "N/A":
fields[pos] = str(int(fields[pos]) + int(self.db.get(self.db.COUNTERS_DB, full_table_id, counter_name)))
fields[pos] = str(int(fields[pos]) + int(counter_data))
cntr = NStats._make(fields)
return cntr

Expand All @@ -78,38 +84,76 @@ class Portstat(object):
cnstat_dict = OrderedDict()
cnstat_dict['time'] = datetime.datetime.now()
for port in natsorted(counter_port_name_map):
cnstat_dict[port] = get_counters(counter_port_name_map[port])

cnstat_dict[port] = get_counters(counter_port_name_map[port])
return cnstat_dict

def table_as_json(self, table):
def get_port_status(self, port_name):
"""
Get the port status
"""
full_table_id = PORT_STATUS_TABLE_PREFIX + port_name
status = self.db.get(self.db.APPL_DB, full_table_id, PORT_STATUS_FIELD)
if status is None:
return "N/A"
elif status.lower() == 'up':
return "U"
elif status.lower() == 'down':
return "D"
else:
return "N/A"

def table_as_json(self, table, print_all):
"""
Print table as json format.
"""
output = {}

for line in table:
if_name = line[0]

# Build a dictionary where the if_name is the key and the value is
# a dictionary that holds MTU, TX_DRP, etc
output[if_name] = {
header[1] : line[1],
header[2] : line[2],
header[3] : line[3],
header[4] : line[4],
header[5] : line[5],
header[6] : line[6],
header[7] : line[7],
header[8] : line[8],
header[9] : line[9],
header[10] : line[10],
header[11] : line[11],
header[12] : line[12]
if print_all:
for line in table:
if_name = line[0]

# Build a dictionary where the if_name is the key and the value is
# a dictionary that holds MTU, TX_DRP, etc
output[if_name] = {
header_all[1] : line[1],
header_all[2] : line[2],
header_all[3] : line[3],
header_all[4] : line[4],
header_all[5] : line[5],
header_all[6] : line[6],
header_all[7] : line[7],
header_all[8] : line[8],
header_all[9] : line[9],
header_all[10] : line[10],
header_all[11] : line[11],
header_all[12] : line[12],
header_all[13] : line[13],
header_all[14] : line[14]
}
else:
for line in table:
if_name = line[0]

# Build a dictionary where the if_name is the key and the value is
# a dictionary that holds MTU, TX_DRP, etc
output[if_name] = {
header[1] : line[1],
header[2] : line[2],
header[3] : line[3],
header[4] : line[4],
header[5] : line[5],
header[6] : line[6],
header[7] : line[7],
header[8] : line[8],
header[9] : line[9],
header[10] : line[10],
header[11] : line[11],
header[12] : line[12],
}

return json.dumps(output, indent=4, sort_keys=True)

def cnstat_print(self, cnstat_dict, use_json):
def cnstat_print(self, cnstat_dict, use_json, print_all):
"""
Print the cnstat.
"""
Expand All @@ -118,19 +162,22 @@ class Portstat(object):
for key, data in cnstat_dict.iteritems():
if key == 'time':
continue
table.append((key,
data.rx_ok, "N/A", "N/A", data.rx_err,
table.append((key, self.get_port_status(key),
data.rx_ok, "N/A", "N/A", "N/A", data.rx_err,
data.rx_drop, data.rx_ovr,
data.tx_ok, "N/A", "N/A", data.tx_err,
data.tx_ok, "N/A", "N/A", "N/A", data.tx_err,
data.tx_drop, data.tx_ovr))

if use_json:
print self.table_as_json(table)
print self.table_as_json(table, print_all)

else:
print tabulate(table, header, tablefmt='simple', stralign='right')
if print_all:
print tabulate(table, header_all, tablefmt='simple', stralign='right')
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, use_json, print_all):
"""
Print the difference between two cnstat results.
"""
Expand All @@ -144,9 +191,9 @@ class Portstat(object):
new, old = int(newstr), int(oldstr)
return '{:,}'.format(new - old)

def ns_rate(newstr, oldstr, delta):
def ns_brate(newstr, oldstr, delta):
"""
Calculate the rate.
Calculate the byte rate.
"""
if newstr == "N/A" or oldstr == "N/A":
return "N/A"
Expand All @@ -160,6 +207,16 @@ class Portstat(object):
rate = "{:.2f}".format(rate)+' B'
return rate+'/s'

def ns_prate(newstr, oldstr, delta):
"""
Calculate the packet rate.
"""
if newstr == "N/A" or oldstr == "N/A":
return "N/A"
else:
rate = int(ns_diff(newstr, oldstr).replace(',',''))/delta
return "{:.2f}".format(rate)+'/s'

def ns_util(newstr, oldstr, delta):
"""
Calculate the util.
Expand All @@ -182,39 +239,77 @@ class Portstat(object):
if key in cnstat_old_dict:
old_cntr = cnstat_old_dict.get(key)

if old_cntr is not None:
table.append((key,
ns_diff(cntr.rx_ok, old_cntr.rx_ok),
ns_rate(cntr.rx_byt, old_cntr.rx_byt, time_gap),
ns_util(cntr.rx_byt, old_cntr.rx_byt, time_gap),
ns_diff(cntr.rx_err, old_cntr.rx_err),
ns_diff(cntr.rx_drop, old_cntr.rx_drop),
ns_diff(cntr.rx_ovr, old_cntr.rx_ovr),
ns_diff(cntr.tx_ok, old_cntr.tx_ok),
ns_rate(cntr.tx_byt, old_cntr.tx_byt, time_gap),
ns_util(cntr.tx_byt, old_cntr.tx_byt, time_gap),
ns_diff(cntr.tx_err, old_cntr.tx_err),
ns_diff(cntr.tx_drop, old_cntr.tx_drop),
ns_diff(cntr.tx_ovr, old_cntr.tx_ovr)))
if print_all:
if old_cntr is not None:
table.append((key, self.get_port_status(key),
ns_diff(cntr.rx_ok, old_cntr.rx_ok),
ns_brate(cntr.rx_byt, old_cntr.rx_byt, time_gap),
ns_prate(cntr.rx_ok, old_cntr.rx_ok, time_gap),
ns_util(cntr.rx_byt, old_cntr.rx_byt, time_gap),
ns_diff(cntr.rx_err, old_cntr.rx_err),
ns_diff(cntr.rx_drop, old_cntr.rx_drop),
ns_diff(cntr.rx_ovr, old_cntr.rx_ovr),
ns_diff(cntr.tx_ok, old_cntr.tx_ok),
ns_brate(cntr.tx_byt, old_cntr.tx_byt, time_gap),
ns_prate(cntr.tx_ok, old_cntr.tx_ok, time_gap),
ns_util(cntr.tx_byt, old_cntr.tx_byt, time_gap),
ns_diff(cntr.tx_err, old_cntr.tx_err),
ns_diff(cntr.tx_drop, old_cntr.tx_drop),
ns_diff(cntr.tx_ovr, old_cntr.tx_ovr)))
else:
table.append((key, self.get_port_status(key),
cntr.rx_ok,
"N/A",
"N/A",
"N/A",
cntr.rx_err,
cntr.rx_drop,
cntr.rx_ovr,
cntr.tx_ok,
"N/A",
"N/A",
"N/A",
cntr.tx_err,
cntr.tx_drop,
cntr.tx_err))
else:
table.append((key,
cntr.rx_ok,
"N/A",
"N/A",
cntr.rx_err,
cntr.rx_drop,
cntr.rx_ovr,
cntr.tx_ok,
"N/A",
"N/A",
cntr.tx_err,
cntr.tx_drop,
cntr.tx_err))
if old_cntr is not None:
table.append((key, self.get_port_status(key),
ns_diff(cntr.rx_ok, old_cntr.rx_ok),
ns_brate(cntr.rx_byt, old_cntr.rx_byt, time_gap),
ns_util(cntr.rx_byt, old_cntr.rx_byt, time_gap),
ns_diff(cntr.rx_err, old_cntr.rx_err),
ns_diff(cntr.rx_drop, old_cntr.rx_drop),
ns_diff(cntr.rx_ovr, old_cntr.rx_ovr),
ns_diff(cntr.tx_ok, old_cntr.tx_ok),
ns_brate(cntr.tx_byt, old_cntr.tx_byt, time_gap),
ns_util(cntr.tx_byt, old_cntr.tx_byt, time_gap),
ns_diff(cntr.tx_err, old_cntr.tx_err),
ns_diff(cntr.tx_drop, old_cntr.tx_drop),
ns_diff(cntr.tx_ovr, old_cntr.tx_ovr)))
else:
table.append((key, self.get_port_status(key),
cntr.rx_ok,
"N/A",
"N/A",
cntr.rx_err,
cntr.rx_drop,
cntr.rx_ovr,
cntr.tx_ok,
"N/A",
"N/A",
cntr.tx_err,
cntr.tx_drop,
cntr.tx_err))

if use_json:
print self.table_as_json(table)
print self.table_as_json(table, print_all)
else:
print tabulate(table, header, tablefmt='simple', stralign='right')
if print_all:
print tabulate(table, header_all, tablefmt='simple', stralign='right')
else:
print tabulate(table, header, tablefmt='simple', stralign='right')


def main():
parser = argparse.ArgumentParser(description='Wrapper for netstat',
Expand All @@ -227,6 +322,7 @@ Examples:
portstat -d -t test
portstat
portstat -r
portstat -a
portstat -p 20
""")

Expand All @@ -235,6 +331,7 @@ Examples:
parser.add_argument('-D', '--delete-all', action='store_true', help='Delete all saved stats')
parser.add_argument('-j', '--json', action='store_true', help='Display in JSON format')
parser.add_argument('-r', '--raw', action='store_true', help='Raw stats (unmodified output of netstat)')
parser.add_argument('-a', '--all', action='store_true', help='Display all the stats counters')
parser.add_argument('-t', '--tag', type=str, help='Save stats with name TAG', default=None)
parser.add_argument('-p', '--period', type=int, help='Display stats over a specified period (in seconds).', default=0)
args = parser.parse_args()
Expand All @@ -247,6 +344,7 @@ Examples:
tag_name = args.tag
uid = str(os.getuid())
wait_time_in_seconds = args.period
print_all = args.all

if not os.geteuid() == 0:
raise RuntimeError("must be root to run")
Expand Down Expand Up @@ -287,7 +385,7 @@ Examples:

# Now decide what information to display
if raw_stats:
portstat.cnstat_print(cnstat_dict, use_json)
portstat.cnstat_print(cnstat_dict, use_json, print_all)
sys.exit(0)

# At this point, either we'll create a file or open an existing one.
Expand All @@ -314,20 +412,21 @@ Examples:
try:
cnstat_cached_dict = pickle.load(open(cnstat_fqn_file, 'r'))
print "Last cached time was " + str(cnstat_cached_dict.get('time'))
portstat.cnstat_diff_print(cnstat_dict, cnstat_cached_dict, use_json)
portstat.cnstat_diff_print(cnstat_dict, cnstat_cached_dict, use_json, print_all)
except IOError as e:
print e.errno, e
else:
if tag_name:
print "\nFile '%s' does not exist" % cnstat_fqn_file
print "Did you run 'portstat -c -t %s' to record the counters via tag %s?\n" % (tag_name, tag_name)
else:
portstat.cnstat_print(cnstat_dict, use_json)
portstat.cnstat_print(cnstat_dict, use_json, print_all)
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 = portstat.get_cnstat()
portstat.cnstat_diff_print(cnstat_new_dict, cnstat_dict, use_json)
portstat.cnstat_diff_print(cnstat_new_dict, cnstat_dict, use_json, print_all)

if __name__ == "__main__":
main()
2 changes: 1 addition & 1 deletion sonic_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ def default(interfacename, sfp):
@interfaces.command()
def counters():
"""Show interface counters"""
run_command("portstat", pager=True)
run_command("portstat -a -p 30", pager=True)

# 'portchannel' subcommand
@interfaces.command()
Expand Down

0 comments on commit 9ea7ae4

Please sign in to comment.