Skip to content

Commit a813215

Browse files
bktsim-aristardjeric-aristakenneth-arista
authored
Fix multi-asic behaviour for dropstat (sonic-net#3059)
* Fixes dropstat multi-asic behaviour by using multi-asic helpers and ensuring that dropstat iterates through correct namespaces when 'show' command is run. Co-authored-by: rdjeric <[email protected]> Co-authored-by: Kenneth Cheung <[email protected]>
1 parent 772ee79 commit a813215

File tree

5 files changed

+272
-53
lines changed

5 files changed

+272
-53
lines changed

scripts/dropstat

+66-52
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,18 @@
1111
# - Refactor calls to COUNTERS_DB to reduce redundancy
1212
# - Cache DB queries to reduce # of expensive queries
1313

14+
import click
1415
import json
15-
import argparse
1616
import os
1717
import socket
1818
import sys
1919

2020
from collections import OrderedDict
2121
from natsort import natsorted
2222
from tabulate import tabulate
23+
from sonic_py_common import multi_asic
24+
from utilities_common.general import load_db_config
25+
import utilities_common.multi_asic as multi_asic_util
2326

2427
# mock the redis for unit test purposes #
2528
try:
@@ -28,9 +31,14 @@ try:
2831
test_path = os.path.join(modules_path, "tests")
2932
sys.path.insert(0, modules_path)
3033
sys.path.insert(0, test_path)
31-
import mock_tables.dbconnector
34+
from tests.mock_tables import dbconnector
3235
socket.gethostname = lambda: 'sonic_drops_test'
3336
os.getuid = lambda: 27
37+
if os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] == "multi_asic":
38+
import tests.mock_tables.mock_multi_asic
39+
dbconnector.load_namespace_config()
40+
else:
41+
dbconnector.load_database_config()
3442
except KeyError:
3543
pass
3644

@@ -90,30 +98,32 @@ def get_dropstat_dir():
9098

9199

92100
class DropStat(object):
93-
def __init__(self):
94-
self.config_db = ConfigDBConnector()
95-
self.config_db.connect()
96-
97-
self.db = SonicV2Connector(use_unix_socket_path=False)
98-
self.db.connect(self.db.COUNTERS_DB)
99-
self.db.connect(self.db.ASIC_DB)
100-
self.db.connect(self.db.APPL_DB)
101-
self.db.connect(self.db.CONFIG_DB)
101+
def __init__(self, namespace):
102+
self.namespaces = multi_asic.get_namespace_list(namespace)
103+
self.multi_asic = multi_asic_util.MultiAsic(namespace_option=namespace)
104+
self.db = None
105+
self.config_db = None
106+
self.cached_namespace = None
102107

103108
dropstat_dir = get_dropstat_dir()
104109
self.port_drop_stats_file = os.path.join(dropstat_dir, 'port-stats')
105-
self.switch_drop_stats_file = os.path.join(dropstat_dir + 'switch-stats')
106-
self.switch_std_drop_stats_file = os.path.join(dropstat_dir, 'switch-std-drop-stats')
110+
self.switch_drop_stats_file = os.path.join(dropstat_dir, 'switch-stats')
111+
self.switch_std_drop_stats_file = os.path.join(dropstat_dir, 'switch-std-drop-stats')
107112

108113
self.stat_lookup = {}
109114
self.reverse_stat_lookup = {}
110115

116+
@multi_asic_util.run_on_multi_asic
111117
def show_drop_counts(self, group, counter_type):
112118
"""
113119
Prints out the current drop counts at the port-level and
114120
switch-level.
115121
"""
116122

123+
if os.environ.get("UTILITIES_UNIT_TESTING_DROPSTAT_CLEAN_CACHE", "0") == "1":
124+
# Temp cache needs to be cleard to avoid interference from previous test cases
125+
UserCache().remove()
126+
117127
self.show_switch_std_drop_counts(group, counter_type)
118128
self.show_port_drop_counts(group, counter_type)
119129
print('')
@@ -124,16 +134,36 @@ class DropStat(object):
124134
Clears the current drop counts.
125135
"""
126136

127-
try:
128-
json.dump(self.get_counts_table(self.gather_counters(std_port_rx_counters + std_port_tx_counters, DEBUG_COUNTER_PORT_STAT_MAP), COUNTERS_PORT_NAME_MAP),
129-
open(self.port_drop_stats_file, 'w+'))
137+
counters_port_drop = {}
138+
counters_switch_drop = {}
139+
counters_switch_std_drop = {}
140+
for ns in self.namespaces:
141+
self.config_db = multi_asic.connect_config_db_for_ns(ns)
142+
self.db = multi_asic.connect_to_all_dbs_for_ns(ns)
143+
144+
counts = self.get_counts_table(self.gather_counters(std_port_rx_counters + std_port_tx_counters, DEBUG_COUNTER_PORT_STAT_MAP), COUNTERS_PORT_NAME_MAP)
145+
if counts:
146+
counters_port_drop.update(counts)
147+
130148
counters = self.gather_counters([], DEBUG_COUNTER_SWITCH_STAT_MAP)
131149
if counters:
132-
json.dump(self.get_counts(counters, self.get_switch_id()), open(self.switch_drop_stats_file, 'w+'))
150+
counts = self.get_counts(counters, self.get_switch_id())
151+
counters_switch_drop.update(counts)
133152

134153
counters = self.get_configured_counters(DEBUG_COUNTER_SWITCH_STAT_MAP, True)
135154
if counters:
136-
json.dump(self.get_counts(counters, self.get_switch_id()), open(self.switch_std_drop_stats_file, 'w+'))
155+
counts = self.get_counts(counters, self.get_switch_id())
156+
counters_switch_std_drop.update(counts)
157+
158+
try:
159+
if counters_port_drop:
160+
json.dump(counters_port_drop, open(self.port_drop_stats_file, 'w+'))
161+
162+
if counters_switch_drop:
163+
json.dump(counters_switch_drop, open(self.switch_drop_stats_file, 'w+'))
164+
165+
if counters_switch_std_drop:
166+
json.dump(counters_switch_std_drop, open(self.switch_std_drop_stats_file, 'w+'))
137167
except IOError as e:
138168
print(e)
139169
sys.exit(e.errno)
@@ -321,12 +351,13 @@ class DropStat(object):
321351
the given object type.
322352
"""
323353

354+
if self.cached_namespace != self.multi_asic.current_namespace:
355+
self.stat_lookup = {}
356+
self.cached_namespace = self.multi_asic.current_namespace
357+
324358
if not self.stat_lookup.get(object_stat_map, None):
325359
stats_map = self.db.get_all(self.db.COUNTERS_DB, object_stat_map)
326-
if stats_map:
327-
self.stat_lookup[object_stat_map] = stats_map
328-
else:
329-
self.stat_lookup[object_stat_map] = None
360+
self.stat_lookup[object_stat_map] = stats_map if stats_map else None
330361

331362
return self.stat_lookup[object_stat_map]
332363

@@ -457,39 +488,22 @@ class DropStat(object):
457488
else:
458489
return PORT_STATE_NA
459490

460-
461-
def main():
462-
parser = argparse.ArgumentParser(description='Display drop counters',
463-
formatter_class=argparse.RawTextHelpFormatter,
464-
epilog="""
465-
Examples:
466-
dropstat
467-
""")
468-
469-
# Version
470-
parser.add_argument('-v', '--version', action='version', version='%(prog)s 1.0')
471-
472-
# Actions
473-
parser.add_argument('-c', '--command', type=str, help='Desired action to perform')
474-
475-
# Variables
476-
parser.add_argument('-g', '--group', type=str, help='The group of the target drop counter', default=None)
477-
parser.add_argument('-t', '--type', type=str, help='The type of the target drop counter', default=None)
478-
479-
args = parser.parse_args()
480-
481-
command = args.command
482-
483-
group = args.group
484-
counter_type = args.type
485-
486-
dcstat = DropStat()
491+
@click.command(help='Display drop counters')
492+
@click.option('-c', '--command', required=True, help='Desired action to perform',
493+
type=click.Choice(['clear', 'show'], case_sensitive=False))
494+
@click.option('-g', '--group', default=None, help='The group of the target drop counter')
495+
@click.option('-t', '--type', 'counter_type', default=None, help='The type of the target drop counter')
496+
@click.option('-n', '--namespace', help='Namespace name', default=None,
497+
type=click.Choice(multi_asic.get_namespace_list()))
498+
@click.version_option(version='1.0')
499+
def main(command, group, counter_type, namespace):
500+
load_db_config()
501+
502+
dcstat = DropStat(namespace)
487503
if command == 'clear':
488504
dcstat.clear_drop_counts()
489-
elif command == 'show':
490-
dcstat.show_drop_counts(group, counter_type)
491505
else:
492-
print("Command not recognized")
506+
dcstat.show_drop_counts(group, counter_type)
493507

494508

495509
if __name__ == '__main__':

show/dropcounters.py

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import click
22
import utilities_common.cli as clicommon
3+
import utilities_common.multi_asic as multi_asic_util
34

45

56
#
@@ -41,7 +42,8 @@ def capabilities(verbose):
4142
@click.option('-g', '--group', required=False)
4243
@click.option('-t', '--counter_type', required=False)
4344
@click.option('--verbose', is_flag=True, help="Enable verbose output")
44-
def counts(group, counter_type, verbose):
45+
@multi_asic_util.multi_asic_click_option_namespace
46+
def counts(group, counter_type, verbose, namespace):
4547
"""Show drop counts"""
4648
cmd = ['dropstat', '-c', 'show']
4749

@@ -51,4 +53,7 @@ def counts(group, counter_type, verbose):
5153
if counter_type:
5254
cmd += ['-t', str(counter_type)]
5355

56+
if namespace:
57+
cmd += ['-n', str(namespace)]
58+
5459
clicommon.run_command(cmd, display_cmd=verbose)

tests/mock_tables/asic1/asic_db.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"ASIC_STATE:SAI_OBJECT_TYPE_SWITCH:oid:0x21000000000000": {
3+
"SAI_SWITCH_ATTR_INIT_SWITCH": "true",
4+
"SAI_SWITCH_ATTR_SRC_MAC_ADDRESS": "DE:AD:BE:EF:CA:FE"
5+
}
6+
}

tests/multi_asic_dropstat_test.py

+122
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import os
2+
import sys
3+
from .utils import get_result_and_return_code
4+
5+
test_path = os.path.dirname(os.path.abspath(__file__))
6+
modules_path = os.path.dirname(test_path)
7+
scripts_path = os.path.join(modules_path, "scripts")
8+
sys.path.insert(0, test_path)
9+
sys.path.insert(0, modules_path)
10+
11+
dropstat_masic_result_asic0 = """\
12+
IFACE STATE RX_ERR RX_DROPS TX_ERR TX_DROPS DEBUG_0 DEBUG_2
13+
------------ ------- -------- ---------- -------- ---------- --------- ---------
14+
Ethernet0 U 10 100 0 0 80 20
15+
Ethernet4 U 0 1000 0 0 800 100
16+
Ethernet-BP0 U 0 1000 0 0 800 100
17+
Ethernet-BP4 U 0 1000 0 0 800 100
18+
19+
DEVICE DEBUG_1
20+
---------------- ---------
21+
sonic_drops_test 1000
22+
"""
23+
24+
dropstat_masic_result_asic1 = """\
25+
IFACE STATE RX_ERR RX_DROPS TX_ERR TX_DROPS DEBUG_0 DEBUG_2
26+
-------------- ------- -------- ---------- -------- ---------- --------- ---------
27+
Ethernet-BP256 U 10 100 0 0 80 20
28+
Ethernet-BP260 U 0 1000 0 0 800 100
29+
30+
DEVICE DEBUG_1
31+
---------------- ---------
32+
sonic_drops_test 1000
33+
"""
34+
35+
dropstat_masic_result_clear_all = """\
36+
IFACE STATE RX_ERR RX_DROPS TX_ERR TX_DROPS DEBUG_0 DEBUG_2
37+
------------ ------- -------- ---------- -------- ---------- --------- ---------
38+
Ethernet0 U 0 0 0 0 0 0
39+
Ethernet4 U 0 0 0 0 0 0
40+
Ethernet-BP0 U 0 0 0 0 0 0
41+
Ethernet-BP4 U 0 0 0 0 0 0
42+
43+
DEVICE DEBUG_1
44+
---------------- ---------
45+
sonic_drops_test 0
46+
IFACE STATE RX_ERR RX_DROPS TX_ERR TX_DROPS DEBUG_0 DEBUG_2
47+
-------------- ------- -------- ---------- -------- ---------- --------- ---------
48+
Ethernet-BP256 U 0 0 0 0 0 0
49+
Ethernet-BP260 U 0 0 0 0 0 0
50+
51+
DEVICE DEBUG_1
52+
---------------- ---------
53+
sonic_drops_test 0
54+
"""
55+
56+
57+
class TestMultiAsicDropstat(object):
58+
@classmethod
59+
def setup_class(cls):
60+
os.environ["PATH"] += os.pathsep + scripts_path
61+
os.environ["UTILITIES_UNIT_TESTING"] = "1"
62+
os.environ["UTILITIES_UNIT_TESTING_TOPOLOGY"] = "multi_asic"
63+
print("SETUP")
64+
65+
def test_show_dropcount_masic_asic0(self):
66+
os.environ["UTILITIES_UNIT_TESTING_DROPSTAT_CLEAN_CACHE"] = "1"
67+
return_code, result = get_result_and_return_code([
68+
'dropstat', '-c', 'show', '-n', 'asic0'
69+
])
70+
os.environ.pop("UTILITIES_UNIT_TESTING_DROPSTAT_CLEAN_CACHE")
71+
print("return_code: {}".format(return_code))
72+
print("result = {}".format(result))
73+
assert result == dropstat_masic_result_asic0 and return_code == 0
74+
75+
def test_show_dropcount_masic_all_and_clear(self):
76+
os.environ["UTILITIES_UNIT_TESTING_DROPSTAT_CLEAN_CACHE"] = "1"
77+
return_code, result = get_result_and_return_code([
78+
'dropstat', '-c', 'show'
79+
])
80+
os.environ.pop("UTILITIES_UNIT_TESTING_DROPSTAT_CLEAN_CACHE")
81+
print("return_code: {}".format(return_code))
82+
print("result = {}".format(result))
83+
assert result == dropstat_masic_result_asic0 + dropstat_masic_result_asic1
84+
assert return_code == 0
85+
86+
return_code, result = get_result_and_return_code([
87+
'dropstat', '-c', 'clear'
88+
])
89+
print("return_code: {}".format(return_code))
90+
print("result = {}".format(result))
91+
assert result == 'Cleared drop counters\n' and return_code == 0
92+
93+
return_code, result = get_result_and_return_code([
94+
'dropstat', '-c', 'show'
95+
])
96+
print("return_code: {}".format(return_code))
97+
print("result = {}".format(result))
98+
assert result == dropstat_masic_result_clear_all and return_code == 0
99+
100+
def test_show_dropcount_masic_invalid_ns(self):
101+
return_code, result = get_result_and_return_code([
102+
'dropstat', '-c', 'show', '-n', 'asic5'
103+
])
104+
print("return_code: {}".format(return_code))
105+
print("result = {}".format(result))
106+
assert return_code == 2
107+
assert "invalid choice: asic5" in result
108+
109+
def test_show_dropcount_version(self):
110+
return_code, result = get_result_and_return_code([
111+
'dropstat', '--version'
112+
])
113+
print("return_code: {}".format(return_code))
114+
print("result = {}".format(result))
115+
assert return_code == 0
116+
117+
@classmethod
118+
def teardown_class(cls):
119+
os.environ["PATH"] = os.pathsep.join(os.environ["PATH"].split(os.pathsep)[:-1])
120+
os.environ.pop("UTILITIES_UNIT_TESTING")
121+
os.environ.pop("UTILITIES_UNIT_TESTING_TOPOLOGY")
122+
print("TEARDOWN")

0 commit comments

Comments
 (0)