Skip to content

Commit 6f1a794

Browse files
Add queuestat changes for aggregate VOQ counters (#3617)
* Add queuestat changes for aggregate VOQ counters
1 parent d86b2b6 commit 6f1a794

File tree

15 files changed

+400
-34
lines changed

15 files changed

+400
-34
lines changed

scripts/queuestat

Lines changed: 130 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,9 @@ import sys
1515
from collections import namedtuple, OrderedDict
1616
from natsort import natsorted
1717
from 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 #
2123
try:
@@ -25,6 +27,11 @@ try:
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'
8289
cnstat_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+
85125
def 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+
123175
class 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

148192
class 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

tests/chassis_modules_test.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,12 @@
4646
"""
4747

4848
show_chassis_midplane_output="""\
49-
Name IP-Address Reachability
50-
---------- ------------- --------------
51-
LINE-CARD0 192.168.1.100 True
52-
LINE-CARD1 192.168.1.2 False
53-
LINE-CARD2 192.168.1.1 True
49+
Name IP-Address Reachability
50+
---------- ------------ --------------
51+
LINE-CARD0 192.168.3.1 True
52+
LINE-CARD1 192.168.4.1 False
53+
LINE-CARD2 192.168.5.1 True
54+
LINE-CARD3 192.168.6.1 True
5455
"""
5556

5657
show_chassis_system_ports_output_asic0="""\
@@ -346,7 +347,7 @@ def test_midplane_show_all_count_lines(self):
346347
result = runner.invoke(show.cli.commands["chassis"].commands["modules"].commands["midplane-status"], [])
347348
print(result.output)
348349
result_lines = result.output.strip('\n').split('\n')
349-
modules = ["LINE-CARD0", "LINE-CARD1", "LINE-CARD2"]
350+
modules = ["LINE-CARD0", "LINE-CARD1", "LINE-CARD2", "LINE-CARD3"]
350351
for i, module in enumerate(modules):
351352
assert module in result_lines[i + warning_lines + header_lines]
352353
assert len(result_lines) == warning_lines + header_lines + len(modules)

tests/mock_tables/asic0/counters_db.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2788,5 +2788,23 @@
27882788
"PERSISTENT_WATERMARKS:oid:100000000b0ff": {
27892789
"SAI_INGRESS_PRIORITY_GROUP_STAT_SHARED_WATERMARK_BYTES": "507",
27902790
"SAI_INGRESS_PRIORITY_GROUP_STAT_XOFF_ROOM_WATERMARK_BYTES": "507"
2791+
},
2792+
"COUNTERS_VOQ_NAME_MAP": {
2793+
"sonic-lc1|asic0|Ethernet0:0": "oid:0x150000000005c2",
2794+
"sonic-lc2|asic0|Ethernet4:1": "oid:0x1500000000047a"
2795+
},
2796+
"COUNTERS:oid:0x150000000005c2": {
2797+
"SAI_QUEUE_STAT_DROPPED_BYTES": "122",
2798+
"SAI_QUEUE_STAT_DROPPED_PACKETS": "1",
2799+
"SAI_QUEUE_STAT_CREDIT_WD_DELETED_PACKETS": "0",
2800+
"SAI_QUEUE_STAT_BYTES": "122",
2801+
"SAI_QUEUE_STAT_PACKETS": "1"
2802+
},
2803+
"COUNTERS:oid:0x1500000000047a": {
2804+
"SAI_QUEUE_STAT_DROPPED_BYTES": "244",
2805+
"SAI_QUEUE_STAT_DROPPED_PACKETS": "2",
2806+
"SAI_QUEUE_STAT_CREDIT_WD_DELETED_PACKETS": "0",
2807+
"SAI_QUEUE_STAT_BYTES": "366",
2808+
"SAI_QUEUE_STAT_PACKETS": "3"
27912809
}
27922810
}

tests/mock_tables/asic1/counters_db.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1675,5 +1675,23 @@
16751675
},
16761676
"USER_WATERMARKS:oid:0x100000000b115": {
16771677
"SAI_QUEUE_STAT_SHARED_WATERMARK_BYTES": "2"
1678+
},
1679+
"COUNTERS_VOQ_NAME_MAP": {
1680+
"sonic-lc1|asic0|Ethernet0:0": "oid:0x1500000000059d",
1681+
"sonic-lc2|asic0|Ethernet4:1": "oid:0x15000000000571"
1682+
},
1683+
"COUNTERS:oid:0x1500000000059d": {
1684+
"SAI_QUEUE_STAT_DROPPED_BYTES": "1098",
1685+
"SAI_QUEUE_STAT_DROPPED_PACKETS": "9",
1686+
"SAI_QUEUE_STAT_CREDIT_WD_DELETED_PACKETS": "0",
1687+
"SAI_QUEUE_STAT_BYTES": "854",
1688+
"SAI_QUEUE_STAT_PACKETS": "7"
1689+
},
1690+
"COUNTERS:oid:0x15000000000571": {
1691+
"SAI_QUEUE_STAT_DROPPED_BYTES": "732",
1692+
"SAI_QUEUE_STAT_DROPPED_PACKETS": "6",
1693+
"SAI_QUEUE_STAT_CREDIT_WD_DELETED_PACKETS": "0",
1694+
"SAI_QUEUE_STAT_BYTES": "610",
1695+
"SAI_QUEUE_STAT_PACKETS": "5"
16781696
}
16791697
}

tests/mock_tables/asic2/counters_db.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1699,5 +1699,23 @@
16991699
},
17001700
"COUNTERS_DEBUG_NAME_SWITCH_STAT_MAP": {
17011701
"DEBUG_1": "SAI_SWITCH_STAT_IN_DROP_REASON_RANGE_BASE"
1702+
},
1703+
"COUNTERS_VOQ_NAME_MAP": {
1704+
"sonic-lc1|asic0|Ethernet0:0": "oid:0x15000000000532",
1705+
"sonic-lc2|asic0|Ethernet4:1": "oid:0x15000000000521"
1706+
},
1707+
"COUNTERS:oid:0x15000000000532": {
1708+
"SAI_QUEUE_STAT_DROPPED_BYTES": "0",
1709+
"SAI_QUEUE_STAT_DROPPED_PACKETS": "0",
1710+
"SAI_QUEUE_STAT_CREDIT_WD_DELETED_PACKETS": "0",
1711+
"SAI_QUEUE_STAT_BYTES": "0",
1712+
"SAI_QUEUE_STAT_PACKETS": "0"
1713+
},
1714+
"COUNTERS:oid:0x15000000000521": {
1715+
"SAI_QUEUE_STAT_DROPPED_BYTES": "976",
1716+
"SAI_QUEUE_STAT_DROPPED_PACKETS": "8",
1717+
"SAI_QUEUE_STAT_CREDIT_WD_DELETED_PACKETS": "0",
1718+
"SAI_QUEUE_STAT_BYTES": "1220",
1719+
"SAI_QUEUE_STAT_PACKETS": "10"
17021720
}
17031721
}

0 commit comments

Comments
 (0)