Add a new jmxtool which can dump what JMX objects exist and diff

patch by David Capwell, Stephen Mallette; reviewed by Berenguer Blasi, Jon Meredith for CASSANDRA-16082
This commit is contained in:
David Capwell 2020-09-15 15:16:39 -07:00
parent 07f8db31ae
commit 91bcbb2873
12 changed files with 215580 additions and 95 deletions

View File

@ -90,7 +90,7 @@ public class DataOutputBuffer extends BufferedDataOutputStreamPlus
@Override
public void flush() throws IOException
{
throw new UnsupportedOperationException();
}
//The actual value observed in Hotspot is only -2

View File

@ -645,7 +645,7 @@ public class CassandraDaemon
{
applyConfig();
MBeanWrapper.instance.registerMBean(new StandardMBean(new NativeAccess(), NativeAccessMBean.class), MBEAN_NAME, MBeanWrapper.OnException.LOG);
registerNativeAccess();
if (FBUtilities.isWindows)
{
@ -697,6 +697,12 @@ public class CassandraDaemon
}
}
@VisibleForTesting
public static void registerNativeAccess() throws javax.management.NotCompliantMBeanException
{
MBeanWrapper.instance.registerMBean(new StandardMBean(new NativeAccess(), NativeAccessMBean.class), MBEAN_NAME, MBeanWrapper.OnException.LOG);
}
public void applyConfig()
{
DatabaseDescriptor.daemonInitialization();

View File

@ -0,0 +1,848 @@
package org.apache.cassandra.tools;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.function.BiConsumer;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.inject.Inject;
import javax.management.InstanceNotFoundException;
import javax.management.IntrospectionException;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanFeatureInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.ReflectionException;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.Sets;
import com.google.common.collect.Sets.SetView;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.airlift.airline.Arguments;
import io.airlift.airline.Cli;
import io.airlift.airline.Command;
import io.airlift.airline.Help;
import io.airlift.airline.HelpOption;
import io.airlift.airline.Option;
import org.yaml.snakeyaml.TypeDescription;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.constructor.Constructor;
import org.yaml.snakeyaml.nodes.MappingNode;
import org.yaml.snakeyaml.nodes.Node;
import org.yaml.snakeyaml.nodes.Tag;
import org.yaml.snakeyaml.representer.Representer;
public class JMXTool
{
private static final List<String> METRIC_PACKAGES = Arrays.asList("org.apache.cassandra.metrics",
"org.apache.cassandra.db",
"org.apache.cassandra.hints",
"org.apache.cassandra.internal",
"org.apache.cassandra.net",
"org.apache.cassandra.request",
"org.apache.cassandra.service");
private static final Comparator<MBeanOperationInfo> OPERATOR_COMPARATOR = (a, b) -> {
int rc = a.getName().compareTo(b.getName());
if (rc != 0)
return rc;
String[] aSig = Stream.of(a.getSignature()).map(MBeanParameterInfo::getName).toArray(String[]::new);
String[] bSig = Stream.of(b.getSignature()).map(MBeanParameterInfo::getName).toArray(String[]::new);
rc = Integer.compare(aSig.length, bSig.length);
if (rc != 0)
return rc;
for (int i = 0; i < aSig.length; i++)
{
rc = aSig[i].compareTo(bSig[i]);
if (rc != 0)
return rc;
}
return rc;
};
@Command(name = "dump", description = "Dump the Apache Cassandra JMX objects and metadata.")
public static final class Dump implements Callable<Void>
{
@Inject
private HelpOption helpOption;
@Option(title = "url", name = { "-u", "--url" }, description = "JMX url to target")
private String targetUrl = "service:jmx:rmi:///jndi/rmi://localhost:7199/jmxrmi";
@Option(title = "format", name = { "-f", "--format" }, description = "What format to dump content as; supported values are console (default), json, and yaml")
private Format format = Format.console;
public Void call() throws Exception
{
Map<String, Info> map = load(new JMXServiceURL(targetUrl));
format.dump(System.out, map);
return null;
}
public enum Format
{
console
{
void dump(OutputStream output, Map<String, Info> map)
{
@SuppressWarnings("resource")
// output should be released by caller
PrintStream out = toPrintStream(output);
for (Map.Entry<String, Info> e : map.entrySet())
{
String name = e.getKey();
Info info = e.getValue();
out.println(name);
out.println("\tAttributes");
Stream.of(info.attributes).forEach(a -> printRow(out, a.name, a.type, a.access));
out.println("\tOperations");
Stream.of(info.operations).forEach(o -> {
String args = Stream.of(o.parameters)
.map(i -> i.name + ": " + i.type)
.collect(Collectors.joining(",", "(", ")"));
printRow(out, o.name, o.returnType, args);
});
}
}
},
json
{
void dump(OutputStream output, Map<String, Info> map) throws IOException
{
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(output, map);
}
},
yaml
{
void dump(OutputStream output, Map<String, Info> map) throws IOException
{
Representer representer = new Representer();
representer.addClassTag(Info.class, Tag.MAP); // avoid the auto added tag
Yaml yaml = new Yaml(representer);
yaml.dump(map, new OutputStreamWriter(output));
}
};
private static PrintStream toPrintStream(OutputStream output)
{
try
{
return output instanceof PrintStream ? (PrintStream) output : new PrintStream(output, true, "UTF-8");
}
catch (UnsupportedEncodingException e)
{
throw new AssertionError(e); // utf-8 is a required charset for the JVM
}
}
abstract void dump(OutputStream output, Map<String, Info> map) throws IOException;
}
}
@Command(name = "diff", description = "Diff two jmx dump files and report their differences")
public static final class Diff implements Callable<Void>
{
@Inject
private HelpOption helpOption;
@Arguments(title = "files", usage = "<left> <right>", description = "Files to diff")
private List<File> files;
@Option(title = "format", name = { "-f", "--format" }, description = "What format the files are in; only support json and yaml as format")
private Format format = Format.yaml;
@Option(title = "ignore left", name = { "--ignore-missing-on-left" }, description = "Ignore results missing on the left")
private boolean ignoreMissingLeft;
@Option(title = "ignore right", name = { "--ignore-missing-on-right" }, description = "Ignore results missing on the right")
private boolean ignoreMissingRight;
@Option(title = "exclude objects", name = "--exclude-object", description
= "Ignores processing specific objects. " +
"Each usage should take a single object, " +
"but can use this flag multiple times.")
private List<CliPattern> excludeObjects = new ArrayList<>();
@Option(title = "exclude attributes", name = "--exclude-attribute", description
= "Ignores processing specific attributes. " +
"Each usage should take a single attribute, " +
"but can use this flag multiple times.")
private List<CliPattern> excludeAttributes = new ArrayList<>();
@Option(title = "exclude operations", name = "--exclude-operation", description
= "Ignores processing specific operations. " +
"Each usage should take a single operation, " +
"but can use this flag multiple times.")
private List<CliPattern> excludeOperations = new ArrayList<>();
public Void call() throws Exception
{
Preconditions.checkArgument(files.size() == 2, "files requires 2 arguments but given %s", files);
Map<String, Info> left;
Map<String, Info> right;
try (FileInputStream leftStream = new FileInputStream(files.get(0));
FileInputStream rightStream = new FileInputStream(files.get(1)))
{
left = format.load(leftStream);
right = format.load(rightStream);
}
diff(left, right);
return null;
}
private void diff(Map<String, Info> left, Map<String, Info> right)
{
DiffResult<String> objectNames = diff(left.keySet(), right.keySet(), name -> {
for (CliPattern p : excludeObjects)
{
if (p.pattern.matcher(name).matches())
return false;
}
return true;
});
if (!ignoreMissingRight && !objectNames.notInRight.isEmpty())
{
System.out.println("Objects not in right:");
printSet(0, objectNames.notInRight);
}
if (!ignoreMissingLeft && !objectNames.notInLeft.isEmpty())
{
System.out.println("Objects not in left: ");
printSet(0, objectNames.notInLeft);
}
Runnable printHeader = new Runnable()
{
boolean printedHeader = false;
public void run()
{
if (!printedHeader)
{
System.out.println("Difference found in attribute or operation");
printedHeader = true;
}
}
};
for (String key : objectNames.shared)
{
Info leftInfo = left.get(key);
Info rightInfo = right.get(key);
DiffResult<Attribute> attributes = diff(leftInfo.attributeSet(), rightInfo.attributeSet(), attribute -> {
for (CliPattern p : excludeAttributes)
{
if (p.pattern.matcher(attribute.name).matches())
return false;
}
return true;
});
if (!ignoreMissingRight && !attributes.notInRight.isEmpty())
{
printHeader.run();
System.out.println(key + "\tattribute not in right:");
printSet(1, attributes.notInRight);
}
if (!ignoreMissingLeft && !attributes.notInLeft.isEmpty())
{
printHeader.run();
System.out.println(key + "\tattribute not in left:");
printSet(1, attributes.notInLeft);
}
DiffResult<Operation> operations = diff(leftInfo.operationSet(), rightInfo.operationSet(), operation -> {
for (CliPattern p : excludeOperations)
{
if (p.pattern.matcher(operation.name).matches())
return false;
}
return true;
});
if (!ignoreMissingRight && !operations.notInRight.isEmpty())
{
printHeader.run();
System.out.println(key + "\toperation not in right:");
printSet(1, operations.notInRight, (sb, o) ->
rightInfo.getOperation(o.name).ifPresent(match ->
sb.append("\t").append("similar in right: ").append(match)));
}
if (!ignoreMissingLeft && !operations.notInLeft.isEmpty())
{
printHeader.run();
System.out.println(key + "\toperation not in left:");
printSet(1, operations.notInLeft, (sb, o) ->
leftInfo.getOperation(o.name).ifPresent(match ->
sb.append("\t").append("similar in left: ").append(match)));
}
}
}
private static <T extends Comparable<T>> void printSet(int indent, Set<T> set)
{
printSet(indent, set, (i1, i2) -> {});
}
private static <T extends Comparable<T>> void printSet(int indent, Set<T> set, BiConsumer<StringBuilder, T> fn)
{
StringBuilder sb = new StringBuilder();
for (T t : new TreeSet<>(set))
{
sb.setLength(0);
for (int i = 0; i < indent; i++)
sb.append('\t');
sb.append(t);
fn.accept(sb, t);
System.out.println(sb);
}
}
private static <T> DiffResult<T> diff(Set<T> left, Set<T> right, Predicate<T> fn)
{
left = Sets.filter(left, fn);
right = Sets.filter(right, fn);
return new DiffResult<>(Sets.difference(left, right), Sets.difference(right, left), Sets.intersection(left, right));
}
private static final class DiffResult<T>
{
private final SetView<T> notInRight;
private final SetView<T> notInLeft;
private final SetView<T> shared;
private DiffResult(SetView<T> notInRight, SetView<T> notInLeft, SetView<T> shared)
{
this.notInRight = notInRight;
this.notInLeft = notInLeft;
this.shared = shared;
}
}
public enum Format
{
json
{
Map<String, Info> load(InputStream input) throws IOException
{
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(input, new TypeReference<Map<String, Info>>() {});
}
},
yaml
{
Map<String, Info> load(InputStream input) throws IOException
{
Yaml yaml = new Yaml(new CustomConstructor());
return (Map<String, Info>) yaml.load(input);
}
};
abstract Map<String, Info> load(InputStream input) throws IOException;
}
private static final class CustomConstructor extends Constructor
{
private static final String ROOT = "__root__";
private static final TypeDescription INFO_TYPE = new TypeDescription(Info.class);
public CustomConstructor()
{
this.rootTag = new Tag(ROOT);
this.addTypeDescription(INFO_TYPE);
}
protected Object constructObject(Node node)
{
if (ROOT.equals(node.getTag().getValue()) && node instanceof MappingNode)
{
MappingNode mn = (MappingNode) node;
return mn.getValue().stream()
.collect(Collectors.toMap(t -> super.constructObject(t.getKeyNode()),
t -> {
Node child = t.getValueNode();
child.setType(INFO_TYPE.getType());
return super.constructObject(child);
}));
}
else
{
return super.constructObject(node);
}
}
}
}
private static Map<String, Info> load(JMXServiceURL url) throws IOException, MalformedObjectNameException, IntrospectionException, InstanceNotFoundException, ReflectionException
{
try (JMXConnector jmxc = JMXConnectorFactory.connect(url, null))
{
MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
Map<String, Info> map = new TreeMap<>();
for (String pkg : new TreeSet<>(METRIC_PACKAGES))
{
Set<ObjectName> metricNames = new TreeSet<>(mbsc.queryNames(new ObjectName(pkg + ":*"), null));
for (ObjectName name : metricNames)
{
if (mbsc.isRegistered(name))
{
MBeanInfo info = mbsc.getMBeanInfo(name);
map.put(name.toString(), Info.from(info));
}
}
}
return map;
}
}
private static String getAccess(MBeanAttributeInfo a)
{
String access;
if (a.isReadable())
{
if (a.isWritable())
access = "read/write";
else
access = "read-only";
}
else if (a.isWritable())
access = "write-only";
else
access = "no-access";
return access;
}
private static String normalizeType(String type)
{
switch (type)
{
case "[Z":
return "boolean[]";
case "[B":
return "byte[]";
case "[S":
return "short[]";
case "[I":
return "int[]";
case "[J":
return "long[]";
case "[F":
return "float[]";
case "[D":
return "double[]";
case "[C":
return "char[]";
}
if (type.startsWith("[L"))
return type.substring(2, type.length() - 1) + "[]"; // -1 will remove the ; at the end
return type;
}
private static final StringBuilder ROW_BUFFER = new StringBuilder();
private static void printRow(PrintStream out, String... args)
{
ROW_BUFFER.setLength(0);
ROW_BUFFER.append("\t\t");
for (String a : args)
ROW_BUFFER.append(a).append("\t");
out.println(ROW_BUFFER);
}
public static final class Info
{
private Attribute[] attributes;
private Operation[] operations;
public Info()
{
}
public Info(Attribute[] attributes, Operation[] operations)
{
this.attributes = attributes;
this.operations = operations;
}
private static Info from(MBeanInfo info)
{
Attribute[] attributes = Stream.of(info.getAttributes())
.sorted(Comparator.comparing(MBeanFeatureInfo::getName))
.map(Attribute::from)
.toArray(Attribute[]::new);
Operation[] operations = Stream.of(info.getOperations())
.sorted(OPERATOR_COMPARATOR)
.map(Operation::from)
.toArray(Operation[]::new);
return new Info(attributes, operations);
}
public Attribute[] getAttributes()
{
return attributes;
}
public void setAttributes(Attribute[] attributes)
{
this.attributes = attributes;
}
public Set<String> attributeNames()
{
return Stream.of(attributes).map(a -> a.name).collect(Collectors.toSet());
}
public Set<Attribute> attributeSet()
{
return new HashSet<>(Arrays.asList(attributes));
}
public Operation[] getOperations()
{
return operations;
}
public void setOperations(Operation[] operations)
{
this.operations = operations;
}
public Set<String> operationNames()
{
return Stream.of(operations).map(o -> o.name).collect(Collectors.toSet());
}
public Set<Operation> operationSet()
{
return new HashSet<>(Arrays.asList(operations));
}
public Optional<Attribute> getAttribute(String name)
{
return Stream.of(attributes).filter(a -> a.name.equals(name)).findFirst();
}
public Attribute getAttributePresent(String name)
{
return getAttribute(name).orElseThrow(AssertionError::new);
}
public Optional<Operation> getOperation(String name)
{
return Stream.of(operations).filter(o -> o.name.equals(name)).findFirst();
}
public Operation getOperationPresent(String name)
{
return getOperation(name).orElseThrow(AssertionError::new);
}
@Override
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Info info = (Info) o;
return Arrays.equals(attributes, info.attributes) &&
Arrays.equals(operations, info.operations);
}
@Override
public int hashCode()
{
int result = Arrays.hashCode(attributes);
result = 31 * result + Arrays.hashCode(operations);
return result;
}
}
public static final class Attribute implements Comparable<Attribute>
{
private String name;
private String type;
private String access;
public Attribute()
{
}
public Attribute(String name, String type, String access)
{
this.name = name;
this.type = type;
this.access = access;
}
private static Attribute from(MBeanAttributeInfo info)
{
return new Attribute(info.getName(), normalizeType(info.getType()), JMXTool.getAccess(info));
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getType()
{
return type;
}
public void setType(String type)
{
this.type = type;
}
public String getAccess()
{
return access;
}
public void setAccess(String access)
{
this.access = access;
}
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Attribute attribute = (Attribute) o;
return Objects.equals(name, attribute.name) &&
Objects.equals(type, attribute.type);
}
public int hashCode()
{
return Objects.hash(name, type);
}
public String toString()
{
return name + ": " + type;
}
public int compareTo(Attribute o)
{
int rc = name.compareTo(o.name);
if (rc != 0)
return rc;
return type.compareTo(o.type);
}
}
public static final class Operation implements Comparable<Operation>
{
private String name;
private Parameter[] parameters;
private String returnType;
public Operation()
{
}
public Operation(String name, Parameter[] parameters, String returnType)
{
this.name = name;
this.parameters = parameters;
this.returnType = returnType;
}
private static Operation from(MBeanOperationInfo info)
{
Parameter[] params = Stream.of(info.getSignature()).map(Parameter::from).toArray(Parameter[]::new);
return new Operation(info.getName(), params, normalizeType(info.getReturnType()));
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public Parameter[] getParameters()
{
return parameters;
}
public void setParameters(Parameter[] parameters)
{
this.parameters = parameters;
}
public List<String> parameterTypes()
{
return Stream.of(parameters).map(p -> p.type).collect(Collectors.toList());
}
public String getReturnType()
{
return returnType;
}
public void setReturnType(String returnType)
{
this.returnType = returnType;
}
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Operation operation = (Operation) o;
return Objects.equals(name, operation.name) &&
Arrays.equals(parameters, operation.parameters) &&
Objects.equals(returnType, operation.returnType);
}
public int hashCode()
{
int result = Objects.hash(name, returnType);
result = 31 * result + Arrays.hashCode(parameters);
return result;
}
public String toString()
{
return name + Stream.of(parameters).map(Parameter::toString).collect(Collectors.joining(", ", "(", ")")) + ": " + returnType;
}
public int compareTo(Operation o)
{
int rc = name.compareTo(o.name);
if (rc != 0)
return rc;
rc = Integer.compare(parameters.length, o.parameters.length);
if (rc != 0)
return rc;
for (int i = 0; i < parameters.length; i++)
{
rc = parameters[i].type.compareTo(o.parameters[i].type);
if (rc != 0)
return rc;
}
return returnType.compareTo(o.returnType);
}
}
public static final class Parameter
{
private String name;
private String type;
public Parameter()
{
}
public Parameter(String name, String type)
{
this.name = name;
this.type = type;
}
private static Parameter from(MBeanParameterInfo info)
{
return new Parameter(info.getName(), normalizeType(info.getType()));
}
public String getName()
{
return name;
}
public void setName(String name)
{
this.name = name;
}
public String getType()
{
return type;
}
public void setType(String type)
{
this.type = type;
}
public boolean equals(Object o)
{
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Parameter parameter = (Parameter) o;
return Objects.equals(type, parameter.type);
}
public int hashCode()
{
return Objects.hash(type);
}
public String toString()
{
return name + ": " + type;
}
}
public static final class CliPattern
{
private final Pattern pattern;
public CliPattern(String pattern)
{
this.pattern = Pattern.compile(pattern);
}
}
public static void main(String[] args) throws Exception
{
Cli.CliBuilder<Callable<Void>> builder = Cli.builder("jmxtool");
builder.withDefaultCommand(Help.class);
builder.withCommands(Help.class, Dump.class, Diff.class);
Cli<Callable<Void>> parser = builder.build();
Callable<Void> command = parser.parse(args);
command.call();
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -42,6 +42,7 @@ import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXConnectorServer;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnectorServer;
import com.google.common.base.Objects;
import com.google.common.base.Strings;
@ -376,6 +377,18 @@ public abstract class CQLTester
TokenMetadata metadata = StorageService.instance.getTokenMetadata();
metadata.clearUnsafe();
if (jmxServer != null && jmxServer instanceof RMIConnectorServer)
{
try
{
((RMIConnectorServer) jmxServer).stop();
}
catch (IOException e)
{
logger.warn("Error shutting down jmx", e);
}
}
}
@Before

View File

@ -0,0 +1,171 @@
package org.apache.cassandra.tools;
import java.util.Arrays;
import java.util.List;
import com.google.common.collect.Lists;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import com.datastax.driver.core.SimpleStatement;
import org.apache.cassandra.cql3.CQLTester;
import org.apache.cassandra.service.CassandraDaemon;
import org.apache.cassandra.service.GCInspector;
import org.apache.cassandra.transport.ProtocolVersion;
import org.assertj.core.api.Assertions;
import static org.apache.cassandra.tools.ToolRunner.Runners.invokeTool;
/**
* This class is to monitor the JMX compatability cross different versions, and relies on a gold set of metrics which
* were generated following the instructions below. These tests only check for breaking changes, so will ignore any
* new metrics added in a release. If the latest release is not finalized yet then the latest version might fail
* if a unrelesed metric gets renamed, if this happens then the gold set should be updated for the latest version.
*
* If a test fails for a previous version, then this means we have a JMX compatability regression, if the metric has
* gone through proper deprecation then the metric can be excluded using the patterns used in other tests, if the metric
* has not gone through proper deprecation then the change should be looked at more carfuly to avoid breaking users.
*
* In order to generate the dump for another version, launch a cluster then run the following
* {@code
* create keyspace cql_test_keyspace WITH replication = {'class': 'SimpleStrategy', 'replication_factor': '1'};
* use cql_test_keyspace;
* CREATE TABLE table_00 (pk int PRIMARY KEY);
* insert into table_00 (pk) values (42);
* select * from table_00 where pk=42;
* }
*/
public class JMXCompatabilityTest extends CQLTester
{
@ClassRule
public static TemporaryFolder TMP = new TemporaryFolder();
private static boolean CREATED_TABLE = false;
@BeforeClass
public static void setup() throws Exception
{
startJMXServer();
}
private void setupStandardTables() throws Throwable
{
if (CREATED_TABLE)
return;
// force loading mbean which CassandraDaemon creates
GCInspector.register();
CassandraDaemon.registerNativeAccess();
String name = KEYSPACE + "." + createTable("CREATE TABLE %s (pk int PRIMARY KEY)");
// use net to register everything like storage proxy
executeNet(ProtocolVersion.CURRENT, new SimpleStatement("INSERT INTO " + name + " (pk) VALUES (?)", 42));
executeNet(ProtocolVersion.CURRENT, new SimpleStatement("SELECT * FROM " + name + " WHERE pk=?", 42));
String script = "tools/bin/jmxtool dump -f yaml --url service:jmx:rmi:///jndi/rmi://" + jmxHost + ":" + jmxPort + "/jmxrmi > " + TMP.getRoot().getAbsolutePath() + "/out.yaml";
invokeTool("bash", "-c", script).assertOnExitCode().assertCleanStdErr();
CREATED_TABLE = true;
}
@Test
public void diff30() throws Throwable
{
List<String> excludeObjects = Arrays.asList("org.apache.cassandra.metrics:type=ThreadPools.*",
"org.apache.cassandra.internal:.*",
"org.apache.cassandra.metrics:type=DroppedMessage.*",
"org.apache.cassandra.metrics:type=ClientRequest,scope=CASRead,name=ConditionNotMet",
"org.apache.cassandra.metrics:type=Client,name=connectedThriftClients", // removed in CASSANDRA-11115
"org.apache.cassandra.request:type=ReadRepairStage", // removed in CASSANDRA-13910
"org.apache.cassandra.db:type=HintedHandoffManager", // removed in CASSANDRA-15939
// dropped tables
"org.apache.cassandra.metrics:type=Table,keyspace=system,scope=(schema_aggregates|schema_columnfamilies|schema_columns|schema_functions|schema_keyspaces|schema_triggers|schema_usertypes),name=.*",
".*keyspace=system,(scope|table|columnfamily)=views_builds_in_progress.*",
".*keyspace=system,(scope|table|columnfamily)=range_xfers.*",
".*keyspace=system,(scope|table|columnfamily)=hints.*",
".*keyspace=system,(scope|table|columnfamily)=batchlog.*");
List<String> excludeAttributes = Arrays.asList("RPCServerRunning", // removed in CASSANDRA-11115
"MaxNativeProtocolVersion");
List<String> excludeOperations = Arrays.asList("startRPCServer", "stopRPCServer", // removed in CASSANDRA-11115
// nodetool apis that were changed,
"decommission", // -> decommission(boolean)
"forceRepairAsync", // -> repairAsync
"forceRepairRangeAsync", // -> repairAsync
"beginLocalSampling", // -> beginLocalSampling(p1: java.lang.String, p2: int, p3: int): void
"finishLocalSampling" // -> finishLocalSampling(p1: java.lang.String, p2: int): java.util.List
);
diff(excludeObjects, excludeAttributes, excludeOperations, "test/data/jmxdump/cassandra-3.0-jmx.yaml");
}
@Test
public void diff311() throws Throwable
{
List<String> excludeObjects = Arrays.asList("org.apache.cassandra.metrics:type=ThreadPools.*",
"org.apache.cassandra.internal:.*",
"org.apache.cassandra.metrics:type=DroppedMessage.*",
"org.apache.cassandra.metrics:type=ClientRequest,scope=CASRead,name=ConditionNotMet",
"org.apache.cassandra.metrics:type=Client,name=connectedThriftClients", // removed in CASSANDRA-11115
"org.apache.cassandra.request:type=ReadRepairStage", // removed in CASSANDRA-13910
"org.apache.cassandra.db:type=HintedHandoffManager", // removed in CASSANDRA-15939
// dropped tables
"org.apache.cassandra.metrics:type=Table,keyspace=system,scope=(schema_aggregates|schema_columnfamilies|schema_columns|schema_functions|schema_keyspaces|schema_triggers|schema_usertypes),name=.*",
".*keyspace=system,(scope|table|columnfamily)=views_builds_in_progress.*",
".*keyspace=system,(scope|table|columnfamily)=range_xfers.*",
".*keyspace=system,(scope|table|columnfamily)=hints.*",
".*keyspace=system,(scope|table|columnfamily)=batchlog.*"
);
List<String> excludeAttributes = Arrays.asList("RPCServerRunning", // removed in CASSANDRA-11115
"MaxNativeProtocolVersion",
"StreamingSocketTimeout");
List<String> excludeOperations = Arrays.asList("startRPCServer", "stopRPCServer", // removed in CASSANDRA-11115
// nodetool apis that were changed,
"decommission", // -> decommission(boolean)
"forceRepairAsync", // -> repairAsync
"forceRepairRangeAsync", // -> repairAsync
"beginLocalSampling", // -> beginLocalSampling(p1: java.lang.String, p2: int, p3: int): void
"finishLocalSampling" // -> finishLocalSampling(p1: java.lang.String, p2: int): java.util.List
);
diff(excludeObjects, excludeAttributes, excludeOperations, "test/data/jmxdump/cassandra-3.11-jmx.yaml");
}
@Test
public void diff40() throws Throwable
{
List<String> excludeObjects = Arrays.asList();
List<String> excludeAttributes = Arrays.asList();
List<String> excludeOperations = Arrays.asList();
diff(excludeObjects, excludeAttributes, excludeOperations, "test/data/jmxdump/cassandra-4.0-jmx.yaml");
}
private void diff(List<String> excludeObjects, List<String> excludeAttributes, List<String> excludeOperations, String original) throws Throwable
{
setupStandardTables();
List<String> args = Lists.newArrayList("tools/bin/jmxtool", "diff",
"-f", "yaml",
"--ignore-missing-on-left",
original, TMP.getRoot().getAbsolutePath() + "/out.yaml");
excludeObjects.forEach(a -> {
args.add("--exclude-object");
args.add(a);
});
excludeAttributes.forEach(a -> {
args.add("--exclude-attribute");
args.add(a);
});
excludeOperations.forEach(a -> {
args.add("--exclude-operation");
args.add(a);
});
ToolRunner result = invokeTool(args);
result.assertOnExitCode().assertCleanStdErr();
Assertions.assertThat(result.getStdout()).isEmpty();
}
}

View File

@ -0,0 +1,171 @@
package org.apache.cassandra.tools;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.junit.Test;
import org.apache.cassandra.io.util.DataInputBuffer;
import org.apache.cassandra.io.util.DataOutputBuffer;
import org.apache.cassandra.utils.Generators;
import org.assertj.core.api.Assertions;
import org.quicktheories.core.Gen;
import org.quicktheories.generators.SourceDSL;
import static org.apache.cassandra.utils.FailingConsumer.orFail;
import static org.quicktheories.QuickTheory.qt;
public class JMXToolTest
{
@Test
public void jsonSerde()
{
serde(JMXTool.Dump.Format.json, JMXTool.Diff.Format.json);
}
@Test
public void yamlSerde()
{
serde(JMXTool.Dump.Format.yaml, JMXTool.Diff.Format.yaml);
}
@Test
public void cliHelp()
{
ToolRunner result = jmxtool();
result.assertCleanStdErr().assertOnExitCode();
Assertions.assertThat(result.getStdout())
.isEqualTo("usage: jmxtool <command> [<args>]\n" +
"\n" +
"The most commonly used jmxtool commands are:\n" +
" diff Diff two jmx dump files and report their differences\n" +
" dump Dump the Apache Cassandra JMX objects and metadata.\n" +
" help Display help information\n" +
"\n" +
"See 'jmxtool help <command>' for more information on a specific command.\n" +
"\n");
}
@Test
public void cliHelpDiff()
{
ToolRunner result = jmxtool("help", "diff");
result.assertCleanStdErr().assertOnExitCode();
Assertions.assertThat(result.getStdout())
.isEqualTo("NAME\n" +
" jmxtool diff - Diff two jmx dump files and report their differences\n" +
"\n" +
"SYNOPSIS\n" +
" jmxtool diff [--exclude-attribute <exclude attributes>...]\n" +
" [--exclude-object <exclude objects>...]\n" +
" [--exclude-operation <exclude operations>...]\n" +
" [(-f <format> | --format <format>)] [(-h | --help)]\n" +
" [--ignore-missing-on-left] [--ignore-missing-on-right] [--] <left>\n" +
" <right>\n" +
"\n" +
"OPTIONS\n" +
" --exclude-attribute <exclude attributes>\n" +
" Ignores processing specific attributes. Each usage should take a\n" +
" single attribute, but can use this flag multiple times.\n" +
"\n" +
" --exclude-object <exclude objects>\n" +
" Ignores processing specific objects. Each usage should take a single\n" +
" object, but can use this flag multiple times.\n" +
"\n" +
" --exclude-operation <exclude operations>\n" +
" Ignores processing specific operations. Each usage should take a\n" +
" single operation, but can use this flag multiple times.\n" +
"\n" +
" -f <format>, --format <format>\n" +
" What format the files are in; only support json and yaml as format\n" +
"\n" +
" -h, --help\n" +
" Display help information\n" +
"\n" +
" --ignore-missing-on-left\n" +
" Ignore results missing on the left\n" +
"\n" +
" --ignore-missing-on-right\n" +
" Ignore results missing on the right\n" +
"\n" +
" --\n" +
" This option can be used to separate command-line options from the\n" +
" list of argument, (useful when arguments might be mistaken for\n" +
" command-line options\n" +
"\n" +
" <left> <right>\n" +
" Files to diff\n" +
"\n" +
"\n");
}
@Test
public void cliHelpDump()
{
ToolRunner result = jmxtool("help", "dump");
result.assertCleanStdErr().assertOnExitCode();
Assertions.assertThat(result.getStdout())
.isEqualTo("NAME\n" +
" jmxtool dump - Dump the Apache Cassandra JMX objects and metadata.\n" +
"\n" +
"SYNOPSIS\n" +
" jmxtool dump [(-f <format> | --format <format>)] [(-h | --help)]\n" +
" [(-u <url> | --url <url>)]\n" +
"\n" +
"OPTIONS\n" +
" -f <format>, --format <format>\n" +
" What format to dump content as; supported values are console\n" +
" (default), json, and yaml\n" +
"\n" +
" -h, --help\n" +
" Display help information\n" +
"\n" +
" -u <url>, --url <url>\n" +
" JMX url to target\n" +
"\n" +
"\n");
}
private static ToolRunner jmxtool(String... args)
{
List<String> cmd = new ArrayList<>(1 + args.length);
cmd.add("tools/bin/jmxtool");
cmd.addAll(Arrays.asList(args));
return ToolRunner.Runners.invokeTool(cmd);
}
private void serde(JMXTool.Dump.Format serializer, JMXTool.Diff.Format deserializer)
{
DataOutputBuffer buffer = new DataOutputBuffer();
qt().withShrinkCycles(0).forAll(gen()).checkAssert(orFail(map -> serde(serializer, deserializer, buffer, map)));
}
private void serde(JMXTool.Dump.Format serializer,
JMXTool.Diff.Format deserializer,
DataOutputBuffer buffer,
Map<String, JMXTool.Info> map) throws IOException
{
buffer.clear();
serializer.dump(buffer, map);
Map<String, JMXTool.Info> read = deserializer.load(new DataInputBuffer(buffer.buffer(), false));
Assertions.assertThat(read)
.as("property deserialize(serialize(value)) == value failed")
.isEqualTo(map);
}
private static final Gen<JMXTool.Attribute> attributeGen = Generators.IDENTIFIER_GEN.zip(Generators.IDENTIFIER_GEN, Generators.IDENTIFIER_GEN, JMXTool.Attribute::new);
private static final Gen<JMXTool.Parameter> parameterGen = Generators.IDENTIFIER_GEN.zip(Generators.IDENTIFIER_GEN, JMXTool.Parameter::new);
private static final Gen<JMXTool.Operation> operationGen = Generators.IDENTIFIER_GEN.zip(SourceDSL.arrays().ofClass(parameterGen, JMXTool.Parameter.class).withLengthBetween(0, 10), Generators.IDENTIFIER_GEN, JMXTool.Operation::new);
private static final Gen<JMXTool.Info> infoGen = SourceDSL.arrays().ofClass(attributeGen, JMXTool.Attribute.class).withLengthBetween(0, 10).zip(SourceDSL.arrays().ofClass(operationGen, JMXTool.Operation.class).withLengthBetween(0, 10), JMXTool.Info::new);
private static Gen<Map<String, JMXTool.Info>> gen()
{
return SourceDSL.maps().of(Generators.IDENTIFIER_GEN, infoGen).ofSizeBetween(0, 10);
}
}

View File

@ -30,14 +30,11 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -52,15 +49,16 @@ public class ToolRunner implements AutoCloseable
protected static final Logger logger = LoggerFactory.getLogger(ToolRunner.class);
private static final ImmutableList<String> DEFAULT_CLEANERS = ImmutableList.of("(?im)^picked up.*\\R");
private static final String[] EMPTY_STRING_ARRAY = new String[0];
private final List<String> allArgs = new ArrayList<>();
private Process process;
@SuppressWarnings("resource")
private final ByteArrayOutputStream errBuffer = new ByteArrayOutputStream();
@SuppressWarnings("resource")
private final ByteArrayOutputStream outBuffer = new ByteArrayOutputStream();
private InputStream stdin;
private boolean stdinAutoClose;
private long defaultTimeoutMillis = TimeUnit.SECONDS.toMillis(30);
private Thread ioWatcher;
private Thread[] ioWatchers;
private Map<String, String> envs;
private boolean runOutOfProcess = true;
@ -75,10 +73,9 @@ public class ToolRunner implements AutoCloseable
this.runOutOfProcess = runOutOfProcess;
}
public ToolRunner withStdin(InputStream stdin, boolean autoClose)
public ToolRunner withStdin(InputStream stdin)
{
this.stdin = stdin;
this.stdinAutoClose = autoClose;
return this;
}
@ -114,15 +111,16 @@ public class ToolRunner implements AutoCloseable
originalSysErr.flush();
ByteArrayOutputStream toolOut = new ByteArrayOutputStream();
ByteArrayOutputStream toolErr = new ByteArrayOutputStream();
System.setIn(stdin == null ? originalSysIn : stdin);
int exit = 0;
try (PrintStream newOut = new PrintStream(toolOut); PrintStream newErr = new PrintStream(toolErr);)
int exit;
try (PrintStream newOut = new PrintStream(toolOut); PrintStream newErr = new PrintStream(toolErr))
{
System.setOut(newOut);
System.setErr(newErr);
String clazz = allArgs.get(0);
String[] clazzArgs = allArgs.subList(1, allArgs.size()).toArray(new String[0]);
String[] clazzArgs = allArgs.subList(1, allArgs.size()).toArray(EMPTY_STRING_ARRAY);
exit = runClassAsTool(clazz, clazzArgs);
}
@ -161,8 +159,8 @@ public class ToolRunner implements AutoCloseable
{
if (stdin == null)
return null;
ByteArrayOutputStream out = null;
ByteArrayOutputStream out;
try
{
out = new ByteArrayOutputStream(stdin.available());
@ -176,17 +174,40 @@ public class ToolRunner implements AutoCloseable
}
@Override
public int waitFor() throws InterruptedException
public int waitFor()
{
return exitValue();
}
};
}
ioWatcher = new Thread(this::watchIO);
ioWatcher.setDaemon(true);
ioWatcher.start();
// each stream tends to use a bounded buffer, so need to process each stream in its own thread else we
// might block on an idle stream, not consuming the other stream which is blocked in the other process
// as nothing is consuming
int numWatchers = 2;
// only need a stdin watcher when forking
boolean includeStdinWatcher = runOutOfProcess && stdin != null;
if (includeStdinWatcher)
numWatchers = 3;
ioWatchers = new Thread[numWatchers];
ioWatchers[0] = new Thread(new StreamGobbler<>(process.getErrorStream(), errBuffer));
ioWatchers[0].setDaemon(true);
ioWatchers[0].setName("IO Watcher stderr for " + allArgs);
ioWatchers[0].start();
ioWatchers[1] = new Thread(new StreamGobbler<>(process.getInputStream(), outBuffer));
ioWatchers[1].setDaemon(true);
ioWatchers[1].setName("IO Watcher stdout for " + allArgs);
ioWatchers[1].start();
if (includeStdinWatcher)
{
ioWatchers[2] = new Thread(new StreamGobbler<>(stdin, process.getOutputStream()));
ioWatchers[2].setDaemon(true);
ioWatchers[2].setName("IO Watcher stdin for " + allArgs);
ioWatchers[2].start();
}
}
catch (IOException e)
{
@ -196,53 +217,7 @@ public class ToolRunner implements AutoCloseable
return this;
}
private void watchIO()
{
OutputStream in = process.getOutputStream();
InputStream err = process.getErrorStream();
InputStream out = process.getInputStream();
while (true)
{
boolean errHandled;
boolean outHandled;
try
{
if (stdin != null)
{
IOUtils.copy(stdin, in);
if (stdinAutoClose)
{
in.close();
stdin = null;
}
}
errHandled = IOUtils.copy(err, errBuffer) > 0;
outHandled = IOUtils.copy(out, outBuffer) > 0;
}
catch(IOException e1)
{
logger.error("Error trying to use in/err/out from process");
Thread.currentThread().interrupt();
break;
}
if (!errHandled && !outHandled)
{
if (!process.isAlive())
return;
try
{
Thread.sleep(50L);
}
catch (InterruptedException e)
{
Thread.currentThread().interrupt();
break;
}
}
}
}
public int runClassAsTool(String clazz, String... args)
public static int runClassAsTool(String clazz, String... args)
{
try
{
@ -303,19 +278,16 @@ public class ToolRunner implements AutoCloseable
return process != null && process.isAlive();
}
public boolean waitFor()
{
return waitFor(defaultTimeoutMillis, TimeUnit.MILLISECONDS);
}
public boolean waitFor(long time, TimeUnit timeUnit)
public int waitFor()
{
try
{
if (!process.waitFor(time, timeUnit))
return false;
ioWatcher.join();
return true;
int rc = process.waitFor();
// must call first in order to make sure the stdin ioWatcher will exit
onComplete();
for (Thread t : ioWatchers)
t.join();
return rc;
}
catch (InterruptedException e)
{
@ -325,10 +297,8 @@ public class ToolRunner implements AutoCloseable
public ToolRunner waitAndAssertOnExitCode()
{
assertTrue(String.format("Tool %s didn't terminate",
argsToLogString()),
waitFor());
return assertOnExitCode();
assertExitCode(waitFor());
return this;
}
public ToolRunner waitAndAssertOnCleanExit()
@ -350,14 +320,18 @@ public class ToolRunner implements AutoCloseable
public ToolRunner assertOnExitCode()
{
int code = getExitCode();
assertExitCode(getExitCode());
return this;
}
private void assertExitCode(int code)
{
if (code != 0)
fail(String.format("%s%nexited with code %d%nstderr:%n%s%nstdout:%n%s",
argsToLogString(),
code,
getStderr(),
getStdout()));
return this;
}
public String argsToLogString()
@ -423,16 +397,68 @@ public class ToolRunner implements AutoCloseable
public void close()
{
forceKill();
onComplete();
}
private void onComplete()
{
if (stdin != null)
{
try
{
stdin.close();
}
catch (IOException e)
{
logger.warn("Error closing stdin for {}", allArgs, e);
}
}
}
private static final class StreamGobbler<T extends OutputStream> implements Runnable
{
private static final int BUFFER_SIZE = 8_192;
private final InputStream input;
private final T out;
private StreamGobbler(InputStream input, T out)
{
this.input = input;
this.out = out;
}
public void run()
{
byte[] buffer = new byte[BUFFER_SIZE];
while (true)
{
try
{
int read = input.read(buffer);
if (read == -1)
{
return;
}
out.write(buffer, 0, read);
}
catch (IOException e)
{
logger.error("Unexpected IO Error while reading stream", e);
return;
}
}
}
}
public static class Runners
{
public ToolRunner invokeNodetool(String... args)
public static ToolRunner invokeNodetool(String... args)
{
return invokeNodetool(Arrays.asList(args));
}
public ToolRunner invokeNodetool(List<String> args)
public static ToolRunner invokeNodetool(List<String> args)
{
return invokeTool(buildNodetoolArgs(args), true);
}
@ -442,31 +468,33 @@ public class ToolRunner implements AutoCloseable
return CQLTester.buildNodetoolArgs(args);
}
public ToolRunner invokeClassAsTool(String... args)
public static ToolRunner invokeClassAsTool(String... args)
{
return invokeClassAsTool(Arrays.asList(args));
}
public ToolRunner invokeClassAsTool(List<String> args)
public static ToolRunner invokeClassAsTool(List<String> args)
{
return invokeTool(args, false);
}
public ToolRunner invokeTool(String... args)
public static ToolRunner invokeTool(String... args)
{
return invokeTool(Arrays.asList(args));
}
public ToolRunner invokeTool(List<String> args)
public static ToolRunner invokeTool(List<String> args)
{
return invokeTool(args, true);
}
public ToolRunner invokeTool(List<String> args, boolean runOutOfProcess)
public static ToolRunner invokeTool(List<String> args, boolean runOutOfProcess)
{
ToolRunner runner = new ToolRunner(args, runOutOfProcess);
runner.start().waitFor();
return runner;
try (ToolRunner runner = new ToolRunner(args, runOutOfProcess).start())
{
runner.waitFor();
return runner;
}
}
}
}

76
tools/bin/jmxtool Executable file
View File

@ -0,0 +1,76 @@
#!/bin/sh
# 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.
if [ "x$CASSANDRA_INCLUDE" = "x" ]; then
# Locations (in order) to use when searching for an include file.
for include in "`dirname "$0"`/cassandra.in.sh" \
"$HOME/.cassandra.in.sh" \
/usr/share/cassandra/cassandra.in.sh \
/usr/local/share/cassandra/cassandra.in.sh \
/opt/cassandra/cassandra.in.sh; do
if [ -r "$include" ]; then
. "$include"
break
fi
done
elif [ -r "$CASSANDRA_INCLUDE" ]; then
. "$CASSANDRA_INCLUDE"
fi
if [ -z "$CASSANDRA_CONF" -o -z "$CLASSPATH" ]; then
echo "You must set the CASSANDRA_CONF and CLASSPATH vars" >&2
exit 1
fi
# Run cassandra-env.sh to pick up JMX_PORT
if [ -f "$CASSANDRA_CONF/cassandra-env.sh" ]; then
JVM_OPTS_SAVE=$JVM_OPTS
MAX_HEAP_SIZE_SAVE=$MAX_HEAP_SIZE
. "$CASSANDRA_CONF/cassandra-env.sh"
MAX_HEAP_SIZE=$MAX_HEAP_SIZE_SAVE
JVM_OPTS=$JVM_OPTS_SAVE
fi
# JMX Port passed via cmd line args (-p 9999 / --port 9999 / --port=9999)
# should override the value from cassandra-env.sh
ARGS=""
JVM_ARGS=""
while true
do
if [ "x" = "x$1" ]; then break; fi
case $1 in
-D*)
JVM_ARGS="$JVM_ARGS $1"
;;
*)
ARGS="$ARGS $1"
;;
esac
shift
done
if [ "x$MAX_HEAP_SIZE" = "x" ]; then
MAX_HEAP_SIZE="512m"
fi
"$JAVA" $JAVA_AGENT -ea -da:net.openhft... -cp "$CLASSPATH" $JVM_OPTS -Xmx$MAX_HEAP_SIZE \
-Dlogback.configurationFile=logback-tools.xml \
$JVM_ARGS \
org.apache.cassandra.tools.JMXTool $ARGS
# vi:ai sw=4 ts=4 tw=0 et

36
tools/bin/jmxtool.bat Normal file
View File

@ -0,0 +1,36 @@
@REM
@REM Licensed to the Apache Software Foundation (ASF) under one or more
@REM contributor license agreements. See the NOTICE file distributed with
@REM this work for additional information regarding copyright ownership.
@REM The ASF licenses this file to You under the Apache License, Version 2.0
@REM (the "License"); you may not use this file except in compliance with
@REM the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing, software
@REM distributed under the License is distributed on an "AS IS" BASIS,
@REM WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@REM See the License for the specific language governing permissions and
@REM limitations under the License.
@echo off
if "%OS%" == "Windows_NT" setlocal
pushd "%~dp0"
call cassandra.in.bat
if NOT DEFINED JAVA_HOME goto :err
set CASSANDRA_PARAMS=%CASSANDRA_PARAMS% -Dcassandra.logdir="%CASSANDRA_HOME%\logs"
"%JAVA_HOME%\bin\java" -cp %CASSANDRA_CLASSPATH% %CASSANDRA_PARAMS% -Dlogback.configurationFile=logback-tools.xml org.apache.cassandra.tools.JMXTool %*
goto finally
:err
echo The JAVA_HOME environment variable must be set to run this program!
pause
:finally
ENDLOCAL & set RC=%ERRORLEVEL%
exit /B %RC%