diff --git a/doc/Command-Reference.md b/doc/Command-Reference.md index f60e29eaa..e4c88d204 100644 --- a/doc/Command-Reference.md +++ b/doc/Command-Reference.md @@ -5164,6 +5164,75 @@ This command displays basic information about the gearbox phys configured on the ``` +**show gearbox interfaces fec-stats** + +This command displays FEC statistics for gearbox interfaces from the GB_COUNTERS_DB. Statistics are shown separately for each port's system side and line side. An optional port name argument filters output to a single port. + +- Usage: + ``` + show gearbox interfaces fec-stats [] + ``` + +- Example (all ports): + + ``` + /home/admin# show gearbox interfaces fec-stats + GB IFACE STATE FEC_CORR FEC_UNCORR FEC_SYMBOL_ERR FEC_PRE_BER FEC_POST_BER FEC_PRE_BER_MAX FEC_MAX_T + --------------- ------- ---------- ------------ ---------------- ------------- -------------- ----------------- ----------- + Ethernet0 Line U 0 0 0 6.05e-10 0.00e+00 N/A 3 + Ethernet0 System U 0 0 0 6.05e-10 0.00e+00 N/A 2 + Ethernet4 Line U 0 0 0 6.05e-10 0.00e+00 N/A 1 + Ethernet4 System U 0 0 0 6.05e-10 0.00e+00 N/A 2 + + ``` + +- Example (single port): + + ``` + /home/admin# show gearbox interfaces fec-stats Ethernet0 + GB IFACE STATE FEC_CORR FEC_UNCORR FEC_SYMBOL_ERR FEC_PRE_BER FEC_POST_BER FEC_PRE_BER_MAX FEC_MAX_T + --------------- ------- ---------- ------------ ---------------- ------------- -------------- ----------------- ----------- + Ethernet0 Line U 0 0 0 6.05e-10 0.00e+00 N/A -1 + Ethernet0 System U 0 0 0 6.05e-10 0.00e+00 N/A -1 + + ``` + + STATE legend: U = Up, D = Down, N/A = Unknown + +**show gearbox interfaces fec-histogram** + +This command displays the FEC codeword error histogram for gearbox interfaces. Histogram bins (BIN0–BIN15) count codewords with a given number of symbol errors. An optional port name argument filters output to a single port. + +- Usage: + ``` + show gearbox interfaces fec-histogram [] + ``` + +- Example (single port): + + ``` + /home/admin# show gearbox interfaces fec-histogram Ethernet0 + + Ethernet0 Line + Symbol Errors Per Codeword Codewords + -------------------------- ----------- + BIN0 12345 + BIN1 1 + BIN2 0 + ... + BIN15 0 + + Ethernet0 System + Symbol Errors Per Codeword Codewords + -------------------------- ----------- + BIN0 12355 + BIN1 0 + BIN2 0 + ... + BIN15 0 + + ``` + Go Back To [Beginning of the document](#) or [Beginning of this section](#gearbox) diff --git a/scripts/gearboxutil b/scripts/gearboxutil index 70b33da85..8e213455b 100755 --- a/scripts/gearboxutil +++ b/scripts/gearboxutil @@ -38,6 +38,8 @@ PHY_LINE_LANES = "line_lanes" PHY_SYSTEM_LANES = "system_lanes" PORT_OPER_STATUS = "oper_status" +PORT_SYSTEM_OPER_STATUS = "system_oper_status" +PORT_LINE_OPER_STATUS = "line_oper_status" PORT_ADMIN_STATUS = "admin_status" PORT_SYSTEM_SPEED = "system_speed" PORT_LINE_SPEED = "line_speed" @@ -99,6 +101,40 @@ def appl_db_interface_keys_get(appl_db): """ return appl_db.keys(appl_db.APPL_DB, GEARBOX_TABLE_INTERFACE_PREFIX.format("*")) +def format_ber(value): + """ + Format BER value to scientific notation with 2 decimal places + Args: + value: String or numeric BER value + Returns: + Formatted string like "6.05e-10" or "N/A" + """ + if value == 'N/A' or value == '-1': + return value + + try: + num_value = float(value) + if num_value == 0: + return "0.00e+00" + # Format with 2 decimal places in scientific notation + return f"{num_value:.2e}" + except (ValueError, TypeError): + return value + +def format_number_with_commas(value): + """ + Format numeric value with comma separators + Args: + value: String or numeric value + Returns: + Formatted string like "219,033,232,324" + """ + try: + num_value = int(value) + return f"{num_value:,}" + except (ValueError, TypeError): + return value + # ========================== phy-status logic ========================== phy_header_status = ['PHY Id', 'Name', 'Firmware'] @@ -195,31 +231,270 @@ class InterfaceStatus(object): self.display_intf_status(appl_db_keys) +# ========================== interfaces-fec logic ========================== + +COUNTERS_PORT_NAME_MAP = "COUNTERS_PORT_NAME_MAP" +COUNTER_TABLE_PREFIX = "COUNTERS:" +RATES_TABLE_PREFIX = "RATES:" + +# FEC Histogram bins (S0-S15) +FEC_HISTOGRAM_BINS = [f"SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S{i}" for i in range(16)] + +class InterfaceFecStats(object): + + def __init__(self, port_name=None, display_type='stats'): + """ + Initialize and display FEC information + Args: + port_name: Optional port name to filter (e.g., "Ethernet0") + If None, display for all ports + display_type: 'histogram' or 'stats' + """ + self.port_name = port_name + self.display_type = display_type + self.gb_db = SonicV2Connector(use_unix_socket_path=False) + self.appl_db = SonicV2Connector(use_unix_socket_path=False) + + if self.gb_db is None or self.appl_db is None: + print("Error: Failed to connect to database") + return + + self.gb_db.connect("GB_COUNTERS_DB") + self.appl_db.connect(self.appl_db.APPL_DB) + + if display_type == 'histogram': + self.display_fec_histogram() + elif display_type == 'stats': + self.display_fec_stats() + + def get_port_name_map(self): + """ + Get the port name to OID mapping from GB_COUNTERS_DB + """ + port_name_map = self.gb_db.get_all('GB_COUNTERS_DB', COUNTERS_PORT_NAME_MAP) + return port_name_map if port_name_map else {} + + def get_counters_for_oid(self, oid): + """ + Get all counters for a specific OID + """ + full_table_id = COUNTER_TABLE_PREFIX + oid + counters = self.gb_db.get_all('GB_COUNTERS_DB', full_table_id) + return counters if counters else {} + + def get_rates_for_oid(self, oid): + """ + Get all rates for a specific OID + """ + full_table_id = RATES_TABLE_PREFIX + oid + rates = self.gb_db.get_all('GB_COUNTERS_DB', full_table_id) + return rates if rates else {} + + def get_port_state(self, base_port_name, side): + """ + Get port operational state from APPL_DB + """ + port_key = PORT_TABLE_ETHERNET_PREFIX.format(base_port_name) + attr = PORT_SYSTEM_OPER_STATUS if side == 'System' else PORT_LINE_OPER_STATUS + oper_status = self.appl_db.get(self.appl_db.APPL_DB, port_key, attr) + if oper_status and oper_status.lower() == 'up': + return 'U' + elif oper_status and oper_status.lower() == 'down': + return 'D' + else: + return 'N/A' + + def get_matching_ports(self): + """ + Get ports to display based on port_name filter + Returns dict of {port_name: oid} + """ + port_name_map = self.get_port_name_map() + + if not port_name_map: + print("Error: No port name mapping found in GB_COUNTERS_DB") + return None + + # If specific port requested, filter to that port (both line and system) + if self.port_name: + matching_ports = {} + for full_port_name, oid in port_name_map.items(): + if full_port_name.startswith(f"{self.port_name}_") and (full_port_name.endswith("_line") or full_port_name.endswith("_system")): + matching_ports[full_port_name] = oid + + if not matching_ports: + print(f"Error: Port '{self.port_name}' not found in GB_COUNTERS_DB") + base_ports = set() + for port in port_name_map.keys(): + if port.endswith('_line'): + base_ports.add(port[:-5]) + elif port.endswith('_system'): + base_ports.add(port[:-7]) + print(f"Available ports: {', '.join(natsorted(base_ports))}") + return None + return matching_ports + else: + return port_name_map + + def extract_port_info(self, port_name): + """ + Extract base name and side from port name + Returns tuple: (base_name, side) + """ + if port_name.endswith('_line'): + return port_name[:-5], 'Line' + elif port_name.endswith('_system'): + return port_name[:-7], 'System' + else: + return port_name, '' + + def display_histogram_for_port(self, counters, port_name): + """ + Display FEC codeword error histogram for a port + """ + base_name, side = self.extract_port_info(port_name) + + print(f"\n{base_name} {side}") + + # Display histogram bins + histogram_data = [] + for i, bin_name in enumerate(FEC_HISTOGRAM_BINS): + value = counters.get(bin_name, '0') + histogram_data.append([f'BIN{i}', value]) + + if histogram_data: + print(tabulate(histogram_data, headers=['Symbol Errors Per Codeword', 'Codewords'], tablefmt='simple')) + + def display_fec_histogram(self): + """ + Display FEC histogram from GB_COUNTERS_DB + """ + try: + ports_to_display = self.get_matching_ports() + if ports_to_display is None: + return + + # Display histogram for each port + for port_name in natsorted(ports_to_display.keys()): + oid = ports_to_display[port_name] + counters = self.get_counters_for_oid(oid) + + if counters: + self.display_histogram_for_port(counters, port_name) + else: + print(f"\n{port_name}") + print("No counter data available") + + except Exception as e: + print(f"Error displaying FEC histogram: {e}") + + def display_fec_stats(self): + """ + Display FEC stats from GB_COUNTERS_DB + """ + try: + ports_to_display = self.get_matching_ports() + if ports_to_display is None: + return + + # Collect stats for each port + table_data = [] + for port_name in natsorted(ports_to_display.keys()): + oid = ports_to_display[port_name] + counters = self.get_counters_for_oid(oid) + rates = self.get_rates_for_oid(oid) + + base_name, side = self.extract_port_info(port_name) + + iface = f"{base_name} {side}" + state = self.get_port_state(base_name, side) + + # Get counter values and format with commas + fec_corr = format_number_with_commas(counters.get('SAI_PORT_STAT_IF_IN_FEC_CORRECTABLE_FRAMES', '0')) + fec_uncorr = format_number_with_commas(counters.get('SAI_PORT_STAT_IF_IN_FEC_NOT_CORRECTABLE_FRAMES', '0')) + fec_symbol_err = format_number_with_commas(counters.get('SAI_PORT_STAT_IF_IN_FEC_SYMBOL_ERRORS', '0')) + + # Format BER values with 2 decimal places + fec_pre_ber = format_ber(rates.get('FEC_PRE_BER', '0')) + fec_post_ber = format_ber(rates.get('FEC_POST_BER', '0')) + fec_pre_ber_max = format_ber(rates.get('FEC_PRE_BER_MAX', 'N/A')) + fec_max_t = rates.get('FEC_MAX_T', '-1') + + table_data.append([ + iface, + state, + fec_corr, + fec_uncorr, + fec_symbol_err, + fec_pre_ber, + fec_post_ber, + fec_pre_ber_max, + fec_max_t + ]) + + # Display table with proper alignment + headers = ['GB IFACE', 'STATE', 'FEC_CORR', 'FEC_UNCORR', 'FEC_SYMBOL_ERR', + 'FEC_PRE_BER', 'FEC_POST_BER', 'FEC_PRE_BER_MAX', 'FEC_MAX_T'] + + # Use 'simple' format with mixed alignment: left for GB IFACE, right for others + # colalign specifies alignment per column + print(tabulate(table_data, headers=headers, tablefmt='simple', + colalign=('left', 'right', 'right', 'right', 'right', 'right', 'right', 'right', 'right'))) + + except Exception as e: + print(f"Error displaying FEC stats: {e}") + import traceback + traceback.print_exc() + def main(args): """ - phy status - interfaces status - interfaces counters + Usage: + gearboxutil phys status + gearboxutil interfaces status + gearboxutil interfaces fec-histogram [] + gearboxutil interfaces fec-stats [] + + Examples: + gearboxutil interfaces fec-histogram # Show histogram for all ports + gearboxutil interfaces fec-histogram Ethernet0 # Show histogram for Ethernet0 (line and system) + gearboxutil interfaces fec-stats # Show FEC stats for all ports + gearboxutil interfaces fec-stats Ethernet0 # Show FEC stats for Ethernet0 (line and system) """ if len(args) == 0: print("No valid arguments provided") + print(main.__doc__) return cmd1 = args[0] if cmd1 != "phys" and cmd1 != "interfaces": print("No valid command provided") + print(main.__doc__) + return + + if len(args) < 2: + print("No valid command provided") + print(main.__doc__) return cmd2 = args[1] - if cmd2 != "status" and cmd2 != "counters": + if cmd2 not in ["status", "fec-histogram", "fec-stats"]: print("No valid command provided") + print(main.__doc__) return if cmd1 == "phys" and cmd2 == "status": PhyStatus() elif cmd1 == "interfaces" and cmd2 == "status": InterfaceStatus() + elif cmd1 == "interfaces" and cmd2 == "fec-histogram": + # Optional port name parameter + port_name = args[2] if len(args) > 2 else None + InterfaceFecStats(port_name, display_type='histogram') + elif cmd1 == "interfaces" and cmd2 == "fec-stats": + # Optional port name parameter + port_name = args[2] if len(args) > 2 else None + InterfaceFecStats(port_name, display_type='stats') sys.exit(0) diff --git a/scripts/generate_dump b/scripts/generate_dump index 19e768061..ba47dc0d8 100755 --- a/scripts/generate_dump +++ b/scripts/generate_dump @@ -2246,6 +2246,16 @@ save_container_files() { done } +############################################################################### +# Save gearbox data +############################################################################### +save_gearbox_data() { + save_cmd "gearboxutil phys status" "gearbox.phys_status" & + save_cmd "gearboxutil interfaces status" "gearbox.interfaces_status" & + save_cmd "gearboxutil interfaces fec-stats" "gearbox.interfaces_fec_stats" & + save_cmd "gearboxutil interfaces fec-histogram" "gearbox.interfaces_fec_histogram" & +} + ############################################################################### # Main generate_dump routine # Globals: @@ -2356,6 +2366,7 @@ main() { save_cmd "show interface transceiver eeprom --dom" "interface.xcvrs.eeprom" & save_cmd "show ip interface -d all" "ip.interface" & save_cmd "sfputil show eeprom-hexdump" "interface.xcvrs.eeprom.raw" & + save_gearbox_data & wait save_cmd "lldpctl" "lldpctl" & diff --git a/sfputil/main.py b/sfputil/main.py index 94183c830..ae4b11940 100644 --- a/sfputil/main.py +++ b/sfputil/main.py @@ -336,6 +336,10 @@ def format_dict_value_to_string(sorted_key_table, def convert_sfp_info_to_output_string(sfp_info_dict): indent = ' ' * 8 output = '' + # Gracefully handle missing/invalid info dicts + if sfp_info_dict is None or not isinstance(sfp_info_dict, dict): + output += '{}EEPROM info: N/A\n'.format(indent) + return output is_sfp_cmis = is_transceiver_cmis(sfp_info_dict) if is_sfp_cmis: # Use the utility function with the local QSFP_DD_DATA_MAP for CMIS transceivers diff --git a/show/gearbox.py b/show/gearbox.py index cbb16302b..91762c187 100644 --- a/show/gearbox.py +++ b/show/gearbox.py @@ -32,3 +32,27 @@ def interfaces(): def status(ctx): """Show gearbox interfaces status""" clicommon.run_command(['gearboxutil', 'interfaces', 'status']) + + +# 'fec-stats' subcommand ("show gearbox interfaces fec-stats") +@interfaces.command('fec-stats') +@click.argument('port_name', required=False) +@click.pass_context +def fec_stats(ctx, port_name): + """Show gearbox interfaces FEC statistics""" + cmd = ['gearboxutil', 'interfaces', 'fec-stats'] + if port_name: + cmd.append(port_name) + clicommon.run_command(cmd) + + +# 'fec-histogram' subcommand ("show gearbox interfaces fec-histogram") +@interfaces.command('fec-histogram') +@click.argument('port_name', required=False) +@click.pass_context +def fec_histogram(ctx, port_name): + """Show gearbox interfaces FEC codeword error histogram""" + cmd = ['gearboxutil', 'interfaces', 'fec-histogram'] + if port_name: + cmd.append(port_name) + clicommon.run_command(cmd) diff --git a/tests/gearbox_test.py b/tests/gearbox_test.py index 658997704..777819dac 100644 --- a/tests/gearbox_test.py +++ b/tests/gearbox_test.py @@ -1,5 +1,9 @@ import sys import os +import importlib.machinery +import importlib.util +from contextlib import redirect_stdout +from io import StringIO from click.testing import CliRunner from unittest import TestCase @@ -13,6 +17,14 @@ import show.main as show +# Load gearboxutil script as a module for direct function testing +# spec_from_file_location returns None for extensionless scripts; use SourceFileLoader explicitly +_gearboxutil_loader = importlib.machinery.SourceFileLoader( + "gearboxutil", os.path.join(scripts_path, "gearboxutil")) +_gearboxutil_spec = importlib.util.spec_from_loader("gearboxutil", _gearboxutil_loader) +gearboxutil = importlib.util.module_from_spec(_gearboxutil_spec) +_gearboxutil_loader.exec_module(gearboxutil) + class TestGearbox(TestCase): @classmethod def setup_class(cls): @@ -43,9 +55,115 @@ def test_gearbox_interfaces_status_validation(self): " 1 Ethernet200 200,201,202,203 25G 300,301,302,303 25G 304,305 50G down up" ) self.assertEqual(result.output.strip(), expected_output) - + + def test_gearbox_interfaces_fec_stats(self): + result = self.runner.invoke( + show.cli.commands["gearbox"].commands["interfaces"].commands["fec-stats"], ["Ethernet0"]) + print(result.output, file=sys.stderr) + self.assertEqual(result.exit_code, 0) + self.assertIn("FEC_CORR", result.output) + self.assertIn("Ethernet0 Line", result.output) + self.assertIn("Ethernet0 System", result.output) + + def test_gearbox_interfaces_fec_histogram(self): + result = self.runner.invoke( + show.cli.commands["gearbox"].commands["interfaces"].commands["fec-histogram"], ["Ethernet0"]) + print(result.output, file=sys.stderr) + self.assertEqual(result.exit_code, 0) + self.assertIn("Ethernet0", result.output) + self.assertIn("BIN0", result.output) + self.assertIn("BIN15", result.output) + @classmethod def teardown_class(cls): print("TEARDOWN") os.environ["PATH"] = os.pathsep.join(os.environ["PATH"].split(os.pathsep)[:-1]) os.environ["UTILITIES_UNIT_TESTING"] = "0" + + +class TestFormatBer(TestCase): + def test_normal_value(self): + self.assertEqual(gearboxutil.format_ber("6.05e-10"), "6.05e-10") + + def test_small_nonzero_value(self): + self.assertEqual(gearboxutil.format_ber("0.000000000605"), "6.05e-10") + + def test_zero(self): + self.assertEqual(gearboxutil.format_ber("0"), "0.00e+00") + + def test_na_passthrough(self): + self.assertEqual(gearboxutil.format_ber("N/A"), "N/A") + + def test_minus_one_passthrough(self): + self.assertEqual(gearboxutil.format_ber("-1"), "-1") + + def test_non_numeric_passthrough(self): + self.assertEqual(gearboxutil.format_ber("invalid"), "invalid") + + +class TestInterfaceFecStats(TestCase): + @classmethod + def setup_class(cls): + os.environ["UTILITIES_UNIT_TESTING"] = "1" + dbconnector.load_database_config() + + def _capture(self, port_name=None, display_type='stats'): + buf = StringIO() + with redirect_stdout(buf): + gearboxutil.InterfaceFecStats(port_name=port_name, display_type=display_type) + return buf.getvalue() + + def test_fec_stats_all_ports_headers(self): + output = self._capture(display_type='stats') + for col in ['GB IFACE', 'STATE', 'FEC_CORR', 'FEC_UNCORR', 'FEC_SYMBOL_ERR', + 'FEC_PRE_BER', 'FEC_POST_BER', 'FEC_PRE_BER_MAX', 'FEC_MAX_T']: + self.assertIn(col, output) + + def test_fec_stats_all_ports_contains_ethernet0(self): + output = self._capture(display_type='stats') + self.assertIn("Ethernet0", output) + + def test_fec_stats_formatted_ber(self): + output = self._capture(display_type='stats') + self.assertIn("6.05e-10", output) + + def test_fec_stats_specific_port(self): + output = self._capture(port_name="Ethernet0", display_type='stats') + self.assertIn("Ethernet0", output) + self.assertIn("FEC_CORR", output) + + def test_fec_stats_invalid_port(self): + output = self._capture(port_name="EthernetInvalid", display_type='stats') + self.assertIn("Error", output) + self.assertIn("EthernetInvalid", output) + + def test_fec_stats_state_uses_system_and_line_oper_status(self): + # Mock has system_oper_status=up -> 'U' and line_oper_status=down -> 'D' + # Verifies get_port_state reads the correct per-side field, not generic oper_status + output = self._capture(port_name="Ethernet0", display_type='stats') + lines = [row for row in output.splitlines() if "Ethernet0" in row] + system_line = next((row for row in lines if "System" in row), None) + line_line = next((row for row in lines if "Line" in row), None) + self.assertIsNotNone(system_line, "Expected a System row in fec-stats output") + self.assertIsNotNone(line_line, "Expected a Line row in fec-stats output") + self.assertIn("U", system_line, "System side should show U (system_oper_status=up)") + self.assertIn("D", line_line, "Line side should show D (line_oper_status=down)") + + def test_fec_histogram_all_ports_bins(self): + output = self._capture(display_type='histogram') + self.assertIn("Ethernet0", output) + for i in range(16): + self.assertIn(f"BIN{i}", output) + + def test_fec_histogram_specific_port(self): + output = self._capture(port_name="Ethernet0", display_type='histogram') + self.assertIn("Ethernet0", output) + self.assertIn("BIN0", output) + + def test_fec_histogram_invalid_port(self): + output = self._capture(port_name="EthernetInvalid", display_type='histogram') + self.assertIn("Error", output) + + @classmethod + def teardown_class(cls): + os.environ["UTILITIES_UNIT_TESTING"] = "0" diff --git a/tests/mock_tables/appl_db.json b/tests/mock_tables/appl_db.json index 06a64ff9b..698910c2c 100644 --- a/tests/mock_tables/appl_db.json +++ b/tests/mock_tables/appl_db.json @@ -35,6 +35,8 @@ "description": "ARISTA01T2:Ethernet1", "speed": "25000", "oper_status": "down", + "system_oper_status": "up", + "line_oper_status": "down", "pfc_asym": "off", "mtu": "9100", "tpid": "0x9200", diff --git a/tests/mock_tables/database_config.json b/tests/mock_tables/database_config.json index 9d6125fc7..1e11ceb9c 100644 --- a/tests/mock_tables/database_config.json +++ b/tests/mock_tables/database_config.json @@ -67,6 +67,11 @@ "separator": "|", "instance" : "redis" }, + "GB_COUNTERS_DB" : { + "id" : 8, + "separator": ":", + "instance" : "redis" + }, "BMP_STATE_DB" : { "id" : 20, "separator": "|", diff --git a/tests/mock_tables/gb_counters_db.json b/tests/mock_tables/gb_counters_db.json new file mode 100644 index 000000000..de015f7fb --- /dev/null +++ b/tests/mock_tables/gb_counters_db.json @@ -0,0 +1,60 @@ +{ + "COUNTERS_PORT_NAME_MAP": { + "Ethernet0_system": "oid:0x1000000000001", + "Ethernet0_line": "oid:0x1000000000002" + }, + "COUNTERS:oid:0x1000000000001": { + "SAI_PORT_STAT_IF_IN_FEC_CORRECTABLE_FRAMES": "1234567", + "SAI_PORT_STAT_IF_IN_FEC_NOT_CORRECTABLE_FRAMES": "3", + "SAI_PORT_STAT_IF_IN_FEC_SYMBOL_ERRORS": "1024", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S0": "26751321841084", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S1": "5678", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S2": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S3": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S4": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S5": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S6": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S7": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S8": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S9": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S10": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S11": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S12": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S13": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S14": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S15": "0" + }, + "COUNTERS:oid:0x1000000000002": { + "SAI_PORT_STAT_IF_IN_FEC_CORRECTABLE_FRAMES": "0", + "SAI_PORT_STAT_IF_IN_FEC_NOT_CORRECTABLE_FRAMES": "0", + "SAI_PORT_STAT_IF_IN_FEC_SYMBOL_ERRORS": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S0": "26751321841084", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S1": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S2": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S3": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S4": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S5": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S6": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S7": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S8": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S9": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S10": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S11": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S12": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S13": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S14": "0", + "SAI_PORT_STAT_IF_IN_FEC_CODEWORD_ERRORS_S15": "0" + }, + "RATES:oid:0x1000000000001": { + "FEC_PRE_BER": "6.05e-10", + "FEC_POST_BER": "1.23e-15", + "FEC_PRE_BER_MAX": "9.99e-08", + "FEC_MAX_T": "42" + }, + "RATES:oid:0x1000000000002": { + "FEC_PRE_BER": "0", + "FEC_POST_BER": "0", + "FEC_PRE_BER_MAX": "N/A", + "FEC_MAX_T": "0" + } +} diff --git a/tests/sfputil_test.py b/tests/sfputil_test.py index e6f347f5f..c8c0b9dd4 100644 --- a/tests/sfputil_test.py +++ b/tests/sfputil_test.py @@ -2007,3 +2007,8 @@ def test_set_output_cli( ]) def test_get_subport_lane_mask(self, subport, lane_count, expected_mask): assert sfputil_debug.get_subport_lane_mask(subport, lane_count) == expected_mask + + def test_convert_sfp_info_to_output_string_none(self): + import sfputil.main as sfputil + output = sfputil.convert_sfp_info_to_output_string(None) + assert output == " EEPROM info: N/A\n"