mirror of https://github.com/apache/cassandra
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
This commit is contained in:
parent
9cfe1f478a
commit
664ab193d6
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* 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.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.cassandra.tools.NodeProbe;
|
||||
|
||||
/**
|
||||
* 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 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));
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue