You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cassandra.apache.org by sm...@apache.org on 2021/07/13 14:54:17 UTC

[cassandra] branch cassandra-3.0 updated: Backport CASSANDRA-7950 (Support long names in nodetool output)

This is an automated email from the ASF dual-hosted git repository.

smiklosovic pushed a commit to branch cassandra-3.0
in repository https://gitbox.apache.org/repos/asf/cassandra.git


The following commit(s) were added to refs/heads/cassandra-3.0 by this push:
     new 15c22fa  Backport CASSANDRA-7950 (Support long names in nodetool output)
15c22fa is described below

commit 15c22fa2f1e1ec04efb76646fd0ed5ceaff889cf
Author: kurt <ku...@instaclustr.com>
AuthorDate: Wed Mar 14 04:51:08 2018 +0000

    Backport CASSANDRA-7950 (Support long names in nodetool output)
    
    this patch also backports chronological sorting of output for compactionhistory command found in versions from 3.2 included (CASSANDRA-10464)
    
    patch by Kurt Greaves; reviewed by Stefan Miklosovic and Ekaterina Dimitrova for CASSANDRA-14162
---
 CHANGES.txt                                        |   1 +
 .../tools/nodetool/CompactionHistory.java          |  79 ++++++++++++--
 .../cassandra/tools/nodetool/CompactionStats.java  |  46 ++-------
 .../cassandra/tools/nodetool/ListSnapshots.java    |  13 +--
 .../tools/nodetool/formatter/TableBuilder.java     | 103 +++++++++++++++++++
 .../tools/nodetool/formatter/TableBuilderTest.java | 114 +++++++++++++++++++++
 6 files changed, 307 insertions(+), 49 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index eec1e74..a0bd1cf 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 3.0.25:
+ * Support long names in nodetool output (CASSANDRA-14162)
  * Handle correctly the exceptions thrown by custom QueryHandler constructors (CASSANDRA-16703)
  * Adding columns via ALTER TABLE can generate corrupt sstables (CASSANDRA-16735)
  * Add flag to disable ALTER...DROP COMPACT STORAGE statements (CASSANDRA-16733)
diff --git a/src/java/org/apache/cassandra/tools/nodetool/CompactionHistory.java b/src/java/org/apache/cassandra/tools/nodetool/CompactionHistory.java
index 55f7fc5..4624335 100644
--- a/src/java/org/apache/cassandra/tools/nodetool/CompactionHistory.java
+++ b/src/java/org/apache/cassandra/tools/nodetool/CompactionHistory.java
@@ -17,17 +17,22 @@
  */
 package org.apache.cassandra.tools.nodetool;
 
-import static com.google.common.collect.Iterables.toArray;
-import io.airlift.command.Command;
-
 import java.io.PrintStream;
+import java.time.Instant;
+import java.time.LocalDateTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 import java.util.Set;
-
 import javax.management.openmbean.TabularData;
 
+import io.airlift.command.Command;
 import org.apache.cassandra.tools.NodeProbe;
 import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
+
+import static com.google.common.collect.Iterables.toArray;
 
 @Command(name = "compactionhistory", description = "Print history of compaction")
 public class CompactionHistory extends NodeToolCmd
@@ -45,15 +50,75 @@ public class CompactionHistory extends NodeToolCmd
             return;
         }
 
-        String format = "%-41s%-19s%-29s%-26s%-15s%-15s%s%n";
+        TableBuilder table = new TableBuilder();
         List<String> indexNames = tabularData.getTabularType().getIndexNames();
-        out.printf(format, toArray(indexNames, Object.class));
+        table.add(toArray(indexNames, String.class));
 
         Set<?> values = tabularData.keySet();
+        List<CompactionHistoryRow> chr = new ArrayList<>();
         for (Object eachValue : values)
         {
             List<?> value = (List<?>) eachValue;
-            out.printf(format, toArray(value, Object.class));
+            CompactionHistoryRow chc = new CompactionHistoryRow((String)value.get(0),
+                                                                (String)value.get(1),
+                                                                (String)value.get(2),
+                                                                (Long)value.get(3),
+                                                                (Long)value.get(4),
+                                                                (Long)value.get(5),
+                                                                (String)value.get(6));
+            chr.add(chc);
+        }
+        Collections.sort(chr);
+        for (CompactionHistoryRow eachChc : chr)
+        {
+            table.add(eachChc.getAllAsArray());
+        }
+        table.printTo(out);
+    }
+
+    /**
+     * Allows the Compaction History output to be ordered by 'compactedAt' - that is the
+     * time at which compaction finished.
+     */
+    private static class CompactionHistoryRow implements Comparable<CompactionHistoryRow>
+    {
+        private final String id;
+        private final String ksName;
+        private final String cfName;
+        private final long compactedAt;
+        private final long bytesIn;
+        private final long bytesOut;
+        private final String rowMerged;
+
+        CompactionHistoryRow(String id, String ksName, String cfName, long compactedAt, long bytesIn, long bytesOut, String rowMerged)
+        {
+            this.id = id;
+            this.ksName = ksName;
+            this.cfName = cfName;
+            this.compactedAt = compactedAt;
+            this.bytesIn = bytesIn;
+            this.bytesOut = bytesOut;
+            this.rowMerged = rowMerged;
+        }
+
+        public int compareTo(CompactionHistoryRow chc)
+        {
+            return Long.signum(chc.compactedAt - this.compactedAt);
+        }
+
+        public String[] getAllAsArray()
+        {
+            String[] obj = new String[7];
+            obj[0] = this.id;
+            obj[1] = this.ksName;
+            obj[2] = this.cfName;
+            Instant instant = Instant.ofEpochMilli(this.compactedAt);
+            LocalDateTime ldt = LocalDateTime.ofInstant(instant, ZoneId.systemDefault());
+            obj[3] = ldt.toString();
+            obj[4] = Long.toString(this.bytesIn);
+            obj[5] = Long.toString(this.bytesOut);
+            obj[6] = this.rowMerged;
+            return obj;
         }
     }
 }
diff --git a/src/java/org/apache/cassandra/tools/nodetool/CompactionStats.java b/src/java/org/apache/cassandra/tools/nodetool/CompactionStats.java
index 30830dd..a18ac74 100644
--- a/src/java/org/apache/cassandra/tools/nodetool/CompactionStats.java
+++ b/src/java/org/apache/cassandra/tools/nodetool/CompactionStats.java
@@ -17,23 +17,22 @@
  */
 package org.apache.cassandra.tools.nodetool;
 
-import static java.lang.String.format;
-import io.airlift.command.Command;
-import io.airlift.command.Option;
-
 import java.io.PrintStream;
 import java.text.DecimalFormat;
-import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
 
-import org.apache.cassandra.db.compaction.CompactionInfo;
+import io.airlift.command.Command;
+import io.airlift.command.Option;
+import org.apache.cassandra.db.compaction.CompactionInfo.Unit;
 import org.apache.cassandra.db.compaction.CompactionManagerMBean;
 import org.apache.cassandra.db.compaction.OperationType;
-import org.apache.cassandra.db.compaction.CompactionInfo.Unit;
 import org.apache.cassandra.io.util.FileUtils;
 import org.apache.cassandra.tools.NodeProbe;
 import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
+
+import static java.lang.String.format;
 
 @Command(name = "compactionstats", description = "Print statistics on compactions")
 public class CompactionStats extends NodeToolCmd
@@ -50,14 +49,12 @@ public class CompactionStats extends NodeToolCmd
         CompactionManagerMBean cm = probe.getCompactionManagerProxy();
         out.println("pending tasks: " + probe.getCompactionMetric("PendingTasks"));
         long remainingBytes = 0;
+        TableBuilder table = new TableBuilder();
         List<Map<String, String>> compactions = cm.getCompactions();
         if (!compactions.isEmpty())
         {
             int compactionThroughput = probe.getCompactionThroughput();
-            List<String[]> lines = new ArrayList<>();
-            int[] columnSizes = new int[] { 0, 0, 0, 0, 0, 0, 0, 0 };
-
-            addLine(lines, columnSizes, "id", "compaction type", "keyspace", "table", "completed", "total", "unit", "progress");
+            table.add("id", "compaction type", "keyspace", "table", "completed", "total", "unit", "progress");
             for (Map<String, String> c : compactions)
             {
                 long total = Long.parseLong(c.get("total"));
@@ -71,24 +68,12 @@ public class CompactionStats extends NodeToolCmd
                 String totalStr = toFileSize ? FileUtils.stringifyFileSize(total) : Long.toString(total);
                 String percentComplete = total == 0 ? "n/a" : new DecimalFormat("0.00").format((double) completed / total * 100) + "%";
                 String id = c.get("compactionId");
-                addLine(lines, columnSizes, id, taskType, keyspace, columnFamily, completedStr, totalStr, unit, percentComplete);
+                table.add(id, taskType, keyspace, columnFamily, completedStr, totalStr, unit, percentComplete);
                 if (taskType.equals(OperationType.COMPACTION.toString()))
                     remainingBytes += total - completed;
             }
 
-            StringBuilder buffer = new StringBuilder();
-            for (int columnSize : columnSizes) {
-                buffer.append("%");
-                buffer.append(columnSize + 3);
-                buffer.append("s");
-            }
-            buffer.append("%n");
-            String format = buffer.toString();
-
-            for (String[] line : lines)
-            {
-                out.printf(format, line[0], line[1], line[2], line[3], line[4], line[5], line[6], line[7]);
-            }
+            table.printTo(out);
 
             String remainingTime = "n/a";
             if (compactionThroughput != 0)
@@ -99,15 +84,4 @@ public class CompactionStats extends NodeToolCmd
             out.printf("%25s%10s%n", "Active compaction remaining time : ", remainingTime);
         }
     }
-
-    private void addLine(List<String[]> lines, int[] columnSizes, String... columns)
-    {
-        String[] newColumns = new String[columns.length];
-        for (int i = 0; i < columns.length; i++)
-        {
-            columnSizes[i] = Math.max(columnSizes[i], columns[i] != null ? columns[i].length() : 1);
-            newColumns[i] = columns[i] != null ? columns[i] : "";
-        }
-        lines.add(newColumns);
-    }
 }
diff --git a/src/java/org/apache/cassandra/tools/nodetool/ListSnapshots.java b/src/java/org/apache/cassandra/tools/nodetool/ListSnapshots.java
index 18d8053..09e19dd 100644
--- a/src/java/org/apache/cassandra/tools/nodetool/ListSnapshots.java
+++ b/src/java/org/apache/cassandra/tools/nodetool/ListSnapshots.java
@@ -17,18 +17,17 @@
  */
 package org.apache.cassandra.tools.nodetool;
 
-import io.airlift.command.Command;
-
 import java.io.PrintStream;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-
 import javax.management.openmbean.TabularData;
 
+import io.airlift.command.Command;
 import org.apache.cassandra.io.util.FileUtils;
 import org.apache.cassandra.tools.NodeProbe;
 import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
 
 @Command(name = "listsnapshots", description = "Lists all the snapshots along with the size on disk and true size.")
 public class ListSnapshots extends NodeToolCmd
@@ -49,10 +48,11 @@ public class ListSnapshots extends NodeToolCmd
             }
 
             final long trueSnapshotsSize = probe.trueSnapshotsSize();
-            final String format = "%-40s %-29s %-29s %-19s %-19s%n";
+
+            TableBuilder table = new TableBuilder();
             // display column names only once
             final List<String> indexNames = snapshotDetails.entrySet().iterator().next().getValue().getTabularType().getIndexNames();
-            out.printf(format, (Object[]) indexNames.toArray(new String[indexNames.size()]));
+            table.add(indexNames.toArray(new String[indexNames.size()]));
 
             for (final Map.Entry<String, TabularData> snapshotDetail : snapshotDetails.entrySet())
             {
@@ -60,9 +60,10 @@ public class ListSnapshots extends NodeToolCmd
                 for (Object eachValue : values)
                 {
                     final List<?> value = (List<?>) eachValue;
-                    out.printf(format, value.toArray(new Object[value.size()]));
+                    table.add(value.toArray(new String[value.size()]));
                 }
             }
+            table.printTo(out);
 
             out.println("\nTotal TrueDiskSpaceUsed: " + FileUtils.stringifyFileSize(trueSnapshotsSize) + "\n");
         }
diff --git a/src/java/org/apache/cassandra/tools/nodetool/formatter/TableBuilder.java b/src/java/org/apache/cassandra/tools/nodetool/formatter/TableBuilder.java
new file mode 100644
index 0000000..a56e52e
--- /dev/null
+++ b/src/java/org/apache/cassandra/tools/nodetool/formatter/TableBuilder.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.tools.nodetool.formatter;
+
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import javax.annotation.Nonnull;
+
+/**
+ * Build and print table.
+ *
+ * usage:
+ * <pre>
+ * {@code
+ * TableBuilder table = new TableBuilder();
+ * for (String[] row : data)
+ * {
+ *     table.add(row);
+ * }
+ * table.print(System.out);
+ * }
+ * </pre>
+ */
+public class TableBuilder
+{
+    // column delimiter char
+    private final char columnDelimiter;
+
+    private int[] maximumColumnWidth;
+    private final List<String[]> rows = new ArrayList<>();
+
+    public TableBuilder()
+    {
+        this(' ');
+    }
+
+    public TableBuilder(char columnDelimiter)
+    {
+        this.columnDelimiter = columnDelimiter;
+    }
+
+    public void add(@Nonnull String... row)
+    {
+        Objects.requireNonNull(row);
+
+        if (rows.isEmpty())
+        {
+            maximumColumnWidth = new int[row.length];
+        }
+
+        // expand max column widths if given row has more columns
+        if (row.length > maximumColumnWidth.length)
+        {
+            int[] tmp = new int[row.length];
+            System.arraycopy(maximumColumnWidth, 0, tmp, 0, maximumColumnWidth.length);
+            maximumColumnWidth = tmp;
+        }
+        // calculate maximum column width
+        int i = 0;
+        for (String col : row)
+        {
+            maximumColumnWidth[i] = Math.max(maximumColumnWidth[i], col != null ? col.length() : 1);
+            i++;
+        }
+        rows.add(row);
+    }
+
+    public void printTo(PrintStream out)
+    {
+        if (rows.isEmpty())
+            return;
+
+        for (String[] row : rows)
+        {
+            for (int i = 0; i < maximumColumnWidth.length; i++)
+            {
+                String col = i < row.length ? row[i] : "";
+                out.print(String.format("%-" + maximumColumnWidth[i] + 's', col != null ? col : ""));
+                if (i < maximumColumnWidth.length - 1)
+                    out.print(columnDelimiter);
+            }
+            out.println();
+        }
+    }
+}
diff --git a/test/unit/org/apache/cassandra/tools/nodetool/formatter/TableBuilderTest.java b/test/unit/org/apache/cassandra/tools/nodetool/formatter/TableBuilderTest.java
new file mode 100644
index 0000000..9782b5b
--- /dev/null
+++ b/test/unit/org/apache/cassandra/tools/nodetool/formatter/TableBuilderTest.java
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.tools.nodetool.formatter;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class TableBuilderTest
+{
+    @Test
+    public void testEmptyRow()
+    {
+        TableBuilder table = new TableBuilder();
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try (PrintStream out = new PrintStream(baos))
+        {
+            table.printTo(out);
+        }
+        assertEquals("", baos.toString());
+    }
+
+    @Test
+    public void testOneRow()
+    {
+        TableBuilder table = new TableBuilder();
+
+        table.add("a", "bb", "ccc");
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try (PrintStream out = new PrintStream(baos))
+        {
+            table.printTo(out);
+        }
+        assertEquals(String.format("a bb ccc%n"), baos.toString());
+    }
+
+    @Test
+    public void testRows()
+    {
+        TableBuilder table = new TableBuilder();
+        table.add("a", "bb", "ccc");
+        table.add("aaa", "bb", "c");
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try (PrintStream out = new PrintStream(baos))
+        {
+            table.printTo(out);
+        }
+        assertEquals(String.format("a   bb ccc%naaa bb c  %n"), baos.toString());
+    }
+
+    @Test
+    public void testNullColumn()
+    {
+        TableBuilder table = new TableBuilder();
+        table.add("a", "b", "c");
+        table.add("a", null, "c");
+        table.add("a", null, null);
+        table.add(null, "b", "c");
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try (PrintStream out = new PrintStream(baos))
+        {
+            table.printTo(out);
+        }
+        assertEquals(String.format("a b c%na   c%na    %n  b c%n"), baos.toString());
+    }
+
+    @Test
+    public void testRowsOfDifferentSize()
+    {
+        TableBuilder table = new TableBuilder();
+        table.add("a", "b", "c");
+        table.add("a", "b", "c", "d", "e");
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try (PrintStream out = new PrintStream(baos))
+        {
+            table.printTo(out);
+        }
+        assertEquals(baos.toString(), String.format("a b c    %na b c d e%n"), baos.toString());
+    }
+
+    @Test
+    public void testDelimiter()
+    {
+        TableBuilder table = new TableBuilder('\t');
+
+        table.add("a", "bb", "ccc");
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        try (PrintStream out = new PrintStream(baos))
+        {
+            table.printTo(out);
+        }
+        assertEquals(String.format("a\tbb\tccc%n"), baos.toString());
+    }
+}
\ No newline at end of file

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@cassandra.apache.org
For additional commands, e-mail: commits-help@cassandra.apache.org