| /* |
| * Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. |
| * |
| * This code 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 |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| * |
| */ |
| |
| package com.sun.hotspot.tools.compiler; |
| |
| import java.io.ByteArrayOutputStream; |
| import java.io.File; |
| import java.io.FileNotFoundException; |
| import java.io.FileOutputStream; |
| import java.io.PrintStream; |
| import java.util.*; |
| |
| import org.xml.sax.*; |
| import org.xml.sax.helpers.*; |
| |
| /** |
| * The LogCompilation tool parses log files generated by HotSpot using the |
| * {@code -XX:+LogCompilation} command line flag, and outputs the data |
| * collected therein in a nicely formatted way. There are various sorting |
| * options available, as well as options that select specific compilation |
| * events (such as inlining decisions) for inclusion in the output. |
| * |
| * The tool is also capable of fixing broken compilation logs as sometimes |
| * generated by Java 1.5 JVMs. |
| */ |
| public class LogCompilation extends DefaultHandler implements ErrorHandler { |
| |
| /** |
| * Print usage information and terminate with a given exit code. |
| */ |
| public static void usage(int exitcode) { |
| System.out.println("Usage: LogCompilation [ -v ] [ -c ] [ -s ] [ -e | -n ] file1 ..."); |
| System.out.println("By default, the tool will print the logged compilations ordered by start time."); |
| System.out.println(" -c: clean up malformed 1.5 xml"); |
| System.out.println(" -i: print inlining decisions"); |
| System.out.println(" -S: print compilation statistics"); |
| System.out.println(" -U: print uncommon trap statistics"); |
| System.out.println(" -t: print with time stamps"); |
| System.out.println(" -s: sort events by start time (default)"); |
| System.out.println(" -e: sort events by elapsed time"); |
| System.out.println(" -n: sort events by name and start"); |
| System.out.println(" -C: compare logs (give files to compare on command line)"); |
| System.out.println(" -d: do not print compilation IDs"); |
| System.exit(exitcode); |
| } |
| |
| /** |
| * Process command line arguments, parse log files and trigger desired |
| * functionality. |
| */ |
| public static void main(String[] args) throws Exception { |
| Comparator<LogEvent> sort = LogParser.sortByStart; |
| boolean statistics = false; |
| boolean printInlining = false; |
| boolean cleanup = false; |
| boolean trapHistory = false; |
| boolean printTimeStamps = false; |
| boolean compare = false; |
| boolean printID = true; |
| int index = 0; |
| |
| while (args.length > index) { |
| String a = args[index]; |
| if (a.equals("-e")) { |
| sort = LogParser.sortByElapsed; |
| index++; |
| } else if (a.equals("-n")) { |
| sort = LogParser.sortByNameAndStart; |
| index++; |
| } else if (a.equals("-s")) { |
| sort = LogParser.sortByStart; |
| index++; |
| } else if (a.equals("-t")) { |
| printTimeStamps = true; |
| index++; |
| } else if (a.equals("-c")) { |
| cleanup = true; |
| index++; |
| } else if (a.equals("-S")) { |
| statistics = true; |
| index++; |
| } else if (a.equals("-U")) { |
| trapHistory = true; |
| index++; |
| } else if (a.equals("-h")) { |
| usage(0); |
| } else if (a.equals("-i")) { |
| printInlining = true; |
| index++; |
| } else if (a.equals("-C")) { |
| compare = true; |
| index++; |
| } else if (a.equals("-d")) { |
| printID = false; |
| index++; |
| } else { |
| if (a.charAt(0) == '-') { |
| System.out.println("Unknown option '" + a + "', assuming file name."); |
| } |
| break; |
| } |
| } |
| |
| if (index >= args.length) { |
| usage(1); |
| } |
| |
| if (compare) { |
| compareLogs(index, args); |
| return; |
| } |
| |
| while (index < args.length) { |
| ArrayList<LogEvent> events = null; |
| try { |
| events = LogParser.parse(args[index], cleanup); |
| } catch (FileNotFoundException fnfe) { |
| System.out.println("File not found: " + args[index]); |
| System.exit(1); |
| } |
| |
| Collections.sort(events, sort); |
| |
| if (statistics) { |
| printStatistics(events, System.out); |
| } else if (trapHistory) { |
| printTrapHistory(events, System.out); |
| } else { |
| for (LogEvent c : events) { |
| if (c instanceof NMethod) { |
| // skip these |
| continue; |
| } |
| if (printTimeStamps) { |
| System.out.print(c.getStart() + ": "); |
| } |
| if (c instanceof Compilation) { |
| Compilation comp = (Compilation) c; |
| comp.print(System.out, printID, printInlining); |
| } else { |
| c.print(System.out, printID); |
| } |
| } |
| } |
| index++; |
| } |
| } |
| |
| /** |
| * Print extensive statistics from parsed log files. |
| */ |
| public static void printStatistics(ArrayList<LogEvent> events, PrintStream out) { |
| // track code cache size |
| long cacheSize = 0; |
| long maxCacheSize = 0; |
| // track number of nmethods |
| int nmethodsCreated = 0; |
| int nmethodsLive = 0; |
| // track how many compilations were attempted multiple times |
| // (indexed by attempts, mapping to number of compilations) |
| int[] attempts = new int[32]; |
| int maxattempts = 0; |
| |
| // track time spent in compiler phases |
| LinkedHashMap<String, Double> phaseTime = new LinkedHashMap<>(7); |
| // track nodes created per phase |
| LinkedHashMap<String, Integer> phaseNodes = new LinkedHashMap<>(7); |
| double elapsed = 0; |
| |
| for (LogEvent e : events) { |
| if (e instanceof Compilation) { |
| Compilation c = (Compilation) e; |
| c.printShort(out); |
| out.printf(" %6.4f\n", c.getElapsedTime()); |
| attempts[c.getAttempts()]++; |
| maxattempts = Math.max(maxattempts,c.getAttempts()); |
| elapsed += c.getElapsedTime(); |
| for (Phase phase : c.getPhases()) { |
| Double v = phaseTime.get(phase.getName()); |
| if (v == null) { |
| v = Double.valueOf(0.0); |
| } |
| phaseTime.put(phase.getName(), Double.valueOf(v.doubleValue() + phase.getElapsedTime())); |
| |
| Integer v2 = phaseNodes.get(phase.getName()); |
| if (v2 == null) { |
| v2 = Integer.valueOf(0); |
| } |
| phaseNodes.put(phase.getName(), Integer.valueOf(v2.intValue() + phase.getNodes())); |
| // Print phase name, elapsed time, nodes at the start of |
| // the phase, nodes created in the phase, live nodes at the |
| // start of the phase, live nodes added in the phase. |
| out.printf("\t%s %6.4f %d %d %d %d\n", phase.getName(), phase.getElapsedTime(), phase.getStartNodes(), phase.getNodes(), phase.getStartLiveNodes(), phase.getAddedLiveNodes()); |
| } |
| } else if (e instanceof MakeNotEntrantEvent) { |
| MakeNotEntrantEvent mne = (MakeNotEntrantEvent) e; |
| NMethod nm = mne.getNMethod(); |
| if (mne.isZombie()) { |
| if (nm == null) { |
| System.err.println("zombie make not entrant event without nmethod: " + mne.getId()); |
| } |
| cacheSize -= nm.getSize(); |
| nmethodsLive--; |
| } |
| } else if (e instanceof NMethod) { |
| nmethodsLive++; |
| nmethodsCreated++; |
| NMethod nm = (NMethod) e; |
| cacheSize += nm.getSize(); |
| maxCacheSize = Math.max(cacheSize, maxCacheSize); |
| } |
| } |
| out.printf("NMethods: %d created %d live %d bytes (%d peak) in the code cache\n", nmethodsCreated, nmethodsLive, cacheSize, maxCacheSize); |
| out.println("Phase times:"); |
| for (String name : phaseTime.keySet()) { |
| Double v = phaseTime.get(name); |
| Integer v2 = phaseNodes.get(name); |
| out.printf("%20s %6.4f %d\n", name, v.doubleValue(), v2.intValue()); |
| } |
| out.printf("%20s %6.4f\n", "total", elapsed); |
| |
| if (maxattempts > 0) { |
| out.println("Distribution of regalloc passes:"); |
| for (int i = 0; i <= maxattempts; i++) { |
| out.printf("%2d %8d\n", i, attempts[i]); |
| } |
| } |
| } |
| |
| /** |
| * Container class for a pair of a method and a bytecode instruction index |
| * used by a compiler. This is used in |
| * {@linkplain #compareLogs() comparing logs}. |
| */ |
| static class MethodBCIPair { |
| public MethodBCIPair(Method m, int b, String c) { |
| method = m; |
| bci = b; |
| compiler = c; |
| } |
| |
| Method method; |
| int bci; |
| String compiler; |
| |
| public boolean equals(Object other) { |
| if (!(other instanceof MethodBCIPair)) { |
| return false; |
| } |
| MethodBCIPair otherp = (MethodBCIPair)other; |
| return (otherp.bci == bci && |
| otherp.method.equals(method) && |
| otherp.compiler.equals(compiler)); |
| } |
| |
| public int hashCode() { |
| return method.hashCode() + bci; |
| } |
| |
| public String toString() { |
| if (bci != -1) { |
| return method + "@" + bci + " (" + compiler + ")"; |
| } else { |
| return method + " (" + compiler + ")"; |
| } |
| } |
| } |
| |
| /** |
| * Compare a number of compilation log files. Each of the logs is parsed, |
| * and all compilations found therein are written to a sorted file (prefix |
| * {@code sorted-}. A summary is written to a new file {@code summary.txt}. |
| * |
| * @param index the index in the command line arguments at which to start |
| * looking for files to compare. |
| * @param args the command line arguments with which {@link LogCompilation} |
| * was originally invoked. |
| * |
| * @throws Exception in case any exceptions are thrown in the called |
| * methods. |
| */ |
| @SuppressWarnings("unchecked") |
| static void compareLogs(int index, String[] args) throws Exception { |
| HashMap<MethodBCIPair,MethodBCIPair> methods = new HashMap<>(); |
| ArrayList<HashMap<MethodBCIPair,Object>> logs = new ArrayList<>(); |
| PrintStream[] outs = new PrintStream[args.length - index]; |
| PrintStream summary = new PrintStream(new FileOutputStream("summary.txt")); |
| int o = 0; |
| // Process all logs given on the command line: collect compilation |
| // data; in particular, method/bci pairs. |
| while (index < args.length) { |
| String basename = new File(args[index]).getName(); |
| String outname = "sorted-" + basename; |
| System.out.println("Sorting " + basename + " to " + outname); |
| outs[o] = new PrintStream(new FileOutputStream(outname)); |
| o++; |
| System.out.println("Parsing " + args[index]); |
| ArrayList<LogEvent> events = LogParser.parse(args[index], false); |
| HashMap<MethodBCIPair,Object> compiles = new HashMap<>(); |
| logs.add(compiles); |
| for (LogEvent c : events) { |
| if (c instanceof Compilation) { |
| Compilation comp = (Compilation) c; |
| MethodBCIPair key = new MethodBCIPair(comp.getMethod(), comp.getBCI(), |
| comp.getCompiler()); |
| MethodBCIPair e = methods.get(key); |
| if (e == null) { |
| methods.put(key, key); |
| } else { |
| key = e; |
| } |
| Object other = compiles.get(key); |
| if (other == null) { |
| compiles.put(key, comp); |
| } else { |
| if (!(other instanceof List)) { |
| List<Object> l = new LinkedList<>(); |
| l.add(other); |
| l.add(comp); |
| compiles.put(key, l); |
| } else { |
| List<Object> l = (List<Object>) other; |
| l.add(comp); |
| } |
| } |
| } |
| } |
| index++; |
| } |
| |
| // Process the collected method/bci pairs and write the output. |
| for (MethodBCIPair pair : methods.keySet()) { |
| summary.print(pair + " "); |
| int base = -1; |
| String first = null; |
| boolean mismatch = false; |
| boolean different = false; |
| String[] output = new String[outs.length]; |
| o = 0; |
| for (HashMap<MethodBCIPair,Object> set : logs) { |
| Object e = set.get(pair); |
| String thisone = null; |
| Compilation lastc = null; |
| int n; |
| if (e == null) { |
| n = 0; |
| } else if (e instanceof Compilation) { |
| n = 1; |
| lastc = (Compilation) e; |
| } else { |
| // Compare the last compilation that was done for this method |
| n = ((List<Object>) e).size(); |
| lastc = (Compilation) ((List<Object>) e).get(n - 1); |
| } |
| if (lastc != null) { |
| n = 1; |
| ByteArrayOutputStream baos = new ByteArrayOutputStream(); |
| PrintStream ps = new PrintStream(baos); |
| lastc.print(ps, false); |
| ps.close(); |
| thisone = new String(baos.toByteArray()); |
| } |
| if (base == -1) { |
| base = n; |
| } else if (base != n) { |
| mismatch = true; |
| } |
| output[o++] = thisone; |
| if (thisone != null) { |
| if (first == null) { |
| first = thisone; |
| } else { |
| if (!first.equals(thisone)) { |
| different = true; |
| } |
| } |
| } |
| if (different) { |
| summary.print(n + "d "); |
| } else { |
| summary.print(n + " "); |
| } |
| } |
| if (mismatch) { |
| summary.print("mismatch"); |
| } |
| summary.println(); |
| if (different) { |
| for (int i = 0; i < outs.length; i++) { |
| if (output[i] != null) { |
| outs[i].println(output[i]); |
| } |
| } |
| } |
| } |
| for (int i = 0; i < outs.length; i++) { |
| outs[i].close(); |
| } |
| if (summary != System.out) { |
| summary.close(); |
| } |
| } |
| |
| /** |
| * Print the history of uncommon trap events. |
| */ |
| public static void printTrapHistory(ArrayList<LogEvent> events, PrintStream out) { |
| // map method names to a list of log events |
| LinkedHashMap<String, ArrayList<LogEvent>> traps = new LinkedHashMap<>(); |
| // map compilation IDs to compilations |
| HashMap<Integer, Compilation> comps = new HashMap<>(); |
| |
| // First, iterate over all logged events, collecting data about |
| // uncommon trap events. |
| for (LogEvent e : events) { |
| if (e instanceof NMethod) { |
| // skip these |
| continue; |
| } |
| if (e instanceof Compilation) { |
| Compilation c = (Compilation) e; |
| String name = c.getMethod().getFullName(); |
| ArrayList<LogEvent> elist = traps.get(name); |
| if (elist != null && comps.get(c.getId()) == null) { |
| comps.put(c.getId(), c); |
| // If there were previous events for the method |
| // then keep track of later compiles too. |
| elist.add(c); |
| } |
| continue; |
| } |
| if (e instanceof BasicLogEvent) { |
| BasicLogEvent ble = (BasicLogEvent) e; |
| Compilation c = ble.getCompilation(); |
| if (c == null) { |
| if (!(ble instanceof NMethod)) { |
| throw new InternalError("only nmethods should have a null compilation; here's a " + ble.getClass()); |
| } |
| continue; |
| } |
| String name = c.getMethod().getFullName(); |
| ArrayList<LogEvent> elist = traps.get(name); |
| if (elist == null) { |
| elist = new ArrayList<LogEvent>(); |
| traps.put(name, elist); |
| } |
| int bleId = Integer.parseInt(ble.getId()); |
| if (comps.get(bleId) == null) { |
| comps.put(bleId, c); |
| // Add the associated compile to the list. It |
| // will likely go at the end but we need to search |
| // backwards for the proper insertion point. |
| double start = c.getStart(); |
| int ipoint = 0; |
| while (ipoint < elist.size() && elist.get(ipoint).getStart() < start) { |
| ipoint++; |
| } |
| if (ipoint == elist.size()) { |
| elist.add(c); |
| } else { |
| elist.add(ipoint, c); |
| } |
| } |
| elist.add(ble); |
| } |
| } |
| |
| // Second, iterate over collected traps and output information. |
| for (String c: traps.keySet()) { |
| ArrayList<LogEvent> elist = traps.get(c); |
| String name = ((Compilation) elist.get(0)).getMethod().getFullName(); |
| System.out.println(name); |
| double start = 0; |
| for (LogEvent e: elist) { |
| if (start > e.getStart() && e.getStart() != 0) { |
| throw new InternalError("wrong sorting order for traps"); |
| } |
| start = e.getStart(); |
| out.print(e.getStart() + ": "); |
| if (e instanceof Compilation) { |
| ((Compilation) e).print(out, true, true, true); |
| } else { |
| e.print(out, true); |
| } |
| } |
| out.println(); |
| } |
| } |
| |
| } |