diff --git a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/queue/StatQueue.java b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/queue/StatQueue.java index 5791d0ab6b..634da392bd 100644 --- a/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/queue/StatQueue.java +++ b/artemis-cli/src/main/java/org/apache/activemq/artemis/cli/commands/queue/StatQueue.java @@ -16,6 +16,11 @@ */ package org.apache.activemq.artemis.cli.commands.queue; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.TreeMap; + import io.airlift.airline.Command; import io.airlift.airline.Option; import org.apache.activemq.artemis.api.core.JsonUtil; @@ -23,17 +28,10 @@ import org.apache.activemq.artemis.api.core.client.ClientMessage; import org.apache.activemq.artemis.api.core.management.ManagementHelper; import org.apache.activemq.artemis.cli.commands.AbstractAction; import org.apache.activemq.artemis.cli.commands.ActionContext; - import org.apache.activemq.artemis.json.JsonArray; import org.apache.activemq.artemis.json.JsonObject; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.TreeMap; -@Command(name = "stat", description = "prints out basic stats associated with queues. Output includes CONSUMER_COUNT (number of consumers), MESSAGE_COUNT (current message count on the queue, including scheduled, paged and in-delivery messages), MESSAGES_ADDED (messages added to the queue), DELIVERING_COUNT (messages broker is currently delivering to consumer(s)), MESSAGES_ACKED (messages acknowledged from the consumer(s))." + " Queues can be filtered using EITHER '--queueName X' where X is contained in the queue name OR using a full filter '--field NAME --operation EQUALS --value X'." - -) +@Command(name = "stat", description = "prints out basic stats associated with queues. Output includes CONSUMER_COUNT (number of consumers), MESSAGE_COUNT (current message count on the queue, including scheduled, paged and in-delivery messages), MESSAGES_ADDED (messages added to the queue), DELIVERING_COUNT (messages broker is currently delivering to consumer(s)), MESSAGES_ACKED (messages acknowledged from the consumer(s))." + " Queues can be filtered using EITHER '--queueName X' where X is contained in the queue name OR using a full filter '--field NAME --operation EQUALS --value X'.") public class StatQueue extends AbstractAction { public enum FIELD { @@ -68,6 +66,8 @@ public class StatQueue extends AbstractAction { public static final int DEFAULT_MAX_ROWS = 50; + public static final int DEFAULT_MAX_COLUMN_SIZE = 25; + @Option(name = "--queueName", description = "display queue stats for queue(s) with names containing this string.") private String queueName; @@ -83,6 +83,9 @@ public class StatQueue extends AbstractAction { @Option(name = "--maxRows", description = "max number of queues displayed. Default is 50.") private int maxRows = DEFAULT_MAX_ROWS; + @Option(name = "--maxColumnSize", description = "max width of data column. Set to -1 for no limit. Default is 25.") + private int maxColumnSize = DEFAULT_MAX_COLUMN_SIZE; + //easier for testing public StatQueue setQueueName(String queueName) { this.queueName = queueName; @@ -109,6 +112,20 @@ public class StatQueue extends AbstractAction { return this; } + public StatQueue setMaxColumnSize(int maxColumnSize) { + int maxFieldSize = 0; + for (FIELD e : FIELD.values()) { + if (e.jsonId.length() > maxFieldSize) { + maxFieldSize = e.jsonId.length(); + } + } + if (maxColumnSize != -1 && maxColumnSize < maxFieldSize) { + throw new IllegalArgumentException("maxColumnSize must be " + maxFieldSize + " or greater or -1 (i.e. no limit)."); + } + this.maxColumnSize = maxColumnSize; + return this; + } + public StatQueue setverbose(boolean verbose) { this.verbose = verbose; return this; @@ -154,7 +171,6 @@ public class StatQueue extends AbstractAction { } private void printStats(String result) { - printHeadings(); //should not happen but... if (result == null) { @@ -168,8 +184,21 @@ public class StatQueue extends AbstractAction { int count = queuesAsJsonObject.getInt("count"); JsonArray array = queuesAsJsonObject.getJsonArray("data"); + int[] columnSizes = new int[FIELD.values().length]; + + FIELD[] fields = FIELD.values(); + for (int i = 0; i < fields.length; i++) { + columnSizes[i] = fields[i].toString().length(); + } + for (int i = 0; i < array.size(); i++) { - printQueueStats(array.getJsonObject(i)); + getColumnSizes(array.getJsonObject(i), columnSizes); + } + + printHeadings(columnSizes); + + for (int i = 0; i < array.size(); i++) { + printQueueStats(array.getJsonObject(i), columnSizes); } if (count > maxRows) { @@ -177,14 +206,33 @@ public class StatQueue extends AbstractAction { } } - private void printHeadings() { + private void getColumnSizes(JsonObject jsonObject, int[] columnSizes) { + int i = 0; + for (FIELD e: FIELD.values()) { + if (jsonObject.getString(e.jsonId).length() > columnSizes[i]) { + columnSizes[i] = jsonObject.getString(e.jsonId).length(); + } + // enforce max + if (columnSizes[i] > maxColumnSize && maxColumnSize != -1) { + columnSizes[i] = maxColumnSize; + } + i++; + } + } - StringBuilder stringBuilder = new StringBuilder(134).append('|').append(paddingString(new StringBuilder(FIELD.NAME.toString()), 25)).append('|').append(paddingString(new StringBuilder(FIELD.ADDRESS.toString()), 25)).append('|').append(paddingString(new StringBuilder(FIELD.CONSUMER_COUNT.toString() + " "), 15)).append('|').append(paddingString(new StringBuilder(FIELD.MESSAGE_COUNT.toString() + " "), 14)).append('|').append(paddingString(new StringBuilder(FIELD.MESSAGES_ADDED.toString() + " "), 15)).append('|').append(paddingString(new StringBuilder(FIELD.DELIVERING_COUNT.toString() + " "), 17)).append('|').append(paddingString(new StringBuilder(FIELD.MESSAGES_ACKED.toString() + " "), 15)).append('|').append(paddingString(new StringBuilder(FIELD.SCHEDULED_COUNT.toString() + " "), 16)).append('|').append(paddingString(new StringBuilder(FIELD.ROUTING_TYPE.toString() + " "), 13)).append('|'); + private void printHeadings(int[] columnSizes) { + // add 10 for the various '|' characters + StringBuilder stringBuilder = new StringBuilder(Arrays.stream(columnSizes).sum() + FIELD.values().length + 1).append('|'); + + int i = 0; + for (FIELD e: FIELD.values()) { + stringBuilder.append(paddingString(new StringBuilder(e.toString()), columnSizes[i++])).append('|'); + } context.out.println(stringBuilder); } - private void printQueueStats(JsonObject jsonObject) { + private void printQueueStats(JsonObject jsonObject, int[] columnSizes) { //should not happen but just in case.. if (jsonObject == null) { @@ -194,16 +242,22 @@ public class StatQueue extends AbstractAction { return; } - StringBuilder stringBuilder = new StringBuilder(134).append('|').append(paddingString(new StringBuilder(jsonObject.getString(FIELD.NAME.getJsonId())), 25)).append('|').append(paddingString(new StringBuilder(jsonObject.getString(FIELD.ADDRESS.getJsonId())), 25)).append('|').append(paddingString(new StringBuilder(jsonObject.getString(FIELD.CONSUMER_COUNT.getJsonId())), 15)).append('|').append(paddingString(new StringBuilder(jsonObject.getString(FIELD.MESSAGE_COUNT.getJsonId())), 14)).append('|').append(paddingString(new StringBuilder(jsonObject.getString(FIELD.MESSAGES_ADDED.getJsonId())), 15)).append('|').append(paddingString(new StringBuilder(jsonObject.getString(FIELD.DELIVERING_COUNT.getJsonId())), 17)).append('|').append(paddingString(new StringBuilder(jsonObject.getString(FIELD.MESSAGES_ACKED.getJsonId())), 15)).append('|').append(paddingString(new StringBuilder(jsonObject.getString(FIELD.SCHEDULED_COUNT.getJsonId())), 16)).append('|').append(paddingString(new StringBuilder(jsonObject.getString(FIELD.ROUTING_TYPE.getJsonId())), 13)).append('|'); + // add 10 for the various '|' characters + StringBuilder stringBuilder = new StringBuilder(Arrays.stream(columnSizes).sum() + FIELD.values().length + 1).append('|'); + + int i = 0; + for (FIELD e: FIELD.values()) { + stringBuilder.append(paddingString(new StringBuilder(jsonObject.getString(e.jsonId)), columnSizes[i++])).append('|'); + } context.out.println(stringBuilder); } - private StringBuilder paddingString(StringBuilder value, int size) { + private StringBuilder paddingString(StringBuilder value, int maxColumnSize) { //should not happen but just in case ... if (value == null) { - return new StringBuilder(size); + return new StringBuilder(maxColumnSize); } //would expect to have some data @@ -212,12 +266,13 @@ public class StatQueue extends AbstractAction { } int length = value.length(); - if (length >= size) { - //no padding required - return value; + + if (length > maxColumnSize && this.maxColumnSize != -1) { + // truncate if necessary + return new StringBuilder(value.substring(0, maxColumnSize - 3) + "..."); } - for (int i = 1; (i + length) <= size; i++) { + for (int i = 1; (i + length) <= maxColumnSize; i++) { value.append(' '); } diff --git a/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java b/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java index 6020f78094..de08be629b 100644 --- a/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java +++ b/artemis-cli/src/test/java/org/apache/activemq/cli/test/ArtemisTest.java @@ -1737,6 +1737,80 @@ public class ArtemisTest extends CliTestBase { } + @Test + public void testQstatColumnWidth() throws Exception { + + File instanceQstat = new File(temporaryFolder.getRoot(), "instanceQStat"); + setupAuth(instanceQstat); + Run.setEmbedded(true); + Artemis.main("create", instanceQstat.getAbsolutePath(), "--silent", "--no-fsync", "--no-autotune", "--no-web", "--require-login"); + System.setProperty("artemis.instance", instanceQstat.getAbsolutePath()); + Artemis.internalExecute("run"); + + try (ActiveMQConnectionFactory cf = new ActiveMQConnectionFactory("tcp://localhost:61616"); Connection connection = cf.createConnection("admin", "admin");) { + + //set up some queues with messages and consumers + Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); + connection.start(); + final String NAME = "012345678901234567890123456789"; + sendMessages(session, NAME, 1); + + TestActionContext context = new TestActionContext(); + StatQueue statQueue = new StatQueue(); + statQueue.setUser("admin"); + statQueue.setPassword("admin"); + statQueue.setQueueName(NAME); + statQueue.execute(context); + ArrayList lines = getOutputLines(context, false); + Assert.assertEquals("rows returned", 2, lines.size()); + String[] split = lines.get(1).split("\\|"); + Assert.assertEquals(StatQueue.DEFAULT_MAX_COLUMN_SIZE, split[1].length()); + + context = new TestActionContext(); + statQueue = new StatQueue(); + statQueue.setUser("admin"); + statQueue.setPassword("admin"); + statQueue.setQueueName(NAME); + statQueue.setMaxColumnSize(15); + statQueue.execute(context); + lines = getOutputLines(context, false); + Assert.assertEquals("rows returned", 2, lines.size()); + split = lines.get(1).split("\\|"); + Assert.assertEquals(15, split[1].length()); + + context = new TestActionContext(); + statQueue = new StatQueue(); + statQueue.setUser("admin"); + statQueue.setPassword("admin"); + statQueue.setQueueName(NAME); + statQueue.setMaxColumnSize(50); + statQueue.execute(context); + lines = getOutputLines(context, false); + Assert.assertEquals("rows returned", 2, lines.size()); + split = lines.get(1).split("\\|"); + Assert.assertEquals(NAME.length(), split[1].length()); + + context = new TestActionContext(); + statQueue = new StatQueue(); + statQueue.setUser("admin"); + statQueue.setPassword("admin"); + statQueue.setQueueName(NAME); + statQueue.setMaxColumnSize(-1); + statQueue.execute(context); + lines = getOutputLines(context, false); + for (String line : lines) { + System.out.println(line); + } + Assert.assertEquals("rows returned", 2, lines.size()); + split = lines.get(1).split("\\|"); + Assert.assertEquals(NAME.length(), split[1].length()); + Assert.assertEquals("CONSUMER_COUNT".length(), split[3].length()); + } finally { + stopServer(); + } + + } + @Test public void testQstatErrors() throws Exception {