Remove cost of iterating over a list of TargetEdgeObservers in LabelVisitor.

In a build with many genquery rules, this can account for a significant amount of cpu and garbage.

LabelVisitor now takes in a single TargetEdgeObserver. Callers can create a composite.

PiperOrigin-RevId: 318814266
diff --git a/src/main/java/com/google/devtools/build/lib/query2/query/BlazeQueryEnvironment.java b/src/main/java/com/google/devtools/build/lib/query2/query/BlazeQueryEnvironment.java
index d8700e4..4a27352 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/query/BlazeQueryEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/query/BlazeQueryEnvironment.java
@@ -313,7 +313,6 @@
           keepGoing,
           loadingPhaseThreads,
           maxDepth,
-          errorObserver,
           new GraphBuildingObserver());
     }
 
@@ -379,16 +378,18 @@
           dependencyFilter.apply(((Rule) from), attribute),
           "Disallowed edge from LabelVisitor: %s --> %s", from, to);
       makeEdge(from, to);
+      errorObserver.edge(from, attribute, to);
     }
 
     @Override
     public void node(Target node) {
       graph.createNode(node);
+      errorObserver.node(node);
     }
 
     @Override
     public void missingEdge(Target target, Label to, NoSuchThingException e) {
-      // No - op.
+      errorObserver.missingEdge(target, to, e);
     }
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/query2/query/GraphlessBlazeQueryEnvironment.java b/src/main/java/com/google/devtools/build/lib/query2/query/GraphlessBlazeQueryEnvironment.java
index d3ca5b4..8b97828 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/query/GraphlessBlazeQueryEnvironment.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/query/GraphlessBlazeQueryEnvironment.java
@@ -208,18 +208,21 @@
               keepGoing,
               loadingPhaseThreads,
               maxDepth,
-              errorObserver,
               new TargetEdgeObserver() {
                 @Override
-                public void edge(Target from, Attribute attribute, Target to) {}
+                public void edge(Target from, Attribute attribute, Target to) {
+                  errorObserver.edge(from, attribute, to);
+                }
 
                 @Override
-                public void missingEdge(
-                    @Nullable Target target, Label to, NoSuchThingException e) {}
+                public void missingEdge(@Nullable Target target, Label to, NoSuchThingException e) {
+                  errorObserver.missingEdge(target, to, e);
+                }
 
                 @Override
                 public void node(Target node) {
                   result.add(node);
+                  errorObserver.node(node);
                 }
               });
     }
diff --git a/src/main/java/com/google/devtools/build/lib/query2/query/LabelVisitor.java b/src/main/java/com/google/devtools/build/lib/query2/query/LabelVisitor.java
index 879c14c..712df83 100644
--- a/src/main/java/com/google/devtools/build/lib/query2/query/LabelVisitor.java
+++ b/src/main/java/com/google/devtools/build/lib/query2/query/LabelVisitor.java
@@ -15,7 +15,6 @@
 package com.google.devtools.build.lib.query2.query;
 
 import com.google.common.base.Throwables;
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.util.concurrent.ThreadFactoryBuilder;
 import com.google.devtools.build.lib.cmdline.Label;
@@ -166,14 +165,14 @@
       boolean keepGoing,
       int parallelThreads,
       int maxDepth,
-      TargetEdgeObserver... observers)
+      TargetEdgeObserver observer)
       throws InterruptedException {
     VisitationAttributes nextVisitation = new VisitationAttributes(targetsToVisit, maxDepth);
     if (!lastVisitation.success || !nextVisitation.current(lastVisitation)) {
       lastVisitation = nextVisitation;
       lastVisitation.success =
           redoVisitation(
-              eventHandler, targetsToVisit, keepGoing, parallelThreads, maxDepth, observers);
+              eventHandler, targetsToVisit, keepGoing, parallelThreads, maxDepth, observer);
     }
   }
 
@@ -187,10 +186,10 @@
       boolean keepGoing,
       int parallelThreads,
       int maxDepth,
-      TargetEdgeObserver... observers)
+      TargetEdgeObserver observer)
       throws InterruptedException {
     lastVisitation = NONE;
-    redoVisitation(eventHandler, targetsToVisit, keepGoing, parallelThreads, maxDepth, observers);
+    redoVisitation(eventHandler, targetsToVisit, keepGoing, parallelThreads, maxDepth, observer);
   }
 
   // Does a bounded transitive visitation starting at the given top-level targets.
@@ -200,11 +199,11 @@
       boolean keepGoing,
       int parallelThreads,
       int maxDepth,
-      TargetEdgeObserver... observers)
+      TargetEdgeObserver observer)
       throws InterruptedException {
     visitedTargets.clear();
 
-    Visitor visitor = new Visitor(eventHandler, keepGoing, parallelThreads, maxDepth, observers);
+    Visitor visitor = new Visitor(eventHandler, keepGoing, parallelThreads, maxDepth, observer);
 
     Throwable uncaught = null;
     boolean result;
@@ -232,15 +231,17 @@
     private final QuiescingExecutor executor;
     private final ExtendedEventHandler eventHandler;
     private final int maxDepth;
-    private final Iterable<TargetEdgeObserver> observers;
-    private final TargetEdgeErrorObserver errorObserver;
+
+    // Observers are stored individually instead of in a list to reduce iteration cost.
+    private final TargetEdgeObserver observer;
+    private final TargetEdgeErrorObserver errorObserver = new TargetEdgeErrorObserver();
 
     Visitor(
         ExtendedEventHandler eventHandler,
         boolean keepGoing,
         int parallelThreads,
         int maxDepth,
-        TargetEdgeObserver... observers) {
+        TargetEdgeObserver observer) {
       if (parallelThreads > 1) {
         this.executorService = NamedForkJoinPool.newNamedPool(THREAD_NAME, parallelThreads);
       } else {
@@ -255,11 +256,7 @@
               executorService, /*failFastOnException=*/ !keepGoing, ErrorClassifier.DEFAULT);
       this.eventHandler = eventHandler;
       this.maxDepth = maxDepth;
-      this.errorObserver = new TargetEdgeErrorObserver();
-      ImmutableList.Builder<TargetEdgeObserver> builder = ImmutableList.builder();
-      builder.add(observers);
-      builder.add(errorObserver);
-      this.observers = builder.build();
+      this.observer = observer;
     }
 
     /**
@@ -314,11 +311,9 @@
         final int count) {
       return () -> {
         try {
-          try {
-            visit(from, attr, targetProvider.getTarget(eventHandler, label), depth + 1, count);
-          } catch (NoSuchThingException e) {
-            observeError(from, label, e);
-          }
+          visit(from, attr, targetProvider.getTarget(eventHandler, label), depth + 1, count);
+        } catch (NoSuchThingException e) {
+          observeError(from, label, e);
         } catch (InterruptedException e) {
           Thread.currentThread().interrupt();
         }
@@ -419,22 +414,18 @@
     }
 
     private void observeEdge(Target from, Attribute attribute, Target to) {
-      for (TargetEdgeObserver observer : observers) {
-        observer.edge(from, attribute, to);
-      }
+      observer.edge(from, attribute, to);
+      errorObserver.edge(from, attribute, to);
     }
 
     private void observeNode(Target target) {
-      for (TargetEdgeObserver observer : observers) {
-        observer.node(target);
-      }
+      observer.node(target);
+      errorObserver.node(target);
     }
 
-    private void observeError(Target from, Label label, NoSuchThingException e)
-        throws InterruptedException {
-      for (TargetEdgeObserver observer : observers) {
-        observer.missingEdge(from, label, e);
-      }
+    private void observeError(Target from, Label label, NoSuchThingException e) {
+      observer.missingEdge(from, label, e);
+      errorObserver.missingEdge(from, label, e);
     }
   }
 }