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

CLI addition for PFC counters --history #3778

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
228 changes: 189 additions & 39 deletions scripts/pfcstat
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,18 @@ try:
except KeyError:
pass

from utilities_common.netstat import ns_diff, STATUS_NA, format_number_with_comma
from utilities_common.netstat import ns_diff, STATUS_NA, format_number_with_comma, format_ms_as_datetime
from utilities_common import multi_asic as multi_asic_util
from utilities_common import constants
from utilities_common.cli import json_serial, UserCache


PStats = namedtuple("PStats", "pfc0, pfc1, pfc2, pfc3, pfc4, pfc5, pfc6, pfc7")
header_Rx = ['Port Rx', 'PFC0', 'PFC1', 'PFC2', 'PFC3', 'PFC4', 'PFC5', 'PFC6', 'PFC7']

header_Tx = ['Port Tx', 'PFC0', 'PFC1', 'PFC2', 'PFC3', 'PFC4', 'PFC5', 'PFC6', 'PFC7']
pfc_titles = ['PFC0', 'PFC1', 'PFC2', 'PFC3', 'PFC4', 'PFC5', 'PFC6', 'PFC7']

header_Rx = ['Port Rx'] + pfc_titles
header_Tx = ['Port Tx'] + pfc_titles

counter_bucket_rx_dict = {
'SAI_PORT_STAT_PFC_0_RX_PKTS': 0,
Expand All @@ -68,7 +70,12 @@ counter_bucket_tx_dict = {
}


HistStats = namedtuple("HistStats", "numTransitions, totalPauseTime, recentPauseTimestamp, recentPauseTime")
history_stat_fields = ["TOTAL_PAUSE_TRANSITIONS_", "TOTAL_PAUSE_TIME_MS_", "RECENT_PAUSE_TIMESTAMP_", "RECENT_PAUSE_TIME_MS_"]
header_hist = ['Port', 'Priority', 'RX Pause Transitions', 'Total RX Pause Time MS', 'Recent RX Pause Time MS', 'Recent RX Pause Timestamp']

COUNTER_TABLE_PREFIX = "COUNTERS:"
PFC_STAT_HISTORY_TABLE_PREFIX = "PFC_STAT_HISTORY:"
COUNTERS_PORT_NAME_MAP = "COUNTERS_PORT_NAME_MAP"

class Pfcstat(object):
Expand All @@ -77,6 +84,7 @@ class Pfcstat(object):
self.db = None
self.config_db = None
self.cnstat_dict = OrderedDict()
self.hist_dict = OrderedDict()

@multi_asic_util.run_on_multi_asic
def collect_cnstat(self, rx):
Expand Down Expand Up @@ -126,6 +134,55 @@ class Pfcstat(object):
)
self.cnstat_dict.update(cnstat_dict)

@multi_asic_util.run_on_multi_asic
def collect_history(self):
"""
Get the history stats from database.
"""
def get_history_stats(table_id):
"""
Get the history from specific table.
"""
pfc_dict = OrderedDict()
for pfc_index, pfc in enumerate(pfc_titles):
fields = [STATUS_NA] * len(history_stat_fields)
# TOTAL_PAUSE_TRANSITIONS_0, TOTAL_PAUSE_TIME_MS_0, ...
for stat_index, stat_name in enumerate(history_stat_fields):
full_table_id = PFC_STAT_HISTORY_TABLE_PREFIX + table_id
full_stat_name = stat_name + str(pfc_index)

hist_data = self.db.get(
self.db.COUNTERS_DB, full_table_id, full_stat_name
)
if hist_data is not None:
fields[stat_index] = str(int(hist_data))
stats = HistStats._make(fields)._asdict()

pfc_dict[pfc] = stats
return pfc_dict

# get the port name : oid map
counter_port_name_map = self.db.get_all(
self.db.COUNTERS_DB, COUNTERS_PORT_NAME_MAP
)
if counter_port_name_map is None:
return
display_ports_set = set(counter_port_name_map.keys())
if self.multi_asic.display_option == constants.DISPLAY_EXTERNAL:
display_ports_set = get_external_ports(
display_ports_set, self.multi_asic.current_namespace
)
# Build a dictionary of the stats
hist_dict = OrderedDict()
hist_dict['time'] = datetime.datetime.now()
if counter_port_name_map is not None:
for port in natsorted(counter_port_name_map):
if port in display_ports_set:
hist_dict[port] = get_history_stats(
counter_port_name_map[port]
)
self.hist_dict.update(hist_dict)

def get_cnstat(self, rx):
"""
Get the counters info from database.
Expand All @@ -134,6 +191,14 @@ class Pfcstat(object):
self.collect_cnstat(rx)
return self.cnstat_dict

def get_history(self):
"""
Get the history stats from database.
"""
self.hist_dict.clear()
self.collect_history()
return self.hist_dict

def cnstat_print(self, cnstat_dict, rx):
"""
Print the cnstat.
Expand Down Expand Up @@ -197,6 +262,51 @@ class Pfcstat(object):
else:
print(tabulate(table, header_Tx, tablefmt='simple', stralign='right'))

def history_diff_print(self, hist_new_dict, hist_old_dict={}):
"""
Print the difference between two cnstat history results.
"""
table = []
# time : <>, ethernet0 : { pfc0: {}, pfc1: {} }, ethernet1 :{}
for key, pfc_dict in hist_new_dict.items():
if key == 'time':
continue
# old dict has this port
if key in hist_old_dict:
for pfc, stat in pfc_dict.items():
old_stat = None
if pfc in hist_old_dict.get(key):
old_stat = hist_old_dict.get(key).get(pfc)
# old dict [ port ] has this priority data
if old_stat is not None:
table.append((key,
pfc,
ns_diff(stat['numTransitions'], old_stat['numTransitions']),
ns_diff(stat['totalPauseTime'], old_stat['totalPauseTime']),
format_number_with_comma(stat['recentPauseTime']),
format_ms_as_datetime(stat['recentPauseTimestamp'])))
# use the new data only for this port for this priority
else:
table.append((key,
pfc,
format_number_with_comma(stat['numTransitions']),
format_number_with_comma(stat['totalPauseTime']),
format_number_with_comma(stat['recentPauseTime']),
format_ms_as_datetime(stat['recentPauseTimestamp'])))
# use the new data only for this port
else:
for pfc, stat in pfc_dict.items():
table.append((key,
pfc,
format_number_with_comma(stat['numTransitions']),
format_number_with_comma(stat['totalPauseTime']),
format_number_with_comma(stat['recentPauseTime']),
format_ms_as_datetime(stat['recentPauseTimestamp'])))
# empty line between interfaces
table.append([])

print(tabulate(table, header_hist, tablefmt='simple', stralign='right'))

def main():
parser = argparse.ArgumentParser(description='Display the pfc counters',
formatter_class=argparse.RawTextHelpFormatter,
Expand All @@ -207,6 +317,9 @@ Examples:
pfcstat -d
pfcstat -n asic1
pfcstat -s all -n asic0
pfcstat --history
pfcstat -n asic1 --history
pfcstat -s all -n asic0 --history
""")

parser.add_argument( '-c', '--clear', action='store_true',
Expand All @@ -221,18 +334,26 @@ Examples:
parser.add_argument('-n', '--namespace', default=None,
help='Display interfaces for specific namespace'
)
parser.add_argument('-v', '--version', action='version', version='%(prog)s 1.0')
parser.add_argument(
'-v', '--version', action='version', version='%(prog)s 1.0'
)
parser.add_argument(
'--history', action="store_true", help="Display historical PFC statistics"
)

args = parser.parse_args()

save_fresh_stats = args.clear
delete_all_stats = args.delete
show_pfc_history = args.history

cache = UserCache()
cnstat_file = 'pfcstat'

cnstat_dir = cache.get_directory()
cnstat_fqn_file_rx = os.path.join(cnstat_dir, "{}rx".format(cnstat_file))
cnstat_fqn_file_tx = os.path.join(cnstat_dir, "{}tx".format(cnstat_file))
hist_fqn_file = cnstat_fqn_file_rx + "_hist"

# if '-c' option is provided get stats from all (frontend and backend) ports
if save_fresh_stats:
Expand All @@ -244,55 +365,84 @@ Examples:
if delete_all_stats:
cache.remove()

"""
Get the counters of pfc rx counter
"""
cnstat_dict_rx = deepcopy(pfcstat.get_cnstat(True))

"""
Get the counters of pfc tx counter
"""
cnstat_dict_tx = deepcopy(pfcstat.get_cnstat(False))

# clear both counters + history
if save_fresh_stats:
# Get the counters of pfc rx counter
cnstat_dict_rx = deepcopy(pfcstat.get_cnstat(True))
# Get the counters of pfc tx counter
cnstat_dict_tx = deepcopy(pfcstat.get_cnstat(False))
# Get the history stats of pfc rx
hist_dict = deepcopy(pfcstat.get_history())

try:
json.dump(cnstat_dict_rx, open(cnstat_fqn_file_rx, 'w'), default=json_serial)
json.dump(cnstat_dict_tx, open(cnstat_fqn_file_tx, 'w'), default=json_serial)
json.dump(hist_dict, open(hist_fqn_file, 'w'), default=json_serial)
except IOError as e:
print(e.errno, e)
sys.exit(e.errno)
else:
print("Clear saved counters")
print("Clear saved PFC counters and history")
sys.exit(0)

# save history stats separately
if show_pfc_history:
"""
Get the history stats of pfc rx
"""
hist_dict = deepcopy(pfcstat.get_history())

"""
Print the pfc history stats
"""
if os.path.isfile(hist_fqn_file):
try:
hist_cached_dict = json.load(open(hist_fqn_file, 'r'))
print("Last cached time was " + str(hist_cached_dict.get('time')))
pfcstat.history_diff_print(hist_dict, hist_cached_dict)
except IOError as e:
print(e.errno, e)
else:
pfcstat.history_diff_print(hist_dict)

"""
Print the counters of pfc rx counter
"""
if os.path.isfile(cnstat_fqn_file_rx):
try:
cnstat_cached_dict = json.load(open(cnstat_fqn_file_rx, 'r'))
print("Last cached time was " + str(cnstat_cached_dict.get('time')))
pfcstat.cnstat_diff_print(cnstat_dict_rx, cnstat_cached_dict, True)
except IOError as e:
print(e.errno, e)
else:
pfcstat.cnstat_print(cnstat_dict_rx, True)
"""
Get the counters of pfc rx counter
"""
cnstat_dict_rx = deepcopy(pfcstat.get_cnstat(True))

print("")
"""
Get the counters of pfc tx counter
"""
cnstat_dict_tx = deepcopy(pfcstat.get_cnstat(False))

"""
Print the counters of pfc tx counter
"""
if os.path.isfile(cnstat_fqn_file_tx):
try:
cnstat_cached_dict = json.load(open(cnstat_fqn_file_tx, 'r'))
print("Last cached time was " + str(cnstat_cached_dict.get('time')))
pfcstat.cnstat_diff_print(cnstat_dict_tx, cnstat_cached_dict, False)
except IOError as e:
print(e.errno, e)
else:
pfcstat.cnstat_print(cnstat_dict_tx, False)
"""
Print the counters of pfc rx counter
"""
if os.path.isfile(cnstat_fqn_file_rx):
try:
cnstat_cached_dict = json.load(open(cnstat_fqn_file_rx, 'r'))
print("Last cached time was " + str(cnstat_cached_dict.get('time')))
pfcstat.cnstat_diff_print(cnstat_dict_rx, cnstat_cached_dict, True)
except IOError as e:
print(e.errno, e)
else:
pfcstat.cnstat_print(cnstat_dict_rx, True)

print("")

"""
Print the counters of pfc tx counter
"""
if os.path.isfile(cnstat_fqn_file_tx):
try:
cnstat_cached_dict = json.load(open(cnstat_fqn_file_tx, 'r'))
print("Last cached time was " + str(cnstat_cached_dict.get('time')))
pfcstat.cnstat_diff_print(cnstat_dict_tx, cnstat_cached_dict, False)
except IOError as e:
print(e.errno, e)
else:
pfcstat.cnstat_print(cnstat_dict_tx, False)

sys.exit(0)

Expand Down
5 changes: 4 additions & 1 deletion show/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -638,13 +638,16 @@ def pfc():
# 'counters' subcommand ("show interfaces pfccounters")
@pfc.command()
@multi_asic_util.multi_asic_click_options
@click.option('--history', is_flag=True, help="Display historical PFC statistics")
@click.option('--verbose', is_flag=True, help="Enable verbose output")
def counters(namespace, display, verbose):
def counters(namespace, history, display, verbose):
"""Show pfc counters"""

cmd = ['pfcstat', '-s', str(display)]
if namespace is not None:
cmd += ['-n', str(namespace)]
if history:
cmd += ['--history']

run_command(cmd, display_cmd=verbose)

Expand Down
Loading
Loading