Skip to content

Commit d03ba4f

Browse files
[202012] [portstat, intfstat] added rates and utilization (sonic-net#1812)
#### What I did Backport the [pull/1750](sonic-net#1750) to `202012` branch Depends on [sonic-swss-common/pull/330](sonic-net/sonic-swss-common#330) According to [HLD](https://github.com/Azure/SONiC/blob/master/doc/rates-and-utilization/Rates_and_utilization_HLD.md) added calculation of rates and utilization columns to the `portstat` and `intfstat` scripts output #### How I did it Modified the `portstat` and `intfstat` scripts #### How to verify it Added UT
1 parent 499ad3f commit d03ba4f

File tree

10 files changed

+574
-246
lines changed

10 files changed

+574
-246
lines changed

config/main.py

+28
Original file line numberDiff line numberDiff line change
@@ -4626,5 +4626,33 @@ def delete(ctx):
46264626
sflow_tbl['global'].pop('agent_id')
46274627
config_db.set_entry('SFLOW', 'global', sflow_tbl['global'])
46284628

4629+
#
4630+
# 'rate' group ('config rate ...')
4631+
#
4632+
4633+
@config.group()
4634+
def rate():
4635+
"""Set port rates configuration."""
4636+
pass
4637+
4638+
4639+
@rate.command()
4640+
@click.argument('interval', metavar='<interval>', type=click.IntRange(min=1, max=1000), required=True)
4641+
@click.argument('rates_type', type=click.Choice(['all', 'port', 'rif']), default='all')
4642+
def smoothing_interval(interval, rates_type):
4643+
"""Set rates smoothing interval """
4644+
counters_db = swsssdk.SonicV2Connector()
4645+
counters_db.connect('COUNTERS_DB')
4646+
4647+
alpha = 2.0/(interval + 1)
4648+
4649+
if rates_type in ['port', 'all']:
4650+
counters_db.set('COUNTERS_DB', 'RATES:PORT', 'PORT_SMOOTH_INTERVAL', interval)
4651+
counters_db.set('COUNTERS_DB', 'RATES:PORT', 'PORT_ALPHA', alpha)
4652+
if rates_type in ['rif', 'all']:
4653+
counters_db.set('COUNTERS_DB', 'RATES:RIF', 'RIF_SMOOTH_INTERVAL', interval)
4654+
counters_db.set('COUNTERS_DB', 'RATES:RIF', 'RIF_ALPHA', alpha)
4655+
4656+
46294657
if __name__ == '__main__':
46304658
config()

scripts/intfstat

+81-56
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,37 @@ except KeyError:
2929
from collections import namedtuple, OrderedDict
3030
from natsort import natsorted
3131
from tabulate import tabulate
32-
from utilities_common.netstat import ns_diff, ns_brate, ns_prate, table_as_json, STATUS_NA
32+
from utilities_common.netstat import ns_diff, table_as_json, STATUS_NA, format_brate, format_prate
33+
from swsscommon.swsscommon import SonicV2Connector
34+
35+
nstat_fields = (
36+
"rx_b_ok",
37+
"rx_p_ok",
38+
"tx_b_ok",
39+
"tx_p_ok",
40+
"rx_b_err",
41+
"rx_p_err",
42+
"tx_b_err",
43+
"tx_p_err"
44+
)
45+
46+
NStats = namedtuple("NStats", nstat_fields)
3347

34-
NStats = namedtuple("NStats", "rx_b_ok, rx_p_ok, tx_b_ok, tx_p_ok,\
35-
rx_b_err, rx_p_err, tx_b_err, tx_p_err,")
48+
header = [
49+
'IFACE',
50+
'RX_OK',
51+
'RX_BPS',
52+
'RX_PPS',
53+
'RX_ERR',
54+
'TX_OK',
55+
'TX_BPS',
56+
'TX_PPS',
57+
'TX_ERR'
58+
]
3659

37-
header = ['IFACE', 'RX_OK', 'RX_BPS', 'RX_PPS', 'RX_ERR',
38-
'TX_OK', 'TX_BPS', 'TX_PPS', 'TX_ERR']
60+
rates_key_list = [ 'RX_BPS', 'RX_PPS', 'TX_BPS', 'TX_PPS']
61+
ratestat_fields = ("rx_bps", "rx_pps", "tx_bps", "tx_pps")
62+
RateStats = namedtuple("RateStats", ratestat_fields)
3963

4064
counter_names = (
4165
'SAI_ROUTER_INTERFACE_STAT_IN_OCTETS',
@@ -48,18 +72,10 @@ counter_names = (
4872
'SAI_ROUTER_INTERFACE_STAT_OUT_ERROR_PACKETS'
4973
)
5074

75+
RATES_TABLE_PREFIX = "RATES:"
5176

5277
COUNTER_TABLE_PREFIX = "COUNTERS:"
5378
COUNTERS_RIF_NAME_MAP = "COUNTERS_RIF_NAME_MAP"
54-
COUNTERS_RIF_TYPE_MAP = "COUNTERS_RIF_TYPE_MAP"
55-
56-
INTERFACE_TABLE_PREFIX = "PORT_TABLE:"
57-
INTF_STATUS_VALUE_UP = 'UP'
58-
INTF_STATUS_VALUE_DOWN = 'DOWN'
59-
60-
INTF_STATE_UP = 'U'
61-
INTF_STATE_DOWN = 'D'
62-
INTF_STATE_DISABLED = 'X'
6379

6480
class Intfstat(object):
6581
def __init__(self):
@@ -75,7 +91,7 @@ class Intfstat(object):
7591
"""
7692
Get the counters from specific table.
7793
"""
78-
fields = [STATUS_NA] * (len(header) - 1)
94+
fields = [STATUS_NA] * len(nstat_fields)
7995
for pos, counter_name in enumerate(counter_names):
8096
full_table_id = COUNTER_TABLE_PREFIX + table_id
8197
counter_data = self.db.get(self.db.COUNTERS_DB, full_table_id, counter_name)
@@ -84,13 +100,28 @@ class Intfstat(object):
84100
cntr = NStats._make(fields)
85101
return cntr
86102

103+
def get_rates(table_id):
104+
"""
105+
Get the rates from specific table.
106+
"""
107+
fields = ["0","0","0","0"]
108+
for pos, name in enumerate(rates_key_list):
109+
full_table_id = RATES_TABLE_PREFIX + table_id
110+
counter_data = self.db.get(self.db.COUNTERS_DB, full_table_id, name)
111+
if counter_data is None:
112+
fields[pos] = STATUS_NA
113+
elif fields[pos] != STATUS_NA:
114+
fields[pos] = float(counter_data)
115+
cntr = RateStats._make(fields)
116+
return cntr
117+
87118
# Build a dictionary of the stats
88119
cnstat_dict = OrderedDict()
89120
cnstat_dict['time'] = datetime.datetime.now()
121+
ratestat_dict = OrderedDict()
90122

91123
# Get the info from database
92-
counter_rif_name_map = self.db.get_all(self.db.COUNTERS_DB, COUNTERS_RIF_NAME_MAP);
93-
124+
counter_rif_name_map = self.db.get_all(self.db.COUNTERS_DB, COUNTERS_RIF_NAME_MAP)
94125

95126
if counter_rif_name_map is None:
96127
print("No %s in the DB!" % COUNTERS_RIF_NAME_MAP)
@@ -102,31 +133,15 @@ class Intfstat(object):
102133

103134
if rif:
104135
cnstat_dict[rif] = get_counters(counter_rif_name_map[rif])
105-
return cnstat_dict
136+
ratestat_dict[rif] = get_rates(counter_rif_name_map[rif])
137+
return cnstat_dict, ratestat_dict
106138

107139
for rif in natsorted(counter_rif_name_map):
108140
cnstat_dict[rif] = get_counters(counter_rif_name_map[rif])
109-
return cnstat_dict
110-
111-
def get_intf_state(self, port_name):
112-
"""
113-
Get the port state
114-
"""
115-
full_table_id = PORT_STATUS_TABLE_PREFIX + port_name
116-
admin_state = self.db.get(self.db.APPL_DB, full_table_id, PORT_ADMIN_STATUS_FIELD)
117-
oper_state = self.db.get(self.db.APPL_DB, full_table_id, PORT_OPER_STATUS_FIELD)
118-
if admin_state is None or oper_state is None:
119-
return STATUS_NA
120-
elif admin_state.upper() == PORT_STATUS_VALUE_DOWN:
121-
return PORT_STATE_DISABLED
122-
elif admin_state.upper() == PORT_STATUS_VALUE_UP and oper_state.upper() == PORT_STATUS_VALUE_UP:
123-
return PORT_STATE_UP
124-
elif admin_state.upper() == PORT_STATUS_VALUE_UP and oper_state.upper() == PORT_STATUS_VALUE_DOWN:
125-
return PORT_STATE_DOWN
126-
else:
127-
return STATUS_NA
141+
ratestat_dict[rif] = get_rates(counter_rif_name_map[rif])
142+
return cnstat_dict, ratestat_dict
128143

129-
def cnstat_print(self, cnstat_dict, use_json):
144+
def cnstat_print(self, cnstat_dict, ratestat_dict, use_json):
130145
"""
131146
Print the cnstat.
132147
"""
@@ -136,16 +151,25 @@ class Intfstat(object):
136151
if key == 'time':
137152
continue
138153

139-
table.append((key, data.rx_p_ok, STATUS_NA, STATUS_NA, data.rx_p_err,
140-
data.tx_p_ok, STATUS_NA, STATUS_NA, data.tx_p_err))
154+
rates = ratestat_dict.get(key, RateStats._make([STATUS_NA] * len(rates_key_list)))
155+
156+
table.append((key,
157+
data.rx_p_ok,
158+
format_brate(rates.rx_bps),
159+
format_prate(rates.rx_pps),
160+
data.rx_p_err,
161+
data.tx_p_ok,
162+
format_brate(rates.tx_bps),
163+
format_prate(rates.tx_pps),
164+
data.tx_p_err))
141165

142166
if use_json:
143167
print(table_as_json(table, header))
144168

145169
else:
146170
print(tabulate(table, header, tablefmt='simple', stralign='right'))
147171

148-
def cnstat_diff_print(self, cnstat_new_dict, cnstat_old_dict, use_json):
172+
def cnstat_diff_print(self, cnstat_new_dict, cnstat_old_dict, ratestat_dict, use_json):
149173
"""
150174
Print the difference between two cnstat results.
151175
"""
@@ -154,33 +178,34 @@ class Intfstat(object):
154178

155179
for key, cntr in cnstat_new_dict.items():
156180
if key == 'time':
157-
time_gap = cnstat_new_dict.get('time') - cnstat_old_dict.get('time')
158-
time_gap = time_gap.total_seconds()
159181
continue
160182
old_cntr = None
161183
if key in cnstat_old_dict:
162184
old_cntr = cnstat_old_dict.get(key)
163185

186+
rates = ratestat_dict.get(key, RateStats._make([STATUS_NA] * len(rates_key_list)))
187+
164188
if old_cntr is not None:
165189
table.append((key,
166190
ns_diff(cntr.rx_p_ok, old_cntr.rx_p_ok),
167-
ns_brate(cntr.rx_b_ok, old_cntr.rx_b_ok, time_gap),
168-
ns_prate(cntr.rx_p_ok, old_cntr.rx_p_ok, time_gap),
191+
format_brate(rates.rx_bps),
192+
format_prate(rates.rx_pps),
169193
ns_diff(cntr.rx_p_err, old_cntr.rx_p_err),
170194
ns_diff(cntr.tx_p_ok, old_cntr.tx_p_ok),
171-
ns_brate(cntr.tx_b_ok, old_cntr.tx_b_ok, time_gap),
172-
ns_prate(cntr.tx_p_ok, old_cntr.tx_p_ok, time_gap),
195+
format_brate(rates.tx_bps),
196+
format_prate(rates.tx_pps),
173197
ns_diff(cntr.tx_p_err, old_cntr.tx_p_err)))
174198
else:
175199
table.append((key,
176200
cntr.rx_p_ok,
177-
STATUS_NA,
178-
STATUS_NA,
201+
format_brate(rates.rx_bps),
202+
format_prate(rates.rx_pps),
179203
cntr.rx_p_err,
180204
cntr.tx_p_ok,
181-
STATUS_NA,
182-
STATUS_NA,
205+
format_brate(rates.tx_bps),
206+
format_prate(rates.tx_pps),
183207
cntr.tx_p_err))
208+
184209
if use_json:
185210
print(table_as_json(table, header))
186211
else:
@@ -292,7 +317,7 @@ def main():
292317
sys.exit(0)
293318

294319
intfstat = Intfstat()
295-
cnstat_dict = intfstat.get_cnstat(rif=interface_name)
320+
cnstat_dict, ratestat_dict = intfstat.get_cnstat(rif=interface_name)
296321

297322
# At this point, either we'll create a file or open an existing one.
298323
if not os.path.exists(cnstat_dir):
@@ -346,7 +371,7 @@ def main():
346371
if interface_name:
347372
intfstat.cnstat_single_interface(interface_name, cnstat_dict, cnstat_cached_dict)
348373
else:
349-
intfstat.cnstat_diff_print(cnstat_dict, cnstat_cached_dict, use_json)
374+
intfstat.cnstat_diff_print(cnstat_dict, cnstat_cached_dict, ratestat_dict, use_json)
350375
except IOError as e:
351376
print(e.errno, e)
352377
else:
@@ -357,16 +382,16 @@ def main():
357382
if interface_name:
358383
intfstat.cnstat_single_interface(interface_name, cnstat_dict, None)
359384
else:
360-
intfstat.cnstat_print(cnstat_dict, use_json)
385+
intfstat.cnstat_print(cnstat_dict, ratestat_dict, use_json)
361386
else:
362387
#wait for the specified time and then gather the new stats and output the difference.
363388
time.sleep(wait_time_in_seconds)
364389
print("The rates are calculated within %s seconds period" % wait_time_in_seconds)
365-
cnstat_new_dict = intfstat.get_cnstat(rif=interface_name)
390+
cnstat_new_dict, ratestat_new_dict = intfstat.get_cnstat(rif=interface_name)
366391
if interface_name:
367392
intfstat.cnstat_single_interface(interface_name, cnstat_new_dict, cnstat_dict)
368393
else:
369-
intfstat.cnstat_diff_print(cnstat_new_dict, cnstat_dict, use_json)
394+
intfstat.cnstat_diff_print(cnstat_new_dict, cnstat_dict, ratestat_new_dict, use_json)
370395

371396
if __name__ == "__main__":
372397
main()

0 commit comments

Comments
 (0)