mirror of https://github.com/apache/cassandra
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:
parent
07f8db31ae
commit
91bcbb2873
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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%
|
||||
Loading…
Reference in New Issue