Skip to content

Commit 3f2d7bb

Browse files
hui-malguohan
authored andcommitted
[pfcstat]: create python cli tool to show pfc counters (sonic-net#233)
1 parent 6407444 commit 3f2d7bb

File tree

1 file changed

+260
-0
lines changed

1 file changed

+260
-0
lines changed

scripts/pfcstat

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
#!/usr/bin/env python
2+
3+
#####################################################################
4+
#
5+
# pfcstat is a tool for summarizing Priority-based Flow Control (PFC) statistics.
6+
#
7+
#####################################################################
8+
9+
import swsssdk
10+
import sys
11+
import argparse
12+
import cPickle as pickle
13+
import datetime
14+
import getopt
15+
import json
16+
import os.path
17+
import time
18+
19+
from collections import namedtuple, OrderedDict
20+
from natsort import natsorted
21+
from tabulate import tabulate
22+
23+
24+
PStats = namedtuple("PStats", "pfc0, pfc1, pfc2, pfc3, pfc4, pfc5, pfc6, pfc7")
25+
header_Rx = ['Port Rx', 'PFC0', 'PFC1', 'PFC2', 'PFC3', 'PFC4', 'PFC5', 'PFC6', 'PFC7']
26+
27+
header_Tx = ['Port Tx', 'PFC0', 'PFC1', 'PFC2', 'PFC3', 'PFC4', 'PFC5', 'PFC6', 'PFC7']
28+
29+
counter_bucket_rx_dict = {
30+
'SAI_PORT_STAT_PFC_0_RX_PKTS': 0,
31+
'SAI_PORT_STAT_PFC_1_RX_PKTS': 1,
32+
'SAI_PORT_STAT_PFC_2_RX_PKTS': 2,
33+
'SAI_PORT_STAT_PFC_3_RX_PKTS': 3,
34+
'SAI_PORT_STAT_PFC_4_RX_PKTS': 4,
35+
'SAI_PORT_STAT_PFC_5_RX_PKTS': 5,
36+
'SAI_PORT_STAT_PFC_6_RX_PKTS': 6,
37+
'SAI_PORT_STAT_PFC_7_RX_PKTS': 7
38+
}
39+
40+
counter_bucket_tx_dict = {
41+
'SAI_PORT_STAT_PFC_0_TX_PKTS': 0,
42+
'SAI_PORT_STAT_PFC_1_TX_PKTS': 1,
43+
'SAI_PORT_STAT_PFC_2_TX_PKTS': 2,
44+
'SAI_PORT_STAT_PFC_3_TX_PKTS': 3,
45+
'SAI_PORT_STAT_PFC_4_TX_PKTS': 4,
46+
'SAI_PORT_STAT_PFC_5_TX_PKTS': 5,
47+
'SAI_PORT_STAT_PFC_6_TX_PKTS': 6,
48+
'SAI_PORT_STAT_PFC_7_TX_PKTS': 7
49+
}
50+
51+
STATUS_NA = 'N/A'
52+
53+
COUNTER_TABLE_PREFIX = "COUNTERS:"
54+
COUNTERS_PORT_NAME_MAP = "COUNTERS_PORT_NAME_MAP"
55+
56+
class Pfcstat(object):
57+
def __init__(self):
58+
self.db = swsssdk.SonicV2Connector(host='127.0.0.1')
59+
self.db.connect(self.db.COUNTERS_DB)
60+
61+
def get_cnstat(self, rx):
62+
"""
63+
Get the counters info from database.
64+
"""
65+
def get_counters(table_id):
66+
"""
67+
Get the counters from specific table.
68+
"""
69+
fields = ["0","0","0","0","0","0","0","0"]
70+
if rx:
71+
bucket_dict = counter_bucket_rx_dict
72+
else:
73+
bucket_dict = counter_bucket_tx_dict
74+
for counter_name, pos in bucket_dict.iteritems():
75+
full_table_id = COUNTER_TABLE_PREFIX + table_id
76+
counter_data = self.db.get(self.db.COUNTERS_DB, full_table_id, counter_name)
77+
if counter_data is None:
78+
fields[pos] = STATUS_NA
79+
else:
80+
fields[pos] = str(int(counter_data))
81+
cntr = PStats._make(fields)
82+
return cntr
83+
84+
# Get the info from database
85+
counter_port_name_map = self.db.get_all(self.db.COUNTERS_DB, COUNTERS_PORT_NAME_MAP)
86+
# Build a dictionary of the stats
87+
cnstat_dict = OrderedDict()
88+
cnstat_dict['time'] = datetime.datetime.now()
89+
if counter_port_name_map is None:
90+
return cnstat_dict
91+
for port in natsorted(counter_port_name_map):
92+
cnstat_dict[port] = get_counters(counter_port_name_map[port])
93+
return cnstat_dict
94+
95+
def cnstat_print(self, cnstat_dict, rx):
96+
"""
97+
Print the cnstat.
98+
"""
99+
table = []
100+
101+
for key, data in cnstat_dict.iteritems():
102+
if key == 'time':
103+
continue
104+
table.append((key,
105+
data.pfc0, data.pfc1,
106+
data.pfc2, data.pfc3,
107+
data.pfc4, data.pfc5,
108+
data.pfc6, data.pfc7))
109+
110+
if rx:
111+
print tabulate(table, header_Rx, tablefmt='simple', stralign='right')
112+
else:
113+
print tabulate(table, header_Tx, tablefmt='simple', stralign='right')
114+
115+
def cnstat_diff_print(self, cnstat_new_dict, cnstat_old_dict, rx):
116+
"""
117+
Print the difference between two cnstat results.
118+
"""
119+
def ns_diff(newstr, oldstr):
120+
"""
121+
Calculate the diff.
122+
"""
123+
if newstr == STATUS_NA or oldstr == STATUS_NA:
124+
return STATUS_NA
125+
else:
126+
new, old = int(newstr), int(oldstr)
127+
return '{:,}'.format(new - old)
128+
129+
table = []
130+
131+
for key, cntr in cnstat_new_dict.iteritems():
132+
if key == 'time':
133+
continue
134+
old_cntr = None
135+
if key in cnstat_old_dict:
136+
old_cntr = cnstat_old_dict.get(key)
137+
138+
if old_cntr is not None:
139+
table.append((key,
140+
ns_diff(cntr.pfc0, old_cntr.pfc0),
141+
ns_diff(cntr.pfc1, old_cntr.pfc1),
142+
ns_diff(cntr.pfc2, old_cntr.pfc2),
143+
ns_diff(cntr.pfc3, old_cntr.pfc3),
144+
ns_diff(cntr.pfc4, old_cntr.pfc4),
145+
ns_diff(cntr.pfc5, old_cntr.pfc5),
146+
ns_diff(cntr.pfc6, old_cntr.pfc6),
147+
ns_diff(cntr.pfc7, old_cntr.pfc7)))
148+
else:
149+
table.append((key,
150+
cntr.pfc0, cntr.pfc1,
151+
cntr.pfc2, cntr.pfc3,
152+
cntr.pfc4, cntr.pfc5,
153+
cntr.pfc6, cntr.pfc7))
154+
155+
if rx:
156+
print tabulate(table, header_Rx, tablefmt='simple', stralign='right')
157+
else:
158+
print tabulate(table, header_Tx, tablefmt='simple', stralign='right')
159+
160+
def main():
161+
parser = argparse.ArgumentParser(description='Display the pfc counters',
162+
version='1.0.0',
163+
formatter_class=argparse.RawTextHelpFormatter,
164+
epilog="""
165+
Examples:
166+
pfcstat
167+
pfcstat -c
168+
pfcstat -d
169+
""")
170+
171+
parser.add_argument('-c', '--clear', action='store_true', help='Clear previous stats and save new ones')
172+
parser.add_argument('-d', '--delete', action='store_true', help='Delete saved stats')
173+
args = parser.parse_args()
174+
175+
save_fresh_stats = args.clear
176+
delete_all_stats = args.delete
177+
178+
uid = str(os.getuid())
179+
cnstat_file = uid
180+
181+
cnstat_dir = "/tmp/pfcstat-" + uid
182+
cnstat_fqn_file_rx = cnstat_dir + "/" + cnstat_file + "rx"
183+
cnstat_fqn_file_tx = cnstat_dir + "/" + cnstat_file + "tx"
184+
185+
pfcstat = Pfcstat()
186+
187+
if delete_all_stats:
188+
for file in os.listdir(cnstat_dir):
189+
os.remove(cnstat_dir + "/" + file)
190+
191+
try:
192+
os.rmdir(cnstat_dir)
193+
sys.exit(0)
194+
except IOError as e:
195+
print e.errno, e
196+
sys.exit(e)
197+
198+
"""
199+
Get the counters of pfc rx counter
200+
"""
201+
cnstat_dict_rx = pfcstat.get_cnstat(True)
202+
203+
"""
204+
Get the counters of pfc tx counter
205+
"""
206+
cnstat_dict_tx = pfcstat.get_cnstat(False)
207+
208+
# At this point, either we'll create a file or open an existing one.
209+
if not os.path.exists(cnstat_dir):
210+
try:
211+
os.makedirs(cnstat_dir)
212+
except IOError as e:
213+
print e.errno, e
214+
sys.exit(1)
215+
216+
if save_fresh_stats:
217+
try:
218+
pickle.dump(cnstat_dict_rx, open(cnstat_fqn_file_rx, 'w'))
219+
pickle.dump(cnstat_dict_tx, open(cnstat_fqn_file_tx, 'w'))
220+
except IOError as e:
221+
print e.errno, e
222+
sys.exit(e.errno)
223+
else:
224+
print "Clear saved counters"
225+
sys.exit(0)
226+
227+
228+
"""
229+
Print the counters of pfc rx counter
230+
"""
231+
cnstat_cached_dict = OrderedDict()
232+
if os.path.isfile(cnstat_fqn_file_rx):
233+
try:
234+
cnstat_cached_dict = pickle.load(open(cnstat_fqn_file_rx, 'r'))
235+
print "Last cached time was " + str(cnstat_cached_dict.get('time'))
236+
pfcstat.cnstat_diff_print(cnstat_dict_rx, cnstat_cached_dict, True)
237+
except IOError as e:
238+
print e.errno, e
239+
else:
240+
pfcstat.cnstat_print(cnstat_dict_rx, True)
241+
242+
print
243+
"""
244+
Print the counters of pfc tx counter
245+
"""
246+
cnstat_cached_dict = OrderedDict()
247+
if os.path.isfile(cnstat_fqn_file_tx):
248+
try:
249+
cnstat_cached_dict = pickle.load(open(cnstat_fqn_file_tx, 'r'))
250+
print "Last cached time was " + str(cnstat_cached_dict.get('time'))
251+
pfcstat.cnstat_diff_print(cnstat_dict_tx, cnstat_cached_dict, False)
252+
except IOError as e:
253+
print e.errno, e
254+
else:
255+
pfcstat.cnstat_print(cnstat_dict_tx, False)
256+
257+
sys.exit(0)
258+
259+
if __name__ == "__main__":
260+
main()

0 commit comments

Comments
 (0)