Record time spent in individual Error Prone checks

PiperOrigin-RevId: 245821511
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins/errorprone/ErrorPronePlugin.java b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins/errorprone/ErrorPronePlugin.java
index 18c3fac..df46c38 100644
--- a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins/errorprone/ErrorPronePlugin.java
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/plugins/errorprone/ErrorPronePlugin.java
@@ -14,11 +14,14 @@
 
 package com.google.devtools.build.buildjar.javac.plugins.errorprone;
 
+import static java.util.Comparator.comparing;
+
+import com.google.common.base.Stopwatch;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.buildjar.InvalidCommandLineException;
 import com.google.devtools.build.buildjar.javac.plugins.BlazeJavaCompilerPlugin;
 import com.google.devtools.build.buildjar.javac.statistics.BlazeJavacStatistics;
-import com.google.devtools.build.buildjar.javac.statistics.BlazeJavacStatistics.StopwatchSpan;
 import com.google.errorprone.BaseErrorProneJavaCompiler;
 import com.google.errorprone.ErrorProneAnalyzer;
 import com.google.errorprone.ErrorProneError;
@@ -33,8 +36,10 @@
 import com.sun.tools.javac.main.JavaCompiler;
 import com.sun.tools.javac.util.Context;
 import com.sun.tools.javac.util.Log;
+import java.time.Duration;
 import java.util.Arrays;
 import java.util.List;
+import java.util.Map;
 
 /**
  * A plugin for BlazeJavaCompiler that performs Error Prone analysis. Error Prone is a static
@@ -62,6 +67,51 @@
 
   private ErrorProneAnalyzer errorProneAnalyzer;
   private ErrorProneOptions epOptions;
+  private ErrorProneTimings timings;
+  private final Stopwatch elapsed = Stopwatch.createUnstarted();
+
+  // TODO(cushon): delete this shim after the next Error Prone update
+  static class ErrorProneTimings {
+    static Class<?> clazz;
+
+    static {
+      try {
+        clazz = Class.forName("com.google.errorprone.ErrorProneTimings");
+      } catch (ClassNotFoundException e) {
+        // ignored
+      }
+    }
+
+    private final Object instance;
+
+    public ErrorProneTimings(Object instance) {
+      this.instance = instance;
+    }
+
+    public static ErrorProneTimings instance(Context context) {
+      Object instance = null;
+      if (clazz != null) {
+        try {
+          instance = clazz.getMethod("instance", Context.class).invoke(null, context);
+        } catch (ReflectiveOperationException e) {
+          throw new LinkageError(e.getMessage(), e);
+        }
+      }
+      return new ErrorProneTimings(instance);
+    }
+
+    @SuppressWarnings("unchecked") // reflection
+    public Map<String, Duration> timings() {
+      if (clazz == null) {
+        return ImmutableMap.of();
+      }
+      try {
+        return (Map<String, Duration>) clazz.getMethod("timings").invoke(instance);
+      } catch (ReflectiveOperationException e) {
+        throw new LinkageError(e.getMessage(), e);
+      }
+    }
+  }
 
   /** Registers our message bundle. */
   public static void setupMessageBundle(Context context) {
@@ -108,22 +158,31 @@
     }
     errorProneAnalyzer =
         ErrorProneAnalyzer.createByScanningForPlugins(scannerSupplier, epOptions, context);
+    timings = ErrorProneTimings.instance(context);
   }
 
   /** Run Error Prone analysis after performing dataflow checks. */
   @Override
   public void postFlow(Env<AttrContext> env) {
-    try (StopwatchSpan ignored =
-        statisticsBuilder.newTimingSpan(
-            duration ->
-                statisticsBuilder.addErrorProneTiming(
-                    env.toplevel.sourcefile.getName(), duration))) {
+    elapsed.start();
+    try {
       errorProneAnalyzer.finished(new TaskEvent(Kind.ANALYZE, env.toplevel, env.enclClass.sym));
     } catch (ErrorProneError e) {
       e.logFatalError(log);
       // let the exception propagate to javac's main, where it will cause the compilation to
       // terminate with Result.ABNORMAL
       throw e;
+    } finally {
+      elapsed.stop();
     }
   }
+
+  @Override
+  public void finish() {
+    statisticsBuilder.totalErrorProneTime(elapsed.elapsed());
+    timings.timings().entrySet().stream()
+        .sorted(comparing((Map.Entry<String, Duration> e) -> e.getValue()).reversed())
+        .limit(10) // best-effort to stay under the action metric size limit
+        .forEachOrdered((e) -> statisticsBuilder.addBugpatternTiming(e.getKey(), e.getValue()));
+  }
 }
diff --git a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/statistics/BlazeJavacStatistics.java b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/statistics/BlazeJavacStatistics.java
index 4ba80b3..5f09b30 100644
--- a/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/statistics/BlazeJavacStatistics.java
+++ b/src/java_tools/buildjar/java/com/google/devtools/build/buildjar/javac/statistics/BlazeJavacStatistics.java
@@ -14,17 +14,14 @@
 package com.google.devtools.build.buildjar.javac.statistics;
 
 import com.google.auto.value.AutoValue;
-import com.google.common.base.Stopwatch;
-import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import com.google.errorprone.annotations.MustBeClosed;
 import com.sun.tools.javac.util.Context;
 import java.time.Duration;
 import java.util.Collections;
+import java.util.Optional;
 import java.util.Set;
 import java.util.WeakHashMap;
-import java.util.function.Consumer;
 
 /**
  * A class representing statistics for an invocation of {@link
@@ -68,7 +65,9 @@
 
   public abstract ImmutableMap<AuxiliaryDataSource, byte[]> auxiliaryData();
 
-  public abstract ImmutableListMultimap<String, Duration> errorProneTicks();
+  public abstract Optional<Duration> totalErrorProneTime();
+
+  public abstract ImmutableMap<String, Duration> bugpatternTiming();
 
   public abstract ImmutableSet<String> processors();
 
@@ -103,7 +102,9 @@
   @AutoValue.Builder
   public abstract static class Builder {
 
-    abstract ImmutableListMultimap.Builder<String, Duration> errorProneTicksBuilder();
+    public abstract Builder totalErrorProneTime(Duration totalErrorProneTime);
+
+    abstract ImmutableMap.Builder<String, Duration> bugpatternTimingBuilder();
 
     abstract ImmutableMap.Builder<AuxiliaryDataSource, byte[]> auxiliaryDataBuilder();
 
@@ -117,8 +118,8 @@
 
     public abstract Builder transitiveClasspathFallback(boolean fallback);
 
-    public Builder addErrorProneTiming(String key, Duration value) {
-      errorProneTicksBuilder().put(key, value);
+    public Builder addBugpatternTiming(String key, Duration value) {
+      bugpatternTimingBuilder().put(key, value);
       return this;
     }
 
@@ -138,28 +139,9 @@
       return this;
     }
 
-    @MustBeClosed
-    public final StopwatchSpan newTimingSpan(Consumer<Duration> consumer) {
-      Stopwatch stopwatch = Stopwatch.createStarted();
-      return () -> {
-        stopwatch.stop();
-        consumer.accept(stopwatch.elapsed());
-      };
-    }
-
     public Builder addProcessor(String processor) {
       processorsBuilder().add(processor);
       return this;
     }
   }
-
-  /**
-   * A simple AutoClosable interface where the {@link #close} method doesn't throw an exception.
-   *
-   * <p>Returned from {@link Builder#newTimingSpan(Consumer)}
-   */
-  public interface StopwatchSpan extends AutoCloseable {
-    @Override
-    void close();
-  }
 }