package arkref.ext.fig.basic; import java.io.*; import java.lang.reflect.Method; import java.util.*; /** * The logging output has a tree structure, where each node is a * line of output, and the depth of a node is its indent level. * A run is the sequence of children of some node. * A subset of the lines in the run will get printed. * * WARNING: not thread safe. */ public class LogInfo { public static void track(String format, Object... args) { track(String.format(format, args), false); } public static void track(Object o) { track(o, false); } public synchronized static void track_methods(Object o, String[] methodNames) throws Exception { for (String methodName: methodNames) { track_method(o, methodName); } } /** * Invokes a method and wraps it's invocation in a track. The method must be public and * not take any arguments. * @param o Object to call method on * @param methodName name of method to invoke * @throws Exception */ public synchronized static void track_method(Object o, String methodName, Object...args) throws Exception { Class<? extends Object> c = o.getClass(); Method[] methods = c.getDeclaredMethods(); Method targetMethod = null; for (Method m: methods) { if (m.getName().equals(methodName)) { targetMethod = m; break; } } if (targetMethod == null) { String msg = String.format("Couldn't find method %d in class %s",methodName,c.getName()); throw new IllegalArgumentException(msg); } LogInfo.track(methodName); targetMethod.invoke(o, args); LogInfo.end_track(); } public synchronized static void track_method(Object o, String methodName) throws Exception { track_method(o, methodName, new Object[0]); } public synchronized static void track(Object o, boolean printAllLines) { track(o, printAllLines, false); } public synchronized static void track(Object o, boolean printAllChildLines, boolean printIfParentPrinted) { if(indWithin()) { if(printIfParentPrinted && parentPrinted()) thisRun().forcePrint(); if(thisRun().shouldPrint()) { print(o); buf.append(" {\n"); // Open the block. childRun().init(); childRun().printAllLines = printAllChildLines; } else { stoppedIndLevel = indLevel; maxIndLevel = -maxIndLevel; // Prevent children from outputting. } } indLevel++; } // Convenient way to end and return a value public static <T> T end_track(T x) { end_track(); return x; } public synchronized static void end_track() { indLevel--; if(stoppedIndLevel == indLevel) { stoppedIndLevel = -1; maxIndLevel = -maxIndLevel; // Restore indent level. } if(indWithin()) { if(thisRun().newLine()) { // Note that we pay for the line only at the end // Finish up child level. indLevel++; int n = thisRun().numOmitted(); if(n > 0) print("... " + n + " lines omitted ...\n"); indLevel--; childRun().finish(); if(buf.length() > 0) // Nothing was printed, because buf hasn't been emptied. buf.delete(0, buf.length()); // Just pretend we didn't open the block. else // Something indented was printed. print("}"); // Close the block. // Print time StopWatch ct = childRun().watch; if(ct.ms > 1000) { rawPrint(" [" + ct); if(indLevel > 0) { StopWatch tt = thisRun().watch; rawPrint(", cum. " + new StopWatch(tt.getCurrTimeLong())); } rawPrint("]"); } rawPrint("\n"); } } } // Normal printing public static void logs(String format, Object... args) { logs(String.format(format, args)); } public synchronized static void logs(Object o) { if(forcePrint || (indWithin() && thisRun().newLine())) printLines(o); } // Always print public synchronized static void logsForce(String format, Object...args) { printLines(String.format(format, args)); } public synchronized static void logsForce(Object o) { thisRun().newLine(); printLines(o); } // Print if parent printed public static void logss(String format, Object... args) { logss(String.format(format, args)); } public synchronized static void logss(Object o) { if(parentPrinted()) thisRun().forcePrint(); logs(o); } private static boolean parentPrinted() { // Parent must have been a track, so its run information has not been // updated yet. Therefore, shouldPrint() is valid. return indLevel == 0 || (indLevel <= maxIndLevel && parentIndWithin() && parentRun().shouldPrint()); } // Log different types of information @Deprecated public static void dbg(String format, Object... args) { dbg(String.format(format, args)); } public static void dbgs(String format, Object... args) { dbg(String.format(format, args)); } public static void dbg(Object o) { logss("DBG: " + o); } public static void rants(String format, Object... args) { rant(String.format(format, args)); } public static void rant(Object o) { logss("RANT: " + o); } @Deprecated public static void error(String format, Object... args) { error(String.format(format, args)); } public static void errors(String format, Object... args) { error(String.format(format, args)); } public static void error(Object o) { if(numErrors < maxPrintErrors) print("ERROR: " + o + "\n"); numErrors++; } @Deprecated public static void warning(String format, Object... args) { warning(String.format(format, args)); } public static void warnings(String format, Object... args) { warning(String.format(format, args)); } public static void warning(Object o) { print("WARNING: " + o + "\n"); numWarnings++; } public static void fails(String format, Object... args) { fail(String.format(format, args)); } public static void fail(Object o) { throw Exceptions.bad(o); } // Print random things public static void printProgStatus() { logs("PROG_STATUS: time = " + watch.stop() + ", memory = " + SysInfoUtils.getUsedMemoryStr()); } public static <T> void printList(String s, String lines) { printList(s, Arrays.asList(lines.split("\n"))); } public static <T> void printList(String s, List<T> items) { track(s, true); for(T x : items) logs(x); end_track(); } //////////////////////////////////////////////////////////// // Options @Option(gloss="Maximum indent level.") public static int maxIndLevel = 10; @Option(gloss="Maximum number of milliseconds between consecutive lines of output.") public static int msPerLine = 1000; @Option(gloss="File to write log.") public static String file = ""; @Option(gloss="Whether to output to the console.", name="stdout") public static boolean writeToStdout = true; @Option(gloss="Dummy placeholder for a comment") static public String note = ""; @Option(gloss="Force printing from logs*") static public boolean forcePrint; @Option(gloss="Maximum number of errors (via error()) to print") static public int maxPrintErrors = 10000; static { updateStdStreams(); } public static void updateStdStreams() { try { stdin = CharEncUtils.getReader(System.in); stdout = CharEncUtils.getWriter(System.out); stderr = CharEncUtils.getWriter(System.err); } catch(IOException e) { throw new RuntimeException(e); } } public static void init() { // Write to file, stdout? if(!file.equals("")) { fout = IOUtils.openOutHard(file); } if(writeToStdout) out = stdout; } private static LogRun parentRun() { return runs.get(indLevel-1); } private static LogRun thisRun() { return runs.get(indLevel); } private static LogRun childRun() { return runs.get(indLevel+1); } // If we were to print a new line, should we print? private static boolean indWithin() { return indLevel <= maxIndLevel; } private static boolean parentIndWithin() { return indLevel-1 <= maxIndLevel; } // Internal: don't use these functions directly private static void rawPrint(Object o) { if(out != null) { out.print(o); out.flush(); } if(fout != null) { fout.print(o); fout.flush(); } } // Print with indent; flush the buffer as necessary private static void print(Object o) { rawPrint(buf); buf.delete(0, buf.length()); for(int i = 0; i < indLevel; i++) rawPrint(" "); rawPrint(o); } // If there are new lines, put indents before them private static void printLines(Object o) { if(o == null) o = "null"; String s = StrUtils.toString(o); if(s.indexOf('\n') == -1) print(s+"\n"); else for(String t : StrUtils.split(s, "\n")) print(t+"\n"); } public static StopWatch getWatch() { return watch; } public static int getNumErrors() { return numErrors; } public static int getNumWarnings() { return numWarnings; } public static BufferedReader stdin; public static PrintWriter stdout, stderr; // Private state. static PrintWriter out, fout; static int indLevel; // Current indent level. static int stoppedIndLevel; // At what level did we stop printing static StringBuilder buf; // The buffer to be flushed out the next time _logs is called. static ArrayList<LogRun> runs; // Indent level -> state static StopWatch watch; // StopWatch that starts at the beginning of the program static int numErrors; // Number of errors made static int numWarnings; // Number of warnings // Default setup static { buf = new StringBuilder(); indLevel = 0; stoppedIndLevel = -1; runs = new ArrayList<LogRun>(128); for(int i = 0; i < 128; i++) runs.add(new LogRun()); watch = new StopWatch(); watch.start(); out = stdout; } } /** * A run is a sequence of lines of text, some of which are printed. * Stores the state associated with a run. */ class LogRun { public LogRun() { watch = new StopWatch(); init(); } void init() { numLines = 0; numLinesPrinted = 0; nextLineToPrint = 0; printAllLines = false; watch.reset(); watch.start(); } void finish() { // Make it clear that this run is not printed. // Otherwise, logss might think its // parent was printed when it really wasn't. nextLineToPrint = -1; watch.stop(); } void forcePrint() { forcePrint = true; } boolean shouldPrint() { return forcePrint || nextLineToPrint == numLines; } int numOmitted() { return numLines - numLinesPrinted; } /** * Decide whether to print the next line. If yes, * then you must print it. * @return Whether the next line should be printed. */ boolean newLine() { boolean p = shouldPrint(); numLines++; if(!p) return false; // Assume forcePrint == false // Ok, we're going to print this line. numLinesPrinted++; // Decide next line to print. int msPerLine = LogInfo.msPerLine; if(numLines <= 2 || // Print first few lines anyway. msPerLine == 0 || // Print everything. printAllLines || // Print every line in this run (by fiat). forcePrint) // Force-printed things shouldn't affect timing. nextLineToPrint++; else { long elapsed_ms = watch.getCurrTimeLong(); if(elapsed_ms == 0) { // No time has elapsed. // This usually applies in the beginning of a run when we have // no idea how long things are going to take nextLineToPrint *= 2; // Exponentially increase time between lines. } else // Try to maintain the number of lines per second. nextLineToPrint += (int)Math.max((double)numLines * msPerLine / elapsed_ms, 1); } forcePrint = false; return true; } int numLines; // Number of lines that we've gone through so far in this run. int numLinesPrinted; // Number of lines actually printed. int nextLineToPrint; // Next line to be printed (lines are 0-based). StopWatch watch; // Keeps track of time spent on this run. boolean printAllLines; // Whether or not to force the printing of each line. boolean forcePrint; // Whether to print out the next item (is reset afterwards). }