Skip to content

Commit 7919077

Browse files
authored
Add FEC correctable and uncorrectable port stats (sonic-net#2027)
* Add FEC correctable and uncorrectable port stats Signed-off-by: Prince George <[email protected]> * fix pytest failures Signed-off-by: Prince George <[email protected]> * fix pytest failure * Added separate command for fec stats * Fix test failure * Fix LGTM warning * Improve code coveraged
1 parent ecb9136 commit 7919077

File tree

4 files changed

+125
-20
lines changed

4 files changed

+125
-20
lines changed

scripts/portstat

+38-9
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,14 @@ NStats = namedtuple("NStats", "rx_ok, rx_err, rx_drop, rx_ovr, tx_ok,\
5151
rx_uca, rx_mca, rx_bca, rx_all,\
5252
tx_64, tx_65_127, tx_128_255, tx_256_511, tx_512_1023, tx_1024_1518, tx_1519_2047, tx_2048_4095, tx_4096_9216, tx_9217_16383,\
5353
tx_uca, tx_mca, tx_bca, tx_all,\
54-
rx_jbr, rx_frag, rx_usize, rx_ovrrun")
54+
rx_jbr, rx_frag, rx_usize, rx_ovrrun,\
55+
fec_corr, fec_uncorr, fec_symbol_err")
5556
header_all = ['IFACE', 'STATE', 'RX_OK', 'RX_BPS', 'RX_PPS', 'RX_UTIL', 'RX_ERR', 'RX_DRP', 'RX_OVR',
5657
'TX_OK', 'TX_BPS', 'TX_PPS', 'TX_UTIL', 'TX_ERR', 'TX_DRP', 'TX_OVR']
5758
header_std = ['IFACE', 'STATE', 'RX_OK', 'RX_BPS', 'RX_UTIL', 'RX_ERR', 'RX_DRP', 'RX_OVR',
5859
'TX_OK', 'TX_BPS', 'TX_UTIL', 'TX_ERR', 'TX_DRP', 'TX_OVR']
5960
header_errors_only = ['IFACE', 'STATE', 'RX_ERR', 'RX_DRP', 'RX_OVR', 'TX_ERR', 'TX_DRP', 'TX_OVR']
61+
header_fec_only = ['IFACE', 'STATE', 'FEC_CORR', 'FEC_UNCORR', 'FEC_SYMBOL_ERR']
6062
header_rates_only = ['IFACE', 'STATE', 'RX_OK', 'RX_BPS', 'RX_PPS', 'RX_UTIL', 'TX_OK', 'TX_BPS', 'TX_PPS', 'TX_UTIL']
6163

6264
rates_key_list = [ 'RX_BPS', 'RX_PPS', 'RX_UTIL', 'TX_BPS', 'TX_PPS', 'TX_UTIL' ]
@@ -67,7 +69,7 @@ RateStats = namedtuple("RateStats", ratestat_fields)
6769
The order and count of statistics mentioned below needs to be in sync with the values in portstat script
6870
So, any fields added/deleted in here should be reflected in portstat script also
6971
"""
70-
BUCKET_NUM = 42
72+
BUCKET_NUM = 45
7173
counter_bucket_dict = {
7274
0:['SAI_PORT_STAT_IF_IN_UCAST_PKTS', 'SAI_PORT_STAT_IF_IN_NON_UCAST_PKTS'],
7375
1:['SAI_PORT_STAT_IF_IN_ERRORS'],
@@ -110,7 +112,10 @@ counter_bucket_dict = {
110112
38:['SAI_PORT_STAT_ETHER_STATS_JABBERS'],
111113
39:['SAI_PORT_STAT_ETHER_STATS_FRAGMENTS'],
112114
40:['SAI_PORT_STAT_ETHER_STATS_UNDERSIZE_PKTS'],
113-
41:['SAI_PORT_STAT_IP_IN_RECEIVES']
115+
41:['SAI_PORT_STAT_IP_IN_RECEIVES'],
116+
42:['SAI_PORT_STAT_IF_IN_FEC_CORRECTABLE_FRAMES'],
117+
43:['SAI_PORT_STAT_IF_IN_FEC_NOT_CORRECTABLE_FRAMES'],
118+
44:['SAI_PORT_STAT_IF_IN_FEC_SYMBOL_ERRORS']
114119
}
115120

116121
STATUS_NA = 'N/A'
@@ -250,7 +255,7 @@ class Portstat(object):
250255
return STATUS_NA
251256

252257

253-
def cnstat_print(self, cnstat_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, rates_only, detail=False):
258+
def cnstat_print(self, cnstat_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, fec_stats_only, rates_only, detail=False):
254259
"""
255260
Print the cnstat.
256261
"""
@@ -295,6 +300,12 @@ class Portstat(object):
295300
format_number_with_comma(data.tx_err),
296301
format_number_with_comma(data.tx_drop),
297302
format_number_with_comma(data.tx_ovr)))
303+
elif fec_stats_only:
304+
header = header_fec_only
305+
table.append((key, self.get_port_state(key),
306+
format_number_with_comma(data.fec_corr),
307+
format_number_with_comma(data.fec_uncorr),
308+
format_number_with_comma(data.fec_symbol_err)))
298309
elif rates_only:
299310
header = header_rates_only
300311
table.append((key, self.get_port_state(key),
@@ -388,7 +399,10 @@ class Portstat(object):
388399
print("Time Since Counters Last Cleared............... " + str(cnstat_old_dict.get('time')))
389400

390401

391-
def cnstat_diff_print(self, cnstat_new_dict, cnstat_old_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, rates_only, detail=False):
402+
def cnstat_diff_print(self, cnstat_new_dict, cnstat_old_dict,
403+
ratestat_dict, intf_list, use_json,
404+
print_all, errors_only, fec_stats_only,
405+
rates_only, detail=False):
392406
"""
393407
Print the difference between two cnstat results.
394408
"""
@@ -465,6 +479,19 @@ class Portstat(object):
465479
format_number_with_comma(cntr.tx_err),
466480
format_number_with_comma(cntr.tx_drop),
467481
format_number_with_comma(cntr.tx_ovr)))
482+
elif fec_stats_only:
483+
header = header_fec_only
484+
if old_cntr is not None:
485+
table.append((key, self.get_port_state(key),
486+
ns_diff(cntr.fec_corr, old_cntr.fec_corr),
487+
ns_diff(cntr.fec_uncorr, old_cntr.fec_uncorr),
488+
ns_diff(cntr.fec_symbol_err, old_cntr.fec_symbol_err)))
489+
else:
490+
table.append((key, self.get_port_state(key),
491+
format_number_with_comma(cntr.fec_corr),
492+
format_number_with_comma(cntr.fec_uncorr),
493+
format_number_with_comma(cntr.fec_symbol_err)))
494+
468495
elif rates_only:
469496
header = header_rates_only
470497
if old_cntr is not None:
@@ -551,6 +578,7 @@ Examples:
551578
parser.add_argument('-d', '--delete', action='store_true', help='Delete saved stats, either the uid or the specified tag')
552579
parser.add_argument('-D', '--delete-all', action='store_true', help='Delete all saved stats')
553580
parser.add_argument('-e', '--errors', action='store_true', help='Display interface errors')
581+
parser.add_argument('-f', '--fec-stats', action='store_true', help='Display FEC related statistics')
554582
parser.add_argument('-j', '--json', action='store_true', help='Display in JSON format')
555583
parser.add_argument('-r', '--raw', action='store_true', help='Raw stats (unmodified output of netstat)')
556584
parser.add_argument('-R', '--rate', action='store_true', help='Display interface rates')
@@ -567,6 +595,7 @@ Examples:
567595
delete_saved_stats = args.delete
568596
delete_all_stats = args.delete_all
569597
errors_only = args.errors
598+
fec_stats_only = args.fec_stats
570599
rates_only = args.rate
571600
use_json = args.json
572601
raw_stats = args.raw
@@ -603,7 +632,7 @@ Examples:
603632

604633
# Now decide what information to display
605634
if raw_stats:
606-
portstat.cnstat_print(cnstat_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, rates_only)
635+
portstat.cnstat_print(cnstat_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, fec_stats_only, rates_only)
607636
sys.exit(0)
608637

609638
if save_fresh_stats:
@@ -622,21 +651,21 @@ Examples:
622651
cnstat_cached_dict = pickle.load(open(cnstat_fqn_file, 'rb'))
623652
if not detail:
624653
print("Last cached time was " + str(cnstat_cached_dict.get('time')))
625-
portstat.cnstat_diff_print(cnstat_dict, cnstat_cached_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, rates_only, detail)
654+
portstat.cnstat_diff_print(cnstat_dict, cnstat_cached_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, fec_stats_only, rates_only, detail)
626655
except IOError as e:
627656
print(e.errno, e)
628657
else:
629658
if tag_name:
630659
print("\nFile '%s' does not exist" % cnstat_fqn_file)
631660
print("Did you run 'portstat -c -t %s' to record the counters via tag %s?\n" % (tag_name, tag_name))
632661
else:
633-
portstat.cnstat_print(cnstat_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, rates_only, detail)
662+
portstat.cnstat_print(cnstat_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, fec_stats_only, rates_only, detail)
634663
else:
635664
#wait for the specified time and then gather the new stats and output the difference.
636665
time.sleep(wait_time_in_seconds)
637666
print("The rates are calculated within %s seconds period" % wait_time_in_seconds)
638667
cnstat_new_dict, ratestat_new_dict = portstat.get_cnstat_dict()
639-
portstat.cnstat_diff_print(cnstat_new_dict, cnstat_dict, ratestat_new_dict, intf_list, use_json, print_all, errors_only, rates_only, detail)
668+
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, detail)
640669

641670
if __name__ == "__main__":
642671
main()

show/interfaces/__init__.py

+25-8
Original file line numberDiff line numberDiff line change
@@ -338,15 +338,15 @@ def expected(db, interfacename):
338338
@click.pass_context
339339
def mpls(ctx, interfacename, namespace, display):
340340
"""Show Interface MPLS status"""
341-
341+
342342
#Edge case: Force show frontend interfaces on single asic
343343
if not (multi_asic.is_multi_asic()):
344344
if (display == 'frontend' or display == 'all' or display is None):
345345
display = None
346346
else:
347347
print("Error: Invalid display option command for single asic")
348348
return
349-
349+
350350
display = "all" if interfacename else display
351351
masic = multi_asic_util.MultiAsic(display_option=display, namespace_option=namespace)
352352
ns_list = masic.get_ns_list_based_on_options()
@@ -372,13 +372,13 @@ def mpls(ctx, interfacename, namespace, display):
372372
if (interfacename is not None):
373373
if (interfacename != ifname):
374374
continue
375-
375+
376376
intf_found = True
377-
377+
378378
if (display != "all"):
379379
if ("Loopback" in ifname):
380380
continue
381-
381+
382382
if ifname.startswith("Ethernet") and multi_asic.is_port_internal(ifname, ns):
383383
continue
384384

@@ -391,11 +391,11 @@ def mpls(ctx, interfacename, namespace, display):
391391
if 'mpls' not in mpls_intf or mpls_intf['mpls'] == 'disable':
392392
intfs_data.update({ifname: 'disable'})
393393
else:
394-
intfs_data.update({ifname: mpls_intf['mpls']})
395-
394+
intfs_data.update({ifname: mpls_intf['mpls']})
395+
396396
# Check if interface is valid
397397
if (interfacename is not None and not intf_found):
398-
ctx.fail('interface {} doesn`t exist'.format(interfacename))
398+
ctx.fail('interface {} doesn`t exist'.format(interfacename))
399399

400400
header = ['Interface', 'MPLS State']
401401
body = []
@@ -558,6 +558,23 @@ def errors(verbose, period, namespace, display):
558558

559559
clicommon.run_command(cmd, display_cmd=verbose)
560560

561+
# 'fec-stats' subcommand ("show interfaces counters errors")
562+
@counters.command('fec-stats')
563+
@click.option('-p', '--period')
564+
@multi_asic_util.multi_asic_click_options
565+
@click.option('--verbose', is_flag=True, help="Enable verbose output")
566+
def fec_stats(verbose, period, namespace, display):
567+
"""Show interface counters fec-stats"""
568+
cmd = "portstat -f"
569+
if period is not None:
570+
cmd += " -p {}".format(period)
571+
572+
cmd += " -s {}".format(display)
573+
if namespace is not None:
574+
cmd += " -n {}".format(namespace)
575+
576+
clicommon.run_command(cmd, display_cmd=verbose)
577+
561578
# 'rates' subcommand ("show interfaces counters rates")
562579
@counters.command()
563580
@click.option('-p', '--period')

tests/mock_tables/counters_db.json

+12-3
Original file line numberDiff line numberDiff line change
@@ -855,7 +855,10 @@
855855
"SAI_PORT_STAT_ETHER_OUT_PKTS_9217_TO_16383_OCTETS": "0",
856856
"SAI_PORT_STAT_ETHER_STATS_FRAGMENTS": "0",
857857
"SAI_PORT_STAT_ETHER_STATS_UNDERSIZE_PKTS": "0",
858-
"SAI_PORT_STAT_ETHER_STATS_JABBERS": "0"
858+
"SAI_PORT_STAT_ETHER_STATS_JABBERS": "0",
859+
"SAI_PORT_STAT_IF_IN_FEC_CORRECTABLE_FRAMES": "130402",
860+
"SAI_PORT_STAT_IF_IN_FEC_NOT_CORRECTABLE_FRAMES": "3",
861+
"SAI_PORT_STAT_IF_IN_FEC_SYMBOL_ERRORS": "4"
859862
},
860863
"COUNTERS:oid:0x1000000000013": {
861864
"SAI_PORT_STAT_IF_IN_UCAST_PKTS": "4",
@@ -912,7 +915,10 @@
912915
"SAI_PORT_STAT_ETHER_OUT_PKTS_9217_TO_16383_OCTETS": "0",
913916
"SAI_PORT_STAT_ETHER_STATS_FRAGMENTS": "0",
914917
"SAI_PORT_STAT_ETHER_STATS_UNDERSIZE_PKTS": "0",
915-
"SAI_PORT_STAT_ETHER_STATS_JABBERS": "0"
918+
"SAI_PORT_STAT_ETHER_STATS_JABBERS": "0",
919+
"SAI_PORT_STAT_IF_IN_FEC_CORRECTABLE_FRAMES": "110412",
920+
"SAI_PORT_STAT_IF_IN_FEC_NOT_CORRECTABLE_FRAMES": "1",
921+
"SAI_PORT_STAT_IF_IN_FEC_SYMBOL_ERRORS": "0"
916922
},
917923
"COUNTERS:oid:0x1000000000014": {
918924
"SAI_PORT_STAT_IF_IN_UCAST_PKTS": "6",
@@ -969,7 +975,10 @@
969975
"SAI_PORT_STAT_ETHER_OUT_PKTS_9217_TO_16383_OCTETS": "0",
970976
"SAI_PORT_STAT_ETHER_STATS_FRAGMENTS": "0",
971977
"SAI_PORT_STAT_ETHER_STATS_UNDERSIZE_PKTS": "0",
972-
"SAI_PORT_STAT_ETHER_STATS_JABBERS": "0"
978+
"SAI_PORT_STAT_ETHER_STATS_JABBERS": "0",
979+
"SAI_PORT_STAT_IF_IN_FEC_CORRECTABLE_FRAMES": "100317",
980+
"SAI_PORT_STAT_IF_IN_FEC_NOT_CORRECTABLE_FRAMES": "0",
981+
"SAI_PORT_STAT_IF_IN_FEC_SYMBOL_ERRORS": "0"
973982
},
974983
"COUNTERS:oid:0x21000000000000": {
975984
"SAI_SWITCH_STAT_OUT_DROP_REASON_RANGE_BASE": "1000",

tests/portstat_test.py

+50
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,23 @@
3434
Ethernet8 N/A 6 1350.00 KB/s 9000.00/s N/A 100 10 N/A 60 13.37 MB/s 9000.00/s N/A N/A N/A N/A
3535
"""
3636

37+
intf_fec_counters = """\
38+
IFACE STATE FEC_CORR FEC_UNCORR FEC_SYMBOL_ERR
39+
--------- ------- ---------- ------------ ----------------
40+
Ethernet0 D 130,402 3 4
41+
Ethernet4 N/A 110,412 1 0
42+
Ethernet8 N/A 100,317 0 0
43+
"""
44+
45+
intf_fec_counters_period = """\
46+
The rates are calculated within 3 seconds period
47+
IFACE STATE FEC_CORR FEC_UNCORR FEC_SYMBOL_ERR
48+
--------- ------- ---------- ------------ ----------------
49+
Ethernet0 D 0 0 0
50+
Ethernet4 N/A 0 0 0
51+
Ethernet8 N/A 0 0 0
52+
"""
53+
3754
intf_counters_period = """\
3855
The rates are calculated within 3 seconds period
3956
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
@@ -258,6 +275,39 @@ def test_show_intf_counters_all(self):
258275
assert return_code == 0
259276
assert result == intf_counters_all
260277

278+
def test_show_intf_fec_counters(self):
279+
runner = CliRunner()
280+
result = runner.invoke(
281+
show.cli.commands["interfaces"].commands["counters"].commands["fec-stats"], [])
282+
print(result.exit_code)
283+
print(result.output)
284+
assert result.exit_code == 0
285+
assert result.output == intf_fec_counters
286+
287+
return_code, result = get_result_and_return_code('portstat -f')
288+
print("return_code: {}".format(return_code))
289+
print("result = {}".format(result))
290+
assert return_code == 0
291+
assert result == intf_fec_counters
292+
293+
def test_show_intf_fec_counters_period(self):
294+
runner = CliRunner()
295+
result = runner.invoke(show.cli.commands["interfaces"].commands["counters"].commands["fec-stats"],
296+
["-p {}".format(TEST_PERIOD)])
297+
print(result.exit_code)
298+
print(result.output)
299+
assert result.exit_code == 0
300+
assert result.output == intf_fec_counters_period
301+
302+
return_code, result = get_result_and_return_code(
303+
'portstat -f -p {}'.format(TEST_PERIOD))
304+
print("return_code: {}".format(return_code))
305+
print("result = {}".format(result))
306+
assert return_code == 0
307+
assert result == intf_fec_counters_period
308+
309+
310+
261311
def test_show_intf_counters_period(self):
262312
runner = CliRunner()
263313
result = runner.invoke(show.cli.commands["interfaces"].commands["counters"], [

0 commit comments

Comments
 (0)