diff --git a/clear/main.py b/clear/main.py index 9798b7b5a5..17e91ced7d 100755 --- a/clear/main.py +++ b/clear/main.py @@ -4,6 +4,8 @@ import os import subprocess from click_default_group import DefaultGroup +from natsort import natsorted +from swsssdk import ConfigDBConnector try: # noinspection PyPep8Naming @@ -245,6 +247,63 @@ def clear_pwm_pg_shared(): command = 'watermarkstat -c -p -t pg_shared' run_command(command) +def interface_name_is_valid(interface_name): + """Check if the interface name is valid + """ + config_db = ConfigDBConnector() + config_db.connect() + port_dict = config_db.get_table('PORT') + port_channel_dict = config_db.get_table('PORTCHANNEL') + + if interface_name is not None: + if not port_dict: + click.echo("port_dict is None!") + raise click.Abort() + for port_name in port_dict.keys(): + if interface_name == port_name: + return True + if port_channel_dict: + for port_channel_name in port_channel_dict.keys(): + if interface_name == port_channel_name: + return True + return False + +@priority_group.command('threshold') +@click.argument('port_name', metavar='', required=False) +@click.argument('pg_index', metavar='', required=False, type=int) +@click.argument('threshold_type', required=False, type=click.Choice(['shared', 'headroom'])) +@click.pass_context +def threshold(ctx, port_name, pg_index, threshold_type): + """ Clear priority group threshold """ + # If no params are provided, clear all priority-group entries. + config_db = ConfigDBConnector() + config_db.connect() + + all = False + + if port_name is None and pg_index is None and threshold_type is None: + # clear all entries. + key = 'priority-group' + all = True + elif port_name is None or pg_index is None or threshold_type is None: + ctx.fail("port_name, pg_index and threshold_type are mandatory parameters.") + else: + if pg_index not in range(0, 8): + ctx.fail("priority-group must be in range 0-7") + if interface_name_is_valid(port_name) is False: + ctx.fail("Interface name is invalid!!") + key = 'priority-group' + '|' + threshold_type + '|' + port_name + '|' + str(pg_index) + + if all is True: + entry_table = config_db.get_keys('THRESHOLD_TABLE') + # Clear data for all keys + for k in natsorted(entry_table): + if k[0] == 'priority-group': + config_db.set_entry('THRESHOLD_TABLE', k, None) + else: + entry = config_db.get_entry('THRESHOLD_TABLE', key) + if entry: + config_db.set_entry('THRESHOLD_TABLE', key, None) @cli.group() def queue(): @@ -287,6 +346,44 @@ def clear_pwm_q_multi(): command = 'watermarkstat -c -p -t q_shared_multi' run_command(command) +@queue.command('threshold') +@click.argument('port_name', metavar='', required=False) +@click.argument('queue_index', metavar='', required=False, type=int) +@click.argument('queue_type', required=False, type=click.Choice(['unicast', 'multicast'])) +@click.pass_context +def threshold(ctx, port_name, queue_index, queue_type): + """ Clear queue threshold for a queue on a port """ + # If no params are provided, clear all priority-group entries. + config_db = ConfigDBConnector() + config_db.connect() + + all = False + + if port_name is None and queue_index is None and queue_type is None: + # clear all entries. + key = 'queue' + all = True + elif port_name is None or queue_index is None or queue_type is None: + ctx.fail("port_name, queue_index and queue_type are mandatory parameters.") + else: + if queue_index not in range(0, 8): + ctx.fail("queue index must be in range 0-7") + if interface_name_is_valid(port_name) is False: + ctx.fail("Interface name is invalid!!") + key = 'queue' + '|' + queue_type + '|' + port_name + '|' + str(queue_index) + + if all is True: + entry_table = config_db.get_keys('THRESHOLD_TABLE') + # Clear data for all keys + for k in natsorted(entry_table): + if k[0] == 'queue': + config_db.set_entry('THRESHOLD_TABLE', k, None) + else: + entry = config_db.get_entry('THRESHOLD_TABLE', key) + if entry: + config_db.set_entry('THRESHOLD_TABLE', key, None) + + # # 'arp' command #### # @@ -380,6 +477,22 @@ def line(linenum): cmd = "consutil clear " + str(linenum) run_command(cmd) + +@cli.group('threshold') +def threshold(): + """Clear threshold breach entries""" + pass + +@threshold.command() +@click.argument('id', type=int, required=False) +def breach(id): + """Clear threshold breach entries all | event-id""" + if id is not None: + cmd = "thresholdbreach -c -cnt {}".format(id) + else: + cmd = 'thresholdbreach -c' + run_command(cmd) + # # 'nat' group ("clear nat ...") # diff --git a/config/main.py b/config/main.py index 28eebf8c0b..321432b12a 100755 --- a/config/main.py +++ b/config/main.py @@ -467,6 +467,7 @@ def _stop_services(): 'pmon', 'bgp', 'hostcfgd', + 'tam', 'nat' ] @@ -492,7 +493,8 @@ def _reset_failed_services(): 'syncd', 'teamd', 'nat', - 'sflow' + 'sflow', + 'tam' ] execute_systemctl(services_to_reset, SYSTEMCTL_ACTION_RESET_FAILED) @@ -511,6 +513,7 @@ def _restart_services(): 'lldp', 'hostcfgd', 'nat', + 'tam', 'sflow', ] @@ -2279,6 +2282,75 @@ def naming_mode_alias(): """Set CLI interface naming mode to ALIAS (Vendor port alias)""" set_interface_naming_mode('alias') +@config.group('priority-group') +def priority_group(): + """ Configure priority group thresholds """ + pass + +@priority_group.command('threshold') +@click.argument('port_name', metavar='', required=True) +@click.argument('pg_index', metavar='', required=True, type=int) +@click.argument('threshold_type', type=click.Choice(['shared', 'headroom'])) +@click.argument('threshold_value', metavar='', required=True, type=int) +@click.pass_context +def threshold(ctx, port_name, pg_index, threshold_type, threshold_value): + """ Configure priority group threshold value for a priority group on a port """ + + config_db = ConfigDBConnector() + config_db.connect() + + if pg_index not in range(0, 8): + ctx.fail("priority-group must be in range 0-7") + + if threshold_value not in range(1, 101): + ctx.fail("threshold value must be in range 1-100") + + if interface_name_is_valid(config_db, port_name) is False: + ctx.fail("Interface name is invalid!!") + + key = 'priority-group' + '|' + threshold_type + '|' + port_name + '|' + str(pg_index) + entry = config_db.get_entry('THRESHOLD_TABLE', key) + if entry is None: + config_db.set_entry('THRESHOLD_TABLE', key, {'threshold' : threshold_value}) + else: + entry_value = entry.get('threshold', []) + if entry_value != threshold_value: + config_db.mod_entry('THRESHOLD_TABLE', key, {'threshold' : threshold_value}) + +@config.group('queue') +def queue(): + """ Configure queue thresholds """ + pass + +@queue.command('threshold') +@click.argument('port_name', metavar='', required=True) +@click.argument('queue_index', metavar='', required=True, type=int) +@click.argument('queue_type', type=click.Choice(['unicast', 'multicast'])) +@click.argument('threshold_value', metavar='', required=True, type=int) +@click.pass_context +def threshold(ctx, port_name, queue_index, queue_type, threshold_value): + """ Configure queue threshold value for a queue on a port """ + config_db = ConfigDBConnector() + config_db.connect() + + if queue_index not in range(0, 8): + ctx.fail("queue index must be in range 0-7") + + if threshold_value not in range(1, 101): + ctx.fail("threshold value must be in range 1-100") + + if interface_name_is_valid(config_db, port_name) is False: + ctx.fail("Interface name is invalid!!") + + key = 'queue' + '|' + queue_type + '|' + port_name + '|' + str(queue_index) + entry = config_db.get_entry('THRESHOLD_TABLE', key) + if entry is None: + config_db.set_entry('THRESHOLD_TABLE', key, {'threshold' : threshold_value}) + else: + entry_value = entry.get('threshold', []) + if entry_value != threshold_value: + config_db.mod_entry('THRESHOLD_TABLE', key, {'threshold' : threshold_value}) + @config.group() def ztp(): """ Configure Zero Touch Provisioning """ @@ -2314,6 +2386,7 @@ def enable(enable): command = "ztp enable" run_command(command, display_cmd=True) + # # 'syslog' group ('config syslog ...') # diff --git a/scripts/fast-reboot b/scripts/fast-reboot index faea099806..be560334f6 100755 --- a/scripts/fast-reboot +++ b/scripts/fast-reboot @@ -430,6 +430,11 @@ debug "Stopped bgp ..." docker kill lldp &> /dev/null || debug "Docker lldp is not running ($?) ..." systemctl stop lldp +# Kill tam docker +debug "Stopping tam" +docker kill tam > /dev/null +debug "Stopped tam" + if [[ "$REBOOT_TYPE" = "fast-reboot" ]]; then # Kill teamd processes inside of teamd container with SIGUSR2 to allow them to send last LACP frames # We call `docker kill teamd` to ensure the container stops as quickly as possible, diff --git a/scripts/thresholdbreach b/scripts/thresholdbreach new file mode 100755 index 0000000000..94d631be97 --- /dev/null +++ b/scripts/thresholdbreach @@ -0,0 +1,120 @@ +#!/usr/bin/python + +############################################################################ +# +# thresholdbreach is a tool for displaying threshold breaches. +# +############################################################################ + +import argparse +import getopt +import json +import sys +import swsssdk +from natsort import natsorted +from tabulate import tabulate +from swsssdk import ConfigDBConnector + + +THRESHOLD_BREACH_TABLE_PREFIX = "THRESHOLD_BREACH" +header = ['Event-id', 'Buffer', 'Type', 'Port', 'Index', 'Breach Value(%)', 'Breach Value(bytes)', 'Time-stamp'] + +class Thresholdbreach(object): + + def __init__(self): + # connect COUNTER DB + self.counters_db = ConfigDBConnector() + self.counters_db.db_connect('COUNTERS_DB') + + def get_threshold_breach_info(self, k): + breach_data = {} + key = THRESHOLD_BREACH_TABLE_PREFIX + ':' + k + # k is of the format "breach-report:1". Extract event-id "1" + id = k.split(':') + eventid = id[1] + breach_data['eventid'] = eventid + data = self.counters_db.get_all(self.counters_db.COUNTERS_DB, key) + if data is not None: + breach_data['buffer'] = data['buffer'] + breach_data['type'] = data['type'] + breach_data['port'] = data['port'] + breach_data['index'] = data['index'] + breach_data['breach_value'] = data['breach_value'] + breach_data['time-stamp'] = data['time-stamp'] + if data['type'] == 'shared': + breach_data['counter'] = data['SAI_INGRESS_PRIORITY_GROUP_STAT_SHARED_WATERMARK_BYTES'] + elif data['type'] == 'headroom': + breach_data['counter'] = data['SAI_INGRESS_PRIORITY_GROUP_STAT_XOFF_ROOM_WATERMARK_BYTES'] + elif data['type'] == 'unicast': + breach_data['counter'] = data['SAI_QUEUE_STAT_SHARED_WATERMARK_BYTES'] + elif data['type'] == 'multicast': + breach_data['counter'] = data['SAI_QUEUE_STAT_SHARED_WATERMARK_BYTES'] + return breach_data + + def get_print_all_threshold_breach(self, count): + table_data = self.counters_db.get_keys(THRESHOLD_BREACH_TABLE_PREFIX) + # Get data for all keys + table = [] + iter = 0 + maxcount = int(count) + + for k in natsorted(table_data, reverse=True): + if (maxcount != 0) and (iter == maxcount): + break + + if k == "event-id": + continue + + iter = iter + 1 + data = self.get_threshold_breach_info(k) + table.append((data['eventid'], data['buffer'], data['type'], data['port'], data['index'], + data['breach_value'], data['counter'], data['time-stamp'])) + + print tabulate(table, header, tablefmt='simple', stralign='right') + return + + def clear_all_threshold_breach(self, count): + if count is 0: + table_data = self.counters_db.get_keys(THRESHOLD_BREACH_TABLE_PREFIX) + # Get data for all keys + for k in natsorted(table_data): + self.counters_db.set_entry(THRESHOLD_BREACH_TABLE_PREFIX, k, None) + else: + key = "breach-report" + ":" + str(count) + entry = self.counters_db.get_entry(THRESHOLD_BREACH_TABLE_PREFIX, key) + if entry: + self.counters_db.set_entry(THRESHOLD_BREACH_TABLE_PREFIX, key, None) + return + +def main(): + + parser = argparse.ArgumentParser(description='Display the queue/pg threshold breaches', + version='1.0.0', + formatter_class=argparse.RawTextHelpFormatter, + epilog=""") +Examples: + thresholdbreach + thresholdbreach -c + thresholdbreach -cnt count +""") + + parser.add_argument('-c', '--clear', action='store_true', help='Clear threshold breach entries') + parser.add_argument('-cnt', '--count', required=False, help='Display threshold breach entries as per count') + args = parser.parse_args() + + thresholdbreach = Thresholdbreach() + count = 0 + + if args.clear: + if args.count: + count = args.count + thresholdbreach.clear_all_threshold_breach(count) + else: + if args.count: + count = args.count + thresholdbreach.get_print_all_threshold_breach(count) + + sys.exit(0) + +if __name__ == "__main__": + main() diff --git a/scripts/thresholdcfg b/scripts/thresholdcfg new file mode 100644 index 0000000000..832af51e52 --- /dev/null +++ b/scripts/thresholdcfg @@ -0,0 +1,236 @@ +#!/usr/bin/env python + +############################################################################ +# +# thresholdcfg is a tool for displaying queue and priority-group thresholds. +# +############################################################################ + +import argparse +import getopt +import json +import sys +import swsssdk +from swsssdk import ConfigDBConnector +from natsort import natsorted +from tabulate import tabulate + +headerPg = ['Port', 'PG0', 'PG1', 'PG2', 'PG3', 'PG4', 'PG5', 'PG6', 'PG7'] +headerUc = ['Port', 'UC0', 'UC1', 'UC2', 'UC3', 'UC4', 'UC5', 'UC6', 'UC7'] +headerMc = ['Port', 'MC0', 'MC1', 'MC2', 'MC3', 'MC4', 'MC5', 'MC6', 'MC7'] + + +THRESHOLD_DEFAULT = 0 + +QUEUE_TYPE_MC = 'MC' +QUEUE_TYPE_UC = 'UC' +QUEUE_TYPE_ALL = 'ALL' +SAI_QUEUE_TYPE_MULTICAST = "SAI_QUEUE_TYPE_MULTICAST" +SAI_QUEUE_TYPE_UNICAST = "SAI_QUEUE_TYPE_UNICAST" +SAI_QUEUE_TYPE_ALL = "SAI_QUEUE_TYPE_ALL" + +THRESHOLD_TABLE_PREFIX = "THRESHOLD|" + +COUNTERS_PORT_NAME_MAP = "COUNTERS_PORT_NAME_MAP" +COUNTERS_QUEUE_NAME_MAP = "COUNTERS_QUEUE_NAME_MAP" +COUNTERS_QUEUE_TYPE_MAP = "COUNTERS_QUEUE_TYPE_MAP" +COUNTERS_QUEUE_INDEX_MAP = "COUNTERS_QUEUE_INDEX_MAP" +COUNTERS_QUEUE_PORT_MAP = "COUNTERS_QUEUE_PORT_MAP" +COUNTERS_PG_NAME_MAP = "COUNTERS_PG_NAME_MAP" +COUNTERS_PG_PORT_MAP = "COUNTERS_PG_PORT_MAP" +COUNTERS_PG_INDEX_MAP = "COUNTERS_PG_INDEX_MAP" + + +class Thresholdcfg(object): + + def __init__(self): + # connect COUNTER DB + self.counters_db = swsssdk.SonicV2Connector(host='127.0.0.1') + self.counters_db.connect(self.counters_db.COUNTERS_DB) + + # connect APP DB + self.config_db = ConfigDBConnector() + self.config_db.connect() + + def get_queue_type(table_id): + queue_type = self.counters_db.get(self.counters_db.COUNTERS_DB, COUNTERS_QUEUE_TYPE_MAP, table_id) + if queue_type is None: + print "Queue Type is not available!", table_id + sys.exit(1) + elif queue_type == SAI_QUEUE_TYPE_MULTICAST: + return QUEUE_TYPE_MC + + elif queue_type == SAI_QUEUE_TYPE_UNICAST: + return QUEUE_TYPE_UC + elif queue_type == SAI_QUEUE_TYPE_ALL: + return QUEUE_TYPE_ALL + else: + print "Queue Type is invalid:", table_id, queue_type + sys.exit(1) + + def get_queue_port(table_id): + port_table_id = self.counters_db.get(self.counters_db.COUNTERS_DB, COUNTERS_QUEUE_PORT_MAP, table_id) + if port_table_id is None: + print "Port is not available!", table_id + sys.exit(1) + + return port_table_id + + def get_pg_port(table_id): + port_table_id = self.counters_db.get(self.counters_db.COUNTERS_DB, COUNTERS_PG_PORT_MAP, table_id) + if port_table_id is None: + print "Port is not available!", table_id + sys.exit(1) + + return port_table_id + + # Get all ports + self.counter_port_name_map = self.counters_db.get_all(self.counters_db.COUNTERS_DB, COUNTERS_PORT_NAME_MAP) + if self.counter_port_name_map is None: + print "COUNTERS_PORT_NAME_MAP is empty!" + sys.exit(1) + + self.port_uc_queues_map = {} + self.port_mc_queues_map = {} + self.port_pg_map = {} + self.port_name_map = {} + + for port in self.counter_port_name_map: + self.port_uc_queues_map[port] = {} + self.port_mc_queues_map[port] = {} + self.port_pg_map[port] = {} + self.port_name_map[self.counter_port_name_map[port]] = port + + # Get Queues for each port + counter_queue_name_map = self.counters_db.get_all(self.counters_db.COUNTERS_DB, COUNTERS_QUEUE_NAME_MAP) + if counter_queue_name_map is None: + print "COUNTERS_QUEUE_NAME_MAP is empty!" + sys.exit(1) + + for queue in counter_queue_name_map: + port = self.port_name_map[get_queue_port(counter_queue_name_map[queue])] + if get_queue_type(counter_queue_name_map[queue]) == QUEUE_TYPE_UC: + self.port_uc_queues_map[port][queue] = counter_queue_name_map[queue] + + elif get_queue_type(counter_queue_name_map[queue]) == QUEUE_TYPE_MC: + self.port_mc_queues_map[port][queue] = counter_queue_name_map[queue] + + # Get PGs for each port + counter_pg_name_map = self.counters_db.get_all(self.counters_db.COUNTERS_DB, COUNTERS_PG_NAME_MAP) + if counter_pg_name_map is None: + print "COUNTERS_PG_NAME_MAP is empty!" + sys.exit(1) + + for pg in counter_pg_name_map: + port = self.port_name_map[get_pg_port(counter_pg_name_map[pg])] + self.port_pg_map[port][pg] = counter_pg_name_map[pg] + + self.threshold_types = { + "pg_headroom": {"message": "Ingress headroom threshold per PG:", + "obj_map": self.port_pg_map, + "idx_func": self.get_pg_index, + "th_name": "threshold", + "header": headerPg}, + "pg_shared": {"message": "Ingress shared pool threshold per PG:", + "obj_map": self.port_pg_map, + "idx_func": self.get_pg_index, + "th_name": "threshold", + "header": headerPg}, + "q_shared_uni": {"message": "Egress shared pool threshold per unicast queue:", + "obj_map": self.port_uc_queues_map, + "idx_func": self.get_queue_index, + "th_name": "threshold", + "header": headerUc}, + "q_shared_multi": {"message": "Egress shared pool threshold per multicast queue:", + "obj_map": self.port_mc_queues_map, + "idx_func": self.get_queue_index, + "th_name": "threshold", + "header": headerMc} + } + + def get_queue_index(self, table_id): + queue_index = self.counters_db.get(self.counters_db.COUNTERS_DB, COUNTERS_QUEUE_INDEX_MAP, table_id) + if queue_index is None: + print "Queue index is not available!", table_id + sys.exit(1) + + return queue_index + + def get_pg_index(self, table_id): + pg_index = self.counters_db.get(self.counters_db.COUNTERS_DB, COUNTERS_PG_INDEX_MAP, table_id) + if pg_index is None: + print "Priority group index is not available!", table_id + sys.exit(1) + + return pg_index + + def get_counters(self, table_prefix, port, port_obj, idx_func, threshold): + """ + Get the threshold from specific table. + """ + + fields = ["0"]*8 + + for name, obj_id in port_obj.items(): + pos = int(idx_func(obj_id)) % len(fields) + full_table_id = table_prefix + port + '|' + str(pos) + threshold_data = self.config_db.get(self.config_db.CONFIG_DB, full_table_id, threshold) + + if threshold_data is None: + fields[pos] = THRESHOLD_DEFAULT + elif fields[pos] != THRESHOLD_DEFAULT: + fields[pos] = str(int(threshold_data)) + + cntr = tuple(fields) + return cntr + + def get_print_all_stat(self, table_prefix, type): + # Get stat for each port + table = [] + for port in natsorted(self.counter_port_name_map): + data = self.get_counters(table_prefix, port, + type["obj_map"][port], type["idx_func"], type["th_name"]) + table.append((port, data[0], data[1], data[2], data[3], + data[4], data[5], data[6], data[7])) + print(type["message"]) + print tabulate(table, type["header"], tablefmt='simple', stralign='right') + return + + +def main(): + + parser = argparse.ArgumentParser(description='Display the queue/pg thresholds', + version='1.0.0', + formatter_class=argparse.RawTextHelpFormatter, + epilog=""" +Examples: + thresholdcfg -t pg_headroom + thresholdcfg -t pg_shared + thresholdcfg -t q_shared_uni + thresholdcfg -t q_shared_multi +""") + + parser.add_argument('-t', '--type', required=True, action='store', + choices=['pg_headroom', 'pg_shared', 'q_shared_uni', 'q_shared_multi'], + help='The type of threshold') + args = parser.parse_args() + th_type = args.type + + thresholdcfg = Thresholdcfg() + + if th_type is not None: + if th_type == "pg_shared": + table_prefix = THRESHOLD_TABLE_PREFIX + "priority-group" + "|" + "shared" + "|" + elif th_type == "pg_headroom": + table_prefix = THRESHOLD_TABLE_PREFIX + "priority-group" + "|" + "headroom" + "|" + elif th_type == "q_shared_uni": + table_prefix = THRESHOLD_TABLE_PREFIX + "queue" + "|" + "unicast" + "|" + elif th_type == "q_shared_multi": + table_prefix = THRESHOLD_TABLE_PREFIX + "queue" + "|" + "multicast" + "|" + + thresholdcfg.get_print_all_stat(table_prefix, thresholdcfg.threshold_types[th_type]) + sys.exit(0) + + +if __name__ == "__main__": + main() diff --git a/setup.py b/setup.py index 47bd6e2fb0..b35c88830e 100644 --- a/setup.py +++ b/setup.py @@ -97,6 +97,8 @@ 'scripts/sfpshow', 'scripts/syseeprom-to-json', 'scripts/teamshow', + 'scripts/thresholdbreach', + 'scripts/thresholdcfg', 'scripts/tempershow', 'scripts/update_json.py', 'scripts/warm-reboot', diff --git a/show/main.py b/show/main.py index f1c2b77f07..90028125ac 100755 --- a/show/main.py +++ b/show/main.py @@ -1171,6 +1171,23 @@ def pwm_q_multi(): command = 'watermarkstat -p -t q_shared_multi' run_command(command) +@queue.group() +def threshold(): + """Show queue threshold config""" + pass + +@threshold.command('unicast') +def th_q_uni(): + """Show threshold config for unicast queues""" + command = 'thresholdcfg -t q_shared_uni' + run_command(command) + +@threshold.command('multicast') +def th_q_multi(): + """Show threshold config for multicast queues""" + command = 'thresholdcfg -t q_shared_multi' + run_command(command) + # # 'priority-group' group ("show priority-group ...") @@ -1214,6 +1231,23 @@ def pwm_pg_shared(): command = 'watermarkstat -p -t pg_shared' run_command(command) +@priority_group.group() +def threshold(): + """Show priority group threshold config""" + pass + +@threshold.command('headroom') +def th_pg_headroom(): + """Show headroom threshold config for pg""" + command = 'thresholdcfg -t pg_headroom' + run_command(command) + +@threshold.command('shared') +def th_pg_shared(): + """Show shared threshold config for pg""" + command = 'thresholdcfg -t pg_shared' + run_command(command) + # # 'buffer_pool' group ("show buffer_pool ...") @@ -2599,6 +2633,27 @@ def line(): run_command(cmd, display_cmd=verbose) return +# +# 'threshold breaches' command ("show threshold breaches") +# + +@cli.group('threshold') +def threshold(): + """Show threshold breaches""" + pass + +@threshold.command('breaches') +@click.argument('count', metavar='', required=False, type=int) +def threshold_breaches(count): + """Show threshold breaches""" + + if count is not None: + cmd = "thresholdbreach -cnt {}".format(count) + else: + cmd = "thresholdbreach" + + run_command(cmd) + return @cli.group(cls=AliasedGroup, default_if_no_args=False) def warm_restart():