aboutsummaryrefslogtreecommitdiff
path: root/src/main/java/com/sk89q
diff options
context:
space:
mode:
authorAlbert Pham <the.sk89q@gmail.com>2013-06-13 15:55:12 -0700
committerAlbert Pham <the.sk89q@gmail.com>2013-06-13 15:55:12 -0700
commit280ad8b8bf2942f055daf5fb6f8ae55a7f88243a (patch)
tree05b6fa56f6b826242f6a42f1f8fb2514e3d568d9 /src/main/java/com/sk89q
downloadspark-280ad8b8bf2942f055daf5fb6f8ae55a7f88243a.tar.gz
spark-280ad8b8bf2942f055daf5fb6f8ae55a7f88243a.tar.bz2
spark-280ad8b8bf2942f055daf5fb6f8ae55a7f88243a.zip
Initial commit.
Diffstat (limited to 'src/main/java/com/sk89q')
-rw-r--r--src/main/java/com/sk89q/warmroast/ClassMapping.java67
-rw-r--r--src/main/java/com/sk89q/warmroast/DataViewServlet.java76
-rw-r--r--src/main/java/com/sk89q/warmroast/McpMapping.java112
-rw-r--r--src/main/java/com/sk89q/warmroast/RoastOptions.java49
-rw-r--r--src/main/java/com/sk89q/warmroast/StackNode.java162
-rw-r--r--src/main/java/com/sk89q/warmroast/StackTraceNode.java96
-rw-r--r--src/main/java/com/sk89q/warmroast/WarmRoast.java309
7 files changed, 871 insertions, 0 deletions
diff --git a/src/main/java/com/sk89q/warmroast/ClassMapping.java b/src/main/java/com/sk89q/warmroast/ClassMapping.java
new file mode 100644
index 0000000..ad3f7c0
--- /dev/null
+++ b/src/main/java/com/sk89q/warmroast/ClassMapping.java
@@ -0,0 +1,67 @@
+/*
+ * WarmRoast
+ * Copyright (C) 2013 Albert Pham <http://www.sk89q.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package com.sk89q.warmroast;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ClassMapping {
+
+ private final String obfuscated;
+ private final String actual;
+ private final Map<String, List<String>> methods = new HashMap<>();
+
+ public ClassMapping(String obfuscated, String actual) {
+ this.obfuscated = obfuscated;
+ this.actual = actual;
+ }
+
+ public String getObfuscated() {
+ return obfuscated;
+ }
+
+ public String getActual() {
+ return actual;
+ }
+
+ public void addMethod(String obfuscated, String actual) {
+ List<String> m = methods.get(obfuscated);
+ if (m == null) {
+ m = new ArrayList<>();
+ methods.put(obfuscated, m);
+ }
+ m.add(actual);
+ }
+
+ public List<String> mapMethod(String obfuscated) {
+ List<String> m = methods.get(obfuscated);
+ if (m == null) {
+ return new ArrayList<>();
+ }
+ return m;
+ }
+
+ @Override
+ public String toString() {
+ return getObfuscated() + "->" + getActual();
+ }
+
+}
diff --git a/src/main/java/com/sk89q/warmroast/DataViewServlet.java b/src/main/java/com/sk89q/warmroast/DataViewServlet.java
new file mode 100644
index 0000000..205dd3e
--- /dev/null
+++ b/src/main/java/com/sk89q/warmroast/DataViewServlet.java
@@ -0,0 +1,76 @@
+/*
+ * WarmRoast
+ * Copyright (C) 2013 Albert Pham <http://www.sk89q.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package com.sk89q.warmroast;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.Collection;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+public class DataViewServlet extends HttpServlet {
+
+ private static final long serialVersionUID = -2331397310804298286L;
+
+ private final WarmRoast roast;
+
+ public DataViewServlet(WarmRoast roast) {
+ this.roast = roast;
+ }
+
+ @Override
+ protected void doGet(HttpServletRequest request,
+ HttpServletResponse response) throws ServletException, IOException {
+ response.setContentType("text/html; charset=utf-8");
+ response.setStatus(HttpServletResponse.SC_OK);
+
+ PrintWriter w = response.getWriter();
+ w.println("<!DOCTYPE html><html><head><title>WarmRoast</title>");
+ w.println("<link rel=\"stylesheet\" type=\"text/css\" href=\"style.css\">");
+ w.println("</head><body>");
+ w.println("<h1>WarmRoast</h1>");
+ w.println("<div class=\"loading\">Downloading snapshot; please wait...</div>");
+ w.println("<div class=\"stack\" style=\"display: none\">");
+ synchronized (roast) {
+ Collection<StackNode> nodes = roast.getData().values();
+ for (StackNode node : nodes) {
+ w.println(node.toHtml(roast.getMapping()));
+ }
+ if (nodes.size() == 0) {
+ w.println("<p class=\"no-results\">There are no results. " +
+ "(Thread filter does not match thread?)</p>");
+ }
+ }
+ w.println("</div>");
+ w.println("<p class=\"legend\">Legend: ");
+ w.println("<span class=\"matched\">Mapped</span> ");
+ w.println("<span class=\"multiple-matches\">Multiple Mappings</span> ");
+ w.println("</p>");
+ w.println("<div id=\"overlay\"></div>");
+ w.println("<p class=\"footer\">");
+ w.println("Icons from <a href=\"http://www.fatcow.com/\">FatCow</a> &mdash; ");
+ w.println("<a href=\"http://github.com/sk89q/warmroast\">github.com/sk89q/warmroast</a></p>");
+ w.println("<script src=\"//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js\"></script>");
+ w.println("<script src=\"warmroast.js\"></script>");
+ w.println("</body></html>");
+ }
+} \ No newline at end of file
diff --git a/src/main/java/com/sk89q/warmroast/McpMapping.java b/src/main/java/com/sk89q/warmroast/McpMapping.java
new file mode 100644
index 0000000..5ef2ff9
--- /dev/null
+++ b/src/main/java/com/sk89q/warmroast/McpMapping.java
@@ -0,0 +1,112 @@
+/*
+ * WarmRoast
+ * Copyright (C) 2013 Albert Pham <http://www.sk89q.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package com.sk89q.warmroast;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.commons.io.FileUtils;
+
+import au.com.bytecode.opencsv.CSVReader;
+
+public class McpMapping {
+
+ private static final Pattern clPattern =
+ Pattern.compile("CL: (?<obfuscated>[^ ]+) (?<actual>[^ ]+)");
+ private static final Pattern mdPattern =
+ Pattern.compile("MD: (?<obfuscatedClass>[^ /]+)/(?<obfuscatedMethod>[^ ]+) " +
+ "[^ ]+ (?<method>[^ ]+) [^ ]+");
+
+ private final Map<String, ClassMapping> classes = new HashMap<>();
+ private final Map<String, String> methods = new HashMap<>();
+
+ public ClassMapping mapClass(String obfuscated) {
+ return classes.get(obfuscated);
+ }
+
+ public void read(File joinedFile, File methodsFile) throws IOException {
+ try (FileReader r = new FileReader(methodsFile)) {
+ try (CSVReader reader = new CSVReader(r)) {
+ List<String[]> entries = reader.readAll();
+ processMethodNames(entries);
+ }
+ }
+
+ List<String> lines = FileUtils.readLines(joinedFile, "UTF-8");
+ processClasses(lines);
+ processMethods(lines);
+ }
+
+ public String fromMethodId(String id) {
+ String method = methods.get(id);
+ if (method == null) {
+ return id;
+ }
+ return method;
+ }
+
+ private void processMethodNames(List<String[]> entries) {
+ boolean first = true;
+ for (String[] entry : entries) {
+ if (entry.length < 2) {
+ continue;
+ }
+ if (first) { // Header
+ first = false;
+ continue;
+ }
+ methods.put(entry[0], entry[1]);
+ }
+ }
+
+ private void processClasses(List<String> lines) {
+ for (String line : lines) {
+ Matcher m = clPattern.matcher(line);
+ if (m.matches()) {
+ String obfuscated = m.group("obfuscated");
+ String actual = m.group("actual").replace("/", ".");
+ classes.put(obfuscated, new ClassMapping(obfuscated, actual));
+ }
+ }
+ }
+
+ private void processMethods(List<String> lines) {
+ for (String line : lines) {
+ Matcher m = mdPattern.matcher(line);
+ if (m.matches()) {
+ String obfuscatedClass = m.group("obfuscatedClass");
+ String obfuscatedMethod = m.group("obfuscatedMethod");
+ String method = m.group("method");
+ String methodId = method.substring(method.lastIndexOf('/') + 1);
+ ClassMapping mapping = mapClass(obfuscatedClass);
+ if (mapping != null) {
+ mapping.addMethod(obfuscatedMethod,
+ fromMethodId(methodId));
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/sk89q/warmroast/RoastOptions.java b/src/main/java/com/sk89q/warmroast/RoastOptions.java
new file mode 100644
index 0000000..edb6a0e
--- /dev/null
+++ b/src/main/java/com/sk89q/warmroast/RoastOptions.java
@@ -0,0 +1,49 @@
+/*
+ * WarmRoast
+ * Copyright (C) 2013 Albert Pham <http://www.sk89q.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package com.sk89q.warmroast;
+
+import com.beust.jcommander.Parameter;
+
+public class RoastOptions {
+
+ @Parameter(names = { "-h", "--help" }, help = true)
+ public boolean help;
+
+ @Parameter(names = { "--bind" }, description = "The address to bind the HTTP server to")
+ public String bindAddress = "0.0.0.0";
+
+ @Parameter(names = { "-p", "--port" }, description = "The port to bind the HTTP server to")
+ public Integer port = 23000;
+
+ @Parameter(names = { "--pid" }, description = "The PID of the VM to attach to")
+ public Integer pid;
+
+ @Parameter(names = { "--name" }, description = "The name of the VM to attach to")
+ public String vmName;
+
+ @Parameter(names = { "-t", "--thread" }, description = "Optionally specify a thread to log only")
+ public String threadName;
+
+ @Parameter(names = { "-m", "--mappings" }, description = "A directory with joined.srg and methods.csv")
+ public String mappingsDir;
+
+ @Parameter(names = { "--interval" }, description = "The sample rate, in milliseconds")
+ public Integer interval = 100;
+
+}
diff --git a/src/main/java/com/sk89q/warmroast/StackNode.java b/src/main/java/com/sk89q/warmroast/StackNode.java
new file mode 100644
index 0000000..216ca5f
--- /dev/null
+++ b/src/main/java/com/sk89q/warmroast/StackNode.java
@@ -0,0 +1,162 @@
+/*
+ * WarmRoast
+ * Copyright (C) 2013 Albert Pham <http://www.sk89q.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package com.sk89q.warmroast;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class StackNode implements Comparable<StackNode> {
+
+ private final String name;
+ private final Map<String, StackNode> children = new HashMap<>();
+ private long totalTime;
+
+ public StackNode(String name) {
+ this.name = name;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getNameHtml(McpMapping mapping) {
+ return escapeHtml(getName());
+ }
+
+ public Collection<StackNode> getChildren() {
+ List<StackNode> list = new ArrayList<>(children.values());
+ Collections.sort(list);
+ return list;
+ }
+
+ public StackNode getChild(String name) {
+ StackNode child = children.get(name);
+ if (child == null) {
+ child = new StackNode(name);
+ children.put(name, child);
+ }
+ return child;
+ }
+
+ public StackNode getChild(String className, String methodName) {
+ StackTraceNode node = new StackTraceNode(className, methodName);
+ StackNode child = children.get(node.getName());
+ if (child == null) {
+ child = node;
+ children.put(node.getName(), node);
+ }
+ return child;
+ }
+
+ public long getTotalTime() {
+ return totalTime;
+ }
+
+ public void log(long time) {
+ totalTime += time;
+ }
+
+ private void log(StackTraceElement[] elements, int skip, long time) {
+ log(time);
+
+ if (elements.length - skip == 0) {
+ return;
+ }
+
+ StackTraceElement bottom = elements[elements.length - (skip + 1)];
+ getChild(bottom.getClassName(), bottom.getMethodName())
+ .log(elements, skip + 1, time);
+ }
+
+ public void log(StackTraceElement[] elements, long time) {
+ log(elements, 0, time);
+ }
+
+ @Override
+ public int compareTo(StackNode o) {
+ return getName().compareTo(o.getName());
+ }
+
+ private void writeHtml(StringBuilder builder, McpMapping mapping, long totalTime) {
+ builder.append("<div class=\"node collapsed\">");
+ builder.append("<div class=\"name\">");
+ builder.append(getNameHtml(mapping));
+ builder.append("<span class=\"percent\">");
+ builder
+ .append(String.format("%.2f", getTotalTime() / (double) totalTime * 100))
+ .append("%");
+ builder.append("</span>");
+ builder.append("<span class=\"time\">");
+ builder.append(getTotalTime()).append("ms");
+ builder.append("</span>");
+ builder.append("<span class=\"bar\">");
+ builder.append("<span class=\"bar-inner\" style=\"width:")
+ .append(String.format("%.2f", getTotalTime() / (double) totalTime * 100))
+ .append("%\">");
+ builder.append("</span>");
+ builder.append("</span>");
+ builder.append("</div>");
+ builder.append("<ul class=\"children\">");
+ for (StackNode child : getChildren()) {
+ builder.append("<li>");
+ child.writeHtml(builder, mapping, totalTime);
+ builder.append("</li>");
+ }
+ builder.append("</ul>");
+ builder.append("</div>");
+ }
+
+ public String toHtml(McpMapping mapping) {
+ StringBuilder builder = new StringBuilder();
+ writeHtml(builder, mapping, getTotalTime());
+ return builder.toString();
+ }
+
+ private void writeString(StringBuilder builder, int indent) {
+ StringBuilder b = new StringBuilder();
+ for (int i = 0; i < indent; i++) {
+ b.append(" ");
+ }
+ String padding = b.toString();
+
+ for (StackNode child : getChildren()) {
+ builder.append(padding).append(child.getName());
+ builder.append(" ");
+ builder.append(getTotalTime()).append("ms");
+ builder.append("\n");
+ child.writeString(builder, indent + 1);
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ writeString(builder, 0);
+ return builder.toString();
+ }
+
+ protected static String escapeHtml(String str) {
+ return str.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;");
+ }
+
+}
diff --git a/src/main/java/com/sk89q/warmroast/StackTraceNode.java b/src/main/java/com/sk89q/warmroast/StackTraceNode.java
new file mode 100644
index 0000000..1857cd1
--- /dev/null
+++ b/src/main/java/com/sk89q/warmroast/StackTraceNode.java
@@ -0,0 +1,96 @@
+/*
+ * WarmRoast
+ * Copyright (C) 2013 Albert Pham <http://www.sk89q.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package com.sk89q.warmroast;
+
+import java.util.List;
+
+public class StackTraceNode extends StackNode {
+
+ private final String className;
+ private final String methodName;
+
+ public StackTraceNode(String className, String methodName) {
+ super(className + "." + methodName + "()");
+ this.className = className;
+ this.methodName = methodName;
+ }
+
+ public String getClassName() {
+ return className;
+ }
+
+ public String getMethodName() {
+ return methodName;
+ }
+
+ @Override
+ public String getNameHtml(McpMapping mapping) {
+ ClassMapping classMapping = mapping.mapClass(getClassName());
+ if (classMapping != null) {
+ String className = "<span class=\"matched\" title=\"" +
+ escapeHtml(getClassName()) + "\">" +
+ escapeHtml(classMapping.getActual()) + "</span>";
+
+ List<String> actualMethods = classMapping.mapMethod(getMethodName());
+ if (actualMethods.size() == 0) {
+ return className + "." + escapeHtml(getMethodName()) + "()";
+ } else if (actualMethods.size() == 1) {
+ return className +
+ ".<span class=\"matched\" title=\"" +
+ escapeHtml(getMethodName()) + "\">" +
+ escapeHtml(actualMethods.get(0)) + "</span>()";
+ } else {
+ StringBuilder builder = new StringBuilder();
+ boolean first = true;
+ for (String m : actualMethods) {
+ if (!first) {
+ builder.append(" ");
+ }
+ builder.append(m);
+ first = false;
+ }
+ return className +
+ ".<span class=\"multiple-matches\" title=\"" +
+ builder.toString() + "\">" + escapeHtml(getMethodName()) + "</span>()";
+ }
+ } else {
+ String actualMethod = mapping.fromMethodId(getMethodName());
+ if (actualMethod == null) {
+ return escapeHtml(getClassName()) + "." + escapeHtml(getMethodName()) + "()";
+ } else {
+ return className +
+ ".<span class=\"matched\" title=\"" +
+ escapeHtml(getMethodName()) + "\">" +
+ escapeHtml(actualMethod) + "</span>()";
+ }
+ }
+ }
+
+ @Override
+ public int compareTo(StackNode o) {
+ if (getTotalTime() == o.getTotalTime()) {
+ return 0;
+ } else if (getTotalTime()> o.getTotalTime()) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+}
diff --git a/src/main/java/com/sk89q/warmroast/WarmRoast.java b/src/main/java/com/sk89q/warmroast/WarmRoast.java
new file mode 100644
index 0000000..6ddd9ca
--- /dev/null
+++ b/src/main/java/com/sk89q/warmroast/WarmRoast.java
@@ -0,0 +1,309 @@
+/*
+ * WarmRoast
+ * Copyright (C) 2013 Albert Pham <http://www.sk89q.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+package com.sk89q.warmroast;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.lang.management.ManagementFactory;
+import java.lang.management.ThreadInfo;
+import java.lang.management.ThreadMXBean;
+import java.net.InetSocketAddress;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.SortedMap;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.TreeMap;
+
+import javax.management.MBeanServerConnection;
+import javax.management.MalformedObjectNameException;
+import javax.management.ObjectName;
+import javax.management.remote.JMXConnector;
+import javax.management.remote.JMXConnectorFactory;
+import javax.management.remote.JMXServiceURL;
+
+import org.eclipse.jetty.server.Server;
+import org.eclipse.jetty.server.handler.HandlerList;
+import org.eclipse.jetty.server.handler.ResourceHandler;
+import org.eclipse.jetty.servlet.ServletContextHandler;
+import org.eclipse.jetty.servlet.ServletHolder;
+
+import com.beust.jcommander.JCommander;
+import com.sun.tools.attach.AgentInitializationException;
+import com.sun.tools.attach.AgentLoadException;
+import com.sun.tools.attach.AttachNotSupportedException;
+import com.sun.tools.attach.VirtualMachine;
+import com.sun.tools.attach.VirtualMachineDescriptor;
+
+public class WarmRoast extends TimerTask {
+
+ private static final String SEPARATOR =
+ "------------------------------------------------------------------------";
+
+ private final int interval;
+ private final VirtualMachine vm;
+ private final Timer timer = new Timer("Roast Pan", true);
+ private final McpMapping mapping = new McpMapping();
+ private final SortedMap<String, StackNode> nodes = new TreeMap<>();
+ private JMXConnector connector;
+ private MBeanServerConnection mbsc;
+ private ThreadMXBean threadBean;
+ private String filterThread;
+
+ public WarmRoast(VirtualMachine vm, int interval) {
+ this.vm = vm;
+ this.interval = interval;
+ }
+
+ public Map<String, StackNode> getData() {
+ return nodes;
+ }
+
+ private StackNode getNode(String name) {
+ StackNode node = nodes.get(name);
+ if (node == null) {
+ node = new StackNode(name);
+ nodes.put(name, node);
+ }
+ return node;
+ }
+
+ public McpMapping getMapping() {
+ return mapping;
+ }
+
+ public String getFilterThread() {
+ return filterThread;
+ }
+
+ public void setFilterThread(String filterThread) {
+ this.filterThread = filterThread;
+ }
+
+ public void connect()
+ throws IOException, AgentLoadException, AgentInitializationException {
+ // Load the agent
+ String connectorAddr = vm.getAgentProperties().getProperty(
+ "com.sun.management.jmxremote.localConnectorAddress");
+ if (connectorAddr == null) {
+ String agent = vm.getSystemProperties().getProperty("java.home")
+ + File.separator + "lib" + File.separator
+ + "management-agent.jar";
+ vm.loadAgent(agent);
+ connectorAddr = vm.getAgentProperties().getProperty(
+ "com.sun.management.jmxremote.localConnectorAddress");
+ }
+
+ // Connect
+ JMXServiceURL serviceURL = new JMXServiceURL(connectorAddr);
+ connector = JMXConnectorFactory.connect(serviceURL);
+ mbsc = connector.getMBeanServerConnection();
+ try {
+ threadBean = getThreadMXBean();
+ } catch (MalformedObjectNameException e) {
+ throw new IOException("Bad MX bean name", e);
+ }
+ }
+
+ private ThreadMXBean getThreadMXBean()
+ throws IOException, MalformedObjectNameException {
+ ObjectName objName = new ObjectName(ManagementFactory.THREAD_MXBEAN_NAME);
+ Set<ObjectName> mbeans = mbsc.queryNames(objName, null);
+ for (ObjectName name : mbeans) {
+ return ManagementFactory.newPlatformMXBeanProxy(
+ mbsc, name.toString(), ThreadMXBean.class);
+ }
+ throw new IOException("No thread MX bean found");
+ }
+
+ @Override
+ public synchronized void run() {
+ ThreadInfo[] threadDumps = threadBean.dumpAllThreads(false, false);
+ for (ThreadInfo threadInfo : threadDumps) {
+ String threadName = threadInfo.getThreadName();
+ StackTraceElement[] stack = threadInfo.getStackTrace();
+
+ if (threadName == null || stack == null) {
+ continue;
+ }
+
+ if (filterThread != null && !filterThread.equals(threadName)) {
+ continue;
+ }
+
+ StackNode node = getNode(threadName);
+ node.log(stack, interval);
+ }
+ }
+
+ public void start(InetSocketAddress address) throws Exception {
+ timer.scheduleAtFixedRate(this, interval, interval);
+
+ Server server = new Server(address);
+
+ ServletContextHandler context = new ServletContextHandler();
+ context.setContextPath("/");
+ context.addServlet(new ServletHolder(new DataViewServlet(this)), "/stack");
+
+ ResourceHandler resources = new ResourceHandler();
+ String filesDir = WarmRoast.class.getResource("/www").toExternalForm();
+ resources.setResourceBase(filesDir);
+ resources.setDirectoriesListed(true);
+ resources.setWelcomeFiles(new String[]{ "index.html" });
+
+ HandlerList handlers = new HandlerList();
+ handlers.addHandler(context);
+ handlers.addHandler(resources);
+ server.setHandler(handlers);
+
+ server.start();
+ server.join();
+ }
+
+ public static void main(String[] args) throws AgentLoadException {
+ RoastOptions opt = new RoastOptions();
+ JCommander jc = new JCommander(opt, args);
+ jc.setProgramName("warmroast");
+
+ if (opt.help) {
+ jc.usage();
+ System.exit(0);
+ }
+
+ System.err.println(SEPARATOR);
+ System.err.println("WarmRoast");
+ System.err.println("http://github.com/sk89q/warmroast");
+ System.err.println(SEPARATOR);
+ System.err.println("");
+
+ VirtualMachine vm = null;
+
+ if (opt.pid != null) {
+ try {
+ vm = VirtualMachine.attach(String.valueOf(opt.pid));
+ System.err.println("Attaching to PID " + opt.pid + "...");
+ } catch (AttachNotSupportedException | IOException e) {
+ System.err.println("Failed to attach VM by PID " + opt.pid);
+ System.exit(1);
+ }
+ } else if (opt.vmName != null) {
+ for (VirtualMachineDescriptor desc : VirtualMachine.list()) {
+ if (desc.displayName().contains(opt.vmName)) {
+ try {
+ vm = VirtualMachine.attach(desc);
+ System.err.println("Attaching to '" + desc.displayName() + "'...");
+
+ break;
+ } catch (AttachNotSupportedException | IOException e) {
+ System.err.println("Failed to attach VM by name '" + opt.vmName + "'");
+ System.exit(1);
+ }
+ }
+ }
+ }
+
+ if (vm == null) {
+
+ List<VirtualMachineDescriptor> descriptors = VirtualMachine.list();
+ System.err.println("Choose a VM:");
+
+ Collections.sort(descriptors, new Comparator<VirtualMachineDescriptor>() {
+ @Override
+ public int compare(VirtualMachineDescriptor o1,
+ VirtualMachineDescriptor o2) {
+ return o1.displayName().compareTo(o2.displayName());
+ }
+ });
+
+ // Print list of VMs
+ int i = 1;
+ for (VirtualMachineDescriptor desc : descriptors) {
+ System.err.println("[" + (i++) + "] " + desc.displayName());
+ }
+
+ // Ask for choice
+ System.err.println("");
+ System.err.print("Enter choice #: ");
+ BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
+ String s;
+ try {
+ s = reader.readLine();
+ } catch (IOException e) {
+ return;
+ }
+
+ // Get the VM
+ try {
+ int choice = Integer.parseInt(s) - 1;
+ if (choice < 0 || choice >= descriptors.size()) {
+ System.err.println("");
+ System.err.println("Given choice is out of range.");
+ System.exit(1);
+ }
+ vm = VirtualMachine.attach(descriptors.get(choice));
+ } catch (NumberFormatException e) {
+ System.err.println("");
+ System.err.println("That's not a number. Bye.");
+ System.exit(1);
+ } catch (AttachNotSupportedException | IOException e) {
+ System.err.println("");
+ System.err.println("Failed to attach VM");
+ System.exit(1);
+ }
+ }
+
+ InetSocketAddress address = new InetSocketAddress(opt.bindAddress, opt.port);
+
+ WarmRoast roast = new WarmRoast(vm, opt.interval);
+ if (opt.mappingsDir != null) {
+ File dir = new File(opt.mappingsDir);
+ File joined = new File(dir, "joined.srg");
+ File methods = new File(dir, "methods.csv");
+ try {
+ roast.getMapping().read(joined, methods);
+ } catch (IOException e) {
+ System.err.println(
+ "Failed to read the mappings files (joined.srg, methods.csv) " +
+ "from " + dir.getAbsolutePath() + ": " + e.getMessage());
+ System.exit(2);
+ }
+ }
+
+ roast.setFilterThread(opt.threadName);
+
+ System.err.println(SEPARATOR);
+
+ System.err.println("Starting a server on " + address.toString() + "...");
+ System.err.println("Once the server starts (shortly), visit the URL in your browser.");
+
+ try {
+ roast.connect();
+ roast.start(address);
+ } catch (Throwable t) {
+ t.printStackTrace();
+ System.exit(3);
+ }
+ }
+
+}