Improve output of the `dump` command with `--skyframe=detailed`.

* Output deps in groups. This helps to better visualize how the skyframe requests are made.
* Add a flag `--skyfunction_filter` to filter output to certain functions.

PiperOrigin-RevId: 419829752
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/commands/DumpCommand.java b/src/main/java/com/google/devtools/build/lib/runtime/commands/DumpCommand.java
index b7a5b72..dc0e08b 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/commands/DumpCommand.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/commands/DumpCommand.java
@@ -36,6 +36,9 @@
 import com.google.devtools.build.lib.server.FailureDetails.FailureDetail;
 import com.google.devtools.build.lib.skyframe.SkyframeExecutor;
 import com.google.devtools.build.lib.skyframe.SkyframeExecutor.RuleStat;
+import com.google.devtools.build.lib.util.RegexFilter;
+import com.google.devtools.build.lib.util.RegexFilter.RegexFilterConverter;
+import com.google.devtools.build.skyframe.MemoizingEvaluator;
 import com.google.devtools.common.options.EnumConverter;
 import com.google.devtools.common.options.Option;
 import com.google.devtools.common.options.OptionDocumentationCategory;
@@ -56,7 +59,6 @@
 
 /** Implementation of the dump command. */
 @Command(
-  allowResidue = false,
   mustRunInWorkspace = false,
   options = {DumpCommand.DumpOptions.class},
   help =
@@ -70,8 +72,8 @@
 public class DumpCommand implements BlazeCommand {
 
   /**
-   * NB! Any changes to this class must be kept in sync with anyOutput variable
-   * value in the {@link DumpCommand#exec(CommandEnvironment,OptionsProvider)} method below.
+   * NB! Any changes to this class must be kept in sync with anyOutput variable value in the {@link
+   * DumpCommand#exec} method below.
    */
   public static class DumpOptions extends OptionsBase {
 
@@ -130,6 +132,15 @@
       help = "Dump Skyframe graph: 'off', 'summary', or 'detailed'."
     )
     public SkyframeDumpOption dumpSkyframe;
+
+    @Option(
+        name = "skyfunction_filter",
+        defaultValue = ".*",
+        converter = RegexFilterConverter.class,
+        documentationCategory = OptionDocumentationCategory.OUTPUT_SELECTION,
+        effectTags = {OptionEffectTag.BAZEL_MONITORING},
+        help = "Regex filter of SkyFunction names to output. Only used with --skyframe=detailed.")
+    public RegexFilter skyFunctionFilter;
   }
 
   /**
@@ -161,7 +172,7 @@
             || dumpOptions.dumpRuleClasses
             || dumpOptions.dumpRules
             || dumpOptions.starlarkMemory != null
-            || (dumpOptions.dumpSkyframe != SkyframeDumpOption.OFF);
+            || dumpOptions.dumpSkyframe != SkyframeDumpOption.OFF;
     if (!anyOutput) {
       Collection<Class<? extends OptionsBase>> optionList = new ArrayList<>();
       optionList.add(DumpOptions.class);
@@ -219,10 +230,17 @@
         }
       }
 
-      if (dumpOptions.dumpSkyframe != SkyframeDumpOption.OFF) {
-        dumpSkyframe(
-            env.getSkyframeExecutor(), dumpOptions.dumpSkyframe == SkyframeDumpOption.SUMMARY, out);
-        out.println();
+      MemoizingEvaluator evaluator = env.getSkyframeExecutor().getEvaluator();
+      switch (dumpOptions.dumpSkyframe) {
+        case OFF:
+          break;
+        case SUMMARY:
+          evaluator.dumpSummary(out);
+          break;
+        case DETAILED:
+          evaluator.dumpDetailed(
+              out, k -> dumpOptions.skyFunctionFilter.test(k.functionName().getName()));
+          break;
       }
 
       return failure.orElse(BlazeCommandResult.success());
@@ -242,10 +260,6 @@
     return true;
   }
 
-  private static void dumpSkyframe(SkyframeExecutor executor, boolean summarize, PrintStream out) {
-    executor.dump(summarize, out);
-  }
-
   private static void dumpRuleClasses(BlazeRuntime runtime, PrintStream out) {
     PackageFactory factory = runtime.getPackageFactory();
     List<String> ruleClassNames = new ArrayList<>(factory.getRuleClassNames());
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
index e3ebb9b..8ddec32 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeExecutor.java
@@ -722,10 +722,6 @@
         fileCache, actionInputPrefetcher, DiscoveredModulesPruner.DEFAULT);
   }
 
-  public void dump(boolean summarize, PrintStream out) {
-    memoizingEvaluator.dump(summarize, out);
-  }
-
   @ForOverride
   protected abstract void dumpPackages(PrintStream out);
 
diff --git a/src/main/java/com/google/devtools/build/skyframe/InMemoryMemoizingEvaluator.java b/src/main/java/com/google/devtools/build/skyframe/InMemoryMemoizingEvaluator.java
index 374ccbc..5b385b6 100644
--- a/src/main/java/com/google/devtools/build/skyframe/InMemoryMemoizingEvaluator.java
+++ b/src/main/java/com/google/devtools/build/skyframe/InMemoryMemoizingEvaluator.java
@@ -13,8 +13,6 @@
 // limitations under the License.
 package com.google.devtools.build.skyframe;
 
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
@@ -26,6 +24,7 @@
 import com.google.devtools.build.lib.profiler.GoogleAutoProfilerUtils;
 import com.google.devtools.build.lib.profiler.Profiler;
 import com.google.devtools.build.lib.profiler.SilentCloseable;
+import com.google.devtools.build.lib.util.GroupedList;
 import com.google.devtools.build.skyframe.Differencer.Diff;
 import com.google.devtools.build.skyframe.InvalidatingNodeVisitor.DeletingInvalidationState;
 import com.google.devtools.build.skyframe.InvalidatingNodeVisitor.DirtyingInvalidationState;
@@ -366,39 +365,48 @@
   }
 
   @Override
-  public void dump(boolean summarize, PrintStream out) {
-    if (summarize) {
-      long nodes = 0;
-      long edges = 0;
-      for (InMemoryNodeEntry entry : graph.getAllValues().values()) {
-        nodes++;
-        if (entry.isDone()) {
-          edges += Iterables.size(entry.getDirectDeps());
-        }
-      }
-      out.println("Node count: " + nodes);
-      out.println("Edge count: " + edges);
-    } else {
-      Function<SkyKey, String> keyFormatter =
-          key ->
-              String.format(
-                  "%s:%s", key.functionName(), key.argument().toString().replace('\n', '_'));
-
-      for (Map.Entry<SkyKey, InMemoryNodeEntry> mapPair : graph.getAllValues().entrySet()) {
-        SkyKey key = mapPair.getKey();
-        InMemoryNodeEntry entry = mapPair.getValue();
-        if (entry.isDone()) {
-          out.print(keyFormatter.apply(key));
-          out.print("|");
-          if (entry.keepEdges() == NodeEntry.KeepEdgesPolicy.NONE) {
-            out.println(" (direct deps not stored)");
-          } else {
-            out.println(
-                Joiner.on('|').join(Iterables.transform(entry.getDirectDeps(), keyFormatter)));
-          }
-        }
+  public void dumpSummary(PrintStream out) {
+    long nodes = 0;
+    long edges = 0;
+    for (InMemoryNodeEntry entry : graph.getAllValues().values()) {
+      nodes++;
+      if (entry.isDone()) {
+        edges += Iterables.size(entry.getDirectDeps());
       }
     }
+    out.println("Node count: " + nodes);
+    out.println("Edge count: " + edges);
+  }
+
+  @Override
+  public void dumpDetailed(PrintStream out, Predicate<SkyKey> filter) {
+    graph
+        .getAllValues()
+        .forEach(
+            (key, entry) -> {
+              if (!filter.test(key) || !entry.isDone()) {
+                return;
+              }
+              printKey(key, out);
+              if (entry.keepEdges() == NodeEntry.KeepEdgesPolicy.NONE) {
+                out.println("  (direct deps not stored)");
+              } else {
+                GroupedList<SkyKey> deps =
+                    GroupedList.create(entry.getCompressedDirectDepsForDoneEntry());
+                for (int i = 0; i < deps.listSize(); i++) {
+                  out.format("  Group %d:\n", i + 1);
+                  for (SkyKey dep : deps.get(i)) {
+                    out.print("    ");
+                    printKey(dep, out);
+                  }
+                }
+              }
+              out.println();
+            });
+  }
+
+  private static void printKey(SkyKey key, PrintStream out) {
+    out.format("%s:%s\n", key.functionName(), key.argument().toString().replace('\n', '_'));
   }
 
   public ImmutableMap<SkyFunctionName, SkyFunction> getSkyFunctionsForTesting() {
diff --git a/src/main/java/com/google/devtools/build/skyframe/MemoizingEvaluator.java b/src/main/java/com/google/devtools/build/skyframe/MemoizingEvaluator.java
index 89fa0e3..808df9c 100644
--- a/src/main/java/com/google/devtools/build/skyframe/MemoizingEvaluator.java
+++ b/src/main/java/com/google/devtools/build/skyframe/MemoizingEvaluator.java
@@ -177,11 +177,21 @@
   }
 
   /**
-   * Write the graph to the output stream. Not necessarily thread-safe. Use only for debugging
-   * purposes.
+   * Writes a brief summary about the graph to the given output stream.
+   *
+   * <p>Not necessarily thread-safe. Use only for debugging purposes.
    */
   @ThreadHostile
-  void dump(boolean summarize, PrintStream out);
+  void dumpSummary(PrintStream out);
+
+  /**
+   * Writes a detailed summary of the graph to the given output stream, omitting keys that do not
+   * match the given filter.
+   *
+   * <p>Not necessarily thread-safe. Use only for debugging purposes.
+   */
+  @ThreadHostile
+  void dumpDetailed(PrintStream out, Predicate<SkyKey> filter);
 
   /** A supplier for creating instances of a particular evaluator implementation. */
   interface EvaluatorSupplier {