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

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

commit 009146959afccc67adbae24e59a7ba38930fea87
Author: maoling <maol...@apache.org>
AuthorDate: Wed Jun 5 23:50:00 2024 +0800

    Fix nodetool gcstats output, support human-readable units and more output 
formats
    
    nodetool gcstats output was broken as such / not aligned.
    This command was using a completely custom way of displaying the statistics 
which was fixed.
    
    This patch also supports three output modes: table, yaml, json under -F 
flag.
    There is also a possibility to output the figures in human-friendly format 
via -H flag.
    
    The patch also adds more JVM statistics related to direct memory besides
    already existing "allocated direct memory" being:
     - max direct memory
     - reserved direct memory
    
    patch by Ling Mao; reviewed by Brad Schoening, Stefan Miklosovic for 
CASSANDRA-19022
    
    Co-authored-by: Stefan Miklosovic <smikloso...@apache.org>
---
 CHANGES.txt                                        |   1 +
 .../org/apache/cassandra/service/GCInspector.java  |  70 ++++++++--
 .../apache/cassandra/tools/nodetool/GcStats.java   |  23 +--
 .../tools/nodetool/stats/GcStatsHolder.java        |  55 ++++++--
 .../tools/nodetool/stats/GcStatsPrinter.java       |  48 +++++--
 .../cassandra/tools/nodetool/GcStatsTest.java      | 154 ++++++++++++---------
 6 files changed, 243 insertions(+), 108 deletions(-)

diff --git a/CHANGES.txt b/CHANGES.txt
index 0449ba45d5..94399222f3 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
 5.1
+ * Fix nodetool gcstats output, support human-readable units and more output 
formats (CASSANDRA-19022)
  * Various gossip to TCM upgrade fixes (CASSANDRA-20483)
  * Add nodetool command to abort failed nodetool cms initialize 
(CASSANDRA-20482)
  * Repair Paxos for the distributed metadata log when CMS membership changes 
(CASSANDRA-20467)
diff --git a/src/java/org/apache/cassandra/service/GCInspector.java 
b/src/java/org/apache/cassandra/service/GCInspector.java
index 16b6665c69..4bdc3a05ba 100644
--- a/src/java/org/apache/cassandra/service/GCInspector.java
+++ b/src/java/org/apache/cassandra/service/GCInspector.java
@@ -26,6 +26,7 @@ import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReference;
 
 import javax.management.MBeanServer;
@@ -59,24 +60,32 @@ public class GCInspector implements NotificationListener, 
GCInspectorMXBean
      * bytes of direct memory requires via ByteBuffer.allocateDirect that have 
not been GCed.
      */
     final static Field BITS_TOTAL_CAPACITY;
+    // The hard limit for direct memory allocation, typically controlled by 
the -XX:MaxDirectMemorySize JVM option.
+    final static Field BITS_MAX;
+    // This represents the amount of direct memory that has been reserved for 
future use but not necessarily allocated yet.
+    final static Field BITS_RESERVED;
 
-    
     static
     {
-        Field temp = null;
+        Field totalTempField = null;
+        Field maxTempField = null;
+        Field reservedTempField = null;
         try
         {
             Class<?> bitsClass = Class.forName("java.nio.Bits");
-            Field f = bitsClass.getDeclaredField("TOTAL_CAPACITY");
-            f.setAccessible(true);
-            temp = f;
+            totalTempField = getField(bitsClass, "TOTAL_CAPACITY");
+            // Returns the maximum amount of allocatable direct buffer memory.
+            maxTempField = getField(bitsClass, "MAX_MEMORY");
+            reservedTempField = getField(bitsClass, "RESERVED_MEMORY");
         }
         catch (Throwable t)
         {
             logger.debug("Error accessing field of java.nio.Bits", t);
             //Don't care, will just return the dummy value -1 if we can't get 
at the field in this JVM
         }
-        BITS_TOTAL_CAPACITY = temp;
+        BITS_TOTAL_CAPACITY = totalTempField;
+        BITS_MAX = maxTempField;
+        BITS_RESERVED = reservedTempField;
     }
 
     static final class State
@@ -303,28 +312,65 @@ public class GCInspector implements NotificationListener, 
GCInspectorMXBean
     public double[] getAndResetStats()
     {
         State state = getTotalSinceLastCheck();
-        double[] r = new double[7];
+        double[] r = new double[9];
         r[0] = TimeUnit.NANOSECONDS.toMillis(nanoTime() - state.startNanos);
         r[1] = state.maxRealTimeElapsed;
         r[2] = state.totalRealTimeElapsed;
         r[3] = state.sumSquaresRealTimeElapsed;
         r[4] = state.totalBytesReclaimed;
         r[5] = state.count;
-        r[6] = getAllocatedDirectMemory();
+        r[6] = getTotalDirectMemory();
+        r[7] = getMaxDirectMemory();
+        r[8] = getReservedDirectMemory();
 
         return r;
     }
 
-    private static long getAllocatedDirectMemory()
+    private static long getTotalDirectMemory()
     {
-        if (BITS_TOTAL_CAPACITY == null) return -1;
+        return getFieldValue(BITS_TOTAL_CAPACITY, true);
+    }
+
+    private static long getMaxDirectMemory()
+    {
+        return getFieldValue(BITS_MAX, false);
+    }
+
+    private static long getReservedDirectMemory()
+    {
+        return getFieldValue(BITS_RESERVED, true);
+    }
+
+    private static Field getField(Class<?> clazz, String fieldName)
+    {
+        try
+        {
+            Field field = clazz.getDeclaredField(fieldName);
+            field.setAccessible(true);
+            return field;
+        }
+        catch (Throwable t)
+        {
+            logger.trace("Error accessing field {} of {}", fieldName, 
clazz.getName(), t);
+            // Return null to indicate failure
+            return null;
+        }
+    }
+
+    /**
+     * From the implementation of java.nio.Bits, we can infer that 
TOTAL_CAPACITY/RESERVED_MEMORY is AtomicLong
+     * and MAX_MEMORY is long. This method works well with JDK 11/17
+     * */
+    private static long getFieldValue(Field field, boolean isAtomicLong)
+    {
+        if (field == null) return -1;
         try
         {
-            return BITS_TOTAL_CAPACITY.getLong(null);
+            return isAtomicLong ? ((AtomicLong) field.get(null)).get() : 
field.getLong(null);
         }
         catch (Throwable t)
         {
-            logger.trace("Error accessing field of java.nio.Bits", t);
+            logger.trace("Error accessing field value of {}", field.getName(), 
t);
             //Don't care how or why we failed to get the value in this JVM. 
Return -1 to indicate failure
             return -1;
         }
diff --git a/src/java/org/apache/cassandra/tools/nodetool/GcStats.java 
b/src/java/org/apache/cassandra/tools/nodetool/GcStats.java
index f65095bcaf..2378226f75 100644
--- a/src/java/org/apache/cassandra/tools/nodetool/GcStats.java
+++ b/src/java/org/apache/cassandra/tools/nodetool/GcStats.java
@@ -17,31 +17,32 @@
  */
 package org.apache.cassandra.tools.nodetool;
 
+import io.airlift.airline.Command;
+import io.airlift.airline.Option;
 import org.apache.cassandra.tools.NodeProbe;
 import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
 import org.apache.cassandra.tools.nodetool.stats.GcStatsHolder;
 import org.apache.cassandra.tools.nodetool.stats.GcStatsPrinter;
-import org.apache.cassandra.tools.nodetool.stats.StatsPrinter;
-
-import io.airlift.airline.Command;
-import io.airlift.airline.Option;
 
 @Command(name = "gcstats", description = "Print GC Statistics")
 public class GcStats extends NodeToolCmd
 {
     @Option(title = "format",
-            name = {"-F", "--format"},
-            description = "Output format (json, yaml)")
+    name = { "-F", "--format" },
+    description = "Output format (json, yaml, table)")
     private String outputFormat = "";
 
+    @Option(title = "human_readable",
+    name = { "-H", "--human-readable" },
+    description = "Display gcstats with human-readable units")
+    private boolean humanReadable = false;
+
     @Override
     public void execute(NodeProbe probe)
     {
-        if (!outputFormat.isEmpty() && !"json".equals(outputFormat) && 
!"yaml".equals(outputFormat))
-            throw new IllegalArgumentException("arguments for -F are json, 
yaml only.");
+        if (!outputFormat.isEmpty() && !"json".equals(outputFormat) && 
!"yaml".equals(outputFormat) && !"table".equals(outputFormat))
+            throw new IllegalArgumentException("arguments for -F are json, 
yaml, table only.");
 
-        GcStatsHolder data = new GcStatsHolder(probe);
-        StatsPrinter<GcStatsHolder> printer = 
GcStatsPrinter.from(outputFormat);
-        printer.print(data, probe.output().out);
+        GcStatsPrinter.from(outputFormat).print(new GcStatsHolder(probe, 
humanReadable), probe.output().out);
     }
 }
\ No newline at end of file
diff --git 
a/src/java/org/apache/cassandra/tools/nodetool/stats/GcStatsHolder.java 
b/src/java/org/apache/cassandra/tools/nodetool/stats/GcStatsHolder.java
index 7533de0e33..e6ecb1c917 100644
--- a/src/java/org/apache/cassandra/tools/nodetool/stats/GcStatsHolder.java
+++ b/src/java/org/apache/cassandra/tools/nodetool/stats/GcStatsHolder.java
@@ -18,21 +18,50 @@
 
 package org.apache.cassandra.tools.nodetool.stats;
 
+import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.Map;
 
 import org.apache.cassandra.tools.NodeProbe;
 
+import static org.apache.cassandra.io.util.FileUtils.stringifyFileSize;
+
 /**
  * Holds and converts GC statistics to a map structure.
  */
 public class GcStatsHolder implements StatsHolder
 {
-    public final NodeProbe probe;
+    public static final String INTERVAL = "interval_ms";
+    public static final String MAX_GC = "max_gc_elapsed_ms";
+    public static final String TOTAL_GC = "total_gc_elapsed_ms";
+    public static final String STDEV_GC = "stdev_gc_elapsed_ms";
+    public static final String RECLAIMED_GC = "gc_reclaimed_mb";
+    public static final String GC_COUNT = "gc_count";
+    public static final String ALLOCATED_DIRECT_MEMORY = 
"allocated_direct_memory_bytes";
+    public static final String MAX_DIRECT_MEMORY = "max_direct_memory_bytes";
+    public static final String RESERVED_DIRECT_MEMORY = 
"reserved_direct_memory_bytes";
+
+    public static final Map<String, String> columnDescriptionMap = 
Collections.unmodifiableMap(new LinkedHashMap<>()
+    {{
+        put(INTERVAL, "Interval (ms)");
+        put(MAX_GC, "Max GC Elapsed (ms)");
+        put(TOTAL_GC, "Total GC Elapsed (ms)");
+        put(STDEV_GC, "Stdev GC Elapsed (ms)");
+        put(RECLAIMED_GC, "GC Reclaimed Bytes");
+        put(GC_COUNT, "GC Count");
+        put(ALLOCATED_DIRECT_MEMORY, "Allocated Direct Memory Bytes");
+        put(MAX_DIRECT_MEMORY, "Max Direct Memory Bytes");
+        put(RESERVED_DIRECT_MEMORY, "Reserved Direct Memory Bytes");
+    }});
 
-    public GcStatsHolder(NodeProbe probe)
+    private final NodeProbe probe;
+    private final boolean humanReadable;
+
+    public GcStatsHolder(NodeProbe probe, boolean humanReadable)
     {
         this.probe = probe;
+        this.humanReadable = humanReadable;
     }
 
     /**
@@ -43,19 +72,25 @@ public class GcStatsHolder implements StatsHolder
     @Override
     public Map<String, Object> convert2Map()
     {
-        HashMap<String, Object> result = new HashMap<>();
+        HashMap<String, Object> result = new LinkedHashMap<>();
 
         double[] stats = probe.getAndResetGCStats();
         double mean = stats[2] / stats[5];
         double stdev = Math.sqrt((stats[3] / stats[5]) - (mean * mean));
 
-        result.put("interval_ms", stats[0]);
-        result.put("max_gc_elapsed_ms", stats[1]);
-        result.put("total_gc_elapsed_ms", stats[2]);
-        result.put("stdev_gc_elapsed_ms", stdev);
-        result.put("gc_reclaimed_mb", stats[4]);
-        result.put("collections", stats[5]);
-        result.put("direct_memory_bytes", (long) stats[6]);
+        long totalDirect = (long) stats[6];
+        long maxDirect = (long) stats[7];
+        long reservedDirect = (long) stats[8];
+
+        result.put(INTERVAL, String.format("%.0f", stats[0]));
+        result.put(MAX_GC, String.format("%.0f", stats[1]));
+        result.put(TOTAL_GC, String.format("%.0f", stats[2]));
+        result.put(STDEV_GC, String.format("%.0f", stdev));
+        result.put(RECLAIMED_GC, stringifyFileSize((long) stats[4], 
humanReadable));
+        result.put(GC_COUNT, String.valueOf((long) stats[5]));
+        result.put(ALLOCATED_DIRECT_MEMORY, totalDirect == -1 ? Double.NaN : 
stringifyFileSize((long) stats[6], humanReadable));
+        result.put(MAX_DIRECT_MEMORY, maxDirect == -1 ? Double.NaN : 
stringifyFileSize((long) stats[7], humanReadable));
+        result.put(RESERVED_DIRECT_MEMORY, reservedDirect == -1 ? Double.NaN : 
stringifyFileSize((long) stats[8], humanReadable));
 
         return result;
     }
diff --git 
a/src/java/org/apache/cassandra/tools/nodetool/stats/GcStatsPrinter.java 
b/src/java/org/apache/cassandra/tools/nodetool/stats/GcStatsPrinter.java
index 87b639184d..d84b5ffff6 100644
--- a/src/java/org/apache/cassandra/tools/nodetool/stats/GcStatsPrinter.java
+++ b/src/java/org/apache/cassandra/tools/nodetool/stats/GcStatsPrinter.java
@@ -19,8 +19,12 @@
 package org.apache.cassandra.tools.nodetool.stats;
 
 import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Map;
 
+import org.apache.cassandra.tools.nodetool.formatter.TableBuilder;
+
 /**
  * Printer for GC statistics.
  */
@@ -40,32 +44,54 @@ public class GcStatsPrinter
                 return new StatsPrinter.JsonPrinter<>();
             case "yaml":
                 return new StatsPrinter.YamlPrinter<>();
+            case "table":
+                return new TablePrinter();
             default:
-                return new DefaultPrinter();
+                return new LegacyPrinter();
         }
     }
 
-    /**
-     * Default printer for GC statistics.
-     */
-    public static class DefaultPrinter implements StatsPrinter<GcStatsHolder>
+    public static class TablePrinter implements StatsPrinter<GcStatsHolder>
     {
         /**
          * Prints GC statistics in a human-readable table format.
          *
          * @param data The GC statistics data holder.
-         * @param out The output stream to print to.
+         * @param out  The output stream to print to.
          */
         @Override
         public void print(GcStatsHolder data, PrintStream out)
         {
             Map<String, Object> stats = data.convert2Map();
+            TableBuilder tableBuilder = new TableBuilder();
+
+            for (Map.Entry<String, Object> entry : stats.entrySet())
+                
tableBuilder.add(GcStatsHolder.columnDescriptionMap.get(entry.getKey()), 
entry.getValue().toString());
+
+            tableBuilder.printTo(out);
+        }
+    }
+
+    /**
+     * Default printer for GC statistics.
+     */
+    public static class LegacyPrinter implements StatsPrinter<GcStatsHolder>
+    {
+        @Override
+        public void print(GcStatsHolder data, PrintStream out)
+        {
+            Map<String, Object> stats = data.convert2Map();
+            TableBuilder tableBuilder = new TableBuilder();
+
+            tableBuilder.add(new 
ArrayList<>(GcStatsHolder.columnDescriptionMap.values()));
+
+            List<String> values = new ArrayList<>();
+            for (String key : GcStatsHolder.columnDescriptionMap.keySet())
+                values.add(stats.get(key).toString());
+
+            tableBuilder.add(values);
 
-            out.printf("%20s%20s%20s%20s%20s%20s%25s%n", "Interval (ms)", "Max 
GC Elapsed (ms)", "Total GC Elapsed (ms)",
-                    "Stdev GC Elapsed (ms)", "GC Reclaimed (MB)", 
"Collections", "Direct Memory Bytes");
-            out.printf("%20.0f%20.0f%20.0f%20.0f%20.0f%20.0f%25d%n", 
stats.get("interval_ms"), stats.get("max_gc_elapsed_ms"),
-                    stats.get("total_gc_elapsed_ms"), 
stats.get("stdev_gc_elapsed_ms"), stats.get("gc_reclaimed_mb"),
-                    stats.get("collections"), (long) 
stats.get("direct_memory_bytes"));
+            tableBuilder.printTo(out);
         }
     }
 }
\ No newline at end of file
diff --git a/test/unit/org/apache/cassandra/tools/nodetool/GcStatsTest.java 
b/test/unit/org/apache/cassandra/tools/nodetool/GcStatsTest.java
index 107b37a873..56ad5d2611 100644
--- a/test/unit/org/apache/cassandra/tools/nodetool/GcStatsTest.java
+++ b/test/unit/org/apache/cassandra/tools/nodetool/GcStatsTest.java
@@ -18,16 +18,23 @@
 
 package org.apache.cassandra.tools.nodetool;
 
-import java.util.Arrays;
+import org.apache.commons.lang3.StringUtils;
+import org.junit.BeforeClass;
+import org.junit.Test;
 
 import org.apache.cassandra.cql3.CQLTester;
 import org.apache.cassandra.service.GCInspector;
 import org.apache.cassandra.tools.ToolRunner;
+import org.apache.cassandra.tools.ToolRunner.ToolResult;
+import org.apache.cassandra.tools.nodetool.stats.GcStatsHolder;
 import org.apache.cassandra.utils.JsonUtils;
-import org.junit.BeforeClass;
-import org.junit.Test;
 import org.yaml.snakeyaml.Yaml;
 
+import static java.lang.Double.parseDouble;
+import static java.util.Arrays.asList;
+import static 
org.apache.cassandra.tools.nodetool.stats.GcStatsHolder.MAX_DIRECT_MEMORY;
+import static 
org.apache.cassandra.tools.nodetool.stats.GcStatsHolder.RESERVED_DIRECT_MEMORY;
+import static 
org.apache.cassandra.tools.nodetool.stats.GcStatsHolder.ALLOCATED_DIRECT_MEMORY;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatCode;
 
@@ -46,41 +53,44 @@ public class GcStatsTest extends CQLTester
     public void testMaybeChangeDocs()
     {
         // If you added, modified options or help, please update docs if 
necessary
-        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("help", 
"gcstats");
+        ToolResult tool = ToolRunner.invokeNodetool("help", "gcstats");
         tool.assertOnCleanExit();
 
-        String help =   "NAME\n" +
-                        "        nodetool gcstats - Print GC Statistics\n" +
-                        "\n" +
-                        "SYNOPSIS\n" +
-                        "        nodetool [(-h <host> | --host <host>)] [(-p 
<port> | --port <port>)]\n" +
-                        "                [(-pp | --print-port)] [(-pw 
<password> | --password <password>)]\n" +
-                        "                [(-pwf <passwordFilePath> | 
--password-file <passwordFilePath>)]\n" +
-                        "                [(-u <username> | --username 
<username>)] gcstats\n" +
-                        "                [(-F <format> | --format 
<format>)]\n" +
-                        "\n" +
-                        "OPTIONS\n" +
-                        "        -F <format>, --format <format>\n" +
-                        "            Output format (json, yaml)\n" +
-                        "\n" +
-                        "        -h <host>, --host <host>\n" +
-                        "            Node hostname or ip address\n" +
-                        "\n" +
-                        "        -p <port>, --port <port>\n" +
-                        "            Remote jmx agent port number\n" +
-                        "\n" +
-                        "        -pp, --print-port\n" +
-                        "            Operate in 4.0 mode with hosts 
disambiguated by port number\n" +
-                        "\n" +
-                        "        -pw <password>, --password <password>\n" +
-                        "            Remote jmx agent password\n" +
-                        "\n" +
-                        "        -pwf <passwordFilePath>, --password-file 
<passwordFilePath>\n" +
-                        "            Path to the JMX password file\n" +
-                        "\n" +
-                        "        -u <username>, --username <username>\n" +
-                        "            Remote jmx agent username\n" +
-                        "\n";
+        String help = "NAME\n" +
+                      "        nodetool gcstats - Print GC Statistics\n" +
+                      "\n" +
+                      "SYNOPSIS\n" +
+                      "        nodetool [(-h <host> | --host <host>)] [(-p 
<port> | --port <port>)]\n" +
+                      "                [(-pp | --print-port)] [(-pw <password> 
| --password <password>)]\n" +
+                      "                [(-pwf <passwordFilePath> | 
--password-file <passwordFilePath>)]\n" +
+                      "                [(-u <username> | --username 
<username>)] gcstats\n" +
+                      "                [(-F <format> | --format <format>)] 
[(-H | --human-readable)]\n" +
+                      "\n" +
+                      "OPTIONS\n" +
+                      "        -F <format>, --format <format>\n" +
+                      "            Output format (json, yaml, table)\n" +
+                      "\n" +
+                      "        -h <host>, --host <host>\n" +
+                      "            Node hostname or ip address\n" +
+                      "\n" +
+                      "        -H, --human-readable\n" +
+                      "            Display gcstats with human-readable 
units\n" +
+                      "\n" +
+                      "        -p <port>, --port <port>\n" +
+                      "            Remote jmx agent port number\n" +
+                      "\n" +
+                      "        -pp, --print-port\n" +
+                      "            Operate in 4.0 mode with hosts 
disambiguated by port number\n" +
+                      "\n" +
+                      "        -pw <password>, --password <password>\n" +
+                      "            Remote jmx agent password\n" +
+                      "\n" +
+                      "        -pwf <passwordFilePath>, --password-file 
<passwordFilePath>\n" +
+                      "            Path to the JMX password file\n" +
+                      "\n" +
+                      "        -u <username>, --username <username>\n" +
+                      "            Remote jmx agent username\n" +
+                      "\n";
 
         assertThat(tool.getStdout().trim()).isEqualTo(help.trim());
     }
@@ -88,59 +98,75 @@ public class GcStatsTest extends CQLTester
     @Test
     public void testDefaultGcStatsOutput()
     {
-        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("gcstats");
+        ToolResult tool = ToolRunner.invokeNodetool("gcstats");
         tool.assertOnCleanExit();
         String output = tool.getStdout();
-        assertThat(output).contains("Interval (ms)");
-        assertThat(output).contains("Max GC Elapsed (ms)");
-        assertThat(output).contains("Total GC Elapsed (ms)");
-        assertThat(output).contains("GC Reclaimed (MB)");
-        assertThat(output).contains("Collections");
-        assertThat(output).contains("Direct Memory Bytes");
+        for (String value : GcStatsHolder.columnDescriptionMap.values())
+            assertThat(output).contains(value);
     }
 
     @Test
     public void testJsonGcStatsOutput()
     {
-        Arrays.asList("-F", "--format").forEach(arg -> {
-            ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("gcstats", 
arg, "json");
+        asList("-F", "--format").forEach(arg -> {
+            ToolResult tool = ToolRunner.invokeNodetool("gcstats", arg, 
"json");
             tool.assertOnCleanExit();
             String json = tool.getStdout();
             assertThatCode(() -> 
JsonUtils.JSON_OBJECT_MAPPER.readTree(json)).doesNotThrowAnyException();
-            assertThat(json).containsPattern("\"interval_ms\"");
-            assertThat(json).containsPattern("\"stdev_gc_elapsed_ms\"");
-            assertThat(json).containsPattern("\"collections\"");
-            assertThat(json).containsPattern("\"max_gc_elapsed_ms\"");
-            assertThat(json).containsPattern("\"gc_reclaimed_mb\"");
-            assertThat(json).containsPattern("\"total_gc_elapsed_ms\"");
-            assertThat(json).containsPattern("\"direct_memory_bytes\"");
+
+            for (String key : GcStatsHolder.columnDescriptionMap.keySet())
+                assertThat(json).contains(key);
         });
     }
 
     @Test
     public void testYamlGcStatsOutput()
     {
-        Arrays.asList("-F", "--format").forEach(arg -> {
-            ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("gcstats", 
arg, "yaml");
+        asList("-F", "--format").forEach(arg -> {
+            ToolResult tool = ToolRunner.invokeNodetool("gcstats", arg, 
"yaml");
             tool.assertOnCleanExit();
             String yamlOutput = tool.getStdout();
             Yaml yaml = new Yaml();
             assertThatCode(() -> 
yaml.load(yamlOutput)).doesNotThrowAnyException();
-            assertThat(yamlOutput).containsPattern("interval_ms:");
-            assertThat(yamlOutput).containsPattern("stdev_gc_elapsed_ms:");
-            assertThat(yamlOutput).containsPattern("collections:");
-            assertThat(yamlOutput).containsPattern("max_gc_elapsed_ms:");
-            assertThat(yamlOutput).containsPattern("gc_reclaimed_mb:");
-            assertThat(yamlOutput).containsPattern("total_gc_elapsed_ms:");
-            assertThat(yamlOutput).containsPattern("direct_memory_bytes:");
+
+            for (String key : GcStatsHolder.columnDescriptionMap.keySet())
+                assertThat(yamlOutput).containsPattern(key);
         });
     }
 
     @Test
-    public void testInvalidFormatOption() throws Exception
+    public void testInvalidFormatOption()
     {
-        ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("gcstats", 
"-F", "invalid_format");
+        ToolResult tool = ToolRunner.invokeNodetool("gcstats", "-F", 
"invalid_format");
         assertThat(tool.getExitCode()).isEqualTo(1);
-        assertThat(tool.getStdout()).contains("arguments for -F are json, yaml 
only.");
+        assertThat(tool.getStdout()).contains("arguments for -F are json, 
yaml, table only.");
+    }
+
+    @Test
+    public void testWithoutNoOption()
+    {
+        ToolResult tool = ToolRunner.invokeNodetool("gcstats");
+        tool.assertOnCleanExit();
+
+        for (String value : GcStatsHolder.columnDescriptionMap.values())
+            assertThat(tool.getStdout()).contains(value);
+    }
+
+    @Test
+    public void testWithHumanReadableOption()
+    {
+        ToolResult tool = ToolRunner.invokeNodetool("gcstats", 
"--human-readable", "-F", "table");
+        tool.assertOnCleanExit();
+        String gcStatsOutput = tool.getStdout();
+
+        for (String value : GcStatsHolder.columnDescriptionMap.values())
+            assertThat(tool.getStdout()).contains(value);
+
+        String total = StringUtils.substringBetween(gcStatsOutput, 
GcStatsHolder.columnDescriptionMap.get(ALLOCATED_DIRECT_MEMORY), "\n").trim();
+        assertThat(parseDouble(total.split(" ")[0])).isGreaterThan(0);
+        String max = StringUtils.substringBetween(gcStatsOutput, 
GcStatsHolder.columnDescriptionMap.get(MAX_DIRECT_MEMORY), "\n").trim();
+        assertThat(parseDouble(max.split(" ")[0])).isGreaterThan(0);
+        String reserved = StringUtils.substringBetween(gcStatsOutput, 
GcStatsHolder.columnDescriptionMap.get(RESERVED_DIRECT_MEMORY), "\n").trim();
+        assertThat(parseDouble(reserved.split(" ")[0])).isGreaterThan(0);
     }
 }
\ No newline at end of file


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

Reply via email to