Repository: sk89q/WarmRoast Branch: master Commit: d814c4f03915 Files: 14 Total size: 43.7 KB Directory structure: gitextract_dl1f2tde/ ├── .github/ │ └── workflows/ │ └── build.yml ├── .gitignore ├── README.md ├── pom.xml └── src/ └── main/ ├── java/ │ └── com/ │ └── sk89q/ │ └── warmroast/ │ ├── ClassMapping.java │ ├── DataViewServlet.java │ ├── McpMapping.java │ ├── RoastOptions.java │ ├── StackNode.java │ ├── StackTraceNode.java │ └── WarmRoast.java └── resources/ └── www/ ├── index.html ├── style.css └── warmroast.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/build.yml ================================================ name: Build on: - push - pull_request jobs: build: name: Build runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 name: Checkout repo - name: Set up JDK 17 (LTS) uses: actions/setup-java@v2 with: java-version: '17' distribution: 'adopt' cache: maven - name: Build with Maven run: mvn install - uses: actions/upload-artifact@v2 name: Upload Artifact with: name: WarmRoast path: target/warmroast-*.jar ================================================ FILE: .gitignore ================================================ # Eclipse stuff /.classpath /.project /.settings # netbeans /nbproject # we use maven! /build.xml # maven /target # vim .*.sw[a-p] # various other potential build files /build /bin /dist /manifest.mf /dependency-reduced-pom.xml # Mac filesystem dust /.DS_Store # intellij *.iml *.ipr *.iws .idea/ ================================================ FILE: README.md ================================================ WarmRoast ========= *Note: This project is not actively maintained but should work. (2024)* WarmRoast attaches to Minecraft (or any Java application) and lets you see what it's doing. While what it's doing can be cryptic, with some practice, you can start to figure out patterns. * Adjustable sampling frequency. * Supports loading MCP mappings for deobfuscating class and method names. * Web-based — perform the profiling on a remote server and view the results in your browser. * Collapse and expand nodes to see details. * Easily view CPU usage per method at a glance. * Hover to highlight all child methods as a group. * See the percentage of CPU time for each method relative to its parent methods. * Maintains style and function with use of "File -> Save As" (in tested browsers). ### Download **Latest Release**: [here](../../releases) **Latest Build**: [here](../../actions/workflows/build.yml) Screenshots ----------- ![Sample output](http://i.imgur.com/Iy7kJ7f.png) Usage ----- Extract the .zip file and place the .jar somewhere. ### For Java 9 and newer ### The `tools.jar` is automatically included into JDK's since Java 9. You only should use something like this: java -cp warmroast-1.0.0-SNAPSHOT.jar com.sk89q.warmroast.WarmRoast --thread "Server thread" ### For Java 7 & 8 ### 1. Note the path of your JDK. 2. Download WarmRoast. 3. Replace `PATH_TO_JDK` in the following commands with the path to your JDK and execute the program. **Note:** The example command line below includes `--thread "Server thread"`, which filters all threads but the main server thread. You can remove it to show all threads. **Modded/vanilla servers:** If you are using a modded server, get a copy of [MCP](http://mcp.ocean-labs.de/index.php/MCP_Releases) for your server's Minecraft version, copy the files from conf/ somewhere, and point WarmRoast to it with `--mappings path/to/folder`. This helps readability a lot. Bukkit uses its own mapping, so a pure non-modded Bukkit server can't use MCP mappings. #### Linux #### java -Djava.library.path=PATH_TO_JDK/jre/bin -cp PATH_TO_JDK/lib/tools.jar:warmroast-1.0.0-SNAPSHOT.jar com.sk89q.warmroast.WarmRoast --thread "Server thread" #### Windows #### An example `PATH_TO_JDK` would be `C:\Program Files\Java\jdk1.7.0_45` java -Djava.library.path=PATH_TO_JDK/jre/bin -cp PATH_TO_JDK/lib/tools.jar;warmroast-1.0.0-SNAPSHOT.jar com.sk89q.warmroast.WarmRoast --thread "Server thread" * The folder `PATH_TO_JDK/jre/bin` should contain "attach.dll" * The folder `PATH_TO_JDK/lib` should contain "tools.jar" Parameters ---------- Usage: warmroast [options] Options: --bind The address to bind the HTTP server to Default: 0.0.0.0 -h, --help Default: false --interval The sample rate, in milliseconds Default: 100 -m, --mappings A directory with joined.srg and methods.csv --name The name of the VM to attach to --pid The PID of the VM to attach to -p, --port The port to bind the HTTP server to Default: 23000 -t, --thread Optionally specify a thread to log only --timeout The number of seconds before ceasing sampling (optional) Hint: `--thread "Server thread"` is useful for Minecraft servers. License ------- The project is licensed under the GNU General Public License, version 3. ================================================ FILE: pom.xml ================================================ 4.0.0 com.sk89q warmroast 1.0.0-SNAPSHOT WarmRoast http://www.sk89q.com scm:git:git://github.com/sk89q/warmroast.git https://github.com/sk89q/warmroast scm:git:git@github.com:sk89q/warmroast.git UTF-8 sk89q-docs-upload ftp://sk89q-maven-deploy/worldedit/ maven.sk89q.com http://maven.sk89q.com/artifactory/libs-release-local maven.sk89q.com-snapshot http://maven.sk89q.com/artifactory/libs-snapshot-local org.eclipse.jetty jetty-servlet 9.0.3.v20130506 commons-io commons-io 2.4 net.sf.opencsv opencsv 2.0 com.beust jcommander 1.30 www false ${basedir}/src/main/resources/www **/* maven-compiler-plugin 3.0 1.7 1.7 org.apache.maven.plugins maven-jar-plugin com.sk89q.warmroast.WarmRoast org.apache.maven.plugins maven-shade-plugin 2.1 package shade *:* META-INF/*.SF META-INF/*.DSA META-INF/*.RSA ================================================ FILE: src/main/java/com/sk89q/warmroast/ClassMapping.java ================================================ /* * 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(); } } ================================================ FILE: src/main/java/com/sk89q/warmroast/DataViewServlet.java ================================================ /* * 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(""); } } ================================================ FILE: src/main/java/com/sk89q/warmroast/McpMapping.java ================================================ /* * 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 mapMethodId(String id) { return methods.get(id); } 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)); } } } } } ================================================ FILE: src/main/java/com/sk89q/warmroast/RoastOptions.java ================================================ /* * 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; @Parameter(names = { "--timeout" }, description = "The number of seconds before ceasing sampling (optional)") public Integer timeout; } ================================================ FILE: src/main/java/com/sk89q/warmroast/StackNode.java ================================================ /* * 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.text.NumberFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Locale; import java.util.Map; public class StackNode implements Comparable { private static final NumberFormat cssDec = NumberFormat.getPercentInstance(Locale.US); private final String name; private final Map children = new HashMap<>(); private long totalTime; static { cssDec.setGroupingUsed(false); cssDec.setMaximumFractionDigits(2); } 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 formatCssPct(double pct) { return cssDec.format(pct); } protected static String escapeHtml(String str) { return str.replace("&", "&").replace("<", "<").replace(">", ">"); } } ================================================ FILE: src/main/java/com/sk89q/warmroast/StackTraceNode.java ================================================ /* * 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.mapMethodId(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; } } } ================================================ FILE: src/main/java/com/sk89q/warmroast/WarmRoast.java ================================================ /* * 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; private long endTime = -1; 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 long getEndTime() { return endTime; } public void setEndTime(long l) { this.endTime = l; } 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() { if (endTime >= 0) { if (endTime <= System.currentTimeMillis()) { cancel(); System.err.println("Sampling has stopped."); return; } } 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); e.printStackTrace(); 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 + "'"); e.printStackTrace(); 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"); e.printStackTrace(); 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); } } System.err.println(SEPARATOR); roast.setFilterThread(opt.threadName); if (opt.timeout != null && opt.timeout > 0) { roast.setEndTime(System.currentTimeMillis() + opt.timeout * 1000); System.err.println("Sampling set to stop in " + opt.timeout + " seconds."); } System.err.println("Starting a server on " + address.toString() + "..."); System.err.println("Once the server starts (shortly), visit the URL in your browser."); System.err.println("Note: The longer you wait before using the output of that " + "webpage, the more accurate the results will be."); try { roast.connect(); roast.start(address); } catch (Throwable t) { t.printStackTrace(); System.exit(3); } } } ================================================ FILE: src/main/resources/www/index.html ================================================ WarmRoast

WarmRoast

View sampler results

================================================ FILE: src/main/resources/www/style.css ================================================ @import url(http://fonts.googleapis.com/css?family=Lato); body { font-family: 'Lato', Arial, sans-serif; font-size: 10pt; line-height: 150%; margin: 0; padding: 54px 20px 20px 20px; } ul { margin: 0; padding: 0 0 0 18px; } li { margin: 0; margin-left: -10px; padding: 0; list-style: none; border-left: 1px solid #ccc; } a:link, a:visited { color: #FF3213; text-decoration: none; border-bottom: 1px solid #CCC; } a:hover, a:active { color: #000; border-color: black; text-decoration: none; } .stack { margin-left: 60px; } .name { background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA10lEQVR4Xt2Tu8rCQBSEzzlJmYBvYJ7EShArfQ1BrPNDyoB5DRHsrUQQQc3lcfxBiKbIHjfiCuayKSwEB6ZYPpjdGVhkZvhEJP3dALMMgmB+FoI7ddUQEYhw7bp/48YAkYtOfzCEJu22m5G2gnjeHIYxxHGiXJwV11dQTzdN4w0QKa4JULplGcymkwo4Rkn7iCjNOcNiuVK3vQZ0ug5gWwAgPnpalgW1+yDqA4hQmmSADVVGBW8bES7RaW+zYOBSNSSENL0etAGe5/UkMKBZ/77vv8APfKY7cvZVTt7VqzwAAAAASUVORK5CYII=) center left no-repeat; padding-left: 20px; cursor: pointer; } .name:hover { background-color: #CCC; } .name:hover + ul { background: #EFEFEF; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4); border-radius: 3px; } .matched { background: #CCC; border-radius: 3px; padding: 0 4px; } .multiple-matches { background: #FF3213; color: #FFF; padding: 0 4px; border-radius: 3px; } .matched:hover, .multiple-matches:hover { background: #000000; color: #FFF; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4); } .percent { color: #6b98ff; font-size: 90%; border-radius: 3px; padding: 0 4px; } .bar { display: inline-block; width: 100px; height: 15px; margin-left: 20px; border: 1px solid #CCC; position: absolute; right: 30px; background: #FFF; } .bar-inner { display: inline-block; height: 16px; background: #6b98ff; } #overlay span { position: absolute; color: #6b98ff; font-size: 90%; z-index: 10; line-height: 150%; left: 20px; width: 50px; text-align: right; } .time { display: none; margin: 0; color: #888; font-size: 90%; border-radius: 3px; padding: 0 4px; } .name:hover .time { display: inline; } ul { display: none; } .collapsed > .name { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA7ElEQVR4Xt1TzYrCQAxOYo8z0GeRZY+eBPGkryGIZ4UeC/Yx/EHvnqTssot/9XF2YUHXQyd2RtuC2OnBg2AgJF/CfJkvwyAzwyNGiT+XwLktBMHwRyl270lDRCDCRb8/aBcSqFi59UYTiuwzXLasEtR18nYbQRTt4f2tqqPGad8uIb2641TyKUSJp307gbH/0wl63U4Ks3y925e/AibOMcN4OofRZKZLOmps6lj2CoBodAohspKUMtePaCcgQqNZCGlw+PFt8nwXWLZE+NttviQrBr6RhoRwOBxXVgLP82pJqECx/fq+n4EX+ExnBI9csQQ1hIoAAAAASUVORK5CYII=); } h1 { background: #FFF; color: #111; position: fixed; top: 0; left: 0; right: 0; padding: 10px 20px; margin: 0; font-size: 14pt; font-weight: normal; box-shadow: 0 0 4px rgba(0, 0, 0, 0.4); z-index: 20; } .footer { background: #FFF; color: #333; margin: 100px 0 0 0; font-size: 10pt; font-weight: normal; text-align: right; } .loading { font-size: 130%; background: #EFEFEF; border: 1px solid #CCC; padding: 8px; border-radius: 3px; } .no-results { font-size: 130%; color: #800000; } ================================================ FILE: src/main/resources/www/warmroast.js ================================================ $(".name").on("click", function(event) { var $parent = $(this).parent(); if ($parent.hasClass("collapsed")) { $parent.removeClass("collapsed"); $parent.children("ul").slideDown(50); } else { $parent.addClass("collapsed"); $parent.children("ul").slideUp(50); } }); function extractTime($el) { var text = $el.children(".name") .children(".time").text().replace(/[^0-9]/, ""); return parseInt(text); } var $overlay = $("#overlay"); $(".name").on("mouseenter", function(event) { var $this = $(this); var thisTime = null; $overlay.empty(); $this.parents(".node").each(function(i, parent) { var $parent = $(parent); var time = extractTime($parent); if (thisTime == null) { thisTime = time; } else { var $el = $(document.createElement("span")); var pos = $parent.position(); var width = $el.outerWidth(); $el.text(((thisTime / time) * 100).toFixed(2) + "%"); $el.css({ top: pos.top + "px" }); $overlay.append($el); } }); }); $(".loading").hide(); $(".stack").show();