From 280ad8b8bf2942f055daf5fb6f8ae55a7f88243a Mon Sep 17 00:00:00 2001 From: Albert Pham Date: Thu, 13 Jun 2013 15:55:12 -0700 Subject: Initial commit. --- .../java/com/sk89q/warmroast/ClassMapping.java | 67 +++++ .../java/com/sk89q/warmroast/DataViewServlet.java | 76 +++++ src/main/java/com/sk89q/warmroast/McpMapping.java | 112 ++++++++ .../java/com/sk89q/warmroast/RoastOptions.java | 49 ++++ src/main/java/com/sk89q/warmroast/StackNode.java | 162 +++++++++++ .../java/com/sk89q/warmroast/StackTraceNode.java | 96 +++++++ src/main/java/com/sk89q/warmroast/WarmRoast.java | 309 +++++++++++++++++++++ 7 files changed, 871 insertions(+) create mode 100644 src/main/java/com/sk89q/warmroast/ClassMapping.java create mode 100644 src/main/java/com/sk89q/warmroast/DataViewServlet.java create mode 100644 src/main/java/com/sk89q/warmroast/McpMapping.java create mode 100644 src/main/java/com/sk89q/warmroast/RoastOptions.java create mode 100644 src/main/java/com/sk89q/warmroast/StackNode.java create mode 100644 src/main/java/com/sk89q/warmroast/StackTraceNode.java create mode 100644 src/main/java/com/sk89q/warmroast/WarmRoast.java (limited to 'src/main/java/com/sk89q') 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 + * + * 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 . +*/ + +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> 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 m = methods.get(obfuscated); + if (m == null) { + m = new ArrayList<>(); + methods.put(obfuscated, m); + } + m.add(actual); + } + + public List mapMethod(String obfuscated) { + List 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 + * + * 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 . +*/ + +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("WarmRoast"); + w.println(""); + w.println(""); + w.println("

WarmRoast

"); + w.println("
Downloading snapshot; please wait...
"); + w.println("
"); + synchronized (roast) { + Collection nodes = roast.getData().values(); + for (StackNode node : nodes) { + w.println(node.toHtml(roast.getMapping())); + } + if (nodes.size() == 0) { + w.println("

There are no results. " + + "(Thread filter does not match thread?)

"); + } + } + w.println("
"); + w.println("

Legend: "); + w.println("Mapped "); + w.println("Multiple Mappings "); + w.println("

"); + w.println("
"); + w.println("

"); + w.println("Icons from FatCow — "); + w.println("github.com/sk89q/warmroast

"); + w.println(""); + w.println(""); + w.println(""); + } +} \ 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 + * + * 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 . +*/ + +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: (?[^ ]+) (?[^ ]+)"); + private static final Pattern mdPattern = + Pattern.compile("MD: (?[^ /]+)/(?[^ ]+) " + + "[^ ]+ (?[^ ]+) [^ ]+"); + + private final Map classes = new HashMap<>(); + private final Map 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 entries = reader.readAll(); + processMethodNames(entries); + } + } + + List 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 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 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 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 + * + * 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 . + */ + +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 + * + * 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 . +*/ + +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 { + + private final String name; + private final Map 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 getChildren() { + List 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("
"); + builder.append("
"); + builder.append(getNameHtml(mapping)); + builder.append(""); + builder + .append(String.format("%.2f", getTotalTime() / (double) totalTime * 100)) + .append("%"); + builder.append(""); + builder.append(""); + builder.append(getTotalTime()).append("ms"); + builder.append(""); + builder.append(""); + builder.append(""); + builder.append(""); + builder.append(""); + builder.append("
"); + builder.append("
    "); + for (StackNode child : getChildren()) { + builder.append("
  • "); + child.writeHtml(builder, mapping, totalTime); + builder.append("
  • "); + } + builder.append("
"); + builder.append("
"); + } + + 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("&", "&").replace("<", "<").replace(">", ">"); + } + +} 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 + * + * 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 . +*/ + +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 = "" + + escapeHtml(classMapping.getActual()) + ""; + + List actualMethods = classMapping.mapMethod(getMethodName()); + if (actualMethods.size() == 0) { + return className + "." + escapeHtml(getMethodName()) + "()"; + } else if (actualMethods.size() == 1) { + return className + + "." + + escapeHtml(actualMethods.get(0)) + "()"; + } else { + StringBuilder builder = new StringBuilder(); + boolean first = true; + for (String m : actualMethods) { + if (!first) { + builder.append(" "); + } + builder.append(m); + first = false; + } + return className + + "." + escapeHtml(getMethodName()) + "()"; + } + } else { + String actualMethod = mapping.fromMethodId(getMethodName()); + if (actualMethod == null) { + return escapeHtml(getClassName()) + "." + escapeHtml(getMethodName()) + "()"; + } else { + return className + + "." + + escapeHtml(actualMethod) + "()"; + } + } + } + + @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 + * + * 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 . +*/ + +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 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 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 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 descriptors = VirtualMachine.list(); + System.err.println("Choose a VM:"); + + Collections.sort(descriptors, new Comparator() { + @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); + } + } + +} -- cgit