Skip to content
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 doc/Command-Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -5268,6 +5268,34 @@ The "fec-stats" subcommand is used to disply the interface fec related statistic
Ethernet16 U 0 0 0 1.77e-20 0.00e+00
```

For debugging link related issues where you need to clear the FEC histogram and monitor the link again, use the following command

- Example (for all ports):
```
root@sonic:~# portstat -fh
Last cached time was 2025-10-02T16:43:57.934081
IFACE BIN0 BIN1 BIN2 BIN3 BIN4 BIN5 BIN6 BIN7 BIN8 BIN9 BIN10 BIN11 BIN12 BIN13 BIN14 BIN15
----------- ------------- ---------- --------- ------ ------ ------ ------ ------ ------ ------ ------- ------- ------- ------- ------- -------
Ethernet0 4,374,661,575 340 1 0 0 0 0 0 0 0 0 0 0 0 0 0
Ethernet8 4,374,590,263 8,069 9 0 0 0 0 0 0 0 0 0 0 0 0 0
Ethernet16 4,374,660,911 3,187 4 0 0 0 0 0 0 0 0 0 0 0 0 0
Ethernet24 4,374,594,305 57,484 502 0 0 0 0 0 0 0 0 0 0 0 0 0
Ethernet32 4,374,649,615 116 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Ethernet40 4,374,650,913 1,212 1 0 0 0 0 0 0 0 0 0 0 0 0 0
```

- Example (for a particular port):
```
root@sonic:~# portstat -fh -i Ethernet504
Last cached time was 2025-10-02T16:43:57.934081
IFACE BIN0 BIN1 BIN2 BIN3 BIN4 BIN5 BIN6 BIN7 BIN8 BIN9 BIN10 BIN11 BIN12 BIN13 BIN14 BIN15
----------- ----------- ------ ------ ------ ------ ------ ------ ------ ------ ------ ------- ------- ------- ------- ------- -------
Ethernet504 624,891,017 13,331 172 0 0 0 0 0 0 0 0 0 0 0 0 0
root@str-7060x6-c09-u25:~#
```

To clear the FEC histogram use `portstat -c`. NOTE: This will clear all counters.

The "trim" subcommand is used to display the interface packet trimming related statistic.

- Example:
Expand Down
10 changes: 6 additions & 4 deletions scripts/portstat
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ Examples:
parser.add_argument('-D', '--delete-all', action='store_true', help='Delete all saved stats')
parser.add_argument('-e', '--errors', action='store_true', help='Display interface errors')
parser.add_argument('-f', '--fec-stats', action='store_true', help='Display FEC related statistics')
parser.add_argument('-fh', '--fec_hist', action='store_true', help='Display FEC histogram')
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('-R', '--rate', action='store_true', help='Display interface rates')
Expand All @@ -88,6 +89,7 @@ Examples:
delete_all_stats = args.delete_all
errors_only = args.errors
fec_stats_only = args.fec_stats
fec_hist_only = args.fec_hist
rates_only = args.rate
use_json = args.json
raw_stats = args.raw
Expand Down Expand Up @@ -125,7 +127,7 @@ Examples:

# Now decide what information to display
if raw_stats:
portstat.cnstat_print(cnstat_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, fec_stats_only, rates_only, trim_stats_only)
portstat.cnstat_print(cnstat_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, fec_stats_only, rates_only, trim_stats_only, fec_hist_only)
sys.exit(0)

if save_fresh_stats:
Expand All @@ -144,21 +146,21 @@ Examples:
cnstat_cached_dict = json.load(open(cnstat_fqn_file, 'r'))
if not detail:
print("Last cached time was " + str(cnstat_cached_dict.get('time')))
portstat.cnstat_diff_print(cnstat_dict, cnstat_cached_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, fec_stats_only, rates_only, trim_stats_only, detail)
portstat.cnstat_diff_print(cnstat_dict, cnstat_cached_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, fec_stats_only, rates_only, trim_stats_only, fec_hist_only, detail)
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, ratestat_dict, intf_list, use_json, print_all, errors_only, fec_stats_only, rates_only, trim_stats_only, detail)
portstat.cnstat_print(cnstat_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, fec_stats_only, rates_only, trim_stats_only, fec_hist_only, detail)
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, ratestat_new_dict = portstat.get_cnstat_dict()
portstat.cnstat_diff_print(cnstat_new_dict, cnstat_dict, ratestat_new_dict, intf_list, use_json, print_all, errors_only, fec_stats_only, rates_only, trim_stats_only, detail)
portstat.cnstat_diff_print(cnstat_new_dict, cnstat_dict, ratestat_new_dict, intf_list, use_json, print_all, errors_only, fec_stats_only, rates_only, trim_stats_only, fec_hist_only, detail)

if __name__ == "__main__":
main()
16 changes: 16 additions & 0 deletions tests/portstat_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,14 @@
BIN15 0
"""

portstat_fec_histogram = """\
IFACE BIN0 BIN1 BIN2 BIN3 BIN4 BIN5 BIN6 BIN7 BIN8 BIN9 BIN10 BIN11 BIN12 BIN13 BIN14 BIN15
--------- --------- ------- ------- ------- ------- ------- ------- ------- ------ ------ ------- ------- ------- ------- ------- -------
Ethernet0 1,000,000 900,000 800,000 700,000 600,000 500,000 400,000 300,000 0 0 0 0 0 0 0 0
Ethernet4 N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A
Ethernet8 N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A N/A
""" # noqa: E501

intf_fec_counters_period = """\
The rates are calculated within 3 seconds period
IFACE STATE FEC_CORR FEC_UNCORR FEC_SYMBOL_ERR
Expand Down Expand Up @@ -422,6 +430,14 @@ def test_show_intf_counters_fec_histogram(self):
assert result.exit_code == 0
assert result.output == intf_fec_counters_fec_hist

def test_portstat_fec_histogram(self):
return_code, result = get_result_and_return_code(
['portstat', '-fh'])
print("return_code: {}".format(return_code))
print("result = {}".format(result))
assert return_code == 0
assert result == portstat_fec_histogram

def test_show_intf_fec_counters_period(self):
runner = CliRunner()
result = runner.invoke(show.cli.commands["interfaces"].commands["counters"].commands["fec-stats"],
Expand Down
67 changes: 63 additions & 4 deletions utilities_common/portstat.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,17 @@
rx_jbr, rx_frag, rx_usize, rx_ovrrun,\
fec_corr, fec_uncorr, fec_symbol_err,\
wred_grn_drp_pkt, wred_ylw_drp_pkt, wred_red_drp_pkt, wred_tot_drp_pkt,\
trim")
trim, fec_bin0, fec_bin1, fec_bin2, fec_bin3,\
fec_bin4, fec_bin5, fec_bin6, fec_bin7, fec_bin8, fec_bin9, fec_bin10,\
fec_bin11, fec_bin12, fec_bin13, fec_bin14, fec_bin15")
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', 'TRIM']
header_std = ['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']
header_errors_only = ['IFACE', 'STATE', 'RX_ERR', 'RX_DRP', 'RX_OVR', 'TX_ERR', 'TX_DRP', 'TX_OVR']
header_fec_only = ['IFACE', 'STATE', 'FEC_CORR', 'FEC_UNCORR', 'FEC_SYMBOL_ERR', 'FEC_PRE_BER', 'FEC_POST_BER']
header_fec_hist_only = ['IFACE', 'BIN0', 'BIN1', 'BIN2', 'BIN3', 'BIN4', 'BIN5', 'BIN6', 'BIN7',
'BIN8', 'BIN9', 'BIN10', 'BIN11', 'BIN12', 'BIN13', 'BIN14', 'BIN15']
header_rates_only = ['IFACE', 'STATE', 'RX_OK', 'RX_BPS', 'RX_PPS', 'RX_UTIL', 'TX_OK', 'TX_BPS', 'TX_PPS', 'TX_UTIL']
header_trim_only = ['IFACE', 'STATE', 'TRIM_PKTS']

Expand All @@ -47,7 +51,7 @@
The order and count of statistics mentioned below needs to be in sync with the values in portstat script
So, any fields added/deleted in here should be reflected in portstat script also
"""
BUCKET_NUM = 50
BUCKET_NUM = 66

wred_green_pkt_stat_capable = "false"
wred_yellow_pkt_stat_capable = "false"
Expand Down Expand Up @@ -109,6 +113,22 @@
47: ['SAI_PORT_STAT_RED_WRED_DROPPED_PACKETS'],
48: ['SAI_PORT_STAT_WRED_DROPPED_PACKETS'],
49: ['SAI_PORT_STAT_TRIM_PACKETS'],
50: ['SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S0'],
51: ['SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S1'],
52: ['SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S2'],
53: ['SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S3'],
54: ['SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S4'],
55: ['SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S5'],
56: ['SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S6'],
57: ['SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S7'],
58: ['SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S8'],
59: ['SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S9'],
60: ['SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S10'],
61: ['SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S11'],
62: ['SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S12'],
63: ['SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S13'],
64: ['SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S14'],
65: ['SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S15'],
}

STATUS_NA = 'N/A'
Expand Down Expand Up @@ -383,7 +403,7 @@ def get_port_state(self, port_name):
return STATUS_NA

def cnstat_print(self, cnstat_dict, ratestat_dict, intf_list, use_json, print_all,
errors_only, fec_stats_only, rates_only, trim_stats_only, detail=False):
errors_only, fec_stats_only, rates_only, trim_stats_only, fec_hist_only, detail=False):
"""
Print the cnstat.
"""
Expand Down Expand Up @@ -440,6 +460,26 @@ def cnstat_print(self, cnstat_dict, ratestat_dict, intf_list, use_json, print_al
format_number_with_comma(data['fec_symbol_err']),
format_fec_ber(rates.fec_pre_ber),
format_fec_ber(rates.fec_post_ber)))
elif fec_hist_only:
header = header_fec_hist_only

table.append((key,
format_number_with_comma(data['fec_bin0']),
format_number_with_comma(data['fec_bin1']),
format_number_with_comma(data['fec_bin2']),
format_number_with_comma(data['fec_bin3']),
format_number_with_comma(data['fec_bin4']),
format_number_with_comma(data['fec_bin5']),
format_number_with_comma(data['fec_bin6']),
format_number_with_comma(data['fec_bin7']),
format_number_with_comma(data['fec_bin8']),
format_number_with_comma(data['fec_bin9']),
format_number_with_comma(data['fec_bin10']),
format_number_with_comma(data['fec_bin11']),
format_number_with_comma(data['fec_bin12']),
format_number_with_comma(data['fec_bin13']),
format_number_with_comma(data['fec_bin14']),
format_number_with_comma(data['fec_bin15'])))
elif rates_only:
header = header_rates_only
table.append((key, self.get_port_state(key),
Expand Down Expand Up @@ -619,7 +659,7 @@ def cnstat_intf_diff_print(self, cnstat_new_dict, cnstat_old_dict, intf_list):
def cnstat_diff_print(self, cnstat_new_dict, cnstat_old_dict,
ratestat_dict, intf_list, use_json,
print_all, errors_only, fec_stats_only,
rates_only, trim_stats_only, detail=False):
rates_only, trim_stats_only, fec_hist_only, detail=False):
"""
Print the difference between two cnstat results.
"""
Expand Down Expand Up @@ -715,6 +755,25 @@ def cnstat_diff_print(self, cnstat_new_dict, cnstat_old_dict,
format_number_with_comma(cntr['fec_corr']),
format_number_with_comma(cntr['fec_uncorr']),
format_number_with_comma(cntr['fec_symbol_err'])))
elif fec_hist_only:
header = header_fec_hist_only

table.append((key, ns_diff(cntr['fec_bin0'], old_cntr['fec_bin0']),
ns_diff(cntr['fec_bin1'], old_cntr['fec_bin1']),
ns_diff(cntr['fec_bin2'], old_cntr['fec_bin2']),
ns_diff(cntr['fec_bin3'], old_cntr['fec_bin3']),
ns_diff(cntr['fec_bin4'], old_cntr['fec_bin4']),
ns_diff(cntr['fec_bin5'], old_cntr['fec_bin5']),
ns_diff(cntr['fec_bin6'], old_cntr['fec_bin6']),
ns_diff(cntr['fec_bin7'], old_cntr['fec_bin7']),
ns_diff(cntr['fec_bin8'], old_cntr['fec_bin8']),
ns_diff(cntr['fec_bin9'], old_cntr['fec_bin9']),
ns_diff(cntr['fec_bin10'], old_cntr['fec_bin10']),
ns_diff(cntr['fec_bin11'], old_cntr['fec_bin11']),
ns_diff(cntr['fec_bin12'], old_cntr['fec_bin12']),
ns_diff(cntr['fec_bin13'], old_cntr['fec_bin13']),
ns_diff(cntr['fec_bin14'], old_cntr['fec_bin14']),
ns_diff(cntr['fec_bin15'], old_cntr['fec_bin15'])))

elif rates_only:
header = header_rates_only
Expand Down
Loading