/* Copyright (c) 2008-2009 HomeAway, Inc. * All rights reserved. http://www.perf4j.org * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.perf4j; import org.perf4j.helpers.*; import org.perf4j.chart.StatisticsChartGenerator; import org.perf4j.chart.GoogleChartGenerator; import java.io.*; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.List; /** * LogParser provides the main method for reading a log of StopWatch output and generating statistics and graphs * from that output. Run "java -jar pathToPerf4jJar --help" for instructions. * * @author Alex Devine */ public class LogParser { /** * The input log that is being parsed. */ private Reader inputLog; /** * The stream where the GroupedTimingStatistics data will be printed - if null, no statistics will be printed */ private PrintStream statisticsOutput; /** * The stream where graphing output will be printed - if null, no graphing output will be printed. May be the * same stream as statisticsOutput. */ private PrintStream graphingOutput; /** * The chart generator used to send the mean time graph to graphingOutput. */ private StatisticsChartGenerator meanTimeChartGenerator; /** * The chart generator used to send the TPS graph to graphingOutput. */ private StatisticsChartGenerator tpsChartGenerator; /** * The length of time, in milliseconds, of the timeslice of each GroupedTimingStatistics. */ private long timeSlice; /** * Whether or not "rollup statistics" should be created for each GroupedTimingStatistics created. */ private boolean createRollupStatistics; /** * The formatter to use to print statistics. */ private GroupedTimingStatisticsFormatter statisticsFormatter; // --- Constructors --- /** * Default constructor reads input from standard in, writes statistics output to standard out, does not write * graph output, has a time slice window of 30 seconds, and does not create rollup statistics. */ public LogParser() { this(new InputStreamReader(System.in), System.out, null /* no graph output */, 30000L, false /* don't create rollup statistics */, new GroupedTimingStatisticsTextFormatter()); } /** * Creates a new LogParser to parse log data from the input. * * @param inputLog The log being parsed, which should contain {@link org.perf4j.StopWatch} log messages. * @param statisticsOutput The stream where calculated statistics information should be written - if null, * statistics data is not written. * @param graphingOutput The stream where graphing data should be written - if null, graphs are not written. * @param timeSlice The length of time, in milliseconds, of the timeslice of each statistics data created. * @param createRollupStatistics Whether or not "rollup statistics" should be created for each timeslice of data. * @param statisticsFormatter The formatter to use to print GroupedTimingStatistics */ public LogParser(Reader inputLog, PrintStream statisticsOutput, PrintStream graphingOutput, long timeSlice, boolean createRollupStatistics, GroupedTimingStatisticsFormatter statisticsFormatter) { this.inputLog = inputLog; this.statisticsOutput = statisticsOutput; this.graphingOutput = graphingOutput; this.timeSlice = timeSlice; this.createRollupStatistics = createRollupStatistics; if (graphingOutput != null) { this.meanTimeChartGenerator = newMeanTimeChartGenerator(); this.tpsChartGenerator = newTpsChartGenerator(); } this.statisticsFormatter = statisticsFormatter; } // --- Instance Methods --- /** * Reads all the data from the inputLog, parses it, and writes the statistics data and graphing data as desired * to the output streams. */ public void parseLog() { Iterator<StopWatch> stopWatchIter = new StopWatchLogIterator(inputLog); int i = 0; for (GroupingStatisticsIterator statsIter = new GroupingStatisticsIterator(stopWatchIter, timeSlice, createRollupStatistics); statsIter.hasNext();) { GroupedTimingStatistics statistics = statsIter.next(); if (statisticsOutput != null) { statisticsOutput.print(statisticsFormatter.format(statistics)); } if (graphingOutput != null) { meanTimeChartGenerator.appendData(statistics); tpsChartGenerator.appendData(statistics); if ((++i % StatisticsChartGenerator.DEFAULT_MAX_DATA_POINTS == 0) || (!statsIter.hasNext())) { printGraphOutput(); } } } } protected StatisticsChartGenerator newMeanTimeChartGenerator() { return new GoogleChartGenerator(); } protected StatisticsChartGenerator newTpsChartGenerator() { return new GoogleChartGenerator(StatsValueRetriever.TPS_VALUE_RETRIEVER); } protected void printGraphOutput() { graphingOutput.println("<br/><br/><img src=\"" + meanTimeChartGenerator.getChartUrl() + "\"/>"); graphingOutput.println("<br/><br/><img src=\"" + tpsChartGenerator.getChartUrl() + "\"/>"); } // --- Main and Static Methods --- public static void main(String[] args) { System.exit(runMain(args)); } public static int runMain(String[] args) { try { List<String> argsList = new ArrayList<String>(Arrays.asList(args)); if (printUsage(argsList)) { return 0; } PrintStream statisticsOutput = openStatisticsOutput(argsList); PrintStream graphingOutput = openGraphingOutput(argsList); long timeSlice = getTimeSlice(argsList); boolean rollupStatistics = getRollupStatistics(argsList); GroupedTimingStatisticsFormatter formatter = getStatisticsFormatter(argsList); Reader input = openInput(argsList); if (!argsList.isEmpty()) { printUnknownArgs(argsList); return 1; } new LogParser(input, statisticsOutput, graphingOutput, timeSlice, rollupStatistics, formatter).parseLog(); closeGraphingOutput(graphingOutput); } catch ( Exception e ) { e.printStackTrace(); return 1; } return 0; } protected static boolean printUsage(List<String> argsList) { if (getIndexOfArg(argsList, false, "-h", "--help", "-?", "--usage") >= 0) { System.out.println("Usage: LogParser [-o|--out|--output outputFile] " + "[-g|--graph graphingOutputFile] " + "[-t|--timeslice timeslice] " + "[-r] " + "[-f|--format text|csv] " + "[logInputFile]"); System.out.println("Arguments:"); System.out.println(" logInputFile - The log file to be parsed. If not specified, log data is read from stdin."); System.out.println(" -o|--out|--output outputFile - The file where generated statistics should be written." + " If not specified, statistics are written to stdout."); System.out.println(" -g|--graph graphingOutputFile - The file where generated perf graphs should be written." + " If not specified, no graphs are written."); System.out.println(" -t|--timeslice timeslice - The length of time (in ms) of each timeslice for which" + " statistics should be generated. Defaults to 30000 ms."); System.out.println(" -r - Whether or not statistics rollups should be generated." + " If not specified, rollups are not generated."); System.out.println(" -f|--format text|csv - The format for the statistics output, either plain text or CSV." + " Defaults to text."); System.out.println(" If format is csv, then the columns output are tag, start, stop, mean, min, max, stddev, and count."); System.out.println(); System.out.println("Note that out, stdout, err and stderr can be used as aliases to the standard output" + " streams when specifying output files."); return true; } return false; } protected static PrintStream openStatisticsOutput(List<String> argsList) throws IOException { int indexOfOut = getIndexOfArg(argsList, true, "-o", "--output", "--out"); if (indexOfOut >= 0) { String fileName = argsList.remove(indexOfOut + 1); argsList.remove(indexOfOut); return openStream(fileName); } else { return System.out; } } protected static PrintStream openGraphingOutput(List<String> argsList) throws IOException { int indexOfOut = getIndexOfArg(argsList, true, "-g", "--graph"); if (indexOfOut >= 0) { String fileName = argsList.remove(indexOfOut + 1); argsList.remove(indexOfOut); PrintStream retVal = openStream(fileName); retVal.println("<html>"); retVal.println("<head><title>Perf4J Performance Graphs</title></head>"); retVal.println("<body>"); return retVal; } else { return null; } } protected static void closeGraphingOutput(PrintStream graphingOutput) throws IOException { if (graphingOutput != null) { graphingOutput.println("</body></html>"); if (graphingOutput != System.out && graphingOutput != System.err) { graphingOutput.close(); } } } protected static long getTimeSlice(List<String> argsList) { int indexOfOut = getIndexOfArg(argsList, true, "-t", "--timeslice"); if (indexOfOut >= 0) { String timeslice = argsList.remove(indexOfOut + 1); argsList.remove(indexOfOut); return Long.parseLong(timeslice); } else { return 30000L; } } protected static boolean getRollupStatistics(List<String> argsList) { int indexOfOut = getIndexOfArg(argsList, false, "-r", "--rollup"); if (indexOfOut >= 0) { argsList.remove(indexOfOut); return true; } else { return false; } } protected static GroupedTimingStatisticsFormatter getStatisticsFormatter(List<String> argsList) { int indexOfFormat = getIndexOfArg(argsList, true, "-f", "--format"); if (indexOfFormat >= 0) { String formatString = argsList.remove(indexOfFormat + 1); argsList.remove(indexOfFormat); if ("text".equalsIgnoreCase(formatString)) { return new GroupedTimingStatisticsTextFormatter(); } else if ("csv".equalsIgnoreCase(formatString)) { return new GroupedTimingStatisticsCsvFormatter(); } else { throw new IllegalArgumentException("Unknown format type: " + formatString); } } else { return new GroupedTimingStatisticsTextFormatter(); } } protected static Reader openInput(List<String> argsList) throws IOException { if (argsList.isEmpty()) { return new InputStreamReader(System.in); } else { String fileName = argsList.remove(0); return new BufferedReader(new FileReader(fileName)); } } protected static void printUnknownArgs(List<String> argsList) { System.out.println("Unknown arguments: "); for (String arg : argsList) { System.out.print(arg + " "); } System.out.println(); } protected static int getIndexOfArg(List<String> args, boolean needsParam, String... argNames) { int retVal = -1; boolean foundArg = false; for (String argName : argNames) { int argIndex = args.indexOf(argName); if (argIndex >= 0) { if (foundArg) { throw new IllegalArgumentException("You must specify only one of " + Arrays.toString(argNames)); } retVal = argIndex; foundArg = true; } } if ((retVal >= 0) && needsParam && (retVal == args.size() - 1)) { throw new IllegalArgumentException("You must specify a parameter for the " + args.get(retVal) + " arg"); } return retVal; } protected static PrintStream openStream(String fileName) throws IOException { if ("stdout".equals(fileName) || "out".equals(fileName)) { return System.out; } else if ("stderr".equals(fileName) || "err".equals(fileName)) { return System.err; } else { return new PrintStream(new FileOutputStream(fileName), true); } } }