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

Add Transceiver PM basic CLI support to show output from TRANSCEIVER_PM table for ZR #2615

Merged
merged 5 commits into from
Feb 1, 2023
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
26 changes: 25 additions & 1 deletion doc/Command-Reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -926,7 +926,7 @@ This command displays information for all the interfaces for the transceiver req

- Usage:
```
show interfaces transceiver (eeprom [-d|--dom] | lpmode | presence | error-status [-hw|--fetch-from-hardware]) [<interface_name>]
show interfaces transceiver (eeprom [-d|--dom] | lpmode | presence | error-status [-hw|--fetch-from-hardware] | pm) [<interface_name>]
```

- Example (Decode and display information stored on the EEPROM of SFP transceiver connected to Ethernet0):
Expand Down Expand Up @@ -989,6 +989,30 @@ This command displays information for all the interfaces for the transceiver req
Ethernet100 OK
```

- Example (Display performance monitoring info of SFP transceiver connected to Ethernet100):
```
admin@sonic:~$ show interfaces transceiver pm Ethernet100
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great if the output can indicate the time stamp of when the time bin starts and when it ends.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea, will make sure the corresponding timestamps are displayed when the time window/bin based PM is in place.

Ethernet100:
Parameter Unit Min Avg Max Threshold Threshold Threshold Threshold Threshold Threshold
High High Crossing Low Low Crossing
Alarm Warning Alert-High Alarm Warning Alert-Low
--------------- ------ -------- -------- -------- ----------- ----------- ------------ ----------- ----------- -----------
Tx Power dBm -8.22 -8.23 -8.24 -5.0 -6.0 False -16.99 -16.003 False
Rx Total Power dBm -10.61 -10.62 -10.62 2.0 0.0 False -21.0 -18.0 False
Rx Signal Power dBm -40.0 0.0 40.0 13.0 10.0 True -18.0 -15.0 True
CD-short link ps/nm 0.0 0.0 0.0 1000.0 500.0 False -1000.0 -500.0 False
PDL dB 0.5 0.6 0.6 4.0 4.0 False 0.0 0.0 False
OSNR dB 36.5 36.5 36.5 99.0 99.0 False 0.0 0.0 False
eSNR dB 30.5 30.5 30.5 99.0 99.0 False 0.0 0.0 False
CFO MHz 54.0 70.0 121.0 3800.0 3800.0 False -3800.0 -3800.0 False
DGD ps 5.37 5.56 5.81 7.0 7.0 False 0.0 0.0 False
SOPMD ps^2 0.0 0.0 0.0 655.35 655.35 False 0.0 0.0 False
SOP ROC krad/s 1.0 1.0 2.0 N/A N/A N/A N/A N/A N/A
Pre-FEC BER N/A 4.58E-04 4.66E-04 5.76E-04 1.25E-02 1.10E-02 0.0 0.0 0.0 0.0
Post-FEC BER N/A 0.0 0.0 0.0 1000.0 1.0 False 0.0 0.0 False
EVM % 100.0 100.0 100.0 N/A N/A N/A N/A N/A N/A
```

Go Back To [Beginning of the document](#) or [Beginning of this section](#basic-show-commands)

## AAA & TACACS+
Expand Down
129 changes: 129 additions & 0 deletions scripts/sfpshow
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,36 @@ QSFP_DD_DOM_VALUE_UNIT_MAP = {
'voltage': 'Volts'
}

ZR_PM_HEADER = ['Parameter', 'Unit', 'Min', 'Avg', 'Max',
'Threshold\nHigh\nAlarm', 'Threshold\nHigh\nWarning',
'Threshold\nCrossing\nAlert-High',
'Threshold\nLow\nAlarm', 'Threshold\nLow\nWarning',
'Threshold\nCrossing\nAlert-Low']

ZR_PM_VALUE_KEY_SUFFIXS = ['min', 'avg', 'max']

ZR_PM_THRESHOLD_KEY_SUFFIXS = ['highalarm',
'highwarning', 'lowalarm', 'lowwarning']

# mapping from parameter_name to [unit, parameter_key_prefix]
ZR_PM_INFO_MAP = {
'Tx Power': ['dBm', 'tx_power'],
'Rx Total Power': ['dBm', 'rx_tot_power'],
'Rx Signal Power': ['dBm', 'rx_sig_power'],
'CD-short link': ['ps/nm', 'cd'],
'PDL': ['dB', 'pdl'],
'OSNR': ['dB', 'osnr'],
'eSNR': ['dB', 'esnr'],
'CFO': ['MHz', 'cfo'],
'DGD': ['ps', 'dgd'],
'SOPMD': ['ps^2', 'sopmd'],
'SOP ROC': ['krad/s', 'soproc'],
'Pre-FEC BER': ['N/A', 'prefec_ber'],
'Post-FEC BER': ['N/A', 'uncorr_frames'],
'EVM': ['%', 'evm']
}

ZR_PM_NOT_APPLICABLE_STR = 'Transceiver performance monitoring not applicable'

def display_invalid_intf_eeprom(intf_name):
output = intf_name + ': SFP EEPROM Not detected\n'
Expand All @@ -215,6 +245,10 @@ def display_invalid_intf_presence(intf_name):
click.echo(tabulate(port_table, header))


def display_invalid_intf_pm(intf_name):
output = intf_name + ': %s\n' % ZR_PM_NOT_APPLICABLE_STR
click.echo(output)

class SFPShow(object):
def __init__(self, intf_name, namespace_option, dump_dom=False):
super(SFPShow, self).__init__()
Expand All @@ -223,6 +257,7 @@ class SFPShow(object):
self.dump_dom = dump_dom
self.table = []
self.intf_eeprom: Dict[str, str] = {}
self.intf_pm: Dict[str, str] = {}
self.multi_asic = multi_asic_util.MultiAsic(namespace_option=namespace_option)

# Convert dict values to cli output string
Expand Down Expand Up @@ -402,6 +437,66 @@ class SFPShow(object):

return output

def convert_pm_prefix_to_threshold_prefix(self, pm_prefix):
if pm_prefix == 'uncorr_frames':
return 'postfecber'
elif pm_prefix == 'cd':
return 'cdshort'
else:
return pm_prefix.replace('_', '')

def beautify_pm_field(self, prefix, field):
if field is None:
return 'N/A'
elif prefix in {'prefec_ber'}:
return "{:.2E}".format(field) if field != 0 else '0.0'
else:
return str(field)

def convert_interface_sfp_pm_to_cli_output_string(self, state_db, interface_name):
sfp_pm_dict = state_db.get_all(
self.db.STATE_DB, 'TRANSCEIVER_PM|{}'.format(interface_name))
sfp_threshold_dict = state_db.get_all(
state_db.STATE_DB, 'TRANSCEIVER_DOM_THRESHOLD|{}'.format(interface_name))
table = []
indent_num = 4
indent = ' ' * indent_num
if sfp_pm_dict:
output = '\n' + indent
for param_name, (unit, prefix) in ZR_PM_INFO_MAP.items():
row = [param_name, unit]
values = []
for suffix in ZR_PM_VALUE_KEY_SUFFIXS:
key = prefix + '_' + suffix
values.append(
float(sfp_pm_dict[key]) if key in sfp_pm_dict else None)

thresholds = []
for suffix in ZR_PM_THRESHOLD_KEY_SUFFIXS:
key = self.convert_pm_prefix_to_threshold_prefix(
prefix) + suffix
thresholds.append(
float(sfp_threshold_dict[key]) if key in sfp_threshold_dict else None)

tca_high, tca_low = None, None
if values[2] is not None and thresholds[0] is not None:
# TCA-High: max > high_alarm
tca_high = values[2] > thresholds[0]
if values[0] is not None and thresholds[2] is not None:
# TCA-low: min < low_alarm
tca_low = values[0] < thresholds[2]

for field in values + thresholds[:2] + [tca_high] + thresholds[2:] + [tca_low]:
row.append(self.beautify_pm_field(prefix, field))
table.append(row)

output += tabulate(table,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the test report, it seems that "Min Avg Max" is right aligned whereas the data in the corresponding columns is left aligned. Can you please help in rectifying this.

 Parameter        Unit              Min            Avg            Max  Threshold    Threshold             Threshold     Threshold    Threshold    Threshold
                                                                          High         High                  Crossing      Low          Low          Crossing
                                                                          Alarm        Warning               Alart-High    Alarm        Warning      Alart-Low
    ---------------  ------  -------------  -------------  -------------  -----------  --------------------  ------------  -----------  -----------  -----------
    Tx Power         dBm       0              0              0            -5.0         -6.0                  True          -16.99       -16.003      False
    Rx Total Power   dBm       0              0              0            2.0          0.0                   False         -21.0        -18.0        False

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

corrected the alignment.

ZR_PM_HEADER, disable_numparse=True).replace('\n', '\n' + indent)
output += '\n'
else:
output = ZR_PM_NOT_APPLICABLE_STR + '\n'
return output

@multi_asic_util.run_on_multi_asic
def get_eeprom(self):
if self.intf_name is not None:
Expand Down Expand Up @@ -441,6 +536,19 @@ class SFPShow(object):

self.table += port_table

@multi_asic_util.run_on_multi_asic
def get_pm(self):
if self.intf_name is not None:
self.intf_pm[self.intf_name] = self.convert_interface_sfp_pm_to_cli_output_string(
self.db, self.intf_name)
else:
port_table_keys = self.db.keys(self.db.APPL_DB, "PORT_TABLE:*")
for i in port_table_keys:
interface = re.split(':', i, maxsplit=1)[-1].strip()
if interface and interface.startswith(front_panel_prefix()) and not interface.startswith((backplane_prefix(), inband_prefix(), recirc_prefix())):
self.intf_pm[interface] = self.convert_interface_sfp_pm_to_cli_output_string(
self.db, interface)

def display_eeprom(self):
click.echo("\n".join([f"{k}: {v}" for k, v in natsorted(self.intf_eeprom.items())]))

Expand All @@ -449,6 +557,9 @@ class SFPShow(object):
sorted_port_table = natsorted(self.table)
click.echo(tabulate(sorted_port_table, header))

def display_pm(self):
click.echo(
"\n".join([f"{k}: {v}" for k, v in natsorted(self.intf_pm.items())]))
# This is our main entrypoint - the main 'sfpshow' command


Expand Down Expand Up @@ -494,6 +605,24 @@ def presence(port, namespace):
sfp.get_presence()
sfp.display_presence()

# 'pm' subcommand


@cli.command()
@click.option('-p', '--port', metavar='<port_name>', help="Display SFP PM for port <port_name> only")
@click.option('-n', '--namespace', default=None, help="Display interfaces for specific namespace")
def pm(port, namespace):
if port and multi_asic.is_multi_asic() and namespace is None:
try:
namespace = multi_asic.get_namespace_for_port(port)
except Exception:
display_invalid_intf_pm(port)
sys.exit(1)

sfp = SFPShow(port, namespace)
sfp.get_pm()
sfp.display_pm()


if __name__ == "__main__":
cli()
23 changes: 23 additions & 0 deletions show/interfaces/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -446,6 +446,29 @@ def eeprom(interfacename, dump_dom, namespace, verbose):

clicommon.run_command(cmd, display_cmd=verbose)

@transceiver.command()
@click.argument('interfacename', required=False)
@click.option('--namespace', '-n', 'namespace', default=None, show_default=True,
type=click.Choice(multi_asic_util.multi_asic_ns_choices()), help='Namespace name or all')
@click.option('--verbose', is_flag=True, help="Enable verbose output")
def pm(interfacename, namespace, verbose):
"""Show interface transceiver performance monitoring information"""

ctx = click.get_current_context()

cmd = "sfpshow pm"

if interfacename is not None:
interfacename = try_convert_interfacename_from_alias(
ctx, interfacename)

cmd += " -p {}".format(interfacename)

if namespace is not None:
cmd += " -n {}".format(namespace)

clicommon.run_command(cmd, display_cmd=verbose)

@transceiver.command()
@click.argument('interfacename', required=False)
@click.option('--verbose', is_flag=True, help="Enable verbose output")
Expand Down
Loading