Performance counters for tracking repetetive operations.

Rietveld: http://gwt-code-reviews.appspot.com/278801

Review by: spoon@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7864 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/CompilePerms.java b/dev/core/src/com/google/gwt/dev/CompilePerms.java
index fb8c9e9..bfe2df3 100644
--- a/dev/core/src/com/google/gwt/dev/CompilePerms.java
+++ b/dev/core/src/com/google/gwt/dev/CompilePerms.java
@@ -25,6 +25,7 @@
 import com.google.gwt.dev.jjs.PermutationResult;
 import com.google.gwt.dev.jjs.UnifiedAst;
 import com.google.gwt.dev.util.FileBackedObject;
+import com.google.gwt.dev.util.PerfCounter;
 import com.google.gwt.dev.util.Util;
 import com.google.gwt.dev.util.arg.ArgHandlerLocalWorkers;
 import com.google.gwt.dev.util.arg.OptionLocalWorkers;
@@ -206,6 +207,7 @@
   }
 
   public static void main(String[] args) {
+    int exitCode = -1;
     /*
      * NOTE: main always exits with a call to System.exit to terminate any
      * non-daemon threads that were started in Generators. Typically, this is to
@@ -221,11 +223,11 @@
       };
       if (CompileTaskRunner.runWithAppropriateLogger(options, task)) {
         // Exit w/ success code.
-        System.exit(0);
+        exitCode = 0;
       }
     }
-    // Exit w/ non-success code.
-    System.exit(1);
+    PerfCounter.print();
+    System.exit(exitCode);
   }
 
   public static List<FileBackedObject<PermutationResult>> makeResultFiles(
diff --git a/dev/core/src/com/google/gwt/dev/CompilePermsServer.java b/dev/core/src/com/google/gwt/dev/CompilePermsServer.java
index e119a8e..e869898 100644
--- a/dev/core/src/com/google/gwt/dev/CompilePermsServer.java
+++ b/dev/core/src/com/google/gwt/dev/CompilePermsServer.java
@@ -21,6 +21,7 @@
 import com.google.gwt.dev.jjs.PermutationResult;
 import com.google.gwt.dev.jjs.UnifiedAst;
 import com.google.gwt.dev.util.FileBackedObject;
+import com.google.gwt.dev.util.PerfCounter;
 import com.google.gwt.dev.util.arg.ArgHandlerLogLevel;
 import com.google.gwt.dev.util.arg.OptionLogLevel;
 import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
@@ -228,15 +229,18 @@
   }
 
   public static void main(String[] args) {
+    int exitCode = -1;
     final CompileServerOptions options = new CompileServerOptionsImpl();
     if (new ArgProcessor(options).processArgs(args)) {
       PrintWriterTreeLogger logger = new PrintWriterTreeLogger();
       logger.setMaxDetail(options.getLogLevel());
       if (run(options, logger)) {
-        System.exit(0);
+        exitCode = 0;
       }
     }
-    System.exit(-1);
+
+    PerfCounter.print();
+    System.exit(exitCode);
   }
 
   public static boolean run(CompileServerOptions options, TreeLogger logger) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
index 91b1579..1025e52 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -117,6 +117,7 @@
 import com.google.gwt.dev.util.DefaultTextOutput;
 import com.google.gwt.dev.util.Empty;
 import com.google.gwt.dev.util.Memory;
+import com.google.gwt.dev.util.PerfCounter;
 import com.google.gwt.dev.util.PerfLogger;
 import com.google.gwt.dev.util.TextOutput;
 import com.google.gwt.dev.util.Util;
@@ -623,6 +624,7 @@
      */
     jprogram.beginOptimizations();
 
+    PerfCounter.start("JavaToJavaScriptCompiler.optimize");
     PerfLogger.start("optimize");
     do {
       if (Thread.interrupted()) {
@@ -637,12 +639,14 @@
       DataflowOptimizer.exec(jprogram);
     }
 
+    PerfCounter.end("JavaToJavaScriptCompiler.optimize");
     PerfLogger.end();
   }
 
   protected static boolean optimizeLoop(JProgram jprogram,
       boolean isAggressivelyOptimize) {
     PerfLogger.start("optimize loop");
+    PerfCounter.start("JavaToJavaScriptCompiler.optimizeLoop");
 
     // Recompute clinits each time, they can become empty.
     jprogram.typeOracle.recomputeAfterOptimizations();
@@ -681,6 +685,7 @@
     // prove that any types that have been culled from the main tree are
     // unreferenced due to type tightening?
 
+    PerfCounter.end("JavaToJavaScriptCompiler.optimizeLoop");
     PerfLogger.end();
     return didChange;
   }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
index b378371..a81bfa8 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/DeadCodeElimination.java
@@ -62,6 +62,7 @@
 import com.google.gwt.dev.jjs.ast.JVisitor;
 import com.google.gwt.dev.jjs.ast.JWhileStatement;
 import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
+import com.google.gwt.dev.util.PerfCounter;
 
 import java.lang.reflect.Method;
 import java.util.ArrayList;
@@ -1789,11 +1790,23 @@
   }
 
   public static boolean exec(JProgram program) {
-    return new DeadCodeElimination(program).execImpl(program);
+    PerfCounter.start("DeadCodeElimination.exec");
+    boolean didChange = new DeadCodeElimination(program).execImpl(program);
+    PerfCounter.end("DeadCodeElimination.exec");
+    if (didChange) {
+      PerfCounter.inc("DeadCodeElimination.exec.didChange");
+    }
+    return didChange;
   }
 
   public static boolean exec(JProgram program, JNode node) {
-    return new DeadCodeElimination(program).execImpl(node);
+    PerfCounter.start("DeadCodeElimination.execNode");
+    boolean didChange = new DeadCodeElimination(program).execImpl(node);
+    PerfCounter.end("DeadCodeElimination.execNode");
+    if (didChange) {
+      PerfCounter.inc("DeadCodeElimination.execNode.didChange");
+    }
+    return didChange;
   }
 
   private final JProgram program;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/Finalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/Finalizer.java
index 68b57e8..60b4250 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/Finalizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/Finalizer.java
@@ -35,6 +35,7 @@
 import com.google.gwt.dev.jjs.ast.JVariableRef;
 import com.google.gwt.dev.jjs.ast.JVisitor;
 import com.google.gwt.dev.jjs.ast.js.JsniFieldRef;
+import com.google.gwt.dev.util.PerfCounter;
 
 import java.util.HashSet;
 import java.util.Set;
@@ -188,7 +189,13 @@
   }
 
   public static boolean exec(JProgram program) {
-    return new Finalizer().execImpl(program);
+    PerfCounter.start("Finalizer.exec");
+    boolean didChange = new Finalizer().execImpl(program);
+    PerfCounter.end("Finalizer.exec");
+    if (didChange) {
+      PerfCounter.inc("Finalizer.exec.didChange");
+    }
+    return didChange;
   }
 
   private final Set<JMethod> isOverriden = new HashSet<JMethod>();
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java b/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java
index e9d4e99..652b2a7 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/MakeCallsStatic.java
@@ -43,6 +43,7 @@
 import com.google.gwt.dev.js.ast.JsName;
 import com.google.gwt.dev.js.ast.JsParameter;
 import com.google.gwt.dev.js.ast.JsThisRef;
+import com.google.gwt.dev.util.PerfCounter;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -330,7 +331,13 @@
   }
 
   public static boolean exec(JProgram program) {
-    return new MakeCallsStatic(program).execImpl();
+    PerfCounter.start("MakeCallsStatic.exec");
+    boolean didChange = new MakeCallsStatic(program).execImpl();
+    PerfCounter.end("MakeCallsStatic.exec");
+    if (didChange) {
+      PerfCounter.inc("MakeCallsStatic.exec.didChange");
+    }
+    return didChange;
   }
 
   static JExpression makeStaticCall(JMethodCall x, JMethod newMethod) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodCallTightener.java b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodCallTightener.java
index ae43d85..a7af0c0 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodCallTightener.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodCallTightener.java
@@ -27,6 +27,7 @@
 import com.google.gwt.dev.jjs.ast.JNullType;
 import com.google.gwt.dev.jjs.ast.JProgram;
 import com.google.gwt.dev.jjs.ast.JReferenceType;
+import com.google.gwt.dev.util.PerfCounter;
 
 /**
  * Update polymorphic method calls to tighter bindings based on the type of the
@@ -138,7 +139,13 @@
   }
 
   public static boolean exec(JProgram program) {
-    return new MethodCallTightener(program).execImpl();
+    PerfCounter.start("MethodCallTightener.exec");
+    boolean didChange = new MethodCallTightener(program).execImpl();
+    PerfCounter.end("MethodCallTightener.exec");
+    if (didChange) {
+      PerfCounter.inc("MethodCallTightener.exec.didChange");
+    }
+    return didChange;
   }
 
   private final JProgram program;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java
index 53074a2..b2776c8 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/MethodInliner.java
@@ -36,6 +36,7 @@
 import com.google.gwt.dev.jjs.ast.JType;
 import com.google.gwt.dev.jjs.ast.JVisitor;
 import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
+import com.google.gwt.dev.util.PerfCounter;
 
 import java.util.HashSet;
 import java.util.List;
@@ -492,7 +493,13 @@
   }
 
   public static boolean exec(JProgram program) {
-    return new MethodInliner(program).execImpl();
+    PerfCounter.start("MethodInliner.exec");
+    boolean didChange = new MethodInliner(program).execImpl();
+    PerfCounter.end("MethodInliner.exec");
+    if (didChange) {
+      PerfCounter.inc("MethodInliner.exec.didChange");
+    }
+    return didChange;
   }
 
   /**
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java b/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java
index 4c6850e..8e1b4fb 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java
@@ -50,6 +50,7 @@
 import com.google.gwt.dev.jjs.ast.js.JsniMethodBody;
 import com.google.gwt.dev.jjs.ast.js.JsniMethodRef;
 import com.google.gwt.dev.js.ast.JsFunction;
+import com.google.gwt.dev.util.PerfCounter;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -525,7 +526,13 @@
   }
 
   public static boolean exec(JProgram program, boolean noSpecialTypes) {
-    return new Pruner(program, noSpecialTypes).execImpl();
+    PerfCounter.start("Pruner.exec");
+    boolean didChange = new Pruner(program, noSpecialTypes).execImpl();
+    PerfCounter.end("Pruner.exec");
+    if (didChange) {
+      PerfCounter.inc("Pruner.exec.didChange");
+    }
+    return didChange;
   }
 
   /**
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/SameParameterValueOptimizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/SameParameterValueOptimizer.java
index 4dd95c5..41c20d6 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/SameParameterValueOptimizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/SameParameterValueOptimizer.java
@@ -32,6 +32,7 @@
 import com.google.gwt.dev.jjs.ast.JVisitor;
 import com.google.gwt.dev.jjs.ast.js.JsniMethodBody;
 import com.google.gwt.dev.jjs.ast.js.JsniMethodRef;
+import com.google.gwt.dev.util.PerfCounter;
 
 import java.util.HashSet;
 import java.util.IdentityHashMap;
@@ -177,7 +178,13 @@
   }
 
   public static boolean exec(JProgram program) {
-    return new SameParameterValueOptimizer(program).execImpl(program);
+    PerfCounter.start("SameParameterValueOptimizer.exec");
+    boolean didChange = new SameParameterValueOptimizer(program).execImpl(program);
+    PerfCounter.end("SameParameterValueOptimizer.exec");
+    if (didChange) {
+      PerfCounter.inc("SameParameterValueOptimizer.exec.didChange");
+    }
+    return didChange;
   }
 
   /**
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
index 764d198..87de1a3 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeTightener.java
@@ -53,6 +53,7 @@
 import com.google.gwt.dev.jjs.ast.JVisitor;
 import com.google.gwt.dev.jjs.ast.js.JsniFieldRef;
 import com.google.gwt.dev.jjs.ast.js.JsniMethodRef;
+import com.google.gwt.dev.util.PerfCounter;
 
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -804,7 +805,13 @@
   }
 
   public static boolean exec(JProgram program) {
-    return new TypeTightener(program).execImpl();
+    PerfCounter.start("TypeTightener.exec");
+    boolean didChange = new TypeTightener(program).execImpl();
+    PerfCounter.end("TypeTightener.exec");
+    if (didChange) {
+      PerfCounter.inc("TypeTightener.exec.didChange");
+    }
+    return didChange;
   }
 
   private static <T, V> void add(T target, V value, Map<T, Set<V>> map) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/DataflowOptimizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/DataflowOptimizer.java
index 9a70c9b..959905e 100644
--- a/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/DataflowOptimizer.java
+++ b/dev/core/src/com/google/gwt/dev/jjs/impl/gflow/DataflowOptimizer.java
@@ -32,16 +32,19 @@
 import com.google.gwt.dev.jjs.impl.gflow.copy.CopyAnalysis;
 import com.google.gwt.dev.jjs.impl.gflow.liveness.LivenessAnalysis;
 import com.google.gwt.dev.jjs.impl.gflow.unreachable.UnreachableAnalysis;
-import com.google.gwt.dev.util.PerfLogger;
+import com.google.gwt.dev.util.PerfCounter;
 import com.google.gwt.dev.util.Preconditions;
 
 /**
  */
 public class DataflowOptimizer {
   public static boolean exec(JProgram jprogram, JNode node) {
-    PerfLogger.start("DataflowOptimizer");
+    PerfCounter.start("DataflowOptimizer.exec");
     boolean didChange = new DataflowOptimizer(jprogram).execImpl(node);
-    PerfLogger.end();
+    PerfCounter.end("DataflowOptimizer.exec");
+    if (didChange) {
+      PerfCounter.inc("DataflowOptimizer.exec.didChange");
+    }
     return didChange;
   }
 
diff --git a/dev/core/src/com/google/gwt/dev/util/PerfCounter.java b/dev/core/src/com/google/gwt/dev/util/PerfCounter.java
new file mode 100644
index 0000000..ccaf50e
--- /dev/null
+++ b/dev/core/src/com/google/gwt/dev/util/PerfCounter.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2009 Google Inc.
+ * 
+ * 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 com.google.gwt.dev.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A counter which records bunch of statistics for frequently occuring 
+ * operations. Calculates: count, total time, average time, maximum time 
+ * & slow operations. 
+ */
+public class PerfCounter {
+  private static class OperationStats {
+    private long count = 0;
+    private boolean isCounter = false;
+    private long maxTimeNanos = 0;
+    private long slowCount = 0;
+    private long totalSlowTimeNanos = 0;
+    private long totalTimeNanos = 0;
+    
+    @Override
+    public String toString() {
+      StringBuilder result = new StringBuilder();
+      
+      if (!isCounter) {
+        result.append(count);
+        result.append("/");
+        result.append(totalTimeNanos / 1000000000.0);
+        result.append("/");
+        result.append(totalTimeNanos / 1000000000.0 / count);
+        result.append("/");
+        result.append(maxTimeNanos / 1000000000.0);
+        result.append("/");
+        result.append(slowCount);
+        result.append("/");
+        result.append(totalSlowTimeNanos * 1.0 / totalTimeNanos);
+        result.append(" ");
+        result.append(
+            "(count/total_time/avg_time/max_time/slow_count/slow_ratio)");
+      } else {
+        result.append(count);
+        result.append(" (count)");
+      }
+      
+      return result.toString();
+    }
+  }
+
+  /**
+   * Flag for enabling performance logging.
+   */
+  private static boolean enabled = 
+    Boolean.parseBoolean(System.getProperty("gwt.perfcounters"));
+  
+  private static final ThreadLocal<Map<String, Long>> operationsStartTime = 
+    new ThreadLocal<Map<String,Long>>() {
+    @Override
+    protected Map<String, Long> initialValue() {
+      return new HashMap<String, Long>();
+    }
+  };
+  
+  private static final Map<String, OperationStats> operationStats = 
+    new HashMap<String, OperationStats>();
+  
+  /**
+   * Record the end of the operation. 
+   */
+  public static void end(String operation) {
+    if (!enabled) {
+      return;
+    }
+    end(operation, 1 * 1000000000 /* 1 sec */);
+  }
+  
+  /**
+   * Record the end of the operation. 
+   */
+  public static void end(String operation, long slowThresholdNano) {
+    if (!enabled) {
+      return;
+    }
+    long finishTime = System.nanoTime();
+    
+    Map<String, Long> startTimes = operationsStartTime.get();
+    
+    Long startTime;
+    
+    synchronized (startTimes) {
+      startTime = startTimes.remove(operation);
+    }
+    
+    Preconditions.checkNotNull(startTime);
+    
+    synchronized (operationStats) {
+      OperationStats stats = getStats(operation);
+      
+      stats.count++;
+      long elapsedTime = finishTime - startTime.longValue();
+      stats.totalTimeNanos += elapsedTime;
+      stats.maxTimeNanos = Math.max(stats.maxTimeNanos, elapsedTime);
+      if (elapsedTime > slowThresholdNano) {
+        stats.slowCount++;
+        stats.totalSlowTimeNanos += elapsedTime;
+      }
+    }
+  }
+
+  /**
+   * Increment counter. 
+   */
+  public static void inc(String operation) {
+    synchronized (operationStats) {
+      OperationStats stats = getStats(operation);
+      stats.count++;
+      stats.isCounter = true;
+    }
+  }
+
+  public static void print() {
+    if (!enabled) {
+      return;
+    }
+    System.out.println("------------- Perf Counters -------------");
+    synchronized (operationStats) {
+      List<String> keys = new ArrayList<String>(operationStats.keySet());
+      Collections.sort(keys);
+      for (String operation : keys) {
+        System.out.println(operation + ": " + operationStats.get(operation));
+      }
+    }
+    System.out.println("-----------------------------------------");
+  }
+  
+  /**
+   * Start operation.
+   */
+  public static void start(String operation) {
+    if (!enabled) {
+      return;
+    }
+    Map<String, Long> startTimes = operationsStartTime.get();
+    
+    synchronized (startTimes) {
+      Preconditions.checkState(!startTimes.containsKey(operation));
+      long startTime = System.nanoTime();
+      startTimes.put(operation, new Long(startTime));
+    }
+  }
+
+  private static OperationStats getStats(String operation) {
+    OperationStats stats = operationStats.get(operation);
+    if (stats == null) {
+      stats = new OperationStats();
+      operationStats.put(operation, stats);
+    }
+    return stats;
+  }
+}