org.apache.cassandra.tools.JMXTool Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of cassandra-all Show documentation
Show all versions of cassandra-all Show documentation
The Apache Cassandra Project develops a highly scalable second-generation distributed database, bringing together Dynamo's fully distributed design and Bigtable's ColumnFamily-based data model.
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.cassandra.tools;
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.apache.cassandra.io.util.File;
import org.apache.cassandra.io.util.FileInputStreamPlus;
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 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 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
{
@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 map = load(new JMXServiceURL(targetUrl));
format.dump(System.out, map);
return null;
}
public enum Format
{
console
{
void dump(OutputStream output, Map map)
{
@SuppressWarnings("resource")
// output should be released by caller
PrintStream out = toPrintStream(output);
for (Map.Entry 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 map) throws IOException
{
ObjectMapper mapper = new ObjectMapper();
mapper.writeValue(output, map);
}
},
yaml
{
void dump(OutputStream output, Map 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 map) throws IOException;
}
}
@Command(name = "diff", description = "Diff two jmx dump files and report their differences")
public static final class Diff implements Callable
{
@Inject
private HelpOption helpOption;
@Arguments(title = "files", usage = " ", description = "Files to diff")
private List 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 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 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 excludeOperations = new ArrayList<>();
public Void call() throws Exception
{
Preconditions.checkArgument(files.size() == 2, "files requires 2 arguments but given %s", files);
Map left;
Map right;
try (FileInputStreamPlus leftStream = new FileInputStreamPlus(files.get(0));
FileInputStreamPlus rightStream = new FileInputStreamPlus(files.get(1)))
{
left = format.load(leftStream);
right = format.load(rightStream);
}
diff(left, right);
return null;
}
private void diff(Map left, Map right)
{
DiffResult 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 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 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 > void printSet(int indent, Set set)
{
printSet(indent, set, (i1, i2) -> {});
}
private static > void printSet(int indent, Set set, BiConsumer 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 DiffResult diff(Set left, Set right, Predicate 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
{
private final SetView notInRight;
private final SetView notInLeft;
private final SetView shared;
private DiffResult(SetView notInRight, SetView notInLeft, SetView shared)
{
this.notInRight = notInRight;
this.notInLeft = notInLeft;
this.shared = shared;
}
}
public enum Format
{
json
{
Map load(InputStream input) throws IOException
{
ObjectMapper mapper = new ObjectMapper();
return mapper.readValue(input, new TypeReference