diff --git a/build.xml b/build.xml index 0050b306f5..a458023214 100644 --- a/build.xml +++ b/build.xml @@ -583,6 +583,15 @@ + + + + + + + + + @@ -653,6 +662,13 @@ + + + + + + + @@ -764,7 +780,14 @@ - + + + + + + + + args; + + private final Wrapper wrapper = new Wrapper(); + + public void run() + { + wrapper.prepare(args != null ? args.toArray(new String[0]) : new String[]{"help"}); + + if (!wrapper.requiresMbeanServerConn()) + { + // SJK command does not require an MBeanServerConnection, so just invoke it + wrapper.run(null); + } + else + { + // invoke common nodetool handling to establish MBeanServerConnection + super.run(); + } + } + + public void sequenceRun(NodeProbe probe) + { + wrapper.prepare(args != null ? args.toArray(new String[0]) : new String[]{"help"}); + if (!wrapper.run(probe)) + probe.failed(); + } + + protected void execute(NodeProbe probe) + { + if (!wrapper.run(probe)) + probe.failed(); + } + + /** + * Adopted copy of {@link org.gridkit.jvmtool.SJK} from https://github.com/aragozin/jvm-tools. + */ + public static class Wrapper extends CommandLauncher + { + boolean suppressSystemExit; + + private final Map commands = new HashMap<>(); + + private JCommander parser; + + private Runnable cmd; + + public void suppressSystemExit() + { + suppressSystemExit = true; + super.suppressSystemExit(); + } + + public boolean start(String[] args) + { + throw new UnsupportedOperationException(); + } + + public void prepare(String[] args) + { + try + { + + parser = new JCommander(this); + + addCommands(); + + fixCommands(); + + try + { + parser.parse(args); + } + catch (Exception e) + { + failAndPrintUsage(e.toString()); + } + + if (isHelp()) + { + String cmd = parser.getParsedCommand(); + if (cmd == null) + { + parser.usage(); + } + else + { + parser.usage(cmd); + } + } + else if (isListCommands()) + { + for (String cmd : commands.keySet()) + { + System.out.println(String.format("%8s - %s", cmd, parser.getCommandDescription(cmd))); + } + } + else + { + + cmd = commands.get(parser.getParsedCommand()); + + if (cmd == null) + { + failAndPrintUsage(); + } + } + } + catch (CommandAbortedError error) + { + for (String m : error.messages) + { + logError(m); + } + if (isVerbose() && error.getCause() != null) + { + logTrace(error.getCause()); + } + if (error.printUsage && parser != null) + { + if (parser.getParsedCommand() != null) + { + parser.usage(parser.getParsedCommand()); + } + else + { + parser.usage(); + } + } + } + catch (Throwable e) + { + e.printStackTrace(); + } + } + + public boolean run(final NodeProbe probe) + { + try + { + setJmxConnInfo(probe); + + if (cmd != null) + cmd.run(); + + return true; + } + catch (CommandAbortedError error) + { + for (String m : error.messages) + { + logError(m); + } + if (isVerbose() && error.getCause() != null) + { + logTrace(error.getCause()); + } + if (error.printUsage && parser != null) + { + if (parser.getParsedCommand() != null) + { + parser.usage(parser.getParsedCommand()); + } + else + { + parser.usage(); + } + } + return true; + } + catch (Throwable e) + { + e.printStackTrace(); + } + + // abnormal termination + return false; + } + + private void setJmxConnInfo(final NodeProbe probe) throws IllegalAccessException + { + Field f = jmxConnectionInfoField(cmd); + if (f != null) + { + f.setAccessible(true); + f.set(cmd, new JmxConnectionInfo(this) + { + public MBeanServerConnection getMServer() + { + return probe.getMbeanServerConn(); + } + }); + } + f = pidField(cmd); + if (f != null) + { + long pid = probe.getPid(); + + f.setAccessible(true); + if (f.getType() == int.class) + f.setInt(cmd, (int) pid); + if (f.getType() == long.class) + f.setLong(cmd, pid); + if (f.getType() == String.class) + f.set(cmd, Long.toString(pid)); + } + } + + private boolean isHelp() + { + try + { + Field f = CommandLauncher.class.getDeclaredField("help"); + f.setAccessible(true); + return f.getBoolean(this); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + private boolean isListCommands() + { + try + { + Field f = CommandLauncher.class.getDeclaredField("listCommands"); + f.setAccessible(true); + return f.getBoolean(this); + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + protected List getCommandPackages() + { + return Collections.singletonList("org.gridkit.jvmtool.cmd"); + } + + private void addCommands() throws InstantiationException, IllegalAccessException + { + for (String pack : getCommandPackages()) + { + for (Class c : findClasses(pack)) + { + if (CommandLauncher.CmdRef.class.isAssignableFrom(c)) + { + CommandLauncher.CmdRef cmd = (CommandLauncher.CmdRef) c.newInstance(); + String cmdName = cmd.getCommandName(); + Runnable cmdTask = cmd.newCommand(this); + if (commands.containsKey(cmdName)) + { + fail("Ambiguous implementation for '" + cmdName + '\''); + } + commands.put(cmdName, cmdTask); + parser.addCommand(cmdName, cmdTask); + } + } + } + } + + private void fixCommands() throws Exception + { + List fields = Arrays.asList(JCommander.class.getDeclaredField("m_fields"), + JCommander.class.getDeclaredField("m_requiredFields")); + for (Field f : fields) + f.setAccessible(true); + + for (JCommander cmdr : parser.getCommands().values()) + { + for (Field field : fields) { + Map fieldsMap = (Map) field.get(cmdr); + for (Iterator> iPar = fieldsMap.entrySet().iterator(); iPar.hasNext(); ) + { + Map.Entry par = iPar.next(); + switch (par.getKey().getName()) + { + // JmxConnectionInfo fields + case "pid": + case "sockAddr": + case "user": + case "password": + // + case "verbose": + case "help": + case "listCommands": + iPar.remove(); + break; + } + } + } + } + } + + boolean requiresMbeanServerConn() + { + return jmxConnectionInfoField(cmd) != null || pidField(cmd) != null; + } + + private static Field jmxConnectionInfoField(Runnable cmd) + { + if (cmd == null) + return null; + + for (Field f : cmd.getClass().getDeclaredFields()) + { + if (f.getType() == JmxConnectionInfo.class) + { + return f; + } + } + return null; + } + + private static Field pidField(Runnable cmd) + { + if (cmd == null) + return null; + + for (Field f : cmd.getClass().getDeclaredFields()) + { + if ("pid".equals(f.getName()) && + (f.getType() == int.class || f.getType() == long.class || f.getType() == String.class)) + { + return f; + } + } + return null; + } + + private static List> findClasses(String packageName) + { + // TODO this will probably fail with JPMS/Jigsaw + + List> result = new ArrayList<>(); + try + { + String path = packageName.replace('.', '/'); + for (String f : findFiles(path)) + { + if (f.endsWith(".class") && f.indexOf('$') < 0) + { + f = f.substring(0, f.length() - ".class".length()); + f = f.replace('/', '.'); + result.add(Class.forName(f)); + } + } + return result; + } + catch (Exception e) + { + throw new RuntimeException(e); + } + } + + static List findFiles(String path) throws IOException + { + List result = new ArrayList<>(); + ClassLoader cl = Thread.currentThread().getContextClassLoader(); + Enumeration en = cl.getResources(path); + while (en.hasMoreElements()) + { + URL u = en.nextElement(); + listFiles(result, u, path); + } + return result; + } + + static void listFiles(List results, URL packageURL, String path) throws IOException + { + if (packageURL.getProtocol().equals("jar")) + { + String jarFileName; + Enumeration jarEntries; + String entryName; + + // build jar file name, then loop through zipped entries + jarFileName = URLDecoder.decode(packageURL.getFile(), "UTF-8"); + jarFileName = jarFileName.substring(5, jarFileName.indexOf('!')); + try (JarFile jf = new JarFile(jarFileName)) + { + jarEntries = jf.entries(); + while (jarEntries.hasMoreElements()) + { + entryName = jarEntries.nextElement().getName(); + if (entryName.startsWith(path)) + { + results.add(entryName); + } + } + } + } + else + { + // loop through files in classpath + File dir = new File(packageURL.getFile()); + String cp = dir.getCanonicalPath(); + File root = dir; + while (true) + { + if (cp.equals(new File(root, path).getCanonicalPath())) + { + break; + } + root = root.getParentFile(); + } + listFiles(results, root, dir); + } + } + + static void listFiles(List names, File root, File dir) + { + String rootPath = root.getAbsolutePath(); + if (dir.exists() && dir.isDirectory()) + { + for (File file : dir.listFiles()) + { + if (file.isDirectory()) + { + listFiles(names, root, file); + } + else + { + String name = file.getAbsolutePath().substring(rootPath.length() + 1); + name = name.replace('\\', '/'); + names.add(name); + } + } + } + } + } +} diff --git a/src/java/org/apache/cassandra/tools/nodetool/TpStats.java b/src/java/org/apache/cassandra/tools/nodetool/TpStats.java index d7e8f06fb4..307c78d756 100644 --- a/src/java/org/apache/cassandra/tools/nodetool/TpStats.java +++ b/src/java/org/apache/cassandra/tools/nodetool/TpStats.java @@ -17,6 +17,8 @@ */ package org.apache.cassandra.tools.nodetool; +import javax.management.MBeanServerConnection; + import io.airlift.airline.Command; import io.airlift.airline.Option;