Log count and size of output files and top-level files seen this build to the BEP. Augment existing logging for source files to log count in addition to size.

PiperOrigin-RevId: 354612893
diff --git a/src/main/java/com/google/devtools/build/lib/actions/cache/MetadataHandler.java b/src/main/java/com/google/devtools/build/lib/actions/cache/MetadataHandler.java
index 7a5d7e6..de5bf46 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/cache/MetadataHandler.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/cache/MetadataHandler.java
@@ -20,6 +20,7 @@
 import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
 import com.google.devtools.build.lib.actions.FileArtifactValue;
 import com.google.devtools.build.lib.actions.MetadataProvider;
+import com.google.devtools.build.lib.skyframe.TreeArtifactValue;
 import com.google.devtools.build.lib.vfs.FileStatus;
 import java.io.IOException;
 import javax.annotation.Nullable;
@@ -60,6 +61,9 @@
    */
   ImmutableSet<TreeFileArtifact> getTreeArtifactChildren(SpecialArtifact treeArtifact);
 
+  /** Retrieves the metadata for this tree artifact. Data should already be available. */
+  TreeArtifactValue getTreeArtifactValue(SpecialArtifact treeArtifact) throws IOException;
+
   /**
    * Marks an {@link Artifact} as intentionally omitted.
    *
diff --git a/src/main/java/com/google/devtools/build/lib/buildeventstream/proto/build_event_stream.proto b/src/main/java/com/google/devtools/build/lib/buildeventstream/proto/build_event_stream.proto
index 0802821..9a06765 100644
--- a/src/main/java/com/google/devtools/build/lib/buildeventstream/proto/build_event_stream.proto
+++ b/src/main/java/com/google/devtools/build/lib/buildeventstream/proto/build_event_stream.proto
@@ -729,14 +729,16 @@
 
 message BuildMetrics {
   message ActionSummary {
-    // The total number of actions created and registered during the build.
-    // This includes unused actions that were constructed but
-    // not executed during this build.
+    // The total number of actions created and registered during the build. This
+    // includes unused actions that were constructed but not executed during
+    // this build. It does not include actions that were created on prior builds
+    // that are still valid, even if those actions had to be re-executed on this
+    // build. For the total number of actions that would be created if this
+    // invocation were "clean", see BuildGraphMetrics below.
     int64 actions_created = 1;
 
-    // The total number of actions executed during the build.
-    // This includes any remote cache hits, but excludes
-    // local action cache hits.
+    // The total number of actions executed during the build. This includes any
+    // remote cache hits, but excludes local action cache hits.
     int64 actions_executed = 2;
   }
   ActionSummary action_summary = 1;
@@ -754,12 +756,14 @@
   MemoryMetrics memory_metrics = 2;
 
   message TargetMetrics {
-    // Number of targets loaded during this build.
+    // Number of targets loaded during this build. Does not include targets that
+    // were loaded on prior builds on this server and were cached.
     int64 targets_loaded = 1;
 
-    // Number of targets configured during this build. This can
-    // be greater than targets_loaded if the same target is configured
-    // multiple times.
+    // Number of targets configured during this build. This can be greater than
+    // targets_loaded if the same target is configured multiple times. Does not
+    // include targets that were configured on prior builds on this server and
+    // were cached. See BuildGraphMetrics below if you need that.
     int64 targets_configured = 2;
   }
   TargetMetrics target_metrics = 3;
@@ -794,9 +798,29 @@
   CumulativeMetrics cumulative_metrics = 6;
 
   message ArtifactMetrics {
-    // Total size of all source files newly read this build. Will not include
+    reserved 1;
+
+    message FilesMetric {
+      int64 size_in_bytes = 1;
+      int32 count = 2;
+    }
+
+    // Measures all source files newly read this build. Does not include
     // unchanged sources on incremental builds.
-    int64 source_artifact_bytes_read = 1;
+    FilesMetric source_artifacts_read = 2;
+    // Measures all output artifacts from executed actions. This includes
+    // actions that were cached locally (via the action cache) or remotely (via
+    // a remote cache or executor), but does *not* include outputs of actions
+    // that were cached internally in Skyframe.
+    FilesMetric output_artifacts_seen = 3;
+    // Measures all output artifacts from actions that were cached locally
+    // via the action cache. These artifacts were already present on disk at the
+    // start of the build. Does not include Skyframe-cached actions' outputs.
+    FilesMetric output_artifacts_from_action_cache = 4;
+    // Measures all artifacts that belong to a top-level output group. Does not
+    // deduplicate, so if there are two top-level targets in this build that
+    // share an artifact, it will be counted twice.
+    FilesMetric top_level_artifacts = 5;
   }
 
   ArtifactMetrics artifact_metrics = 7;
@@ -804,13 +828,16 @@
   // Information about the size and shape of the build graph. Some fields may
   // not be populated if Bazel was able to skip steps due to caching.
   message BuildGraphMetrics {
-    // How many configured targets/aspects were in this build.
+    // How many configured targets/aspects were in this build, including any
+    // that were analyzed on a prior build and are still valid. May not be
+    // populated if analysis phase was fully cached.
     int32 action_lookup_value_count = 1;
-    // How many actions belonged to those configured targets/aspects. It may not
-    // be necessary to execute all of these actions to build the requested
-    // targets.
+    // How many actions belonged to the configured targets/aspects above. It may
+    // not be necessary to execute all of these actions to build the requested
+    // targets. May not be populated if analysis phase was fully cached.
     int32 action_count = 2;
-    // How many artifacts are outputs of the above actions.
+    // How many artifacts are outputs of the above actions. May not be populated
+    // if analysis phase was fully cached.
     int32 output_artifact_count = 3;
     // How many Skyframe nodes there are in memory at the end of the build. This
     // may underestimate the number of nodes when running with memory-saving
diff --git a/src/main/java/com/google/devtools/build/lib/metrics/MetricsCollector.java b/src/main/java/com/google/devtools/build/lib/metrics/MetricsCollector.java
index 5ef677b..11db50d 100644
--- a/src/main/java/com/google/devtools/build/lib/metrics/MetricsCollector.java
+++ b/src/main/java/com/google/devtools/build/lib/metrics/MetricsCollector.java
@@ -117,7 +117,11 @@
   @SuppressWarnings("unused")
   @Subscribe
   public void onExecutionComplete(ExecutionFinishedEvent event) {
-    artifactMetrics.setSourceArtifactBytesRead(event.sourceArtifactBytesRead());
+    artifactMetrics
+        .setSourceArtifactsRead(event.sourceArtifactsRead())
+        .setOutputArtifactsSeen(event.outputArtifactsSeen())
+        .setOutputArtifactsFromActionCache(event.outputArtifactsFromActionCache())
+        .setTopLevelArtifacts(event.topLevelArtifacts());
   }
 
   @SuppressWarnings("unused")
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionInputMapHelper.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionInputMapHelper.java
index 9fd9c34..b367356 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ActionInputMapHelper.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionInputMapHelper.java
@@ -39,6 +39,28 @@
 
   private ActionInputMapHelper() {}
 
+  static void addToMap(
+      ActionInputMapSink inputMap,
+      Map<Artifact, ImmutableCollection<Artifact>> expandedArtifacts,
+      Map<SpecialArtifact, ArchivedTreeArtifact> archivedTreeArtifacts,
+      Map<Artifact, ImmutableList<FilesetOutputSymlink>> filesetsInsideRunfiles,
+      Map<Artifact, ImmutableList<FilesetOutputSymlink>> topLevelFilesets,
+      Artifact key,
+      SkyValue value,
+      Environment env)
+      throws InterruptedException {
+    addToMap(
+        inputMap,
+        expandedArtifacts,
+        archivedTreeArtifacts,
+        filesetsInsideRunfiles,
+        topLevelFilesets,
+        key,
+        value,
+        env,
+        MetadataConsumerForMetrics.NO_OP);
+  }
+
   /**
    * Adds a value obtained by an Artifact skyvalue lookup to the action input map. May do Skyframe
    * lookups.
@@ -51,19 +73,23 @@
       Map<Artifact, ImmutableList<FilesetOutputSymlink>> topLevelFilesets,
       Artifact key,
       SkyValue value,
-      Environment env)
+      Environment env,
+      MetadataConsumerForMetrics consumer)
       throws InterruptedException {
     if (value instanceof AggregatingArtifactValue) {
       AggregatingArtifactValue aggregatingValue = (AggregatingArtifactValue) value;
       for (Pair<Artifact, FileArtifactValue> entry : aggregatingValue.getFileArtifacts()) {
         Artifact artifact = entry.first;
-        inputMap.put(artifact, entry.second, /*depOwner=*/ key);
+        inputMap.put(artifact, entry.getSecond(), /*depOwner=*/ key);
         if (artifact.isFileset()) {
           ImmutableList<FilesetOutputSymlink> expandedFileset =
               getFilesets(env, (SpecialArtifact) artifact);
           if (expandedFileset != null) {
             filesetsInsideRunfiles.put(artifact, expandedFileset);
+            consumer.accumulate(expandedFileset);
           }
+        } else {
+          consumer.accumulate(entry.getSecond());
         }
       }
       for (Pair<Artifact, TreeArtifactValue> entry : aggregatingValue.getTreeArtifacts()) {
@@ -74,6 +100,7 @@
             archivedTreeArtifacts,
             inputMap,
             /*depOwner=*/ key);
+        consumer.accumulate(entry.getSecond());
       }
       // We have to cache the "digest" of the aggregating value itself, because the action cache
       // checker may want it.
@@ -92,21 +119,32 @@
         expandedArtifacts.put(key, expansionBuilder.build());
       }
     } else if (value instanceof TreeArtifactValue) {
+      TreeArtifactValue treeArtifactValue = (TreeArtifactValue) value;
       expandTreeArtifactAndPopulateArtifactData(
           key,
-          (TreeArtifactValue) value,
+          treeArtifactValue,
           expandedArtifacts,
           archivedTreeArtifacts,
           inputMap,
           /*depOwner=*/ key);
+      consumer.accumulate(treeArtifactValue);
     } else if (value instanceof ActionExecutionValue) {
-      inputMap.put(key, ((ActionExecutionValue) value).getExistingFileArtifactValue(key), key);
+      FileArtifactValue metadata = ((ActionExecutionValue) value).getExistingFileArtifactValue(key);
+      inputMap.put(key, metadata, key);
       if (key.isFileset()) {
-        topLevelFilesets.put(key, getFilesets(env, (SpecialArtifact) key));
+        ImmutableList<FilesetOutputSymlink> filesets = getFilesets(env, (SpecialArtifact) key);
+        if (filesets != null) {
+          topLevelFilesets.put(key, filesets);
+          consumer.accumulate(filesets);
+        }
+      } else {
+        consumer.accumulate(metadata);
       }
     } else {
       Preconditions.checkArgument(value instanceof FileArtifactValue, "Unexpected value %s", value);
-      inputMap.put(key, (FileArtifactValue) value, /*depOwner=*/ key);
+      FileArtifactValue metadata = (FileArtifactValue) value;
+      inputMap.put(key, metadata, /*depOwner=*/ key);
+      consumer.accumulate(metadata);
     }
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java
index fbdc927..81bc902 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionMetadataHandler.java
@@ -17,6 +17,7 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 
+import com.google.common.base.MoreObjects;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -303,7 +304,8 @@
     store.putArtifactData(artifact, FileArtifactValue.createProxy(digest));
   }
 
-  private TreeArtifactValue getTreeArtifactValue(SpecialArtifact artifact) throws IOException {
+  @Override
+  public TreeArtifactValue getTreeArtifactValue(SpecialArtifact artifact) throws IOException {
     checkState(artifact.isTreeArtifact(), "%s is not a tree artifact", artifact);
 
     TreeArtifactValue value = store.getTreeArtifactData(artifact);
@@ -470,6 +472,15 @@
     return store;
   }
 
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("outputs", outputs)
+        .add("store", store)
+        .add("inputArtifactDataSize", inputArtifactData.size())
+        .toString();
+  }
+
   /** Constructs a new {@link FileArtifactValue} by reading from the file system. */
   private FileArtifactValue constructFileArtifactValueFromFilesystem(Artifact artifact)
       throws IOException {
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ArtifactFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ArtifactFunction.java
index 0f3fb05..09e3bdd 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ArtifactFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ArtifactFunction.java
@@ -54,7 +54,6 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
-import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.Supplier;
 import javax.annotation.Nullable;
 
@@ -66,7 +65,7 @@
  */
 class ArtifactFunction implements SkyFunction {
   private final Supplier<Boolean> mkdirForTreeArtifacts;
-  private final AtomicLong sourceArtifactBytesReadThisBuild;
+  private final MetadataConsumerForMetrics sourceArtifactsSeen;
 
   public static final class MissingFileArtifactValue implements SkyValue {
     private final DetailedExitCode detailedExitCode;
@@ -90,9 +89,9 @@
   }
 
   public ArtifactFunction(
-      Supplier<Boolean> mkdirForTreeArtifacts, AtomicLong sourceArtifactBytesReadThisBuild) {
+      Supplier<Boolean> mkdirForTreeArtifacts, MetadataConsumerForMetrics sourceArtifactsSeen) {
     this.mkdirForTreeArtifacts = mkdirForTreeArtifacts;
-    this.sourceArtifactBytesReadThisBuild = sourceArtifactBytesReadThisBuild;
+    this.sourceArtifactsSeen = sourceArtifactsSeen;
   }
 
   @Override
@@ -273,14 +272,14 @@
     }
 
     if (!fileValue.isDirectory() || !TrackSourceDirectoriesFlag.trackSourceDirectories()) {
-      if (fileValue.isFile()) {
-        sourceArtifactBytesReadThisBuild.addAndGet(fileValue.getSize());
-      }
+      FileArtifactValue metadata;
       try {
-        return FileArtifactValue.createForSourceArtifact(artifact, fileValue);
+        metadata = FileArtifactValue.createForSourceArtifact(artifact, fileValue);
       } catch (IOException e) {
         return makeIOExceptionSourceInputFileValue(artifact, e);
       }
+      sourceArtifactsSeen.accumulate(metadata);
+      return metadata;
     }
     // For directory artifacts that are not Filesets, we initiate a directory traversal here, and
     // compute a hash from the directory structure.
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/AspectCompletor.java b/src/main/java/com/google/devtools/build/lib/skyframe/AspectCompletor.java
index e9f3dc2..5420d83 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/AspectCompletor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/AspectCompletor.java
@@ -42,9 +42,14 @@
     implements Completor<AspectValue, AspectCompletionValue, AspectCompletionKey, BuildEventId> {
 
   static SkyFunction aspectCompletionFunction(
-      PathResolverFactory pathResolverFactory, SkyframeActionExecutor skyframeActionExecutor) {
+      PathResolverFactory pathResolverFactory,
+      SkyframeActionExecutor skyframeActionExecutor,
+      MetadataConsumerForMetrics.FilesMetricConsumer topLevelArtifactsMetric) {
     return new CompletionFunction<>(
-        pathResolverFactory, new AspectCompletor(), skyframeActionExecutor);
+        pathResolverFactory,
+        new AspectCompletor(),
+        skyframeActionExecutor,
+        topLevelArtifactsMetric);
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
index 9a6ea87..7726e14e 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/BUILD
@@ -157,6 +157,7 @@
         ":local_repository_lookup_value",
         ":managed_directories_knowledge",
         ":map_as_package_roots",
+        ":metadata_consumer_for_metrics",
         ":output_store",
         ":package_error_function",
         ":package_error_message_function",
@@ -417,6 +418,7 @@
     deps = [
         ":action_execution_value",
         ":aggregating_artifact_value",
+        ":metadata_consumer_for_metrics",
         ":runfiles_artifact_value",
         ":tree_artifact_value",
         "//src/main/java/com/google/devtools/build/lib/actions",
@@ -779,6 +781,7 @@
         ":action_template_expansion_value",
         ":aggregating_artifact_value",
         ":coverage_report_value",
+        ":metadata_consumer_for_metrics",
         ":recursive_filesystem_traversal",
         ":runfiles_artifact_value",
         ":track_source_directories_flag",
@@ -1361,7 +1364,10 @@
 java_library(
     name = "execution_finished_event",
     srcs = ["ExecutionFinishedEvent.java"],
-    deps = ["//third_party:auto_value"],
+    deps = [
+        "//src/main/java/com/google/devtools/build/lib/buildeventstream/proto:build_event_stream_java_proto",
+        "//third_party:auto_value",
+    ],
 )
 
 java_library(
@@ -1670,6 +1676,19 @@
 )
 
 java_library(
+    name = "metadata_consumer_for_metrics",
+    srcs = ["MetadataConsumerForMetrics.java"],
+    deps = [
+        "//src/main/java/com/google/devtools/build/lib/actions:file_metadata",
+        "//src/main/java/com/google/devtools/build/lib/actions:fileset_output_symlink",
+        "//src/main/java/com/google/devtools/build/lib/buildeventstream/proto:build_event_stream_java_proto",
+        "//src/main/java/com/google/devtools/build/lib/concurrent",
+        "//src/main/java/com/google/devtools/build/lib/skyframe:tree_artifact_value",
+        "//third_party:guava",
+    ],
+)
+
+java_library(
     name = "mutable_supplier",
     srcs = ["MutableSupplier.java"],
     deps = ["//third_party:guava"],
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/CompletionFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/CompletionFunction.java
index a2cb5f2..3ae7437 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/CompletionFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/CompletionFunction.java
@@ -43,6 +43,7 @@
 import com.google.devtools.build.lib.events.ExtendedEventHandler;
 import com.google.devtools.build.lib.skyframe.ArtifactFunction.MissingFileArtifactValue;
 import com.google.devtools.build.lib.skyframe.CompletionFunction.TopLevelActionLookupKey;
+import com.google.devtools.build.lib.skyframe.MetadataConsumerForMetrics.FilesMetricConsumer;
 import com.google.devtools.build.lib.util.Pair;
 import com.google.devtools.build.skyframe.SkyFunction;
 import com.google.devtools.build.skyframe.SkyFunctionException;
@@ -156,14 +157,17 @@
   private final PathResolverFactory pathResolverFactory;
   private final Completor<ValueT, ResultT, KeyT, FailureT> completor;
   private final SkyframeActionExecutor skyframeActionExecutor;
+  private final FilesMetricConsumer topLevelArtifactsMetric;
 
   CompletionFunction(
       PathResolverFactory pathResolverFactory,
       Completor<ValueT, ResultT, KeyT, FailureT> completor,
-      SkyframeActionExecutor skyframeActionExecutor) {
+      SkyframeActionExecutor skyframeActionExecutor,
+      FilesMetricConsumer topLevelArtifactsMetric) {
     this.pathResolverFactory = pathResolverFactory;
     this.completor = completor;
     this.skyframeActionExecutor = skyframeActionExecutor;
+    this.topLevelArtifactsMetric = topLevelArtifactsMetric;
   }
 
   @SuppressWarnings("unchecked") // Cast to KeyT
@@ -202,6 +206,8 @@
     MissingInputFileException missingInputException = null;
     NestedSetBuilder<Cause> rootCausesBuilder = NestedSetBuilder.stableOrder();
     ImmutableSet.Builder<Artifact> builtArtifactsBuilder = ImmutableSet.builder();
+    // Don't double-count files due to Skyframe restarts.
+    FilesMetricConsumer currentConsumer = new FilesMetricConsumer();
     for (Artifact input : allArtifacts) {
       try {
         SkyValue artifactValue = inputDeps.get(Artifact.key(input)).get();
@@ -225,7 +231,8 @@
                 topLevelFilesets,
                 input,
                 artifactValue,
-                env);
+                env,
+                currentConsumer);
           }
         }
       } catch (ActionExecutionException e) {
@@ -312,6 +319,7 @@
       return null;
     }
     env.getListener().post(postable);
+    topLevelArtifactsMetric.mergeIn(currentConsumer);
     return completor.getResult();
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ExecutionFinishedEvent.java b/src/main/java/com/google/devtools/build/lib/skyframe/ExecutionFinishedEvent.java
index d593911..0c4b56d 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ExecutionFinishedEvent.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ExecutionFinishedEvent.java
@@ -14,6 +14,7 @@
 package com.google.devtools.build.lib.skyframe;
 
 import com.google.auto.value.AutoValue;
+import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildMetrics.ArtifactMetrics;
 import java.time.Duration;
 
 /**
@@ -24,13 +25,17 @@
 public abstract class ExecutionFinishedEvent {
   // AutoValue Builders require that all fields are populated, so we provide a default.
   public static ExecutionFinishedEvent.Builder builderWithDefaults() {
+    ArtifactMetrics.FilesMetric emptyFilesMetric = ArtifactMetrics.FilesMetric.getDefaultInstance();
     return builder()
         .setOutputDirtyFiles(0)
         .setOutputModifiedFilesDuringPreviousBuild(0)
         .setSourceDiffCheckingDuration(Duration.ZERO)
         .setNumSourceFilesCheckedBecauseOfMissingDiffs(0)
         .setOutputTreeDiffCheckingDuration(Duration.ZERO)
-        .setSourceArtifactBytesRead(0L);
+        .setSourceArtifactsRead(emptyFilesMetric)
+        .setOutputArtifactsSeen(emptyFilesMetric)
+        .setOutputArtifactsFromActionCache(emptyFilesMetric)
+        .setTopLevelArtifacts(emptyFilesMetric);
   }
 
   public abstract int outputDirtyFiles();
@@ -43,7 +48,13 @@
 
   public abstract Duration outputTreeDiffCheckingDuration();
 
-  public abstract long sourceArtifactBytesRead();
+  public abstract ArtifactMetrics.FilesMetric sourceArtifactsRead();
+
+  public abstract ArtifactMetrics.FilesMetric outputArtifactsSeen();
+
+  public abstract ArtifactMetrics.FilesMetric outputArtifactsFromActionCache();
+
+  public abstract ArtifactMetrics.FilesMetric topLevelArtifacts();
 
   static Builder builder() {
     return new AutoValue_ExecutionFinishedEvent.Builder();
@@ -63,7 +74,13 @@
 
     abstract Builder setOutputTreeDiffCheckingDuration(Duration outputTreeDiffCheckingDuration);
 
-    abstract Builder setSourceArtifactBytesRead(long sourceArtifactBytesRead);
+    public abstract Builder setSourceArtifactsRead(ArtifactMetrics.FilesMetric value);
+
+    public abstract Builder setOutputArtifactsSeen(ArtifactMetrics.FilesMetric value);
+
+    public abstract Builder setOutputArtifactsFromActionCache(ArtifactMetrics.FilesMetric value);
+
+    public abstract Builder setTopLevelArtifacts(ArtifactMetrics.FilesMetric value);
 
     abstract ExecutionFinishedEvent build();
   }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/MetadataConsumerForMetrics.java b/src/main/java/com/google/devtools/build/lib/skyframe/MetadataConsumerForMetrics.java
new file mode 100644
index 0000000..9a679f0
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/MetadataConsumerForMetrics.java
@@ -0,0 +1,97 @@
+// Copyright 2021 The Bazel Authors. All rights reserved.
+//
+// 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.devtools.build.lib.skyframe;
+
+import com.google.common.collect.ImmutableList;
+import com.google.devtools.build.lib.actions.FileArtifactValue;
+import com.google.devtools.build.lib.actions.FileStateType;
+import com.google.devtools.build.lib.actions.FilesetOutputSymlink;
+import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildMetrics.ArtifactMetrics;
+import com.google.devtools.build.lib.concurrent.ThreadSafety;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+
+/** Sink for file-related metadata to be used for metrics gathering. */
+@ThreadSafety.ThreadSafe
+public interface MetadataConsumerForMetrics {
+  MetadataConsumerForMetrics NO_OP =
+      new MetadataConsumerForMetrics() {
+        @Override
+        public void accumulate(FileArtifactValue metadata) {}
+
+        @Override
+        public void accumulate(TreeArtifactValue treeArtifactValue) {}
+
+        @Override
+        public void accumulate(ImmutableList<FilesetOutputSymlink> filesetOutputSymlinks) {}
+      };
+
+  void accumulate(FileArtifactValue metadata);
+
+  void accumulate(TreeArtifactValue treeArtifactValue);
+
+  void accumulate(ImmutableList<FilesetOutputSymlink> filesetOutputSymlinks);
+
+  /** Accumulates file metadata for later export to a {@link ArtifactMetrics.FilesMetric} object. */
+  class FilesMetricConsumer implements MetadataConsumerForMetrics {
+    private final AtomicLong size = new AtomicLong();
+    private final AtomicInteger count = new AtomicInteger();
+
+    @Override
+    public void accumulate(FileArtifactValue metadata) {
+      // Exclude directories (might throw in future) and symlinks (duplicate data). In practice,
+      // most symlinks' metadata is that of their target, so they still get duplicated.
+      if (metadata.getType() == FileStateType.REGULAR_FILE) {
+        size.addAndGet(metadata.getSize());
+        count.incrementAndGet();
+      }
+    }
+
+    @Override
+    public void accumulate(TreeArtifactValue treeArtifactValue) {
+      long totalChildBytes = treeArtifactValue.getTotalChildBytes();
+      size.addAndGet(totalChildBytes);
+      if (totalChildBytes > 0) {
+        // Skip omitted/missing tree artifacts: they will throw here.
+        count.addAndGet(treeArtifactValue.getChildren().size());
+      }
+    }
+
+    @Override
+    public void accumulate(ImmutableList<FilesetOutputSymlink> filesetOutputSymlinks) {
+      // This is a bit of a fudge: we include the symlinks as a count, but don't count their
+      // targets' sizes, because (a) plumbing the data is hard, (b) it would double-count symlinks
+      // to output files, and (c) it's not even uniquely generated content for input files.
+      count.addAndGet(filesetOutputSymlinks.size());
+    }
+
+    @ThreadSafety.ThreadSafe
+    public void mergeIn(FilesMetricConsumer otherConsumer) {
+      this.size.addAndGet(otherConsumer.size.get());
+      this.count.addAndGet(otherConsumer.count.get());
+    }
+
+    ArtifactMetrics.FilesMetric toFilesMetricAndReset() {
+      return ArtifactMetrics.FilesMetric.newBuilder()
+          .setSizeInBytes(size.getAndSet(0L))
+          .setCount(count.getAndSet(0))
+          .build();
+    }
+
+    void reset() {
+      size.set(0L);
+      count.set(0);
+    }
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/OutputStore.java b/src/main/java/com/google/devtools/build/lib/skyframe/OutputStore.java
index 9e7588e..79b3e05 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/OutputStore.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/OutputStore.java
@@ -13,6 +13,7 @@
 // limitations under the License.
 package com.google.devtools.build.lib.skyframe;
 
+import com.google.common.base.MoreObjects;
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
 import com.google.devtools.build.lib.actions.Artifact;
@@ -91,4 +92,12 @@
       artifactData.remove(artifact);
     }
   }
+
+  @Override
+  public String toString() {
+    return MoreObjects.toStringHelper(this)
+        .add("artifactData", artifactData)
+        .add("treeArtifactData", treeArtifactData)
+        .toString();
+  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java
index 4bf6271..252a624 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/SkyframeActionExecutor.java
@@ -74,6 +74,7 @@
 import com.google.devtools.build.lib.actions.cache.MetadataHandler;
 import com.google.devtools.build.lib.actions.cache.MetadataInjector;
 import com.google.devtools.build.lib.analysis.config.CoreOptions;
+import com.google.devtools.build.lib.bugreport.BugReport;
 import com.google.devtools.build.lib.buildtool.BuildRequestOptions;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
@@ -154,6 +155,8 @@
       };
 
   private final ActionKeyContext actionKeyContext;
+  private final MetadataConsumerForMetrics outputArtifactsSeen;
+  private final MetadataConsumerForMetrics outputArtifactsFromActionCache;
   private Reporter reporter;
   private Map<String, String> clientEnv = ImmutableMap.of();
   private Executor executorEngine;
@@ -226,9 +229,13 @@
 
   SkyframeActionExecutor(
       ActionKeyContext actionKeyContext,
+      MetadataConsumerForMetrics outputArtifactsSeen,
+      MetadataConsumerForMetrics outputArtifactsFromActionCache,
       AtomicReference<ActionExecutionStatusReporter> statusReporterRef,
       Supplier<ImmutableList<Root>> sourceRootSupplier) {
     this.actionKeyContext = actionKeyContext;
+    this.outputArtifactsSeen = outputArtifactsSeen;
+    this.outputArtifactsFromActionCache = outputArtifactsFromActionCache;
     this.statusReporterRef = statusReporterRef;
     this.sourceRootSupplier = sourceRootSupplier;
   }
@@ -643,7 +650,12 @@
       }
 
       // We still need to check the outputs so that output file data is available to the value.
-      checkOutputs(action, metadataHandler);
+      // Filesets cannot be cached in the action cache, so it is fine to pass null here.
+      checkOutputs(
+          action,
+          metadataHandler,
+          /*filesetOutputSymlinksForMetrics=*/ null,
+          /*isActionCacheHitForMetrics=*/ true);
       if (!eventPosted) {
         eventHandler.post(new CachedActionEvent(action, actionStartTime));
       }
@@ -1110,7 +1122,11 @@
         Preconditions.checkState(action.inputsDiscovered(),
             "Action %s successfully executed, but inputs still not known", action);
 
-        if (!checkOutputs(action, metadataHandler)) {
+        if (!checkOutputs(
+            action,
+            metadataHandler,
+            actionExecutionContext.getOutputSymlinks(),
+            /*isActionCacheHitForMetrics=*/ false)) {
           throw toActionExecutionException(
               "not all outputs were created or valid",
               null,
@@ -1546,7 +1562,11 @@
    *
    * @return false if some outputs are missing, true - otherwise.
    */
-  private boolean checkOutputs(Action action, MetadataHandler metadataHandler) {
+  private boolean checkOutputs(
+      Action action,
+      MetadataHandler metadataHandler,
+      @Nullable ImmutableList<FilesetOutputSymlink> filesetOutputSymlinksForMetrics,
+      boolean isActionCacheHitForMetrics) {
     boolean success = true;
     for (Artifact output : action.getOutputs()) {
       // getMetadata has the side effect of adding the artifact to the cache if it's not there
@@ -1554,7 +1574,15 @@
       // call it if we know the artifact is not omitted.
       if (!metadataHandler.artifactOmitted(output)) {
         try {
-          metadataHandler.getMetadata(output);
+          FileArtifactValue metadata = metadataHandler.getMetadata(output);
+
+          addOutputToMetrics(
+              output,
+              metadata,
+              metadataHandler,
+              filesetOutputSymlinksForMetrics,
+              isActionCacheHitForMetrics,
+              action);
         } catch (IOException e) {
           success = false;
           if (output.isTreeArtifact()) {
@@ -1569,6 +1597,48 @@
     return success;
   }
 
+  private void addOutputToMetrics(
+      Artifact output,
+      FileArtifactValue metadata,
+      MetadataHandler metadataHandler,
+      @Nullable ImmutableList<FilesetOutputSymlink> filesetOutputSymlinks,
+      boolean isActionCacheHit,
+      Action actionForDebugging)
+      throws IOException {
+    if (metadata == null) {
+      BugReport.sendBugReport(
+          new IllegalStateException(
+              String.format(
+                  "Metadata for %s not present in %s (for %s)",
+                  output, metadataHandler, actionForDebugging)));
+      return;
+    }
+    if (output.isFileset() && filesetOutputSymlinks != null) {
+      outputArtifactsSeen.accumulate(filesetOutputSymlinks);
+    } else if (!output.isTreeArtifact()) {
+      outputArtifactsSeen.accumulate(metadata);
+      if (isActionCacheHit) {
+        outputArtifactsFromActionCache.accumulate(metadata);
+      }
+    } else {
+      TreeArtifactValue treeArtifactValue;
+      try {
+        treeArtifactValue = metadataHandler.getTreeArtifactValue((SpecialArtifact) output);
+      } catch (IOException e) {
+        BugReport.sendBugReport(
+            new IllegalStateException(
+                String.format(
+                    "Unexpected IO exception after metadata %s was retrieved for %s (action %s)",
+                    metadata, output, actionForDebugging)));
+        throw e;
+      }
+      outputArtifactsSeen.accumulate(treeArtifactValue);
+      if (isActionCacheHit) {
+        outputArtifactsFromActionCache.accumulate(treeArtifactValue);
+      }
+    }
+  }
+
   /**
    * Convenience function for creating an ActionExecutionException reporting that the action failed
    * due to the exception cause, if there is an additional explanatory message that clarifies the
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 3d12bc4..dc36577 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
@@ -152,6 +152,7 @@
 import com.google.devtools.build.lib.skyframe.DirtinessCheckerUtils.FileDirtinessChecker;
 import com.google.devtools.build.lib.skyframe.ExternalFilesHelper.ExternalFileAction;
 import com.google.devtools.build.lib.skyframe.FileFunction.NonexistentFileReceiver;
+import com.google.devtools.build.lib.skyframe.MetadataConsumerForMetrics.FilesMetricConsumer;
 import com.google.devtools.build.lib.skyframe.PackageFunction.ActionOnIOExceptionReadingBuildFile;
 import com.google.devtools.build.lib.skyframe.PackageFunction.IncrementalityIntent;
 import com.google.devtools.build.lib.skyframe.PackageFunction.LoadedPackageCacheEntry;
@@ -217,7 +218,6 @@
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
@@ -251,10 +251,14 @@
   protected final ExternalFilesHelper externalFilesHelper;
   private final GraphInconsistencyReceiver graphInconsistencyReceiver;
   /**
-   * Tracks the accumulated size of source artifacts read this build. Does not include cached
-   * artifacts, so is not useful on incremental builds.
+   * Measures source artifacts read this build. Does not include cached artifacts, so is less useful
+   * on incremental builds.
    */
-  private final AtomicLong sourceArtifactBytesReadThisBuild = new AtomicLong();
+  private final FilesMetricConsumer sourceArtifactsSeen = new FilesMetricConsumer();
+
+  private final FilesMetricConsumer outputArtifactsSeen = new FilesMetricConsumer();
+  private final FilesMetricConsumer outputArtifactsFromActionCache = new FilesMetricConsumer();
+  private final FilesMetricConsumer topLevelArtifactsMetric = new FilesMetricConsumer();
 
   @Nullable protected OutputService outputService;
 
@@ -441,7 +445,12 @@
     this.ruleClassProvider = (ConfiguredRuleClassProvider) pkgFactory.getRuleClassProvider();
     this.defaultBuildOptions = defaultBuildOptions;
     this.skyframeActionExecutor =
-        new SkyframeActionExecutor(actionKeyContext, statusReporterRef, this::getPathEntries);
+        new SkyframeActionExecutor(
+            actionKeyContext,
+            outputArtifactsSeen,
+            outputArtifactsFromActionCache,
+            statusReporterRef,
+            this::getPathEntries);
     this.artifactFactory =
         new ArtifactFactory(
             /* execRootParent= */ directories.getExecRootBase(),
@@ -586,16 +595,18 @@
     map.put(SkyFunctions.EXTERNAL_PACKAGE, new ExternalPackageFunction(externalPackageHelper));
     map.put(
         SkyFunctions.TARGET_COMPLETION,
-        TargetCompletor.targetCompletionFunction(pathResolverFactory, skyframeActionExecutor));
+        TargetCompletor.targetCompletionFunction(
+            pathResolverFactory, skyframeActionExecutor, topLevelArtifactsMetric));
     map.put(
         SkyFunctions.ASPECT_COMPLETION,
-        AspectCompletor.aspectCompletionFunction(pathResolverFactory, skyframeActionExecutor));
+        AspectCompletor.aspectCompletionFunction(
+            pathResolverFactory, skyframeActionExecutor, topLevelArtifactsMetric));
     map.put(SkyFunctions.TEST_COMPLETION, new TestCompletionFunction());
     map.put(
         Artifact.ARTIFACT,
         new ArtifactFunction(
             () -> !skyframeActionExecutor.actionFileSystemType().inMemoryFileSystem(),
-            sourceArtifactBytesReadThisBuild));
+            sourceArtifactsSeen));
     map.put(
         SkyFunctions.BUILD_INFO_COLLECTION,
         new BuildInfoCollectionFunction(actionKeyContext, artifactFactory));
@@ -2705,7 +2716,10 @@
 
     incrementalBuildMonitor = new SkyframeIncrementalBuildMonitor();
     invalidateTransientErrors();
-    sourceArtifactBytesReadThisBuild.set(0L);
+    sourceArtifactsSeen.reset();
+    outputArtifactsSeen.reset();
+    outputArtifactsFromActionCache.reset();
+    topLevelArtifactsMetric.reset();
   }
 
   private void getActionEnvFromOptions(CoreOptions opt) {
@@ -3028,7 +3042,10 @@
 
   public final ExecutionFinishedEvent createExecutionFinishedEvent() {
     return createExecutionFinishedEventInternal()
-        .setSourceArtifactBytesRead(sourceArtifactBytesReadThisBuild.getAndSet(0L))
+        .setSourceArtifactsRead(sourceArtifactsSeen.toFilesMetricAndReset())
+        .setOutputArtifactsSeen(outputArtifactsSeen.toFilesMetricAndReset())
+        .setOutputArtifactsFromActionCache(outputArtifactsFromActionCache.toFilesMetricAndReset())
+        .setTopLevelArtifacts(topLevelArtifactsMetric.toFilesMetricAndReset())
         .build();
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TargetCompletor.java b/src/main/java/com/google/devtools/build/lib/skyframe/TargetCompletor.java
index 378d34b..3e5143e 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TargetCompletor.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TargetCompletor.java
@@ -42,9 +42,14 @@
         TargetCompletionKey,
         ConfiguredTargetAndData> {
   static SkyFunction targetCompletionFunction(
-      PathResolverFactory pathResolverFactory, SkyframeActionExecutor skyframeActionExecutor) {
+      PathResolverFactory pathResolverFactory,
+      SkyframeActionExecutor skyframeActionExecutor,
+      MetadataConsumerForMetrics.FilesMetricConsumer topLevelArtifactsMetric) {
     return new CompletionFunction<>(
-        pathResolverFactory, new TargetCompletor(), skyframeActionExecutor);
+        pathResolverFactory,
+        new TargetCompletor(),
+        skyframeActionExecutor,
+        topLevelArtifactsMetric);
   }
 
   @Override
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/TreeArtifactValue.java b/src/main/java/com/google/devtools/build/lib/skyframe/TreeArtifactValue.java
index e02e068..777643e 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/TreeArtifactValue.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/TreeArtifactValue.java
@@ -28,6 +28,7 @@
 import com.google.devtools.build.lib.actions.Artifact.SpecialArtifact;
 import com.google.devtools.build.lib.actions.Artifact.TreeFileArtifact;
 import com.google.devtools.build.lib.actions.FileArtifactValue;
+import com.google.devtools.build.lib.actions.FileStateType;
 import com.google.devtools.build.lib.actions.HasDigest;
 import com.google.devtools.build.lib.actions.cache.MetadataDigestUtils;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
@@ -150,11 +151,13 @@
       new TreeArtifactValue(
           MetadataDigestUtils.fromMetadata(ImmutableMap.of()),
           ImmutableSortedMap.of(),
+          0L,
           /*archivedRepresentation=*/ null,
           /*entirelyRemote=*/ false);
 
   private final byte[] digest;
   private final ImmutableSortedMap<TreeFileArtifact, FileArtifactValue> childData;
+  private final long totalChildSize;
 
   /**
    * Optional archived representation of the entire tree artifact which can be sent instead of all
@@ -167,10 +170,12 @@
   private TreeArtifactValue(
       byte[] digest,
       ImmutableSortedMap<TreeFileArtifact, FileArtifactValue> childData,
+      long totalChildSize,
       @Nullable ArchivedRepresentation archivedRepresentation,
       boolean entirelyRemote) {
     this.digest = digest;
     this.childData = childData;
+    this.totalChildSize = totalChildSize;
     this.archivedRepresentation = archivedRepresentation;
     this.entirelyRemote = entirelyRemote;
   }
@@ -194,6 +199,10 @@
     return childData.keySet();
   }
 
+  public long getTotalChildBytes() {
+    return totalChildSize;
+  }
+
   /** Return archived representation of the tree artifact (if present). */
   Optional<ArchivedRepresentation> getArchivedRepresentation() {
     return Optional.ofNullable(archivedRepresentation);
@@ -264,6 +273,7 @@
     return new TreeArtifactValue(
         null,
         ImmutableSortedMap.of(),
+        0L,
         /*archivedRepresentation=*/ null,
         /*entirelyRemote=*/ false) {
       @Override
@@ -475,13 +485,19 @@
       boolean entirelyRemote =
           archivedRepresentation == null || archivedRepresentation.archivedFileValue().isRemote();
 
+      long totalChildSize = 0;
       for (Map.Entry<TreeFileArtifact, FileArtifactValue> childData : finalChildData.entrySet()) {
         // Digest will be deterministic because children are sorted.
         fingerprint.addPath(childData.getKey().getParentRelativePath());
-        childData.getValue().addTo(fingerprint);
+        FileArtifactValue metadata = childData.getValue();
+        metadata.addTo(fingerprint);
 
         // Tolerate a mix of local and remote children (b/152496153#comment80).
-        entirelyRemote &= childData.getValue().isRemote();
+        entirelyRemote &= metadata.isRemote();
+
+        if (metadata.getType() == FileStateType.REGULAR_FILE) {
+          totalChildSize += metadata.getSize();
+        }
       }
 
       if (archivedRepresentation != null) {
@@ -489,7 +505,11 @@
       }
 
       return new TreeArtifactValue(
-          fingerprint.digestAndReset(), finalChildData, archivedRepresentation, entirelyRemote);
+          fingerprint.digestAndReset(),
+          finalChildData,
+          totalChildSize,
+          archivedRepresentation,
+          entirelyRemote);
     }
   }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/actions/util/ActionsTestUtil.java b/src/test/java/com/google/devtools/build/lib/actions/util/ActionsTestUtil.java
index a6d72be..c25ed89 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/util/ActionsTestUtil.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/util/ActionsTestUtil.java
@@ -966,6 +966,11 @@
     }
 
     @Override
+    public TreeArtifactValue getTreeArtifactValue(SpecialArtifact treeArtifact) {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
     public FileArtifactValue constructMetadataForDigest(
         Artifact output, FileStatus statNoFollow, byte[] digest) {
       throw new UnsupportedOperationException();
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTestCase.java b/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTestCase.java
index e533f72..c3c157a 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/ArtifactFunctionTestCase.java
@@ -52,7 +52,6 @@
 import java.io.IOException;
 import java.util.LinkedHashSet;
 import java.util.UUID;
-import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReference;
 import org.junit.Before;
 
@@ -107,7 +106,9 @@
                         new AtomicReference<>(UnixGlob.DEFAULT_SYSCALLS),
                         externalFilesHelper))
                 .put(FileValue.FILE, new FileFunction(pkgLocator))
-                .put(Artifact.ARTIFACT, new ArtifactFunction(() -> true, new AtomicLong()))
+                .put(
+                    Artifact.ARTIFACT,
+                    new ArtifactFunction(() -> true, MetadataConsumerForMetrics.NO_OP))
                 .put(SkyFunctions.ACTION_EXECUTION, new SimpleActionExecutionFunction())
                 .put(
                     SkyFunctions.PACKAGE,
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/BUILD b/src/test/java/com/google/devtools/build/lib/skyframe/BUILD
index 091758a..6b2a59e 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/BUILD
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/BUILD
@@ -40,6 +40,7 @@
         "//src/main/java/com/google/devtools/build/lib/packages",
         "//src/main/java/com/google/devtools/build/lib/rules/platform",
         "//src/main/java/com/google/devtools/build/lib/skyframe:configured_target_key",
+        "//src/main/java/com/google/devtools/build/lib/skyframe:metadata_consumer_for_metrics",
         "//src/main/java/com/google/devtools/build/lib/skyframe:package_value",
         "//src/main/java/com/google/devtools/build/lib/skyframe:sky_functions",
         "//src/main/java/com/google/devtools/build/lib/skyframe:skyframe_cluster",
@@ -202,6 +203,7 @@
         "//src/main/java/com/google/devtools/build/lib/skyframe:glob_value",
         "//src/main/java/com/google/devtools/build/lib/skyframe:local_repository_lookup_value",
         "//src/main/java/com/google/devtools/build/lib/skyframe:managed_directories_knowledge",
+        "//src/main/java/com/google/devtools/build/lib/skyframe:metadata_consumer_for_metrics",
         "//src/main/java/com/google/devtools/build/lib/skyframe:output_store",
         "//src/main/java/com/google/devtools/build/lib/skyframe:package_error_message_value",
         "//src/main/java/com/google/devtools/build/lib/skyframe:package_lookup_function",
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/TimestampBuilderTestCase.java b/src/test/java/com/google/devtools/build/lib/skyframe/TimestampBuilderTestCase.java
index d23b8b3..54edfbf 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/TimestampBuilderTestCase.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/TimestampBuilderTestCase.java
@@ -127,7 +127,6 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
-import java.util.concurrent.atomic.AtomicLong;
 import java.util.concurrent.atomic.AtomicReference;
 import javax.annotation.Nullable;
 import org.junit.Before;
@@ -241,6 +240,8 @@
     final SkyframeActionExecutor skyframeActionExecutor =
         new SkyframeActionExecutor(
             actionKeyContext,
+            MetadataConsumerForMetrics.NO_OP,
+            MetadataConsumerForMetrics.NO_OP,
             new AtomicReference<>(statusReporter),
             /*sourceRootSupplier=*/ () -> ImmutableList.of());
 
@@ -262,7 +263,9 @@
                         new AtomicReference<>(UnixGlob.DEFAULT_SYSCALLS),
                         externalFilesHelper))
                 .put(FileValue.FILE, new FileFunction(pkgLocator))
-                .put(Artifact.ARTIFACT, new ArtifactFunction(() -> true, new AtomicLong()))
+                .put(
+                    Artifact.ARTIFACT,
+                    new ArtifactFunction(() -> true, MetadataConsumerForMetrics.NO_OP))
                 .put(
                     SkyFunctions.ACTION_EXECUTION,
                     new ActionExecutionFunction(skyframeActionExecutor, directories, tsgmRef))