Skip to content

Commit a70ac02

Browse files
prgeorlukasstockner
authored andcommitted
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 b992f09 commit a70ac02

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
@@ -48,12 +48,14 @@ NStats = namedtuple("NStats", "rx_ok, rx_err, rx_drop, rx_ovr, tx_ok,\
4848
rx_uca, rx_mca, rx_bca, rx_all,\
4949
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,\
5050
tx_uca, tx_mca, tx_bca, tx_all,\
51-
rx_jbr, rx_frag, rx_usize, rx_ovrrun")
51+
rx_jbr, rx_frag, rx_usize, rx_ovrrun,\
52+
fec_corr, fec_uncorr, fec_symbol_err")
5253
header_all = ['IFACE', 'STATE', 'RX_OK', 'RX_BPS', 'RX_PPS', 'RX_UTIL', 'RX_ERR', 'RX_DRP', 'RX_OVR',
5354
'TX_OK', 'TX_BPS', 'TX_PPS', 'TX_UTIL', 'TX_ERR', 'TX_DRP', 'TX_OVR']
5455
header_std = ['IFACE', 'STATE', 'RX_OK', 'RX_BPS', 'RX_UTIL', 'RX_ERR', 'RX_DRP', 'RX_OVR',
5556
'TX_OK', 'TX_BPS', 'TX_UTIL', 'TX_ERR', 'TX_DRP', 'TX_OVR']
5657
header_errors_only = ['IFACE', 'STATE', 'RX_ERR', 'RX_DRP', 'RX_OVR', 'TX_ERR', 'TX_DRP', 'TX_OVR']
58+
header_fec_only = ['IFACE', 'STATE', 'FEC_CORR', 'FEC_UNCORR', 'FEC_SYMBOL_ERR']
5759
header_rates_only = ['IFACE', 'STATE', 'RX_OK', 'RX_BPS', 'RX_PPS', 'RX_UTIL', 'TX_OK', 'TX_BPS', 'TX_PPS', 'TX_UTIL']
5860

5961
rates_key_list = [ 'RX_BPS', 'RX_PPS', 'RX_UTIL', 'TX_BPS', 'TX_PPS', 'TX_UTIL' ]
@@ -64,7 +66,7 @@ RateStats = namedtuple("RateStats", ratestat_fields)
6466
The order and count of statistics mentioned below needs to be in sync with the values in portstat script
6567
So, any fields added/deleted in here should be reflected in portstat script also
6668
"""
67-
BUCKET_NUM = 42
69+
BUCKET_NUM = 45
6870
counter_bucket_dict = {
6971
0:['SAI_PORT_STAT_IF_IN_UCAST_PKTS', 'SAI_PORT_STAT_IF_IN_NON_UCAST_PKTS'],
7072
1:['SAI_PORT_STAT_IF_IN_ERRORS'],
@@ -107,7 +109,10 @@ counter_bucket_dict = {
107109
38:['SAI_PORT_STAT_ETHER_STATS_JABBERS'],
108110
39:['SAI_PORT_STAT_ETHER_STATS_FRAGMENTS'],
109111
40:['SAI_PORT_STAT_ETHER_STATS_UNDERSIZE_PKTS'],
110-
41:['SAI_PORT_STAT_IP_IN_RECEIVES']
112+
41:['SAI_PORT_STAT_IP_IN_RECEIVES'],
113+
42:['SAI_PORT_STAT_IF_IN_FEC_CORRECTABLE_FRAMES'],
114+
43:['SAI_PORT_STAT_IF_IN_FEC_NOT_CORRECTABLE_FRAMES'],
115+
44:['SAI_PORT_STAT_IF_IN_FEC_SYMBOL_ERRORS']
111116
}
112117

113118
STATUS_NA = 'N/A'
@@ -246,7 +251,7 @@ class Portstat(object):
246251
return STATUS_NA
247252

248253

249-
def cnstat_print(self, cnstat_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, rates_only, detail=False):
254+
def cnstat_print(self, cnstat_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, fec_stats_only, rates_only, detail=False):
250255
"""
251256
Print the cnstat.
252257
"""
@@ -291,6 +296,12 @@ class Portstat(object):
291296
format_number_with_comma(data.tx_err),
292297
format_number_with_comma(data.tx_drop),
293298
format_number_with_comma(data.tx_ovr)))
299+
elif fec_stats_only:
300+
header = header_fec_only
301+
table.append((key, self.get_port_state(key),
302+
format_number_with_comma(data.fec_corr),
303+
format_number_with_comma(data.fec_uncorr),
304+
format_number_with_comma(data.fec_symbol_err)))
294305
elif rates_only:
295306
header = header_rates_only
296307
table.append((key, self.get_port_state(key),
@@ -384,7 +395,10 @@ class Portstat(object):
384395
print("Time Since Counters Last Cleared............... " + str(cnstat_old_dict.get('time')))
385396

386397

387-
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):
398+
def cnstat_diff_print(self, cnstat_new_dict, cnstat_old_dict,
399+
ratestat_dict, intf_list, use_json,
400+
print_all, errors_only, fec_stats_only,
401+
rates_only, detail=False):
388402
"""
389403
Print the difference between two cnstat results.
390404
"""
@@ -461,6 +475,19 @@ class Portstat(object):
461475
format_number_with_comma(cntr.tx_err),
462476
format_number_with_comma(cntr.tx_drop),
463477
format_number_with_comma(cntr.tx_ovr)))
478+
elif fec_stats_only:
479+
header = header_fec_only
480+
if old_cntr is not None:
481+
table.append((key, self.get_port_state(key),
482+
ns_diff(cntr.fec_corr, old_cntr.fec_corr),
483+
ns_diff(cntr.fec_uncorr, old_cntr.fec_uncorr),
484+
ns_diff(cntr.fec_symbol_err, old_cntr.fec_symbol_err)))
485+
else:
486+
table.append((key, self.get_port_state(key),
487+
format_number_with_comma(cntr.fec_corr),
488+
format_number_with_comma(cntr.fec_uncorr),
489+
format_number_with_comma(cntr.fec_symbol_err)))
490+
464491
elif rates_only:
465492
header = header_rates_only
466493
if old_cntr is not None:
@@ -547,6 +574,7 @@ Examples:
547574
parser.add_argument('-d', '--delete', action='store_true', help='Delete saved stats, either the uid or the specified tag')
548575
parser.add_argument('-D', '--delete-all', action='store_true', help='Delete all saved stats')
549576
parser.add_argument('-e', '--errors', action='store_true', help='Display interface errors')
577+
parser.add_argument('-f', '--fec-stats', action='store_true', help='Display FEC related statistics')
550578
parser.add_argument('-j', '--json', action='store_true', help='Display in JSON format')
551579
parser.add_argument('-r', '--raw', action='store_true', help='Raw stats (unmodified output of netstat)')
552580
parser.add_argument('-R', '--rate', action='store_true', help='Display interface rates')
@@ -563,6 +591,7 @@ Examples:
563591
delete_saved_stats = args.delete
564592
delete_all_stats = args.delete_all
565593
errors_only = args.errors
594+
fec_stats_only = args.fec_stats
566595
rates_only = args.rate
567596
use_json = args.json
568597
raw_stats = args.raw
@@ -619,7 +648,7 @@ Examples:
619648

620649
# Now decide what information to display
621650
if raw_stats:
622-
portstat.cnstat_print(cnstat_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, rates_only)
651+
portstat.cnstat_print(cnstat_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, fec_stats_only, rates_only)
623652
sys.exit(0)
624653

625654
# At this point, either we'll create a file or open an existing one.
@@ -647,21 +676,21 @@ Examples:
647676
cnstat_cached_dict = pickle.load(open(cnstat_fqn_file, 'rb'))
648677
if not detail:
649678
print("Last cached time was " + str(cnstat_cached_dict.get('time')))
650-
portstat.cnstat_diff_print(cnstat_dict, cnstat_cached_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, rates_only, detail)
679+
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)
651680
except IOError as e:
652681
print(e.errno, e)
653682
else:
654683
if tag_name:
655684
print("\nFile '%s' does not exist" % cnstat_fqn_file)
656685
print("Did you run 'portstat -c -t %s' to record the counters via tag %s?\n" % (tag_name, tag_name))
657686
else:
658-
portstat.cnstat_print(cnstat_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, rates_only, detail)
687+
portstat.cnstat_print(cnstat_dict, ratestat_dict, intf_list, use_json, print_all, errors_only, fec_stats_only, rates_only, detail)
659688
else:
660689
#wait for the specified time and then gather the new stats and output the difference.
661690
time.sleep(wait_time_in_seconds)
662691
print("The rates are calculated within %s seconds period" % wait_time_in_seconds)
663692
cnstat_new_dict, ratestat_new_dict = portstat.get_cnstat_dict()
664-
portstat.cnstat_diff_print(cnstat_new_dict, cnstat_dict, ratestat_new_dict, intf_list, use_json, print_all, errors_only, rates_only, detail)
693+
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)
665694

666695
if __name__ == "__main__":
667696
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
@@ -711,7 +711,10 @@
711711
"SAI_PORT_STAT_ETHER_OUT_PKTS_9217_TO_16383_OCTETS": "0",
712712
"SAI_PORT_STAT_ETHER_STATS_FRAGMENTS": "0",
713713
"SAI_PORT_STAT_ETHER_STATS_UNDERSIZE_PKTS": "0",
714-
"SAI_PORT_STAT_ETHER_STATS_JABBERS": "0"
714+
"SAI_PORT_STAT_ETHER_STATS_JABBERS": "0",
715+
"SAI_PORT_STAT_IF_IN_FEC_CORRECTABLE_FRAMES": "130402",
716+
"SAI_PORT_STAT_IF_IN_FEC_NOT_CORRECTABLE_FRAMES": "3",
717+
"SAI_PORT_STAT_IF_IN_FEC_SYMBOL_ERRORS": "4"
715718
},
716719
"COUNTERS:oid:0x1000000000013": {
717720
"SAI_PORT_STAT_IF_IN_UCAST_PKTS": "4",
@@ -768,7 +771,10 @@
768771
"SAI_PORT_STAT_ETHER_OUT_PKTS_9217_TO_16383_OCTETS": "0",
769772
"SAI_PORT_STAT_ETHER_STATS_FRAGMENTS": "0",
770773
"SAI_PORT_STAT_ETHER_STATS_UNDERSIZE_PKTS": "0",
771-
"SAI_PORT_STAT_ETHER_STATS_JABBERS": "0"
774+
"SAI_PORT_STAT_ETHER_STATS_JABBERS": "0",
775+
"SAI_PORT_STAT_IF_IN_FEC_CORRECTABLE_FRAMES": "110412",
776+
"SAI_PORT_STAT_IF_IN_FEC_NOT_CORRECTABLE_FRAMES": "1",
777+
"SAI_PORT_STAT_IF_IN_FEC_SYMBOL_ERRORS": "0"
772778
},
773779
"COUNTERS:oid:0x1000000000014": {
774780
"SAI_PORT_STAT_IF_IN_UCAST_PKTS": "6",
@@ -825,7 +831,10 @@
825831
"SAI_PORT_STAT_ETHER_OUT_PKTS_9217_TO_16383_OCTETS": "0",
826832
"SAI_PORT_STAT_ETHER_STATS_FRAGMENTS": "0",
827833
"SAI_PORT_STAT_ETHER_STATS_UNDERSIZE_PKTS": "0",
828-
"SAI_PORT_STAT_ETHER_STATS_JABBERS": "0"
834+
"SAI_PORT_STAT_ETHER_STATS_JABBERS": "0",
835+
"SAI_PORT_STAT_IF_IN_FEC_CORRECTABLE_FRAMES": "100317",
836+
"SAI_PORT_STAT_IF_IN_FEC_NOT_CORRECTABLE_FRAMES": "0",
837+
"SAI_PORT_STAT_IF_IN_FEC_SYMBOL_ERRORS": "0"
829838
},
830839
"COUNTERS:oid:0x21000000000000": {
831840
"SAI_SWITCH_STAT_OUT_DROP_REASON_RANGE_BASE": "1000",

tests/portstat_test.py

+50
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,23 @@
3333
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
3434
"""
3535

36+
intf_fec_counters = """\
37+
IFACE STATE FEC_CORR FEC_UNCORR FEC_SYMBOL_ERR
38+
--------- ------- ---------- ------------ ----------------
39+
Ethernet0 D 130,402 3 4
40+
Ethernet4 N/A 110,412 1 0
41+
Ethernet8 N/A 100,317 0 0
42+
"""
43+
44+
intf_fec_counters_period = """\
45+
The rates are calculated within 3 seconds period
46+
IFACE STATE FEC_CORR FEC_UNCORR FEC_SYMBOL_ERR
47+
--------- ------- ---------- ------------ ----------------
48+
Ethernet0 D 0 0 0
49+
Ethernet4 N/A 0 0 0
50+
Ethernet8 N/A 0 0 0
51+
"""
52+
3653
intf_counters_period = """\
3754
The rates are calculated within 3 seconds period
3855
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)