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 2024/10/02 18:56:35 UTC
(cassandra) branch trunk updated: Add JSON and YAML output option to nodetool gcstats
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
The following commit(s) were added to refs/heads/trunk by this push:
new 664ab193d6 Add JSON and YAML output option to nodetool gcstats
664ab193d6 is described below
commit 664ab193d6b4db387e5ed751c0dff1635015bf28
Author: Suhel0328 <su...@ksolves.com>
AuthorDate: Fri Aug 30 11:01:45 2024 +0530
Add JSON and YAML output option to nodetool gcstats
patch by Mohammad Suhel; reviewed by Maxim Muzafarov, Maxwell Guo and Stefan Miklosovic for CASSANDRA-19771
---
CHANGES.txt | 1 +
.../apache/cassandra/tools/nodetool/GcStats.java | 22 +++-
.../{GcStats.java => stats/GcStatsHolder.java} | 45 +++++--
.../tools/nodetool/stats/GcStatsPrinter.java | 71 ++++++++++
.../cassandra/tools/nodetool/GcStatsTest.java | 146 +++++++++++++++++++++
5 files changed, 269 insertions(+), 16 deletions(-)
diff --git a/CHANGES.txt b/CHANGES.txt
index 89a6c8abc4..1797b9fdf0 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -1,4 +1,5 @@
5.1
+ * Add JSON and YAML output option to nodetool gcstats (CASSANDRA-19771)
* Introduce metadata serialization version V4 (CASSANDRA-19970)
* Allow CMS reconfiguration to work around DOWN nodes (CASSANDRA-19943)
* Make TableParams.Serializer set allowAutoSnapshots and incrementalBackups (CASSANDRA-19954)
diff --git a/src/java/org/apache/cassandra/tools/nodetool/GcStats.java b/src/java/org/apache/cassandra/tools/nodetool/GcStats.java
index 6e36f25040..f65095bcaf 100644
--- a/src/java/org/apache/cassandra/tools/nodetool/GcStats.java
+++ b/src/java/org/apache/cassandra/tools/nodetool/GcStats.java
@@ -19,19 +19,29 @@ package org.apache.cassandra.tools.nodetool;
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)")
+ private String outputFormat = "";
+
@Override
public void execute(NodeProbe probe)
{
- double[] stats = probe.getAndResetGCStats();
- double mean = stats[2] / stats[5];
- double stdev = Math.sqrt((stats[3] / stats[5]) - (mean * mean));
- probe.output().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");
- probe.output().out.printf("%20.0f%20.0f%20.0f%20.0f%20.0f%20.0f%25d%n", stats[0], stats[1], stats[2], stdev, stats[4], stats[5], (long)stats[6]);
+ if (!outputFormat.isEmpty() && !"json".equals(outputFormat) && !"yaml".equals(outputFormat))
+ throw new IllegalArgumentException("arguments for -F are json, yaml only.");
+
+ GcStatsHolder data = new GcStatsHolder(probe);
+ StatsPrinter<GcStatsHolder> printer = GcStatsPrinter.from(outputFormat);
+ printer.print(data, probe.output().out);
}
-}
+}
\ No newline at end of file
diff --git a/src/java/org/apache/cassandra/tools/nodetool/GcStats.java b/src/java/org/apache/cassandra/tools/nodetool/stats/GcStatsHolder.java
similarity index 51%
copy from src/java/org/apache/cassandra/tools/nodetool/GcStats.java
copy to src/java/org/apache/cassandra/tools/nodetool/stats/GcStatsHolder.java
index 6e36f25040..7533de0e33 100644
--- a/src/java/org/apache/cassandra/tools/nodetool/GcStats.java
+++ b/src/java/org/apache/cassandra/tools/nodetool/stats/GcStatsHolder.java
@@ -15,23 +15,48 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-package org.apache.cassandra.tools.nodetool;
-import org.apache.cassandra.tools.NodeProbe;
-import org.apache.cassandra.tools.NodeTool.NodeToolCmd;
+package org.apache.cassandra.tools.nodetool.stats;
+
+import java.util.HashMap;
+import java.util.Map;
-import io.airlift.airline.Command;
+import org.apache.cassandra.tools.NodeProbe;
-@Command(name = "gcstats", description = "Print GC Statistics")
-public class GcStats extends NodeToolCmd
+/**
+ * Holds and converts GC statistics to a map structure.
+ */
+public class GcStatsHolder implements StatsHolder
{
+ public final NodeProbe probe;
+
+ public GcStatsHolder(NodeProbe probe)
+ {
+ this.probe = probe;
+ }
+
+ /**
+ * Converts the GC statistics gathered from the probe into a map.
+ *
+ * @return A map containing GC statistics with keys such as interval_ms, max_gc_elapsed_ms, etc.
+ */
@Override
- public void execute(NodeProbe probe)
+ public Map<String, Object> convert2Map()
{
+ HashMap<String, Object> result = new HashMap<>();
+
double[] stats = probe.getAndResetGCStats();
double mean = stats[2] / stats[5];
double stdev = Math.sqrt((stats[3] / stats[5]) - (mean * mean));
- probe.output().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");
- probe.output().out.printf("%20.0f%20.0f%20.0f%20.0f%20.0f%20.0f%25d%n", stats[0], stats[1], stats[2], stdev, stats[4], stats[5], (long)stats[6]);
+
+ 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]);
+
+ return result;
}
-}
+}
\ No newline at end of file
diff --git a/src/java/org/apache/cassandra/tools/nodetool/stats/GcStatsPrinter.java b/src/java/org/apache/cassandra/tools/nodetool/stats/GcStatsPrinter.java
new file mode 100644
index 0000000000..87b639184d
--- /dev/null
+++ b/src/java/org/apache/cassandra/tools/nodetool/stats/GcStatsPrinter.java
@@ -0,0 +1,71 @@
+/*
+ * 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.stats;
+
+import java.io.PrintStream;
+import java.util.Map;
+
+/**
+ * Printer for GC statistics.
+ */
+public class GcStatsPrinter
+{
+ /**
+ * Factory method to get a printer based on the format.
+ *
+ * @param format The desired output format (e.g., json, yaml).
+ * @return A StatsPrinter appropriate for the format.
+ */
+ public static StatsPrinter<GcStatsHolder> from(String format)
+ {
+ switch (format)
+ {
+ case "json":
+ return new StatsPrinter.JsonPrinter<>();
+ case "yaml":
+ return new StatsPrinter.YamlPrinter<>();
+ default:
+ return new DefaultPrinter();
+ }
+ }
+
+ /**
+ * Default printer for GC statistics.
+ */
+ public static class DefaultPrinter 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.
+ */
+ @Override
+ public void print(GcStatsHolder data, PrintStream out)
+ {
+ Map<String, Object> stats = data.convert2Map();
+
+ 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"));
+ }
+ }
+}
\ 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
new file mode 100644
index 0000000000..107b37a873
--- /dev/null
+++ b/test/unit/org/apache/cassandra/tools/nodetool/GcStatsTest.java
@@ -0,0 +1,146 @@
+/*
+ * 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;
+
+import java.util.Arrays;
+
+import org.apache.cassandra.cql3.CQLTester;
+import org.apache.cassandra.service.GCInspector;
+import org.apache.cassandra.tools.ToolRunner;
+import org.apache.cassandra.utils.JsonUtils;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.yaml.snakeyaml.Yaml;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+
+public class GcStatsTest extends CQLTester
+{
+ @BeforeClass
+ public static void setUp() throws Exception
+ {
+ requireNetwork();
+ startJMXServer();
+ GCInspector.register();
+ }
+
+ @Test
+ @SuppressWarnings("SingleCharacterStringConcatenation")
+ public void testMaybeChangeDocs()
+ {
+ // If you added, modified options or help, please update docs if necessary
+ ToolRunner.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";
+
+ assertThat(tool.getStdout().trim()).isEqualTo(help.trim());
+ }
+
+ @Test
+ public void testDefaultGcStatsOutput()
+ {
+ ToolRunner.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");
+ }
+
+ @Test
+ public void testJsonGcStatsOutput()
+ {
+ Arrays.asList("-F", "--format").forEach(arg -> {
+ ToolRunner.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\"");
+ });
+ }
+
+ @Test
+ public void testYamlGcStatsOutput()
+ {
+ Arrays.asList("-F", "--format").forEach(arg -> {
+ ToolRunner.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:");
+ });
+ }
+
+ @Test
+ public void testInvalidFormatOption() throws Exception
+ {
+ ToolRunner.ToolResult tool = ToolRunner.invokeNodetool("gcstats", "-F", "invalid_format");
+ assertThat(tool.getExitCode()).isEqualTo(1);
+ assertThat(tool.getStdout()).contains("arguments for -F are json, yaml only.");
+ }
+}
\ 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