[
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Build\n\non:\n  - push\n  - pull_request\n\njobs:\n  build:\n    name: Build\n    runs-on: ubuntu-latest\n\n    steps:\n    - uses: actions/checkout@v2\n      name: Checkout repo\n\n    - name: Set up JDK 17 (LTS)\n      uses: actions/setup-java@v2\n      with:\n        java-version: '17'\n        distribution: 'adopt'\n        cache: maven\n\n    - name: Build with Maven\n      run: mvn install\n\n    - uses: actions/upload-artifact@v2\n      name: Upload Artifact\n      with:\n        name: WarmRoast\n        path: target/warmroast-*.jar"
  },
  {
    "path": ".gitignore",
    "content": "# Eclipse stuff\n/.classpath\n/.project\n/.settings\n\n# netbeans\n/nbproject\n\n# we use maven!\n/build.xml\n\n# maven\n/target\n\n# vim\n.*.sw[a-p]\n\n# various other potential build files\n/build\n/bin\n/dist\n/manifest.mf\n/dependency-reduced-pom.xml\n\n# Mac filesystem dust\n/.DS_Store\n\n# intellij\n*.iml\n*.ipr\n*.iws\n.idea/"
  },
  {
    "path": "README.md",
    "content": "WarmRoast\n=========\n\n*Note: This project is not actively maintained but should work. (2024)*\n\nWarmRoast 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.\n\n* Adjustable sampling frequency.\n* Supports loading MCP mappings for deobfuscating class and method names.\n* Web-based — perform the profiling on a remote server and view the results in your browser.\n * Collapse and expand nodes to see details.\n * Easily view CPU usage per method at a glance.\n * Hover to highlight all child methods as a group.\n * See the percentage of CPU time for each method relative to its parent methods.\n * Maintains style and function with use of \"File -> Save As\" (in tested browsers).\n\n### Download\n\n**Latest Release**: [here](../../releases)\n\n**Latest Build**: [here](../../actions/workflows/build.yml)\n\nScreenshots\n-----------\n\n![Sample output](http://i.imgur.com/Iy7kJ7f.png)\n\nUsage\n-----\n\nExtract the .zip file and place the .jar somewhere.\n\n### For Java 9 and newer ### \n\nThe `tools.jar` is automatically included into JDK's since Java 9. You only should use something like this:\n\n    java -cp warmroast-1.0.0-SNAPSHOT.jar com.sk89q.warmroast.WarmRoast --thread \"Server thread\"\n\n### For Java 7 & 8 ### \n\n1. Note the path of your JDK.\n\n2. Download WarmRoast.\n\n3. Replace `PATH_TO_JDK` in the following commands with the path to your JDK and execute the program.\n\n**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.\n\n**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.\n\n#### Linux ####\n\n    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\"\n\n#### Windows ####\n\nAn example `PATH_TO_JDK` would be `C:\\Program Files\\Java\\jdk1.7.0_45`\n\n    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\"\n\n* The folder `PATH_TO_JDK/jre/bin` should contain \"attach.dll\"\n* The folder `PATH_TO_JDK/lib` should contain \"tools.jar\"\n\nParameters\n----------\n\n    Usage: warmroast [options]\n      Options:\n        --bind\n           The address to bind the HTTP server to\n           Default: 0.0.0.0\n           \n        -h, --help\n           Default: false\n           \n        --interval\n           The sample rate, in milliseconds\n           Default: 100\n           \n        -m, --mappings\n           A directory with joined.srg and methods.csv\n           \n        --name\n           The name of the VM to attach to\n           \n        --pid\n           The PID of the VM to attach to\n           \n        -p, --port\n           The port to bind the HTTP server to\n           Default: 23000\n           \n        -t, --thread\n           Optionally specify a thread to log only\n           \n        --timeout\n           The number of seconds before ceasing sampling (optional)\n\nHint: `--thread \"Server thread\"` is useful for Minecraft servers.\n\nLicense\n-------\n\nThe project is licensed under the GNU General Public License, version 3.\n"
  },
  {
    "path": "pom.xml",
    "content": "<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\">\n  <modelVersion>4.0.0</modelVersion>\n  <groupId>com.sk89q</groupId>\n  <artifactId>warmroast</artifactId>\n  <version>1.0.0-SNAPSHOT</version>\n  <name>WarmRoast</name>\n  <url>http://www.sk89q.com</url>\n\n  <scm>\n    <connection>scm:git:git://github.com/sk89q/warmroast.git</connection>\n    <url>https://github.com/sk89q/warmroast</url>\n    <developerConnection>scm:git:git@github.com:sk89q/warmroast.git</developerConnection>\n  </scm>\n  <properties>\n    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n  </properties>\n  <distributionManagement>\n    <site>\n      <id>sk89q-docs-upload</id>\n      <url>ftp://sk89q-maven-deploy/worldedit/</url>\n    </site>\n    <repository>\n        <id>maven.sk89q.com</id>\n        <url>http://maven.sk89q.com/artifactory/libs-release-local</url>\n    </repository>\n    <snapshotRepository>\n        <id>maven.sk89q.com-snapshot</id>\n        <url>http://maven.sk89q.com/artifactory/libs-snapshot-local</url>\n    </snapshotRepository>\n  </distributionManagement>\n  <dependencies>\n    <dependency>\n      <groupId>org.eclipse.jetty</groupId>\n      <artifactId>jetty-servlet</artifactId>\n      <version>9.0.3.v20130506</version>\n    </dependency>\n    <dependency>\n      <groupId>commons-io</groupId>\n      <artifactId>commons-io</artifactId>\n      <version>2.4</version>\n    </dependency>\n    <dependency>\n      <groupId>net.sf.opencsv</groupId>\n      <artifactId>opencsv</artifactId>\n      <version>2.0</version>\n    </dependency>\n    <dependency>\n      <groupId>com.beust</groupId>\n      <artifactId>jcommander</artifactId>\n      <version>1.30</version>\n    </dependency>\n  </dependencies>\n  <build>\n    <resources>\n      <resource>\n        <targetPath>www</targetPath>\n        <filtering>false</filtering>\n        <directory>${basedir}/src/main/resources/www</directory>\n        <includes>\n          <include>**/*</include>\n        </includes>\n      </resource>\n    </resources>\n    <plugins>\n      <plugin>\n        <artifactId>maven-compiler-plugin</artifactId>\n        <version>3.0</version>\n        <configuration>\n          <source>1.7</source>\n          <target>1.7</target>\n        </configuration>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-jar-plugin</artifactId>\n        <configuration>\n          <archive>\n            <manifest>\n              <mainClass>com.sk89q.warmroast.WarmRoast</mainClass>\n            </manifest>\n          </archive>\n        </configuration>\n      </plugin>\n      <plugin>\n        <groupId>org.apache.maven.plugins</groupId>\n        <artifactId>maven-shade-plugin</artifactId>\n        <version>2.1</version>\n        <executions>\n          <execution>\n            <phase>package</phase>\n            <goals>\n              <goal>shade</goal>\n            </goals>\n            <configuration>\n              <filters>\n                <filter>\n                  <artifact>*:*</artifact>\n                  <excludes>\n                    <exclude>META-INF/*.SF</exclude>\n                    <exclude>META-INF/*.DSA</exclude>\n                    <exclude>META-INF/*.RSA</exclude>\n                  </excludes>\n                </filter>\n              </filters>\n            </configuration>\n          </execution>\n        </executions>\n      </plugin>\n    </plugins>\n  </build>\n</project>\n"
  },
  {
    "path": "src/main/java/com/sk89q/warmroast/ClassMapping.java",
    "content": "/*\n * WarmRoast\n * Copyright (C) 2013 Albert Pham <http://www.sk89q.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\npackage com.sk89q.warmroast;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\npublic class ClassMapping {\n    \n    private final String obfuscated;\n    private final String actual;\n    private final Map<String, List<String>> methods = new HashMap<>();\n    \n    public ClassMapping(String obfuscated, String actual) {\n        this.obfuscated = obfuscated;\n        this.actual = actual;\n    }\n\n    public String getObfuscated() {\n        return obfuscated;\n    }\n    \n    public String getActual() {\n        return actual;\n    }\n    \n    public void addMethod(String obfuscated, String actual) {\n        List<String> m = methods.get(obfuscated);\n        if (m == null) {\n            m = new ArrayList<>();\n            methods.put(obfuscated, m);\n        }\n        m.add(actual);\n    }\n    \n    public List<String> mapMethod(String obfuscated) {\n        List<String> m = methods.get(obfuscated);\n        if (m == null) {\n            return new ArrayList<>();\n        }\n        return m;\n    }\n    \n    @Override\n    public String toString() {\n        return getObfuscated() + \"->\" + getActual();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/sk89q/warmroast/DataViewServlet.java",
    "content": "/*\n * WarmRoast\n * Copyright (C) 2013 Albert Pham <http://www.sk89q.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\npackage com.sk89q.warmroast;\n\nimport java.io.IOException;\nimport java.io.PrintWriter;\nimport java.util.Collection;\n\nimport javax.servlet.ServletException;\nimport javax.servlet.http.HttpServlet;\nimport javax.servlet.http.HttpServletRequest;\nimport javax.servlet.http.HttpServletResponse;\n\npublic class DataViewServlet extends HttpServlet {\n    \n    private static final long serialVersionUID = -2331397310804298286L;\n    \n    private final WarmRoast roast;\n\n    public DataViewServlet(WarmRoast roast) {\n        this.roast = roast;\n    }\n\n    @Override\n    protected void doGet(HttpServletRequest request,\n            HttpServletResponse response) throws ServletException, IOException {\n        response.setContentType(\"text/html; charset=utf-8\");\n        response.setStatus(HttpServletResponse.SC_OK);\n        \n        PrintWriter w = response.getWriter();\n        w.println(\"<!DOCTYPE html><html><head><title>WarmRoast</title>\");\n        w.println(\"<link rel=\\\"stylesheet\\\" type=\\\"text/css\\\" href=\\\"style.css\\\">\");\n        w.println(\"</head><body>\");\n        w.println(\"<h1>WarmRoast</h1>\");\n        w.println(\"<div class=\\\"loading\\\">Downloading snapshot; please wait...</div>\");\n        w.println(\"<div class=\\\"stack\\\" style=\\\"display: none\\\">\");\n        synchronized (roast) {\n            Collection<StackNode> nodes = roast.getData().values();\n            for (StackNode node : nodes) {\n                w.println(node.toHtml(roast.getMapping()));\n            }\n            if (nodes.size() == 0) {\n                w.println(\"<p class=\\\"no-results\\\">There are no results. \" +\n                \t\t\"(Thread filter does not match thread?)</p>\");\n            }\n        }\n        w.println(\"</div>\");\n        w.println(\"<p class=\\\"legend\\\">Legend: \");\n        w.println(\"<span class=\\\"matched\\\">Mapped</span> \");\n        w.println(\"<span class=\\\"multiple-matches\\\">Multiple Mappings</span> \");\n        w.println(\"</p>\");\n        w.println(\"<div id=\\\"overlay\\\"></div>\");\n        w.println(\"<p class=\\\"footer\\\">\");\n        w.println(\"Icons from <a href=\\\"http://www.fatcow.com/\\\">FatCow</a> &mdash; \");\n        w.println(\"<a href=\\\"http://github.com/sk89q/warmroast\\\">github.com/sk89q/warmroast</a></p>\");\n        w.println(\"<script src=\\\"//ajax.googleapis.com/ajax/libs/jquery/1.10.1/jquery.min.js\\\"></script>\");\n        w.println(\"<script src=\\\"warmroast.js\\\"></script>\");\n        w.println(\"</body></html>\");\n    }\n}"
  },
  {
    "path": "src/main/java/com/sk89q/warmroast/McpMapping.java",
    "content": "/*\n * WarmRoast\n * Copyright (C) 2013 Albert Pham <http://www.sk89q.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\npackage com.sk89q.warmroast;\n\nimport java.io.File;\nimport java.io.FileReader;\nimport java.io.IOException;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport org.apache.commons.io.FileUtils;\n\nimport au.com.bytecode.opencsv.CSVReader;\n\npublic class McpMapping {\n\n    private static final Pattern clPattern = \n            Pattern.compile(\"CL: (?<obfuscated>[^ ]+) (?<actual>[^ ]+)\");\n    private static final Pattern mdPattern = \n            Pattern.compile(\"MD: (?<obfuscatedClass>[^ /]+)/(?<obfuscatedMethod>[^ ]+) \" +\n            \t\t\"[^ ]+ (?<method>[^ ]+) [^ ]+\");\n\n    private final Map<String, ClassMapping> classes = new HashMap<>();\n    private final Map<String, String> methods = new HashMap<>();\n    \n    public ClassMapping mapClass(String obfuscated) {\n        return classes.get(obfuscated);\n    }\n\n    public void read(File joinedFile, File methodsFile) throws IOException {\n        try (FileReader r = new FileReader(methodsFile)) {\n            try (CSVReader reader = new CSVReader(r)) {\n                List<String[]> entries = reader.readAll();\n                processMethodNames(entries);\n            }\n        }\n        \n        List<String> lines = FileUtils.readLines(joinedFile, \"UTF-8\");\n        processClasses(lines);\n        processMethods(lines);\n    }\n    \n    public String mapMethodId(String id) {\n        return methods.get(id);\n    }\n    \n    public String fromMethodId(String id) {\n        String method = methods.get(id);\n        if (method == null) {\n            return id;\n        }\n        return method;\n    }\n    \n    private void processMethodNames(List<String[]> entries) {\n        boolean first = true;\n        for (String[] entry : entries) {\n            if (entry.length < 2) {\n                continue;\n            }\n            if (first) { // Header\n                first = false;\n                continue;\n            }\n            methods.put(entry[0], entry[1]);\n        }\n    }\n    \n    private void processClasses(List<String> lines) {\n        for (String line : lines) {\n            Matcher m = clPattern.matcher(line);\n            if (m.matches()) {\n                String obfuscated = m.group(\"obfuscated\");\n                String actual = m.group(\"actual\").replace(\"/\", \".\");\n                classes.put(obfuscated, new ClassMapping(obfuscated, actual));\n            }\n        }\n    }\n    \n    private void processMethods(List<String> lines) {\n        for (String line : lines) {\n            Matcher m = mdPattern.matcher(line);\n            if (m.matches()) {\n                String obfuscatedClass = m.group(\"obfuscatedClass\");\n                String obfuscatedMethod = m.group(\"obfuscatedMethod\");\n                String method = m.group(\"method\");\n                String methodId = method.substring(method.lastIndexOf('/') + 1);\n                ClassMapping mapping = mapClass(obfuscatedClass);\n                if (mapping != null) {\n                    mapping.addMethod(obfuscatedMethod, \n                            fromMethodId(methodId));\n                }\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/sk89q/warmroast/RoastOptions.java",
    "content": "/*\n * WarmRoast\n * Copyright (C) 2013 Albert Pham <http://www.sk89q.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n */\n\npackage com.sk89q.warmroast;\n\nimport com.beust.jcommander.Parameter;\n\npublic class RoastOptions {\n    \n    @Parameter(names = { \"-h\", \"--help\" }, help = true)\n    public boolean help;\n\n    @Parameter(names = { \"--bind\" }, description = \"The address to bind the HTTP server to\")\n    public String bindAddress = \"0.0.0.0\";\n\n    @Parameter(names = { \"-p\", \"--port\" }, description = \"The port to bind the HTTP server to\")\n    public Integer port = 23000;\n\n    @Parameter(names = { \"--pid\" }, description = \"The PID of the VM to attach to\")\n    public Integer pid;\n\n    @Parameter(names = { \"--name\" }, description = \"The name of the VM to attach to\")\n    public String vmName;\n\n    @Parameter(names = { \"-t\", \"--thread\" }, description = \"Optionally specify a thread to log only\")\n    public String threadName;\n\n    @Parameter(names = { \"-m\", \"--mappings\" }, description = \"A directory with joined.srg and methods.csv\")\n    public String mappingsDir;\n\n    @Parameter(names = { \"--interval\" }, description = \"The sample rate, in milliseconds\")\n    public Integer interval = 100;\n    \n    @Parameter(names = { \"--timeout\" }, description = \"The number of seconds before ceasing sampling (optional)\")\n    public Integer timeout;\n\n}\n"
  },
  {
    "path": "src/main/java/com/sk89q/warmroast/StackNode.java",
    "content": "/*\n * WarmRoast\n * Copyright (C) 2013 Albert Pham <http://www.sk89q.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\npackage com.sk89q.warmroast;\n\nimport java.text.NumberFormat;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\n\npublic class StackNode implements Comparable<StackNode> {\n    \n    private static final NumberFormat cssDec = NumberFormat.getPercentInstance(Locale.US);\n    private final String name;\n    private final Map<String, StackNode> children = new HashMap<>();\n    private long totalTime;\n    \n    static {\n        cssDec.setGroupingUsed(false);\n        cssDec.setMaximumFractionDigits(2);\n    }\n\n    public StackNode(String name) {\n        this.name = name;\n    }\n    \n    public String getName() {\n        return name;\n    }\n    \n    public String getNameHtml(McpMapping mapping) {\n        return escapeHtml(getName());\n    }\n\n    public Collection<StackNode> getChildren() {\n        List<StackNode> list = new ArrayList<>(children.values());\n        Collections.sort(list);\n        return list;\n    }\n    \n    public StackNode getChild(String name) {\n        StackNode child = children.get(name);\n        if (child == null) {\n            child = new StackNode(name);\n            children.put(name, child);\n        }\n        return child;\n    }\n    \n    public StackNode getChild(String className, String methodName) {\n        StackTraceNode node = new StackTraceNode(className, methodName);\n        StackNode child = children.get(node.getName());\n        if (child == null) {\n            child = node;\n            children.put(node.getName(), node);\n        }\n        return child;\n    }\n    \n    public long getTotalTime() {\n        return totalTime;\n    }\n\n    public void log(long time) {\n        totalTime += time;\n    }\n    \n    private void log(StackTraceElement[] elements, int skip, long time) {\n        log(time);\n        \n        if (elements.length - skip == 0) {\n            return;\n        }\n        \n        StackTraceElement bottom = elements[elements.length - (skip + 1)];\n        getChild(bottom.getClassName(), bottom.getMethodName())\n                .log(elements, skip + 1, time);\n    }\n    \n    public void log(StackTraceElement[] elements, long time) {\n        log(elements, 0, time);\n    }\n\n    @Override\n    public int compareTo(StackNode o) {\n        return getName().compareTo(o.getName());\n    }\n    \n    private void writeHtml(StringBuilder builder, McpMapping mapping, long totalTime) {\n        builder.append(\"<div class=\\\"node collapsed\\\">\");\n        builder.append(\"<div class=\\\"name\\\">\");\n        builder.append(getNameHtml(mapping));\n        builder.append(\"<span class=\\\"percent\\\">\");\n        builder\n                .append(String.format(\"%.2f\", getTotalTime() / (double) totalTime * 100))\n                .append(\"%\");\n        builder.append(\"</span>\");\n        builder.append(\"<span class=\\\"time\\\">\");\n        builder.append(getTotalTime()).append(\"ms\");\n        builder.append(\"</span>\");\n        builder.append(\"<span class=\\\"bar\\\">\");\n        builder.append(\"<span class=\\\"bar-inner\\\" style=\\\"width:\")\n                .append(formatCssPct(getTotalTime() / (double) totalTime))\n                .append(\"\\\">\");\n        builder.append(\"</span>\");\n        builder.append(\"</span>\");\n        builder.append(\"</div>\");\n        builder.append(\"<ul class=\\\"children\\\">\");\n        for (StackNode child : getChildren()) {\n            builder.append(\"<li>\");\n            child.writeHtml(builder, mapping, totalTime);\n            builder.append(\"</li>\");\n        }\n        builder.append(\"</ul>\");\n        builder.append(\"</div>\");\n    }\n\n    public String toHtml(McpMapping mapping) {\n        StringBuilder builder = new StringBuilder();\n        writeHtml(builder, mapping, getTotalTime());\n        return builder.toString();\n    }\n    \n    private void writeString(StringBuilder builder, int indent) {\n        StringBuilder b = new StringBuilder();\n        for (int i = 0; i < indent; i++) {\n            b.append(\" \");\n        }\n        String padding = b.toString();\n        \n        for (StackNode child : getChildren()) {\n            builder.append(padding).append(child.getName());\n            builder.append(\" \");\n            builder.append(getTotalTime()).append(\"ms\");\n            builder.append(\"\\n\");\n            child.writeString(builder, indent + 1);\n        }\n    }\n    \n    @Override\n    public String toString() {\n        StringBuilder builder = new StringBuilder();\n        writeString(builder, 0);\n        return builder.toString();\n    }\n    \n    protected static String formatCssPct(double pct) {\n        return cssDec.format(pct);\n    }\n    \n    protected static String escapeHtml(String str) {\n        return str.replace(\"&\", \"&amp;\").replace(\"<\", \"&lt;\").replace(\">\", \"&gt;\");\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/sk89q/warmroast/StackTraceNode.java",
    "content": "/*\n * WarmRoast\n * Copyright (C) 2013 Albert Pham <http://www.sk89q.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\npackage com.sk89q.warmroast;\n\nimport java.util.List;\n\npublic class StackTraceNode extends StackNode {\n    \n    private final String className;\n    private final String methodName;\n\n    public StackTraceNode(String className, String methodName) {\n        super(className + \".\" + methodName + \"()\");\n        this.className = className;\n        this.methodName = methodName;\n    }\n\n    public String getClassName() {\n        return className;\n    }\n\n    public String getMethodName() {\n        return methodName;\n    }\n    \n    @Override\n    public String getNameHtml(McpMapping mapping) {\n        ClassMapping classMapping = mapping.mapClass(getClassName());\n        if (classMapping != null) {\n            String className = \"<span class=\\\"matched\\\" title=\\\"\" + \n                    escapeHtml(getClassName()) + \"\\\">\" +\n                    escapeHtml(classMapping.getActual()) + \"</span>\";\n            \n            List<String> actualMethods = classMapping.mapMethod(getMethodName());\n            if (actualMethods.size() == 0) {\n                return className + \".\" + escapeHtml(getMethodName()) + \"()\";\n            } else if (actualMethods.size() == 1) {\n                return className + \n                        \".<span class=\\\"matched\\\" title=\\\"\" + \n                        escapeHtml(getMethodName()) + \"\\\">\" + \n                        escapeHtml(actualMethods.get(0)) + \"</span>()\";\n            } else {\n                StringBuilder builder = new StringBuilder();\n                boolean first = true;\n                for (String m : actualMethods) {\n                    if (!first) {\n                        builder.append(\" \");\n                    }\n                    builder.append(m);\n                    first = false;\n                }\n                return className + \n                        \".<span class=\\\"multiple-matches\\\" title=\\\"\" + \n                            builder.toString() + \"\\\">\" + escapeHtml(getMethodName()) + \"</span>()\";\n            }\n        } else {\n            String actualMethod = mapping.mapMethodId(getMethodName());\n            if (actualMethod == null) {\n                return escapeHtml(getClassName()) + \".\" + escapeHtml(getMethodName()) + \"()\";\n            } else {\n                return className + \n                        \".<span class=\\\"matched\\\" title=\\\"\" + \n                        escapeHtml(getMethodName()) + \"\\\">\" + \n                        escapeHtml(actualMethod) + \"</span>()\";\n            }\n        }\n    }\n\n    @Override\n    public int compareTo(StackNode o) {\n        if (getTotalTime() == o.getTotalTime()) {\n            return 0;\n        } else if (getTotalTime()> o.getTotalTime()) {\n            return -1;\n        } else {\n            return 1;\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/com/sk89q/warmroast/WarmRoast.java",
    "content": "/*\n * WarmRoast\n * Copyright (C) 2013 Albert Pham <http://www.sk89q.com>\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\npackage com.sk89q.warmroast;\n\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.lang.management.ManagementFactory;\nimport java.lang.management.ThreadInfo;\nimport java.lang.management.ThreadMXBean;\nimport java.net.InetSocketAddress;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.SortedMap;\nimport java.util.Timer;\nimport java.util.TimerTask;\nimport java.util.TreeMap;\n\nimport javax.management.MBeanServerConnection;\nimport javax.management.MalformedObjectNameException;\nimport javax.management.ObjectName;\nimport javax.management.remote.JMXConnector;\nimport javax.management.remote.JMXConnectorFactory;\nimport javax.management.remote.JMXServiceURL;\n\nimport org.eclipse.jetty.server.Server;\nimport org.eclipse.jetty.server.handler.HandlerList;\nimport org.eclipse.jetty.server.handler.ResourceHandler;\nimport org.eclipse.jetty.servlet.ServletContextHandler;\nimport org.eclipse.jetty.servlet.ServletHolder;\n\nimport com.beust.jcommander.JCommander;\nimport com.sun.tools.attach.AgentInitializationException;\nimport com.sun.tools.attach.AgentLoadException;\nimport com.sun.tools.attach.AttachNotSupportedException;\nimport com.sun.tools.attach.VirtualMachine;\nimport com.sun.tools.attach.VirtualMachineDescriptor;\n\npublic class WarmRoast extends TimerTask {\n\n    private static final String SEPARATOR = \n            \"------------------------------------------------------------------------\";\n    \n    private final int interval;\n    private final VirtualMachine vm;\n    private final Timer timer = new Timer(\"Roast Pan\", true);\n    private final McpMapping mapping = new McpMapping();\n    private final SortedMap<String, StackNode> nodes = new TreeMap<>();\n    private JMXConnector connector;\n    private MBeanServerConnection mbsc;\n    private ThreadMXBean threadBean;\n    private String filterThread;\n    private long endTime = -1;\n    \n    public WarmRoast(VirtualMachine vm, int interval) {\n        this.vm = vm;\n        this.interval = interval;\n    }\n    \n    public Map<String, StackNode> getData() {\n        return nodes;\n    }\n    \n    private StackNode getNode(String name) {\n        StackNode node = nodes.get(name);\n        if (node == null) {\n            node = new StackNode(name);\n            nodes.put(name, node);\n        }\n        return node;\n    }\n    \n    public McpMapping getMapping() {\n        return mapping;\n    }\n    \n    public String getFilterThread() {\n        return filterThread;\n    }\n\n    public void setFilterThread(String filterThread) {\n        this.filterThread = filterThread;\n    }\n\n    public long getEndTime() {\n        return endTime;\n    }\n\n    public void setEndTime(long l) {\n        this.endTime = l;\n    }\n\n    public void connect() \n            throws IOException, AgentLoadException, AgentInitializationException {\n        // Load the agent\n        String connectorAddr = vm.getAgentProperties().getProperty(\n                \"com.sun.management.jmxremote.localConnectorAddress\");\n        if (connectorAddr == null) {\n            String agent = vm.getSystemProperties().getProperty(\"java.home\")\n                    + File.separator + \"lib\" + File.separator\n                    + \"management-agent.jar\";\n            vm.loadAgent(agent);\n            connectorAddr = vm.getAgentProperties().getProperty(\n                    \"com.sun.management.jmxremote.localConnectorAddress\");\n        }\n\n        // Connect\n        JMXServiceURL serviceURL = new JMXServiceURL(connectorAddr);\n        connector = JMXConnectorFactory.connect(serviceURL);\n        mbsc = connector.getMBeanServerConnection();\n        try {\n            threadBean = getThreadMXBean();\n        } catch (MalformedObjectNameException e) {\n            throw new IOException(\"Bad MX bean name\", e);\n        }\n    }\n\n    private ThreadMXBean getThreadMXBean() \n            throws IOException, MalformedObjectNameException {\n        ObjectName objName = new ObjectName(ManagementFactory.THREAD_MXBEAN_NAME);\n        Set<ObjectName> mbeans = mbsc.queryNames(objName, null);\n        for (ObjectName name : mbeans) {\n            return ManagementFactory.newPlatformMXBeanProxy(\n                    mbsc, name.toString(), ThreadMXBean.class);\n        }\n        throw new IOException(\"No thread MX bean found\");\n    }\n\n    @Override\n    public synchronized void run() {\n        if (endTime >= 0) {\n            if (endTime <= System.currentTimeMillis()) {\n                cancel();\n                System.err.println(\"Sampling has stopped.\");\n                return;\n            }\n        }\n        \n        ThreadInfo[] threadDumps = threadBean.dumpAllThreads(false, false);\n        for (ThreadInfo threadInfo : threadDumps) {\n            String threadName = threadInfo.getThreadName();\n            StackTraceElement[] stack = threadInfo.getStackTrace();\n            \n            if (threadName == null || stack == null) {\n                continue;\n            }\n            \n            if (filterThread != null && !filterThread.equals(threadName)) {\n                continue;\n            }\n            \n            StackNode node = getNode(threadName);\n            node.log(stack, interval);\n        }\n    }\n\n    public void start(InetSocketAddress address) throws Exception {\n        timer.scheduleAtFixedRate(this, interval, interval);\n        \n        Server server = new Server(address);\n\n        ServletContextHandler context = new ServletContextHandler();\n        context.setContextPath(\"/\");\n        context.addServlet(new ServletHolder(new DataViewServlet(this)), \"/stack\");\n\n        ResourceHandler resources = new ResourceHandler();\n        String filesDir = WarmRoast.class.getResource(\"/www\").toExternalForm();\n        resources.setResourceBase(filesDir);\n        resources.setDirectoriesListed(true);\n        resources.setWelcomeFiles(new String[]{ \"index.html\" });\n \n        HandlerList handlers = new HandlerList();\n        handlers.addHandler(context);\n        handlers.addHandler(resources);\n        server.setHandler(handlers);\n\n        server.start();\n        server.join();\n    }\n\n    public static void main(String[] args) throws AgentLoadException {\n        RoastOptions opt = new RoastOptions();\n        JCommander jc = new JCommander(opt, args);\n        jc.setProgramName(\"warmroast\");\n        \n        if (opt.help) {\n            jc.usage();\n            System.exit(0);\n        }\n\n        System.err.println(SEPARATOR);\n        System.err.println(\"WarmRoast\");\n        System.err.println(\"http://github.com/sk89q/warmroast\");\n        System.err.println(SEPARATOR);\n        System.err.println(\"\");\n        \n        VirtualMachine vm = null;\n        \n        if (opt.pid != null) {\n            try {\n                vm = VirtualMachine.attach(String.valueOf(opt.pid));\n                System.err.println(\"Attaching to PID \" + opt.pid + \"...\");\n            } catch (AttachNotSupportedException | IOException e) {\n                System.err.println(\"Failed to attach VM by PID \" + opt.pid);\n                e.printStackTrace();\n                System.exit(1);\n            }\n        } else if (opt.vmName != null) {\n            for (VirtualMachineDescriptor desc : VirtualMachine.list()) {\n                if (desc.displayName().contains(opt.vmName)) {\n                    try {\n                        vm = VirtualMachine.attach(desc);\n                        System.err.println(\"Attaching to '\" + desc.displayName() + \"'...\");\n                        \n                        break;\n                    } catch (AttachNotSupportedException | IOException e) {\n                        System.err.println(\"Failed to attach VM by name '\" + opt.vmName + \"'\");\n                        e.printStackTrace();\n                        System.exit(1);\n                    }\n                }\n            }\n        }\n        \n        if (vm == null) {\n            List<VirtualMachineDescriptor> descriptors = VirtualMachine.list();\n            System.err.println(\"Choose a VM:\");\n            \n            Collections.sort(descriptors, new Comparator<VirtualMachineDescriptor>() {\n                @Override\n                public int compare(VirtualMachineDescriptor o1,\n                        VirtualMachineDescriptor o2) {\n                    return o1.displayName().compareTo(o2.displayName());\n                }\n            });\n            \n            // Print list of VMs\n            int i = 1;\n            for (VirtualMachineDescriptor desc : descriptors) {\n                System.err.println(\"[\" + (i++) + \"] \" + desc.displayName());\n            }\n            \n            // Ask for choice\n            System.err.println(\"\");\n            System.err.print(\"Enter choice #: \");\n            BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));\n            String s;\n            try {\n                s = reader.readLine();\n            } catch (IOException e) {\n                return;\n            }\n            \n            // Get the VM\n            try {\n                int choice = Integer.parseInt(s) - 1;\n                if (choice < 0 || choice >= descriptors.size()) {\n                    System.err.println(\"\");\n                    System.err.println(\"Given choice is out of range.\");\n                    System.exit(1);\n                }\n                vm = VirtualMachine.attach(descriptors.get(choice));\n            } catch (NumberFormatException e) {\n                System.err.println(\"\");\n                System.err.println(\"That's not a number. Bye.\");\n                System.exit(1);\n            } catch (AttachNotSupportedException | IOException e) {\n                System.err.println(\"\");\n                System.err.println(\"Failed to attach VM\");\n                e.printStackTrace();\n                System.exit(1);\n            }\n        }\n        \n        InetSocketAddress address = new InetSocketAddress(opt.bindAddress, opt.port);\n\n        WarmRoast roast = new WarmRoast(vm, opt.interval);\n        if (opt.mappingsDir != null) {\n            File dir = new File(opt.mappingsDir);\n            File joined = new File(dir, \"joined.srg\");\n            File methods = new File(dir, \"methods.csv\");\n            try {\n                roast.getMapping().read(joined, methods);\n            } catch (IOException e) {\n                System.err.println(\n                        \"Failed to read the mappings files (joined.srg, methods.csv) \" +\n                        \"from \" + dir.getAbsolutePath() + \": \" + e.getMessage());\n                System.exit(2);\n            }\n        }\n\n        System.err.println(SEPARATOR);\n        \n        roast.setFilterThread(opt.threadName);\n        \n        if (opt.timeout != null && opt.timeout > 0) {\n            roast.setEndTime(System.currentTimeMillis() + opt.timeout * 1000);\n            System.err.println(\"Sampling set to stop in \" + opt.timeout + \" seconds.\");\n        }\n\n        System.err.println(\"Starting a server on \" + address.toString() + \"...\");\n        System.err.println(\"Once the server starts (shortly), visit the URL in your browser.\");\n        System.err.println(\"Note: The longer you wait before using the output of that \" +\n        \t\t\"webpage, the more accurate the results will be.\");\n        \n        try {\n            roast.connect();\n            roast.start(address);\n        } catch (Throwable t) {\n            t.printStackTrace();\n            System.exit(3);\n        }\n    }\n\n}\n"
  },
  {
    "path": "src/main/resources/www/index.html",
    "content": "<!DOCTYPE html><html><head><title>WarmRoast</title>\n<style>@import url(style.css);</style>\n</head><body>\n<h1>WarmRoast</h1>\n\n<p>\n    <a href=\"/stack\">View sampler results</a>\n</p>\n\n<p class=\"footer\">\n<a href=\"http://github.com/sk89q/warmroast\">github.com/sk89q/warmroast</a></p>\n\n</body></html>\n"
  },
  {
    "path": "src/main/resources/www/style.css",
    "content": "@import url(http://fonts.googleapis.com/css?family=Lato);\n\nbody {\n    font-family: 'Lato', Arial, sans-serif;\n    font-size: 10pt;\n    line-height: 150%;\n    margin: 0;\n    padding: 54px 20px 20px 20px;\n}\n\nul {\n    margin: 0;\n    padding: 0 0 0 18px;\n}\n\nli {\n    margin: 0;\n    margin-left: -10px;\n    padding: 0;\n    list-style: none;\n    border-left: 1px solid #ccc;\n}\n\na:link, a:visited {\n    color: #FF3213;\n    text-decoration: none;\n    border-bottom: 1px solid #CCC;\n}\n\na:hover, a:active {\n    color: #000;\n    border-color: black;\n    text-decoration: none;\n}\n\n.stack {\n    margin-left: 60px;\n}\n\n.name {\n    background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA10lEQVR4Xt2Tu8rCQBSEzzlJmYBvYJ7EShArfQ1BrPNDyoB5DRHsrUQQQc3lcfxBiKbIHjfiCuayKSwEB6ZYPpjdGVhkZvhEJP3dALMMgmB+FoI7ddUQEYhw7bp/48YAkYtOfzCEJu22m5G2gnjeHIYxxHGiXJwV11dQTzdN4w0QKa4JULplGcymkwo4Rkn7iCjNOcNiuVK3vQZ0ug5gWwAgPnpalgW1+yDqA4hQmmSADVVGBW8bES7RaW+zYOBSNSSENL0etAGe5/UkMKBZ/77vv8APfKY7cvZVTt7VqzwAAAAASUVORK5CYII=) center left no-repeat;\n    padding-left: 20px;\n    cursor: pointer;\n}\n\n.name:hover {\n    background-color: #CCC;\n}\n\n.name:hover + ul {\n    background: #EFEFEF;\n    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);\n    border-radius: 3px;\n}\n\n.matched {\n    background: #CCC;\n    border-radius: 3px;\n    padding: 0 4px;\n}\n\n.multiple-matches {\n    background: #FF3213;\n    color: #FFF;\n    padding: 0 4px;\n    border-radius: 3px;\n}\n\n.matched:hover, .multiple-matches:hover {\n    background: #000000;\n    color: #FFF;\n    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.4);\n}\n\n.percent {\n    color: #6b98ff;\n    font-size: 90%;\n    border-radius: 3px;\n    padding: 0 4px;\n}\n\n.bar {\n    display: inline-block;\n    width: 100px;\n    height: 15px;\n    margin-left: 20px;\n    border: 1px solid #CCC;\n    position: absolute;\n    right: 30px;\n    background: #FFF;\n}\n\n.bar-inner {\n    display: inline-block;\n    height: 16px;\n    background: #6b98ff;\n}\n\n#overlay span {\n    position: absolute;\n    color: #6b98ff;\n    font-size: 90%;\n    z-index: 10;\n    line-height: 150%;\n    left: 20px;\n    width: 50px;\n    text-align: right;\n}\n\n.time {\n    display: none;\n    margin: 0;\n    color: #888;\n    font-size: 90%;\n    border-radius: 3px;\n    padding: 0 4px;\n}\n\n.name:hover .time {\n    display: inline;\n}\n\nul {\n    display: none;\n}\n\n.collapsed > .name {\n    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAA7ElEQVR4Xt1TzYrCQAxOYo8z0GeRZY+eBPGkryGIZ4UeC/Yx/EHvnqTssot/9XF2YUHXQyd2RtuC2OnBg2AgJF/CfJkvwyAzwyNGiT+XwLktBMHwRyl270lDRCDCRb8/aBcSqFi59UYTiuwzXLasEtR18nYbQRTt4f2tqqPGad8uIb2641TyKUSJp307gbH/0wl63U4Ks3y925e/AibOMcN4OofRZKZLOmps6lj2CoBodAohspKUMtePaCcgQqNZCGlw+PFt8nwXWLZE+NttviQrBr6RhoRwOBxXVgLP82pJqECx/fq+n4EX+ExnBI9csQQ1hIoAAAAASUVORK5CYII=);\n}\n\nh1 {\n    background: #FFF;\n    color: #111;\n    position: fixed;\n    top: 0;\n    left: 0;\n    right: 0;\n    padding: 10px 20px;\n    margin: 0;\n    font-size: 14pt;\n    font-weight: normal;\n    box-shadow: 0 0 4px rgba(0, 0, 0, 0.4);\n    z-index: 20;\n}\n\n.footer {\n    background: #FFF;\n    color: #333;\n    margin: 100px 0 0 0;\n    font-size: 10pt;\n    font-weight: normal;\n    text-align: right;\n}\n\n.loading {\n    font-size: 130%;\n    background: #EFEFEF;\n    border: 1px solid #CCC;\n    padding: 8px;\n    border-radius: 3px;\n}\n\n.no-results {\n    font-size: 130%;\n    color: #800000;\n}"
  },
  {
    "path": "src/main/resources/www/warmroast.js",
    "content": "$(\".name\").on(\"click\", function(event) {\n    var $parent = $(this).parent();\n    if ($parent.hasClass(\"collapsed\")) {\n        $parent.removeClass(\"collapsed\");\n        $parent.children(\"ul\").slideDown(50);\n    } else {\n        $parent.addClass(\"collapsed\");\n        $parent.children(\"ul\").slideUp(50);\n    }\n});\n\nfunction extractTime($el) {\n    var text = $el.children(\".name\")\n        .children(\".time\").text().replace(/[^0-9]/, \"\");\n    return parseInt(text);\n}\n\nvar $overlay = $(\"#overlay\");\n\n$(\".name\").on(\"mouseenter\", function(event) {\n    var $this = $(this);\n    var thisTime = null;\n    $overlay.empty();\n    $this.parents(\".node\").each(function(i, parent) {\n        var $parent = $(parent);\n        var time = extractTime($parent);\n        if (thisTime == null) {\n            thisTime = time;\n        } else {\n            var $el = $(document.createElement(\"span\"));\n            var pos = $parent.position();\n            var width = $el.outerWidth();\n            $el.text(((thisTime / time) * 100).toFixed(2) + \"%\");\n            $el.css({\n                top: pos.top + \"px\"\n            });\n            $overlay.append($el);\n        }\n    });\n});\n\n$(\".loading\").hide();\n$(\".stack\").show();"
  }
]