/* * Copyright 2007 Sun Microsystems, Inc. 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. * */ package gcparser; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.IOException; import java.io.PrintStream; import java.text.MessageFormat; import java.util.ArrayList; import java.util.BitSet; import java.util.Collection; import java.util.EnumMap; import java.util.Formatter; import java.util.Iterator; import java.util.List; import java.util.ResourceBundle; import java.util.regex.*; import java.util.zip.GZIPInputStream; public class GCParserDriver { public static final int TERSE = 0x01; public static final int VERBOSE = 0x02; public static final int COLLECT_DATA = 0x03; public static final int COMPARE_STATISTICS = 0x04; public static final int PRINT_STATISTICS = 0x05; public static final int SAVE_DATA = 0x06; public static void main(String argv[]) throws IOException { GCParserDriver driver = new GCParserDriver(argv); driver.run(argv, driver.next_arg()); } public GCParserDriver(BitSet actions, EnumMap<GCMetric, Boolean> enabled_map, String prefix, String suffix, int cpu_count) { boolean verbose = actions.get(VERBOSE); _actions = create_actions(actions); _enabled_map = enabled_map; _prefix = prefix; _suffix = suffix; _cpu_count = cpu_count; _next_arg = 0; _has_time_zero = true; _gc_stats = create_gc_stats(_actions, enabled_map, _cpu_count, _has_time_zero); _gc_parsers = create_gc_parsers(_gc_stats, verbose); } public GCParserDriver(BitSet actions) { this(actions, null, null, ".dat", 1); } public GCParserDriver(String argv[], int index) { boolean verbose = false; boolean enable_value = false; String enable_list = null; _actions = new BitSet(); _suffix = ".dat"; _cpu_count = 1; _has_time_zero = true; int i; final int n = argv.length; boolean matched = true; for (i = index; i < n && matched; ++i) { // -c # compare statistics // -d name ... # disable metrics // -e name ... # enable metrics // -h # help // -l # list metrics and exit // -n # number of cpus // -o pattern # output file pattern (use %{metric}) // -p # print statistics (the default) // -s # save data // -t # terse // -v # verbose // // ??? // -z # each file includes time zero // +z # each file does not include time zero String s = argv[i]; if (s == null) { /* empty */ } else if (s.equals("-c") || s.equals("--compare") || s.equals("--comparestats")) { _actions.set(COMPARE_STATISTICS); } else if (i + 1 < n && (s.equals("-d") || s.equals("--disable") || s.equals("--disablemetrics"))) { enable_list = argv[++i]; enable_value = false; } else if (i + 1 < n && (s.equals("-e") || s.equals("--enable") || s.equals("--enablemetrics"))) { enable_list = argv[++i]; enable_value = true; } else if (s.equals("-h") || s.equals("--help")) { help(System.out); System.exit(0); } else if (s.equals("-l") || s.equals("--list") || s.equals("--listmetrics")) { list_metrics(System.out); System.exit(0); } else if (i + 1 < n && (s.equals("-n") || s.equals("--cpu") || s.equals("--cpucount"))) { _cpu_count = Integer.parseInt(argv[++i]); } else if (s.equals("-p") || s.equals("--print") || s.equals("--printstats")) { _actions.set(PRINT_STATISTICS); } else if (i + 1 < n && (s.equals("-o") || s.equals("--ofile") || s.equals("--outputfilepattern"))) { parse_output_file_pattern(argv[++i]); // This option implies -s. _actions.set(COLLECT_DATA); _actions.set(SAVE_DATA); } else if (s.equals("-s") || s.equals("--save") || s.equals("--savedata")) { _actions.set(COLLECT_DATA); _actions.set(SAVE_DATA); } else if (s.equals("-t") || s.equals("--terse")) { _actions.set(TERSE); } else if (s.equals("-v") || s.equals("--verbose")) { _actions.set(VERBOSE); verbose = true; } else if (s.equals("-z") || s.equals("--time-zero")) { _has_time_zero = true; } else if (s.equals("+z") || s.equals("--no-time-zero")) { _has_time_zero = false; } else if (s.equals("--")) { /* empty */ matched = false; } else if (s.startsWith("-") || s.startsWith("+")) { usage(System.err, s); System.exit(2); } else { matched = false; --i; } } _next_arg = i; if (_actions.isEmpty()) _actions.set(PRINT_STATISTICS); ArrayList<String> unknown = new ArrayList<String>(); _enabled_map = create_enabled_map(enable_list, enable_value, unknown); if (unknown.size() != 0) { usage(System.err, unknown); System.exit(2); } _gc_stats = create_gc_stats(_actions, _enabled_map, _cpu_count, _has_time_zero); _gc_parsers = create_gc_parsers(_gc_stats, verbose); } public GCParserDriver(String argv[]) { this(argv, 0); } public GCStats gc_stats() { return _gc_stats; } public BitSet actions() { return _actions; } public boolean should_collect() { return _actions.get(COLLECT_DATA); } public boolean should_compare() { return _actions.get(COMPARE_STATISTICS); } public boolean should_print() { return _actions.get(PRINT_STATISTICS); } public boolean should_save() { return _actions.get(SAVE_DATA); } public String prefix() { return _prefix; } public String suffix() { return _suffix; } /** * Compare the files listed in argv starting at argv[index]. The first * file (at argv[index]) is used as the baseline; the remaining files * are compared against it one at a time. * * <p> * If actions() specifies that the statistics should be printed or * saved, those actions are performed first, before the comparisons. * </p> * * <p> * Each file should include gc logging information produced by the * Java HotSpot(TM) VM with the option -XX:+PrintGCDetails. * </p> * * @param argv an array of strings, with file names at the end. * @param index the index of the first file name in argv. */ public void compare(String argv[], int index) throws IOException { final int driver_cnt = argv.length - index; GCParserDriver d[] = new GCParserDriver[driver_cnt]; for (int i = 0; i < driver_cnt; ++i) { String new_name = argv[index + i]; d[i] = new GCParserDriver(_actions, _enabled_map, new_name + ".", _suffix, _cpu_count); d[i].parse(new File(new_name)); if (should_print()) { d[i].print_statistics(System.out, new_name); } if (should_save()) d[i].save_data(); } final boolean terse = _actions.get(TERSE); for (int i = 1; i < driver_cnt; ++i) { compare_statistics(System.out, argv[index], d[0], argv[index + i], d[i], terse); } } /** * Parse the files listed in argv, starting at argv[index]. The data * from all the files is combined into a single set of statistics. * * <p> * Each file should include gc logging information produced by the * Java HotSpot(TM) VM with the option -XX:+PrintGCDetails. * </p> * * @param argv an array of strings, with file names at the end. * @param index the index of the first file name in argv. */ public void parse(String argv[], int index) throws IOException { if (argv.length == index) { parse(System.in, "-"); } else { // If no prefix was specified and there is just one file // name argument, use the file name as the prefix. if (_prefix == null && index + 1 == argv.length) { _prefix = argv[index] + "."; } int i = index; do { parse(new File(argv[i])); } while(++i < argv.length); } if (should_print()) print_statistics(System.out); if (should_save()) save_data(); } /** * Process the files listed in argv, starting at argv[index]. Depending * on the specified actions(), the files are either parsed and the data * combined into a single set of statistics, or each file is compared. * * <p> * Each file should include gc logging information produced by the * Java HotSpot(TM) VM with the option -XX:+PrintGCDetails. * </p> * * @param argv an array of strings, with file names at the end. * @param index the index of the first file name in argv. */ public void run(String argv[], int index) throws IOException { if (should_compare() && argv.length > index + 1) { compare(argv, index); return; } parse(argv, index); } public boolean parse(List<GCParser> parsers, String filename, int line, String s) { Iterator<GCParser> iterator = parsers.iterator(); boolean matched = false; do { matched = iterator.next().parse(filename, line, s); } while (!matched && iterator.hasNext()); return matched; } public void parse(BufferedReader r, String filename) throws IOException { int line = 0; long matches = 0; String s = r.readLine(); while (s != null) { ++line; if (parse(_gc_parsers, filename, line, s)) { ++matches; boolean should_sort = matches == 128 || matches == 512 || (matches & 0x3ff) == 0; if (should_sort) { sort_gc_parsers(_gc_parsers); } } s = r.readLine(); } _gc_stats.end_of_file(); } public void parse(InputStream is, String filename) throws IOException { InputStreamReader ir = new InputStreamReader(is); parse(new BufferedReader(ir), filename); } public void parse(File file) throws IOException { InputStream inputStreamForFile = streamForFile(file); BufferedReader reader = new BufferedReader(new InputStreamReader(inputStreamForFile)); parse(reader, file.getName()); reader.close(); } private InputStream streamForFile(File logFile) throws IOException, FileNotFoundException { FileInputStream uncompressedFileStream = new FileInputStream(logFile); if (logFile.getName().endsWith(".gz")) { return new GZIPInputStream(uncompressedFileStream); } return uncompressedFileStream; } public static void compare_statistics(PrintStream s, String ref_name, GCStats ref_stats, String new_name, GCStats new_stats, boolean terse) { s.println(ref_name + " vs. " + new_name); ref_stats.print_comparison(s, new_stats, terse); } public static void compare_statistics(PrintStream s, String ref_name, GCParserDriver ref_driver, String new_name, GCParserDriver new_driver, boolean terse) { compare_statistics(s, ref_name, ref_driver.gc_stats(), new_name, new_driver.gc_stats(), terse); } public void print_statistics(PrintStream s) { _gc_stats.print(s); } public void print_statistics(PrintStream s, String name) { s.println(name); print_statistics(s); } public void save_data(String prefix, String suffix) throws IOException { _gc_stats.save(prefix, suffix); } public void save_data() throws IOException { save_data(_prefix, _suffix); } public void list_metrics(PrintStream s) { if (_actions.get(TERSE)) { GCMetric.list(s); return; } describe_metrics(s); } public void describe_metrics(PrintStream s) { ResourceBundle b = ResourceBundle.getBundle("GCMetricHelp"); if (b.containsKey("intro")) s.println(b.getString("intro")); for (GCMetric metric: GCMetric.values()) { String name = metric.name(); s.println(name + '\t' + b.getString(name)); } if (b.containsKey("closing")) s.println(b.getString("closing")); } public void parse_output_file_pattern(String s) { final int pattern_length = 9; int pos = s.indexOf("%{metric}"); if (pos < 0) { _prefix = s; _suffix = ""; return; } _prefix = s.substring(0, pos); _suffix = s.substring(pos + pattern_length); } public static void help(PrintStream s) { ResourceBundle b; b = ResourceBundle.getBundle("Messages"); String prog_nm = b.getString("usage.program"); String fmt_str = b.getString("usage.synopsis"); s.println(MessageFormat.format(fmt_str, prog_nm)); s.println(); s.println(b.getString("usage.summary")); s.println(); s.println(b.getString("usage.option.detail")); } public static void usage(PrintStream s, String option) { ResourceBundle b = ResourceBundle.getBundle("Messages"); String prog_nm = b.getString("usage.program"); String fmt_str = b.getString("usage.option.unknown"); s.println(MessageFormat.format(fmt_str, prog_nm, option)); } public static void usage(PrintStream s, Collection<String> metrics) { ResourceBundle b = ResourceBundle.getBundle("Messages"); String prog_nm = b.getString("usage.program"); String fmt_str = b.getString("usage.metric.unknown"); Iterator<String> iter = metrics.iterator(); while (iter.hasNext()) { String msg = MessageFormat.format(fmt_str, prog_nm, iter.next()); s.println(msg); } } protected int next_arg() { return _next_arg; } protected static BitSet create_actions(BitSet actions) { BitSet b; b = actions == null ? new BitSet() : (BitSet) actions.clone(); if (b.isEmpty()) { b.set(PRINT_STATISTICS); } return b; } public static GCMetric[] parse_metric_names(String names[], Collection<String> unrecognized) { if (names == null || names.length == 0) return null; int errors = 0; ArrayList<GCMetric> metrics; metrics = new ArrayList<GCMetric>(names.length); for (int i = 0; i < names.length; ++i) { GCMetric metric = GCMetric.metric(names[i]); if (metric != null) { metrics.add(metric); } else if (unrecognized != null) { // Unrecognized name. unrecognized.add(names[i]); } } if (metrics.size() == 0) return null; return metrics.toArray(new GCMetric[metrics.size()]); } public static GCMetric[] parse_metric_names(String names[]) { return parse_metric_names(names, null); } public static GCMetric[] parse_metric_names(String list, Collection<String> unrecognized) { if (list == null) return null; return parse_metric_names(list.split("[ \t,:]+"), unrecognized); } public static GCMetric[] parse_metric_names(String list) { return parse_metric_names(list, null); } /** * Creates and returns a map that indicates whether each GCMetric is * enabled or disabled. * * <p> * The enabled status of the remaining metrics (those <b>not</b> listed * in metrics[]) is set to !value. * </p> * * @param metrics[] an array of metrics, or null. * * @param value the enabled status stored for each metric in metrics[]. * * @returns a map giving the enabled status of each GCMetric, or null. */ public static EnumMap<GCMetric, Boolean> create_enabled_map(GCMetric metrics[], boolean value) { if (metrics == null) return null; EnumMap<GCMetric, Boolean> map; map = new EnumMap<GCMetric, Boolean>(GCMetric.class); for (GCMetric metric: GCMetric.values()) { map.put(metric, !value); } for (int i = 0; i < metrics.length; ++i) { map.put(metrics[i], value); } return map; } public static EnumMap<GCMetric, Boolean> create_enabled_map(String metrics, boolean value, Collection<String> unrecognized) { if (metrics == null) return null; GCMetric m[] = parse_metric_names(metrics, unrecognized); return create_enabled_map(m, value); } protected GCStats create_gc_stats(BitSet actions, EnumMap<GCMetric, Boolean> enabled_map, int cpu_count, boolean input_has_time_zero) { if (should_collect()) { return new GCDataStore(enabled_map, cpu_count, input_has_time_zero); } return new GCStats(enabled_map, cpu_count, input_has_time_zero); } /** * Create the set of GCParsers. Subclasses wishing to add a new parser * should override this method. */ protected ArrayList<GCParser> create_gc_parsers(GCStats gc_stats, boolean verbose) { ArrayList<GCParser> parsers = new ArrayList<GCParser>(7); parsers.add(new ParGCYoungGCParser(gc_stats, verbose)); parsers.add(new FWYoungGCParser(gc_stats, verbose)); parsers.add(new ParGCFullGCParser(gc_stats, verbose)); parsers.add(new CMSGCParser(gc_stats, verbose)); parsers.add(new ParCompactPhaseGCParser(gc_stats, verbose)); parsers.add(new FWOldGCParser(gc_stats, verbose)); parsers.add(new FWFullGCParser(gc_stats, verbose)); parsers.add(new VerboseGCParser(gc_stats, verbose)); return parsers; } /** * Sort the GCParsers in descending order by match_count. */ protected ArrayList<GCParser> sort_gc_parsers(ArrayList<GCParser> parsers) { final int n = parsers.size(); // Insertion sort. for (int i = 1; i < n; ++i) { GCParser parser_i = parsers.get(i); long value = parser_i.match_count(); int j = i - 1; while (j >= 0 && parsers.get(j).match_count() < value) { parsers.set(j + 1, parsers.get(j)); --j; } parsers.set(j + 1, parser_i); // System.out.println("i = " + i + ":"); // for (int x = 0; x < n; ++x) { // GCParser px = parsers.get(x); // System.out.println(px + "=" + px.match_count()); // } } // Bubble sort. // for (int i = 0; i < n; ++i) // { // GCParser pi = parsers.get(i); // for (int j = i + 1; j < n; ++j) // { // GCParser pj = parsers.get(j); // if (pj.match_count() > pi.match_count()) // { // parsers.set(i, pj); // parsers.set(j, pi); // pi = pj; // } // } // System.out.println("i = " + i + ":"); // for (int x = 0; x < n; ++x) { // GCParser px = parsers.get(x); // System.out.println(px + "=" + px.match_count()); // } // } return parsers; } private final EnumMap<GCMetric, Boolean> _enabled_map; private final GCStats _gc_stats; private final int _next_arg; private ArrayList<GCParser> _gc_parsers; private BitSet _actions; private String _prefix; private String _suffix; private int _cpu_count; private boolean _has_time_zero; }