@@ -15,7 +15,9 @@ import sys
1515from collections import namedtuple , OrderedDict
1616from natsort import natsorted
1717from tabulate import tabulate
18- from sonic_py_common import multi_asic
18+ from sonic_py_common import multi_asic , device_info
19+ from redis import Redis , exceptions
20+ from swsscommon import swsscommon
1921
2022# mock the redis for unit test purposes #
2123try :
2527 sys .path .insert (0 , modules_path )
2628 sys .path .insert (0 , tests_path )
2729 import mock_tables .dbconnector # lgtm [py/unused-import]
30+
31+ if os .environ ["UTILITIES_UNIT_TESTING_IS_SUP" ] == "1" :
32+ import mock
33+ device_info .is_supervisor = mock .MagicMock (return_value = True )
34+
2835 if os .environ ["UTILITIES_UNIT_TESTING_TOPOLOGY" ] == "multi_asic" :
2936 import mock_tables .mock_multi_asic
3037 mock_tables .dbconnector .load_namespace_config ()
@@ -82,6 +89,39 @@ cnstat_dir = 'N/A'
8289cnstat_fqn_file = 'N/A'
8390
8491
92+ def get_redis_ips (db ):
93+ db .connect (db .STATE_DB )
94+ redis_ips = []
95+ chassis_midplane_table = db .keys (db .STATE_DB , "CHASSIS_MIDPLANE_TABLE*" )
96+ lc_metadata = []
97+ for lc in chassis_midplane_table :
98+ lc_metadata .append (db .get_all (db .STATE_DB , lc ))
99+
100+ db .connect (db .CHASSIS_STATE_DB )
101+ for lc in lc_metadata :
102+ # skip if LC is offline
103+ if lc ['access' ] == "False" :
104+ continue
105+
106+ slot_id = int (lc ['ip_address' ].split ("." )[2 ]) - 1
107+ num_asics = db .get (db .CHASSIS_STATE_DB , f"CHASSIS_MODULE_TABLE|LINE-CARD{ slot_id } " , 'num_asics' )
108+
109+ # Skip if pmon hasn't started on LC yet
110+ if num_asics == None :
111+ continue
112+
113+ # No namespace in single ASIC LC
114+ if num_asics == "1" :
115+ redis_ips .append (lc ['ip_address' ])
116+ else :
117+ prefix , _ = lc ['ip_address' ].rsplit ("." , maxsplit = 1 )
118+ for i in range (int (num_asics )):
119+ prefix , _ , _ = lc ['ip_address' ].rpartition ("." )
120+ redis_ips .append (f"{ prefix } .{ 10 + i } " )
121+
122+ return redis_ips
123+
124+
85125def build_json (port , cnstat , all = False , trim = False , voq = False ):
86126 def ports_stats (k ):
87127 p = {}
@@ -120,6 +160,18 @@ def build_json(port, cnstat, all=False, trim=False, voq=False):
120160 out .update (ports_stats (k ))
121161 return out
122162
163+ def run_queuestat (save_fresh_stats , port_to_show_stats , json_opt , non_zero , ns , db , voq , trim , all_ ):
164+ queuestat = Queuestat (ns , db , all_ , trim , voq )
165+ if save_fresh_stats :
166+ queuestat .save_fresh_stats ()
167+ return
168+
169+ if port_to_show_stats != None :
170+ queuestat .get_print_port_stat (port_to_show_stats , json_opt , non_zero )
171+ else :
172+ queuestat .get_print_all_stat (json_opt , non_zero )
173+
174+
123175class QueuestatWrapper (object ):
124176 """A wrapper to execute queuestat cmd over the correct namespaces"""
125177 def __init__ (self , namespace , all , trim , voq ):
@@ -134,24 +186,20 @@ class QueuestatWrapper(object):
134186
135187 @multi_asic_util .run_on_multi_asic
136188 def run (self , save_fresh_stats , port_to_show_stats , json_opt , non_zero ):
137- queuestat = Queuestat (self .multi_asic .current_namespace , self .db , self .all , self .trim , self .voq )
138- if save_fresh_stats :
139- queuestat .save_fresh_stats ()
140- return
141-
142- if port_to_show_stats != None :
143- queuestat .get_print_port_stat (port_to_show_stats , json_opt , non_zero )
144- else :
145- queuestat .get_print_all_stat (json_opt , non_zero )
146-
189+ run_queuestat (save_fresh_stats , port_to_show_stats , json_opt , non_zero , \
190+ self .multi_asic .current_namespace , self .db , self .voq , self .trim , self .all )
147191
148192class Queuestat (object ):
149193 def __init__ (self , namespace , db , all = False , trim = False , voq = False ):
150194 self .db = db
151195 self .all = all
152196 self .trim = trim
153197 self .voq = voq
198+ self .voq_stats = {}
154199 self .namespace = namespace
200+ if namespace is None :
201+ self .db = SonicV2Connector (use_unix_socket_path = False )
202+ self .db .connect (self .db .COUNTERS_DB )
155203 self .namespace_str = f" for { namespace } " if namespace else ''
156204
157205 def get_queue_port (table_id ):
@@ -164,7 +212,9 @@ class Queuestat(object):
164212
165213 # Get all ports
166214 if voq :
167- self .counter_port_name_map = self .db .get_all (self .db .COUNTERS_DB , COUNTERS_SYSTEM_PORT_NAME_MAP )
215+ # counter_port_name_map is assigned later for supervisor as a list
216+ self .counter_port_name_map = [] if device_info .is_supervisor () else \
217+ self .db .get_all (self .db .COUNTERS_DB , COUNTERS_SYSTEM_PORT_NAME_MAP )
168218 else :
169219 self .counter_port_name_map = self .db .get_all (self .db .COUNTERS_DB , COUNTERS_PORT_NAME_MAP )
170220
@@ -179,6 +229,16 @@ class Queuestat(object):
179229 self .port_queues_map [port ] = {}
180230 self .port_name_map [self .counter_port_name_map [port ]] = port
181231
232+ if self .voq :
233+ counter_bucket_dict .update (voq_counter_bucket_dict )
234+ else :
235+ counter_bucket_dict .update (trim_counter_bucket_dict )
236+
237+ if device_info .is_supervisor ():
238+ self .aggregate_voq_stats ()
239+ self .counter_port_name_map = self .voq_stats .keys ()
240+ return
241+
182242 counter_queue_name_map = None
183243 # Get Queues for each port
184244 if voq :
@@ -194,6 +254,44 @@ class Queuestat(object):
194254 port = self .port_name_map [get_queue_port (counter_queue_name_map [queue ])]
195255 self .port_queues_map [port ][queue ] = counter_queue_name_map [queue ]
196256
257+ def aggregate_voq_stats (self ):
258+ redis_ips = get_redis_ips (self .db )
259+ self .voq_stats = {}
260+
261+ for ip in redis_ips :
262+ asic_counters_db = swsscommon .DBConnector (swsscommon .COUNTERS_DB , ip , 6379 , 0 )
263+ try :
264+ counters_voq_name_map = asic_counters_db .hgetall (COUNTERS_VOQ_NAME_MAP )
265+ if counters_voq_name_map is None :
266+ continue
267+ for voq in counters_voq_name_map :
268+ # key LINECARD|ASIC|EthernetXXX:INDEX
269+ sysPort , idx = voq .split (":" )
270+ for counter_name in counter_bucket_dict :
271+ self .voq_stats .setdefault (sysPort , {}).setdefault (idx , {}).setdefault (counter_name , 0 )
272+ oid = counters_voq_name_map [voq ]
273+ counter_data = asic_counters_db .hget ("COUNTERS:" + oid , counter_name )
274+ if counter_data is not None :
275+ self .voq_stats [sysPort ][idx ][counter_name ] += int (counter_data )
276+
277+ except exceptions .ConnectionError as e :
278+ # Skip further operations for this redis-instance
279+ continue
280+
281+ def get_aggregate_port_stats (self , port ):
282+ # Build a dictionary of stats
283+ cnstat_dict = OrderedDict ()
284+ cnstat_dict ['time' ] = datetime .datetime .now ()
285+ for idx in sorted (self .voq_stats [port ].keys ()):
286+ fields = ["0" ]* len (voq_header )
287+ fields [0 ] = idx
288+ fields [1 ] = QUEUE_TYPE_VOQ
289+ for counter_name , pos in counter_bucket_dict .items ():
290+ fields [pos ] = str (self .voq_stats [port ][idx ][counter_name ])
291+ cntr = VoqStats ._make (fields )._asdict ()
292+ cnstat_dict [port + ":" + idx ] = cntr
293+ return cnstat_dict
294+
197295 def get_cnstat (self , queue_map ):
198296 """
199297 Get the counters info from database.
@@ -230,11 +328,6 @@ class Queuestat(object):
230328 counter_dict = { ** counter_bucket_dict }
231329 fields = [ get_queue_index (table_id ), get_queue_type (table_id ) ]
232330
233- if self .voq :
234- counter_dict .update (voq_counter_bucket_dict )
235- else :
236- counter_dict .update (trim_counter_bucket_dict )
237-
238331 # Layout is per QueueStats/VoqStats type definition
239332 fields .extend (["0" ]* len (counter_dict ))
240333
@@ -325,7 +418,8 @@ class Queuestat(object):
325418 hdr = std_header
326419
327420 if table :
328- print (f"For namespace { self .namespace } :" )
421+ if not device_info .is_supervisor ():
422+ print (f"For namespace { self .namespace } :" )
329423 print (tabulate (table , hdr , tablefmt = 'simple' , stralign = 'right' ))
330424 print ()
331425
@@ -428,7 +522,11 @@ class Queuestat(object):
428522 json_output = {}
429523 for port in natsorted (self .counter_port_name_map ):
430524 json_output [port ] = {}
431- cnstat_dict = self .get_cnstat (self .port_queues_map [port ])
525+ if self .voq and device_info .is_supervisor ():
526+ cnstat_dict = self .get_aggregate_port_stats (port )
527+ else :
528+ cnstat_dict = self .get_cnstat (self .port_queues_map [port ])
529+
432530 cache_ns = ''
433531 if self .voq and self .namespace is not None :
434532 cache_ns = '-' + self .namespace + '-'
@@ -457,12 +555,16 @@ class Queuestat(object):
457555 Get stat for the port
458556 If JSON option is True print data in JSON format
459557 """
460- if not port in self .port_queues_map :
558+ if port not in self . port_queues_map and port not in self .voq_stats :
461559 print ("Port doesn't exist!" , port )
462560 sys .exit (1 )
463561
464562 # Get stat for the port queried
465- cnstat_dict = self .get_cnstat (self .port_queues_map [port ])
563+
564+ if self .voq and device_info .is_supervisor ():
565+ cnstat_dict = self .get_aggregate_port_stats (port )
566+ else :
567+ cnstat_dict = self .get_cnstat (self .port_queues_map [port ])
466568 cache_ns = ''
467569 if self .voq and self .namespace is not None :
468570 cache_ns = '-' + self .namespace + '-'
@@ -542,8 +644,13 @@ def main(port, clear, delete, json_opt, all, trim, voq, non_zero, namespace):
542644 if delete_stats :
543645 cache .remove ()
544646
545- queuestat_wrapper = QueuestatWrapper (namespace , all , trim , voq )
546- queuestat_wrapper .run (save_fresh_stats , port_to_show_stats , json_opt , non_zero )
647+
648+ if device_info .is_supervisor () and namespace is None :
649+ run_queuestat (save_fresh_stats , port_to_show_stats , json_opt , non_zero , namespace , None , voq , trim , all )
650+ else :
651+ queuestat_wrapper = QueuestatWrapper (namespace , all , trim , voq )
652+ queuestat_wrapper .run (save_fresh_stats , port_to_show_stats , json_opt , non_zero )
653+
547654
548655 sys .exit (0 )
549656
0 commit comments