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