Skip to content

Commit 8ed2e16

Browse files
authored
[cmis] add read only cache decorator (sonic-net#562)
* Add read only cache decorator Defined read only cache decorator after logger setup. Cached return values of static get methods to reduce EEPROM reads. Add unit tests. Signed-off-by: Jianyue Wu <[email protected]> * Refactor code and update UT Signed-off-by: Jianyue Wu <[email protected]> * Remove unnecessary decorator Signed-off-by: Jianyue Wu <[email protected]> * Add clear cache Signed-off-by: Jianyue Wu <[email protected]> * Move cache api to utils dir Signed-off-by: Jianyue Wu <[email protected]> * Refactor: relocate cache clearing to test helper Signed-off-by: Jianyue Wu <[email protected]> * Add utils sub package Signed-off-by: Jianyue Wu <[email protected]> * Support cache all non-empty collections Signed-off-by: Jianyue Wu <[email protected]> * Add clear cache when SFP module removal event happen Signed-off-by: Jianyue Wu <[email protected]> * [cmis] add cache enable flag Signed-off-by: Jianyue Wu <[email protected]> * Add transceiver remove api Add remove api for transceiver. Move clear cache api to UT. Signed-off-by: Jianyue Wu <[email protected]> * Move MEDIA_TYPE_FIELD lookup out of the loop Signed-off-by: Jianyue Wu <[email protected]> * Set caching as default enabled Signed-off-by: Jianyue Wu <[email protected]> * Add line back Signed-off-by: Jianyue Wu <[email protected]> --------- Signed-off-by: Jianyue Wu <[email protected]>
1 parent 48e7b10 commit 8ed2e16

File tree

9 files changed

+439
-98
lines changed

9 files changed

+439
-98
lines changed

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
'sonic_platform_base.sonic_xcvr.api.public',
5050
'sonic_platform_base.sonic_xcvr.codes',
5151
'sonic_platform_base.sonic_xcvr.codes.public',
52+
'sonic_platform_base.sonic_xcvr.utils',
5253
'sonic_platform_base.sonic_xcvr.api.credo',
5354
'sonic_platform_base.sonic_xcvr.mem_maps.credo',
5455
'sonic_platform_base.sonic_xcvr.codes.credo',

sonic_platform_base/sfp_base.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,3 +479,9 @@ def get_xcvr_api(self):
479479
if self._xcvr_api is None:
480480
self.refresh_xcvr_api()
481481
return self._xcvr_api
482+
483+
def remove_xcvr_api(self):
484+
"""
485+
Removes the cached XcvrApi so that the next get_xcvr_api() call will refresh it.
486+
"""
487+
self._xcvr_api = None

sonic_platform_base/sonic_xcvr/api/public/cmis.py

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import time
2020
import copy
2121
from collections import defaultdict
22+
from ...utils.cache import read_only_cached_api_return
2223

2324
logger = logging.getLogger(__name__)
2425
logger.addHandler(logging.NullHandler())
@@ -114,6 +115,16 @@ class CmisApi(XcvrApi):
114115
LowPwrRequestSW = 4
115116
LowPwrAllowRequestHW = 6
116117

118+
# Default caching enabled; control via classmethod
119+
cache_enabled = True
120+
121+
@classmethod
122+
def set_cache_enabled(cls, enabled: bool):
123+
"""
124+
Set the cache_enabled flag to control read_only_cached_api_return behavior.
125+
"""
126+
cls.cache_enabled = bool(enabled)
127+
117128
def __init__(self, xcvr_eeprom):
118129
super(CmisApi, self).__init__(xcvr_eeprom)
119130
self.vdm = CmisVdmApi(xcvr_eeprom) if not self.is_flat_memory() else None
@@ -184,12 +195,14 @@ def get_vdm_unfreeze_status(self):
184195
'''
185196
return self.xcvr_eeprom.read(consts.VDM_UNFREEZE_DONE)
186197

198+
@read_only_cached_api_return
187199
def get_manufacturer(self):
188200
'''
189201
This function returns the manufacturer of the module
190202
'''
191203
return self.xcvr_eeprom.read(consts.VENDOR_NAME_FIELD)
192204

205+
@read_only_cached_api_return
193206
def get_model(self):
194207
'''
195208
This function returns the part number of the module
@@ -202,42 +215,49 @@ def get_cable_length_type(self):
202215
'''
203216
return "Length Cable Assembly(m)"
204217

218+
@read_only_cached_api_return
205219
def get_cable_length(self):
206220
'''
207221
This function returns the cable length of the module
208222
'''
209223
return self.xcvr_eeprom.read(consts.LENGTH_ASSEMBLY_FIELD)
210224

225+
@read_only_cached_api_return
211226
def get_vendor_rev(self):
212227
'''
213228
This function returns the revision level for part number provided by vendor
214229
'''
215230
return self.xcvr_eeprom.read(consts.VENDOR_REV_FIELD)
216231

232+
@read_only_cached_api_return
217233
def get_serial(self):
218234
'''
219235
This function returns the serial number of the module
220236
'''
221237
return self.xcvr_eeprom.read(consts.VENDOR_SERIAL_NO_FIELD)
222238

239+
@read_only_cached_api_return
223240
def get_module_type(self):
224241
'''
225242
This function returns the SFF8024Identifier (module type / form-factor). Table 4-1 in SFF-8024 Rev4.6
226243
'''
227244
return self.xcvr_eeprom.read(consts.ID_FIELD)
228245

246+
@read_only_cached_api_return
229247
def get_module_type_abbreviation(self):
230248
'''
231249
This function returns the SFF8024Identifier (module type / form-factor). Table 4-1 in SFF-8024 Rev4.6
232250
'''
233251
return self.xcvr_eeprom.read(consts.ID_ABBRV_FIELD)
234252

253+
@read_only_cached_api_return
235254
def get_connector_type(self):
236255
'''
237256
This function returns module connector. Table 4-3 in SFF-8024 Rev4.6
238257
'''
239258
return self.xcvr_eeprom.read(consts.CONNECTOR_FIELD)
240259

260+
@read_only_cached_api_return
241261
def get_module_hardware_revision(self):
242262
'''
243263
This function returns the module hardware revision
@@ -249,6 +269,7 @@ def get_module_hardware_revision(self):
249269
hw_rev = [str(num) for num in [hw_major_rev, hw_minor_rev]]
250270
return '.'.join(hw_rev)
251271

272+
@read_only_cached_api_return
252273
def get_cmis_rev(self):
253274
'''
254275
This function returns the CMIS version the module complies to
@@ -571,6 +592,7 @@ def is_copper(self):
571592
media_intf = self.get_module_media_type()
572593
return media_intf == "passive_copper_media_interface" if media_intf else None
573594

595+
@read_only_cached_api_return
574596
def is_flat_memory(self):
575597
return self.xcvr_eeprom.read(consts.FLAT_MEM_FIELD) is not False
576598

@@ -810,6 +832,7 @@ def get_tx_los(self):
810832
tx_los_final.append(bool(tx_los[key]))
811833
return tx_los_final
812834

835+
@read_only_cached_api_return
813836
def get_tx_disable_support(self):
814837
return not self.is_flat_memory() and self.xcvr_eeprom.read(consts.TX_DISABLE_SUPPORT_FIELD)
815838

@@ -923,6 +946,7 @@ def set_power_override(self, power_override, power_set):
923946
def get_transceiver_thresholds_support(self):
924947
return not self.is_flat_memory()
925948

949+
@read_only_cached_api_return
926950
def get_lpmode_support(self):
927951
power_class = self.xcvr_eeprom.read(consts.POWER_CLASS_FIELD)
928952
if power_class is None:
@@ -964,13 +988,15 @@ def get_module_media_interface(self):
964988
else:
965989
return 'Unknown media interface'
966990

991+
@read_only_cached_api_return
967992
def is_coherent_module(self):
968993
'''
969994
Returns True if the module follow C-CMIS spec, False otherwise
970995
'''
971996
mintf = self.get_module_media_interface()
972997
return False if 'ZR' not in mintf else True
973998

999+
@read_only_cached_api_return
9741000
def get_datapath_init_duration(self):
9751001
'''
9761002
This function returns the duration of datapath init
@@ -983,6 +1009,7 @@ def get_datapath_init_duration(self):
9831009
value = float(duration)
9841010
return value * DATAPATH_INIT_DURATION_MULTIPLIER if value <= DATAPATH_INIT_DURATION_OVERRIDE_THRESHOLD else value
9851011

1012+
@read_only_cached_api_return
9861013
def get_datapath_deinit_duration(self):
9871014
'''
9881015
This function returns the duration of datapath deinit
@@ -992,6 +1019,7 @@ def get_datapath_deinit_duration(self):
9921019
duration = self.xcvr_eeprom.read(consts.DP_PATH_DEINIT_DURATION)
9931020
return float(duration) if duration is not None else 0
9941021

1022+
@read_only_cached_api_return
9951023
def get_datapath_tx_turnon_duration(self):
9961024
'''
9971025
This function returns the duration of datapath tx turnon
@@ -1001,6 +1029,7 @@ def get_datapath_tx_turnon_duration(self):
10011029
duration = self.xcvr_eeprom.read(consts.DP_TX_TURNON_DURATION)
10021030
return float(duration) if duration is not None else 0
10031031

1032+
@read_only_cached_api_return
10041033
def get_datapath_tx_turnoff_duration(self):
10051034
'''
10061035
This function returns the duration of datapath tx turnoff
@@ -1010,6 +1039,7 @@ def get_datapath_tx_turnoff_duration(self):
10101039
duration = self.xcvr_eeprom.read(consts.DP_TX_TURNOFF_DURATION)
10111040
return float(duration) if duration is not None else 0
10121041

1042+
@read_only_cached_api_return
10131043
def get_module_pwr_up_duration(self):
10141044
'''
10151045
This function returns the duration of module power up
@@ -1019,6 +1049,7 @@ def get_module_pwr_up_duration(self):
10191049
duration = self.xcvr_eeprom.read(consts.MODULE_PWRUP_DURATION)
10201050
return float(duration) if duration is not None else 0
10211051

1052+
@read_only_cached_api_return
10221053
def get_module_pwr_down_duration(self):
10231054
'''
10241055
This function returns the duration of module power down
@@ -1647,7 +1678,6 @@ def get_module_level_flag(self):
16471678
'aux2_low_alarm_flag': aux2_low_alarm_flag,
16481679
'aux2_high_warn_flag': aux2_high_warn_flag,
16491680
'aux2_low_warn_flag': aux2_low_warn_flag}
1650-
16511681
aux1_high_alarm_flag = bool((module_flag_byte2 >> 0) & 0x1)
16521682
aux1_low_alarm_flag = bool((module_flag_byte2 >> 1) & 0x1)
16531683
aux1_high_warn_flag = bool((module_flag_byte2 >> 2) & 0x1)
@@ -2528,6 +2558,7 @@ def get_datapath_deinit(self):
25282558
return None
25292559
return [bool(datapath_deinit & (1 << lane)) for lane in range(self.NUM_CHANNELS)]
25302560

2561+
@read_only_cached_api_return
25312562
def get_application_advertisement(self):
25322563
"""
25332564
Get the application advertisement of the CMIS transceiver
@@ -2557,6 +2588,8 @@ def get_application_advertisement(self):
25572588
logger.error('Failed to read APPLS_ADVT_FIELD_PAGE01: ' + str(e))
25582589
return ret
25592590

2591+
media_type = self.xcvr_eeprom.read(consts.MEDIA_TYPE_FIELD)
2592+
prefix = map.get(media_type)
25602593
for app in range(1, 16):
25612594
buf = {}
25622595

@@ -2566,7 +2599,6 @@ def get_application_advertisement(self):
25662599
continue
25672600
buf['host_electrical_interface_id'] = val
25682601

2569-
prefix = map.get(self.xcvr_eeprom.read(consts.MEDIA_TYPE_FIELD))
25702602
if prefix is None:
25712603
continue
25722604
key = "{}_{}".format(prefix, app)
@@ -3033,3 +3065,4 @@ def get_error_description(self):
30333065
return 'OK'
30343066

30353067
# TODO: other XcvrApi methods
3068+

sonic_platform_base/sonic_xcvr/mem_maps/public/cmis.py

100644100755
File mode changed.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# utils package for transceiver common utilities
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
from collections import abc
2+
import os
3+
4+
def read_only_cached_api_return(func):
5+
"""Cache until func() returns a non-None, non-empty collections cache_value."""
6+
cache_name = f'_{func.__name__}_cache'
7+
def wrapper(self):
8+
if not self.cache_enabled:
9+
return func(self)
10+
if not hasattr(self, cache_name):
11+
cache_value = func(self)
12+
setattr(self, cache_name, cache_value)
13+
else:
14+
cache_value = getattr(self, cache_name)
15+
if cache_value is None or (isinstance(cache_value, abc.Iterable) and not cache_value):
16+
cache_value = func(self)
17+
setattr(self, cache_name, cache_value)
18+
return cache_value
19+
return wrapper

0 commit comments

Comments
 (0)