diff --git a/scripts/wredstat b/scripts/wredstat index 34793797f..f91a4baf3 100755 --- a/scripts/wredstat +++ b/scripts/wredstat @@ -99,7 +99,7 @@ class WredstatWrapper(object): self.db = None @multi_asic_util.run_on_multi_asic - def run(self, save_fresh_stats, port_to_show_stats, json_opt, non_zero): + def run(self, save_fresh_stats, port_to_show_stats, json_opt, non_zero, summary): wredstat = Wredstat(self.multi_asic.current_namespace, self.db, self.voq) if save_fresh_stats: wredstat.save_fresh_stats() @@ -108,7 +108,7 @@ class WredstatWrapper(object): if port_to_show_stats!=None: wredstat.get_print_port_stat(port_to_show_stats, json_opt, non_zero) else: - wredstat.get_print_all_stat(json_opt, non_zero) + wredstat.get_print_all_stat(json_opt, non_zero, summary) class Wredstat(object): @@ -293,7 +293,62 @@ class Wredstat(object): print(tabulate(table, hdr, tablefmt='simple', stralign='right')) print() - def get_print_all_stat(self, json_opt, non_zero): + def _to_int(self, val): + if val is None: + return 0 + s = str(val).strip() + if s == "" or s.upper() == "N/A": + return 0 + try: + return int(s) + except (TypeError, ValueError): + return 0 + + + def aggregate_from_json_output(self, json_output): + """ + json_output: { port: { ... queue_key: {queueindex, ... counters ...}, ... }, ... } + + Returns: + { queueindex: {queueindex, queuetype, wredDrppacket, wredDrpbytes, ecnpacket, ecnbytes}, ... } + """ + agg = {} + + for port, port_blob in (json_output or {}).items(): + if not isinstance(port_blob, dict): + continue + + # Some implementations nest under a key like "cnstat" or similar. + # We'll aggregate from: + # - port_blob directly + # - and if present, one level of nested dicts + candidate_dicts = [port_blob] + for _, maybe_nested in port_blob.items(): + if isinstance(maybe_nested, dict): + candidate_dicts.append(maybe_nested) + + for d in candidate_dicts: + for key, data in d.items(): + if not isinstance(data, dict): + continue + if key == 'time': + continue + + agg.setdefault(key, { + "wredDrppacket": 0, + "wredDrpbytes": 0, + "ecnpacket": 0, + "ecnbytes": 0, + }) + + agg[key]["wredDrppacket"] += self._to_int(data.get("wreddroppacket")) + agg[key]["wredDrpbytes"] += self._to_int(data.get("wreddropbytes")) + agg[key]["ecnpacket"] += self._to_int(data.get("ecnmarkedpacket")) + agg[key]["ecnbytes"] += self._to_int(data.get("ecnmarkedbytes")) + + return agg + + def get_print_all_stat(self, json_opt, non_zero, summary): """ Get stat for each port If JSON option is True, collect data for each port and @@ -308,23 +363,39 @@ class Wredstat(object): if os.path.isfile(cnstat_fqn_file_name): try: cnstat_cached_dict = json.load(open(cnstat_fqn_file_name, 'r')) - if json_opt: + if json_opt or summary: json_output[port].update({"cached_time":cnstat_cached_dict.get('time')}) - json_output.update(self.cnstat_diff_print(port, cnstat_dict, cnstat_cached_dict, json_opt, non_zero)) + json_output.update(self.cnstat_diff_print(port, cnstat_dict, cnstat_cached_dict, True, non_zero)) else: print(port + " Last cached time was " + str(cnstat_cached_dict.get('time'))) self.cnstat_diff_print(port, cnstat_dict, cnstat_cached_dict, json_opt, non_zero) except IOError as e: print(e.errno, e) else: - if json_opt: - json_output.update(self.cnstat_print(port, cnstat_dict, json_opt, non_zero)) + if json_opt or summary: + json_output.update(self.cnstat_print(port, cnstat_dict, True, non_zero)) else: self.cnstat_print(port, cnstat_dict, json_opt, non_zero) - if json_opt: + if json_opt and not summary: print(json_dump(json_output)) + if summary: + agg = self.aggregate_from_json_output(json_output) + if json_opt: + print(json_dump(agg)) + else: + table = [] + for key, data in agg.items(): + table.append((key, + data['wredDrppacket'], + data['wredDrpbytes'], + data['ecnpacket'], + data['ecnbytes'])) + hdr = voq_header[1:] if self.voq else header[1:] + print(tabulate(table, hdr, tablefmt='simple', stralign='right')) + print() + def get_print_port_stat(self, port, json_opt, non_zero): """ Get stat for the port @@ -393,6 +464,7 @@ Examples: parser.add_argument('-V', '--voq', action='store_true', help='display voq stats') parser.add_argument('-nz', '--non_zero', action='store_true', help='display non-zero wred queue stats') parser.add_argument('-n', '--namespace', default=None, help='Display queue counters for specific namespace') + parser.add_argument('-s', '--summary', action='store_true', help='Print summary in JSON format') args = parser.parse_args() save_fresh_stats = args.clear @@ -400,6 +472,7 @@ Examples: voq = args.voq json_opt = args.json_opt namespace = args.namespace + summary = args.summary non_zero = args.non_zero port_to_show_stats = args.port @@ -413,7 +486,7 @@ Examples: cache.remove() wredstat_wrapper = WredstatWrapper(namespace, voq) - wredstat_wrapper.run(save_fresh_stats, port_to_show_stats, json_opt, non_zero) + wredstat_wrapper.run(save_fresh_stats, port_to_show_stats, json_opt, non_zero, summary) sys.exit(0) diff --git a/show/main.py b/show/main.py index 75b010049..e73162e49 100755 --- a/show/main.py +++ b/show/main.py @@ -812,7 +812,8 @@ def counters(interfacename, namespace, display, all, trim, voq, nonzero, json, v @click.option('--json', is_flag=True, help="JSON output") @click.option('--voq', is_flag=True, help="VOQ counters") @click.option('-nz', '--nonzero', is_flag=True, help="Non Zero Counters") -def wredcounters(interfacename, namespace, display, verbose, json, voq, nonzero): +@click.option('--summary', is_flag=True, help="Summary counters") +def wredcounters(interfacename, namespace, display, verbose, json, voq, nonzero, summary): """Show queue wredcounters""" cmd = ["wredstat"] @@ -836,6 +837,9 @@ def wredcounters(interfacename, namespace, display, verbose, json, voq, nonzero) if nonzero: cmd += ["-nz"] + if summary: + cmd += ["-s"] + run_command(cmd, display_cmd=verbose) # diff --git a/tests/wred_queue_counter_test.py b/tests/wred_queue_counter_test.py index ef914ac08..8f6895307 100644 --- a/tests/wred_queue_counter_test.py +++ b/tests/wred_queue_counter_test.py @@ -1361,6 +1361,226 @@ } }""" +show_wred_queue_counters_summary = """\ + TxQ WredDrp/pkts WredDrp/bytes EcnMarked/pkts EcnMarked/bytes +----- -------------- --------------- ---------------- ----------------- + UC0 41 96 70 98 + UC1 116 109 170 128 + UC2 197 162 121 87 + UC3 123 256 130 189 + UC4 73 232 156 250 + UC5 132 161 140 191 + UC6 182 184 151 244 + UC7 62 99 181 242 + UC8 53 242 201 109 + UC9 177 73 270 170 + MC10 214 160 207 164 + MC11 111 175 174 156 + MC12 81 122 170 134 + MC13 208 110 218 184 + MC14 58 150 166 143 + MC15 129 131 182 148 + MC16 144 36 137 151 + MC17 139 154 154 173 + MC18 170 143 89 190 + MC19 134 50 114 141 +ALL20 0 0 0 0 +ALL21 0 0 0 0 +ALL22 0 0 0 0 +ALL23 0 0 0 0 +ALL24 0 0 0 0 +ALL25 0 0 0 0 +ALL26 0 0 0 0 +ALL27 0 0 0 0 +ALL28 0 0 0 0 +ALL29 0 0 0 0 + +""" + +show_queue_counters_summary_json = """\ +{ + "ALL20": { + "ecnbytes": 0, + "ecnpacket": 0, + "wredDrpbytes": 0, + "wredDrppacket": 0 + }, + "ALL21": { + "ecnbytes": 0, + "ecnpacket": 0, + "wredDrpbytes": 0, + "wredDrppacket": 0 + }, + "ALL22": { + "ecnbytes": 0, + "ecnpacket": 0, + "wredDrpbytes": 0, + "wredDrppacket": 0 + }, + "ALL23": { + "ecnbytes": 0, + "ecnpacket": 0, + "wredDrpbytes": 0, + "wredDrppacket": 0 + }, + "ALL24": { + "ecnbytes": 0, + "ecnpacket": 0, + "wredDrpbytes": 0, + "wredDrppacket": 0 + }, + "ALL25": { + "ecnbytes": 0, + "ecnpacket": 0, + "wredDrpbytes": 0, + "wredDrppacket": 0 + }, + "ALL26": { + "ecnbytes": 0, + "ecnpacket": 0, + "wredDrpbytes": 0, + "wredDrppacket": 0 + }, + "ALL27": { + "ecnbytes": 0, + "ecnpacket": 0, + "wredDrpbytes": 0, + "wredDrppacket": 0 + }, + "ALL28": { + "ecnbytes": 0, + "ecnpacket": 0, + "wredDrpbytes": 0, + "wredDrppacket": 0 + }, + "ALL29": { + "ecnbytes": 0, + "ecnpacket": 0, + "wredDrpbytes": 0, + "wredDrppacket": 0 + }, + "MC10": { + "ecnbytes": 164, + "ecnpacket": 207, + "wredDrpbytes": 160, + "wredDrppacket": 214 + }, + "MC11": { + "ecnbytes": 156, + "ecnpacket": 174, + "wredDrpbytes": 175, + "wredDrppacket": 111 + }, + "MC12": { + "ecnbytes": 134, + "ecnpacket": 170, + "wredDrpbytes": 122, + "wredDrppacket": 81 + }, + "MC13": { + "ecnbytes": 184, + "ecnpacket": 218, + "wredDrpbytes": 110, + "wredDrppacket": 208 + }, + "MC14": { + "ecnbytes": 143, + "ecnpacket": 166, + "wredDrpbytes": 150, + "wredDrppacket": 58 + }, + "MC15": { + "ecnbytes": 148, + "ecnpacket": 182, + "wredDrpbytes": 131, + "wredDrppacket": 129 + }, + "MC16": { + "ecnbytes": 151, + "ecnpacket": 137, + "wredDrpbytes": 36, + "wredDrppacket": 144 + }, + "MC17": { + "ecnbytes": 173, + "ecnpacket": 154, + "wredDrpbytes": 154, + "wredDrppacket": 139 + }, + "MC18": { + "ecnbytes": 190, + "ecnpacket": 89, + "wredDrpbytes": 143, + "wredDrppacket": 170 + }, + "MC19": { + "ecnbytes": 141, + "ecnpacket": 114, + "wredDrpbytes": 50, + "wredDrppacket": 134 + }, + "UC0": { + "ecnbytes": 98, + "ecnpacket": 70, + "wredDrpbytes": 96, + "wredDrppacket": 41 + }, + "UC1": { + "ecnbytes": 128, + "ecnpacket": 170, + "wredDrpbytes": 109, + "wredDrppacket": 116 + }, + "UC2": { + "ecnbytes": 87, + "ecnpacket": 121, + "wredDrpbytes": 162, + "wredDrppacket": 197 + }, + "UC3": { + "ecnbytes": 189, + "ecnpacket": 130, + "wredDrpbytes": 256, + "wredDrppacket": 123 + }, + "UC4": { + "ecnbytes": 250, + "ecnpacket": 156, + "wredDrpbytes": 232, + "wredDrppacket": 73 + }, + "UC5": { + "ecnbytes": 191, + "ecnpacket": 140, + "wredDrpbytes": 161, + "wredDrppacket": 132 + }, + "UC6": { + "ecnbytes": 244, + "ecnpacket": 151, + "wredDrpbytes": 184, + "wredDrppacket": 182 + }, + "UC7": { + "ecnbytes": 242, + "ecnpacket": 181, + "wredDrpbytes": 99, + "wredDrppacket": 62 + }, + "UC8": { + "ecnbytes": 109, + "ecnpacket": 201, + "wredDrpbytes": 242, + "wredDrppacket": 53 + }, + "UC9": { + "ecnbytes": 170, + "ecnpacket": 270, + "wredDrpbytes": 73, + "wredDrppacket": 177 + } +}""" + def remove_tmp_cnstat_file(): # remove the tmp wredstat @@ -1514,6 +1734,27 @@ def test_queue_voq_counters_port_json(self): del v["time"] assert json_dump(json_output) == show_queue_port_voq_counters_json + def test_queue_counters_summary(self): + runner = CliRunner() + result = runner.invoke( + show.cli.commands["queue"].commands["wredcounters"], + ["--summary"] + ) + print(result.output) + assert result.exit_code == 0 + assert result.output == show_wred_queue_counters_summary + + def test_queue_counters_summary_json(self): + runner = CliRunner() + result = runner.invoke( + show.cli.commands["queue"].commands["wredcounters"], + ["--summary", "--json"] + ) + assert result.exit_code == 0 + print(result.output) + json_output = json.loads(result.output) + assert json_dump(json_output) == show_queue_counters_summary_json + def test_clear_wredstats(self): wredstat_clear_str = "Clear and update saved counters" runner = CliRunner()