aboutsummaryrefslogtreecommitdiff
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
downloadspark-280ad8b8bf2942f055daf5fb6f8ae55a7f88243a.tar.gz
spark-280ad8b8bf2942f055daf5fb6f8ae55a7f88243a.tar.bz2
spark-280ad8b8bf2942f055daf5fb6f8ae55a7f88243a.zip
Initial commit.
-rw-r--r--.gitignore32
-rw-r--r--README.md77
-rw-r--r--pom.xml145
-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
-rw-r--r--src/main/resources/www/index.html13
-rw-r--r--src/main/resources/www/style.css166
-rw-r--r--src/main/resources/www/warmroast.js43
13 files changed, 1347 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..82db09d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,32 @@
+# 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/ \ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f8edd13
--- /dev/null
+++ b/README.md
@@ -0,0 +1,77 @@
+WarmRoast
+=========
+
+WarmRoast is an easy-to-use CPU sampling tool for JVM applications, but particularly suited for Minecraft servers/clients.
+
+* 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 Version:** http://builds.enginehub.org/job/warmroast/last-successful/
+
+Java 7 and above is required to use WarmRoast.
+
+Screenshots
+-----------
+
+![Sample output](http://i.imgur.com/KCDYkIv.png)
+
+Usage
+-----
+
+1. Note the path of your JDK.
+
+2. Download WarmRoast as `warmroast.jar`.
+
+3. Replace `/path/to/jdk` in the following command line with the path to your JDK and execute the program.
+
+### Linux ###
+
+ java -Djava.library.path=/path/to/jdk/jre/bin -cp /path/to/jdk/lib/tools.jar:warmroast.jar com.sk89q.warmroast.WarmRoast
+
+### Windows ###
+
+ java -Djava.library.path=/path/to/jdk/jre/bin -cp /path/to/jdk/lib/tools.jar;warmroast.jar com.sk89q.warmroast.WarmRoast
+
+Parameters
+----------
+
+ warmroast.WarmRoast --help
+ 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
+
+Hint: `--thread "Server thread"` is useful for Minecraft servers.
+
+License
+-------
+
+The launcher is licensed under the GNU General Public License, version 3.
+
+Contributions by third parties must be dual licensed under the two licenses
+described within LICENSE.txt (GNU General Public License, version 3, and the
+3-clause BSD license).
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..b96ca02
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,145 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com.sk89q</groupId>
+ <artifactId>warmroast</artifactId>
+ <version>1.0.0-SNAPSHOT</version>
+ <name>WarmRoast</name>
+ <url>http://www.sk89q.com</url>
+ <scm>
+ <connection>scm:git:git://github.com/sk89q/warmroast.git</connection>
+ <url>https://github.com/sk89q/warmroast</url>
+ <developerConnection>scm:git:git@github.com:sk89q/warmroast.git</developerConnection>
+ </scm>
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>org.eclipse.jetty</groupId>
+ <artifactId>jetty-servlet</artifactId>
+ <version>9.0.3.v20130506</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>2.4</version>
+ </dependency>
+ <dependency>
+ <groupId>net.sf.opencsv</groupId>
+ <artifactId>opencsv</artifactId>
+ <version>2.0</version>
+ </dependency>
+ <dependency>
+ <groupId>com.beust</groupId>
+ <artifactId>jcommander</artifactId>
+ <version>1.30</version>
+ </dependency>
+ </dependencies>
+ <build>
+ <resources>
+ <resource>
+ <targetPath>www</targetPath>
+ <filtering>false</filtering>
+ <directory>${basedir}/src/main/resources/www</directory>
+ <includes>
+ <include>**/*</include>
+ </includes>
+ </resource>
+ </resources>
+ <plugins>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.0</version>
+ <configuration>
+ <source>1.7</source>
+ <target>1.7</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <configuration>
+ <archive>
+ <manifest>
+ <mainClass>com.sk89q.warmroast.WarmRoast</mainClass>
+ </manifest>
+ </archive>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <version>2.1</version>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <filters>
+ <filter>
+ <artifact>*:*</artifact>
+ <excludes>
+ <exclude>META-INF/*.SF</exclude>
+ <exclude>META-INF/*.DSA</exclude>
+ <exclude>META-INF/*.RSA</exclude>
+ </excludes>
+ </filter>
+ </filters>
+ <artifactSet>
+ <excludes>
+ <exclude>com.sun:tools</exclude>
+ </excludes>
+ </artifactSet>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ <profiles>
+ <profile>
+ <id>tools-default</id>
+ <activation>
+ <activeByDefault>true</activeByDefault>
+ <file>
+ <exists>${java.home}/../lib/tools.jar</exists>
+ </file>
+ </activation>
+ <properties>
+ <toolsJar>${java.home}/../lib/tools.jar</toolsJar>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>com.sun</groupId>
+ <artifactId>tools</artifactId>
+ <version>1.6.0</version>
+ <scope>system</scope>
+ <systemPath>${toolsJar}</systemPath>
+ </dependency>
+ </dependencies>
+ </profile>
+ <profile>
+ <id>tools-mac</id>
+ <activation>
+ <activeByDefault>false</activeByDefault>
+ <file>
+ <exists>${java.home}/../Classes/classes.jar</exists>
+ </file>
+ </activation>
+ <properties>
+ <toolsJar>${java.home}/../Classes/classes.jar</toolsJar>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>com.sun</groupId>
+ <artifactId>tools</artifactId>
+ <version>1.6.0</version>
+ <scope>system</scope>
+ <systemPath>${toolsJar}</systemPath>
+ </dependency>
+ </dependencies>
+ </profile>
+ </profiles>
+</project> \ No newline at end of file
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;
+imp