Skip to content

Commit bb48360

Browse files
authored
Merge pull request #361 from yejianquan/jianquanye/202405-msft-gcu-perf-opt
Backport GCU performance optimizations from PRs #4476 and #4478 to 202405 Cherry-pick of key optimizations from sonic-net/sonic-utilities master: PR #4476: MD5 hash cache for validate_config_db_config, loadData dedup via shared _loaded_sy across validator and find_ref_paths, eliminate redundant copy.deepcopy in validation path, _validate_replace reorder (validate added_paths first to leverage already-loaded config) PR #4478: BulkLeafListMoveGenerator - batches N leaf-list REMOVE ops into a single REPLACE move, reducing DFS search space Performance results on Cisco 8800 VOQ chassis (str3-7800-lc3-1, asic0): Baseline (stock 202405 + VOQ_QUEUE leafref yang fix): 8m50s With this patch: 5m59s (32% improvement, 2m51s saved) Tested with 45-op all-add port provisioning patch (2 ports + full networking stack: PORT, QUEUE, BUFFER_PG, PFC_WD, PORTCHANNEL, BGP) Second run confirmed: 5m59.8s (consistent) Functional verification: add operations: PASS (45-op patch applied correctly) replace operations: PASS (MTU change, 7-8s) remove operations: PASS (DEVICE_NEIGHBOR removal, 6.6s) End state verified: ports at correct speed, all dependent tables present Originally merged to master as commits 5d54e44 (#4476) and bfc67f5 (#4478). Adapted for 202405 method signatures and stock sonic-yang-mgmt (no sonic_yang_path.py dependency, no must_size SWIG patch required). Signed-off-by: jianquanye@microsoft.com
2 parents f2469f3 + e51bea0 commit bb48360

2 files changed

Lines changed: 99 additions & 29 deletions

File tree

generic_config_updater/gu_common.py

Lines changed: 55 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import copy
1010
import re
1111
import os
12+
import hashlib
1213
from sonic_py_common import logger, multi_asic
1314
from enum import Enum
1415

@@ -80,6 +81,9 @@ def __init__(self, yang_dir=YANG_DIR, scope=multi_asic.DEFAULT_NAMESPACE):
8081
self.scope = scope
8182
self.yang_dir = YANG_DIR
8283
self.sonic_yang_with_loaded_models = None
84+
self._validate_config_cache = {}
85+
self._currently_loaded_hash = None
86+
self._loaded_sy = None
8387

8488
def get_config_db_as_json(self):
8589
return get_config_db_as_json(self.scope)
@@ -138,27 +142,41 @@ def validate_sonic_yang_config(self, sonic_yang_as_json):
138142
return False, ex
139143

140144
def validate_config_db_config(self, config_db_as_json):
145+
_cache_key = hashlib.md5(
146+
json.dumps(config_db_as_json, sort_keys=True).encode()
147+
).hexdigest()
148+
if _cache_key in self._validate_config_cache:
149+
return self._validate_config_cache[_cache_key]
150+
141151
sy = self.create_sonic_yang_with_loaded_models()
142152

143153
# TODO: Move these validators to YANG models
144154
supplemental_yang_validators = [self.validate_bgp_peer_group,
145155
self.validate_lanes]
146156

147157
try:
148-
tmp_config_db_as_json = copy.deepcopy(config_db_as_json)
149-
150-
sy.loadData(tmp_config_db_as_json)
158+
sy.loadData(config_db_as_json)
159+
self._currently_loaded_hash = _cache_key
160+
self._loaded_sy = sy
151161

152162
sy.validate_data_tree()
153163

154164
for supplemental_yang_validator in supplemental_yang_validators:
155165
success, error = supplemental_yang_validator(config_db_as_json)
156166
if not success:
157-
return success, error
167+
result = (success, error)
168+
self._validate_config_cache[_cache_key] = result
169+
return result
158170
except sonic_yang.SonicYangException as ex:
159-
return False, ex
171+
self._currently_loaded_hash = None
172+
self._loaded_sy = None
173+
result = (False, ex)
174+
self._validate_config_cache[_cache_key] = result
175+
return result
160176

161-
return True, None
177+
result = (True, None)
178+
self._validate_config_cache[_cache_key] = result
179+
return result
162180

163181
def validate_field_operation(self, old_config, target_config):
164182
"""
@@ -522,7 +540,7 @@ def create_xpath(self, tokens):
522540
def _create_sonic_yang_with_loaded_models(self):
523541
return self.config_wrapper.create_sonic_yang_with_loaded_models()
524542

525-
def find_ref_paths(self, path, config):
543+
def find_ref_paths(self, path, config, reload_config=True):
526544
"""
527545
Finds the paths referencing any line under the given 'path' within the given 'config'.
528546
Example:
@@ -560,22 +578,39 @@ def find_ref_paths(self, path, config):
560578
/ACL_TABLE/EVERFLOW6/ports/1
561579
"""
562580
# TODO: Also fetch references by must statement (check similar statements)
563-
return self._find_leafref_paths(path, config)
564-
565-
def _find_leafref_paths(self, path, config):
566-
sy = self._create_sonic_yang_with_loaded_models()
567-
568-
tmp_config = copy.deepcopy(config)
569-
570-
sy.loadData(tmp_config)
571-
572-
xpath = self.convert_path_to_xpath(path, config, sy)
581+
return self._find_leafref_paths(path, config, reload_config=reload_config)
582+
583+
def _find_leafref_paths(self, path, config, reload_config=True):
584+
if reload_config:
585+
_config_hash = hashlib.md5(
586+
json.dumps(config, sort_keys=True).encode()
587+
).hexdigest()
588+
already_loaded = (
589+
self.config_wrapper is not None and
590+
self.config_wrapper._currently_loaded_hash == _config_hash and
591+
self.config_wrapper._loaded_sy is not None
592+
)
593+
if not already_loaded:
594+
sy = self._create_sonic_yang_with_loaded_models()
595+
sy.loadData(config)
596+
if self.config_wrapper is not None:
597+
self.config_wrapper._currently_loaded_hash = _config_hash
598+
self.config_wrapper._loaded_sy = sy
599+
else:
600+
sy = self.config_wrapper._loaded_sy
601+
else:
602+
sy = self._create_sonic_yang_with_loaded_models()
603+
sy.loadData(config)
573604

574-
leaf_xpaths = self._get_inner_leaf_xpaths(xpath, sy)
605+
if not isinstance(path, list):
606+
path = [path]
575607

576608
ref_xpaths = []
577-
for xpath in leaf_xpaths:
578-
ref_xpaths.extend(sy.find_data_dependencies(xpath))
609+
for inner_path in path:
610+
xpath = self.convert_path_to_xpath(inner_path, config, sy)
611+
leaf_xpaths = self._get_inner_leaf_xpaths(xpath, sy)
612+
for leaf_xpath in leaf_xpaths:
613+
ref_xpaths.extend(sy.find_data_dependencies(leaf_xpath))
579614

580615
ref_paths = []
581616
ref_paths_set = set()

generic_config_updater/patch_sorter.py

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -850,14 +850,14 @@ def _validate_replace(self, move, diff):
850850
simulated_config = move.apply(diff.current_config)
851851
deleted_paths, added_paths = self._get_paths(diff.current_config, simulated_config, [])
852852

853-
# For deleted paths, we check the current config has no dependencies between nodes under the removed path
854-
if not self._validate_paths_config(deleted_paths, diff.current_config):
855-
return False
856-
857853
# For added paths, we check the simulated config has no dependencies between nodes under the added path
858854
if not self._validate_paths_config(added_paths, simulated_config):
859855
return False
860856

857+
# For deleted paths, we check the current config has no dependencies between nodes under the removed path
858+
if not self._validate_paths_config(deleted_paths, diff.current_config):
859+
return False
860+
861861
return True
862862

863863
def _get_paths(self, current_ptr, target_ptr, tokens):
@@ -935,10 +935,7 @@ def _validate_paths_config(self, paths, config):
935935
return True
936936

937937
def _find_ref_paths(self, paths, config):
938-
refs = []
939-
for path in paths:
940-
refs.extend(self.path_addressing.find_ref_paths(path, config))
941-
return refs
938+
return self.path_addressing.find_ref_paths(paths, config)
942939

943940
class NoEmptyTableMoveValidator:
944941
"""
@@ -1055,6 +1052,43 @@ def _get_non_existing_tables_tokens(self, config1, config2):
10551052
if not(table in config2):
10561053
yield [table]
10571054

1055+
1056+
class BulkLeafListMoveGenerator:
1057+
"""
1058+
A class that generates bulk REPLACE moves for leaf-lists (lists of primitive
1059+
values) that differ between current and target configs.
1060+
"""
1061+
def generate(self, diff):
1062+
for move in self._traverse(diff, diff.current_config, diff.target_config, []):
1063+
yield move
1064+
1065+
def _traverse(self, diff, current_ptr, target_ptr, tokens):
1066+
if not isinstance(current_ptr, dict) or not isinstance(target_ptr, dict):
1067+
return
1068+
1069+
for key in current_ptr:
1070+
if key not in target_ptr:
1071+
continue
1072+
1073+
current_val = current_ptr[key]
1074+
target_val = target_ptr[key]
1075+
tokens.append(key)
1076+
1077+
if isinstance(current_val, list) and isinstance(target_val, list):
1078+
if (current_val != target_val and
1079+
self._is_leaf_list(current_val) and
1080+
self._is_leaf_list(target_val)):
1081+
yield JsonMove(diff, OperationType.REPLACE, list(tokens), list(tokens))
1082+
elif isinstance(current_val, dict) and isinstance(target_val, dict):
1083+
for move in self._traverse(diff, current_val, target_val, tokens):
1084+
yield move
1085+
1086+
tokens.pop()
1087+
1088+
@staticmethod
1089+
def _is_leaf_list(lst):
1090+
return all(isinstance(item, (str, int, float, bool)) for item in lst)
1091+
10581092
class KeyLevelMoveGenerator:
10591093
"""
10601094
A class that key level moves. The item name at the root level of ConfigDB is called 'Table', the item
@@ -1652,7 +1686,8 @@ def create(self, algorithm=Algorithm.DFS):
16521686
move_generators = [RemoveCreateOnlyDependencyMoveGenerator(self.path_addressing),
16531687
LowLevelMoveGenerator(self.path_addressing)]
16541688
# TODO: Enable TableLevelMoveGenerator once it is confirmed whole table can be updated at the same time
1655-
move_non_extendable_generators = [KeyLevelMoveGenerator()]
1689+
move_non_extendable_generators = [BulkLeafListMoveGenerator(),
1690+
KeyLevelMoveGenerator()]
16561691
move_extenders = [RequiredValueMoveExtender(self.path_addressing, self.operation_wrapper),
16571692
UpperLevelMoveExtender(),
16581693
DeleteInsteadOfReplaceMoveExtender(),

0 commit comments

Comments
 (0)