Replace all `Duration` fields in `SpawnResult` into `int`-s.

We already ignore non-set state of plenty of metrics before (e.g. `SpawnMetrics` are set to `Duration.ZERO` by default). So losing of this state should not affect execution.

PiperOrigin-RevId: 511758760
Change-Id: Iefa86151e5b0611712972d3f4dff29c8984b4841
diff --git a/src/main/java/com/google/devtools/build/lib/actions/ActionResult.java b/src/main/java/com/google/devtools/build/lib/actions/ActionResult.java
index d3ffa70..8d3304e 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/ActionResult.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/ActionResult.java
@@ -14,16 +14,15 @@
 
 package com.google.devtools.build.lib.actions;
 
-import static com.google.common.base.Preconditions.checkState;
 
 import com.google.auto.value.AutoValue;
 import com.google.common.collect.ImmutableList;
-import java.time.Duration;
 import java.util.List;
 import java.util.function.Function;
 import javax.annotation.Nullable;
 
 /** Holds the result(s) of an action's execution. */
+@SuppressWarnings("GoodTime") // Use ints instead of Durations to improve build time (cl/505728570)
 @AutoValue
 public abstract class ActionResult {
 
@@ -37,34 +36,6 @@
   public static Builder builder() {
     return new AutoValue_ActionResult.Builder();
   }
-
-  /**
-   * Returns the cumulative time taken by a series of {@link SpawnResult}s.
-   *
-   * @param getSpawnResultExecutionTime a selector that returns either the wall, user or system time
-   *     for each {@link SpawnResult} being considered
-   * @return the cumulative time, or null if no spawn results contained this time
-   */
-  @Nullable
-  private Duration getCumulativeTime(Function<SpawnResult, Duration> getSpawnResultExecutionTime) {
-    Long totalMillis = null;
-    for (SpawnResult spawnResult : spawnResults()) {
-      Duration executionTime = getSpawnResultExecutionTime.apply(spawnResult);
-      if (executionTime != null) {
-        if (totalMillis == null) {
-          totalMillis = executionTime.toMillis();
-        } else {
-          totalMillis += executionTime.toMillis();
-        }
-      }
-    }
-    if (totalMillis == null) {
-      return null;
-    } else {
-      return Duration.ofMillis(totalMillis);
-    }
-  }
-
   /**
    * Returns the cumulative total of long values taken from a series of {@link SpawnResult}s.
    *
@@ -88,36 +59,48 @@
   }
 
   /**
+   * Returns the cumulative total of int values taken from a series of {@link SpawnResult}s.
+   *
+   * @param getSpawnResultIntValue a selector that returns an int value for each {@link SpawnResult}
+   *     being considered
+   * @return the total value of this values
+   */
+  private int getCumulativeInt(Function<SpawnResult, Integer> getSpawnResultIntValue) {
+    int intTotal = 0;
+    for (SpawnResult spawnResult : spawnResults()) {
+      intTotal += getSpawnResultIntValue.apply(spawnResult);
+    }
+    return intTotal;
+  }
+
+  /**
    * Returns the cumulative command execution wall time for the {@link Action}.
    *
-   * @return the cumulative measurement, or null in case of execution errors or when the measurement
+   * @return the cumulative measurement, or zero in case of execution errors or when the measurement
    *     is not implemented for the current platform
    */
-  @Nullable
-  public Duration cumulativeCommandExecutionWallTime() {
-    return getCumulativeTime(SpawnResult::getWallTime);
+  public int cumulativeCommandExecutionWallTimeInMs() {
+    return getCumulativeInt(SpawnResult::getWallTimeInMs);
   }
 
   /**
    * Returns the cumulative command execution user time for the {@link Action}.
    *
-   * @return the cumulative measurement, or null in case of execution errors or when the measurement
+   * @return the cumulative measurement, or zero in case of execution errors or when the measurement
    *     is not implemented for the current platform
    */
-  @Nullable
-  public Duration cumulativeCommandExecutionUserTime() {
-    return getCumulativeTime(SpawnResult::getUserTime);
+  public int cumulativeCommandExecutionUserTimeInMs() {
+    return getCumulativeInt(SpawnResult::getUserTimeInMs);
   }
 
   /**
    * Returns the cumulative command execution system time for the {@link Action}.
    *
-   * @return the cumulative measurement, or null in case of execution errors or when the measurement
+   * @return the cumulative measurement, or zero in case of execution errors or when the measurement
    *     is not implemented for the current platform
    */
-  @Nullable
-  public Duration cumulativeCommandExecutionSystemTime() {
-    return getCumulativeTime(SpawnResult::getSystemTime);
+  public int cumulativeCommandExecutionSystemTimeInMs() {
+    return getCumulativeInt(SpawnResult::getSystemTimeInMs);
   }
 
   /**
@@ -169,111 +152,101 @@
   /**
    * Returns the cumulative spawns total time for the {@link Action}.
    *
-   * @return the cumulative measurement, or null in case of execution errors or when the measurement
+   * @return the cumulative measurement, or zero in case of execution errors or when the measurement
    *     is not implemented for the current platform
    */
-  @Nullable
-  public Duration cumulativeSpawnsTotalTime() {
-    return getCumulativeTime(s -> s.getMetrics().totalTime());
+  public int cumulativeSpawnsTotalTimeInMs() {
+    return getCumulativeInt(s -> s.getMetrics().totalTimeInMs());
   }
 
   /**
    * Returns the cumulative spawns parse time for the {@link Action}.
    *
-   * @return the cumulative measurement, or null in case of execution errors or when the measurement
+   * @return the cumulative measurement, or zero in case of execution errors or when the measurement
    *     is not implemented for the current platform
    */
-  @Nullable
-  public Duration cumulativeSpawnsParseTime() {
-    return getCumulativeTime(s -> s.getMetrics().parseTime());
+  public int cumulativeSpawnsParseTimeInMs() {
+    return getCumulativeInt(s -> s.getMetrics().parseTimeInMs());
   }
 
   /**
    * Returns the cumulative spawns network time for the {@link Action}.
    *
-   * @return the cumulative measurement, or null in case of execution errors or when the measurement
+   * @return the cumulative measurement, or zero in case of execution errors or when the measurement
    *     is not implemented for the current platform
    */
-  @Nullable
-  public Duration cumulativeSpawnsNetworkTime() {
-    return getCumulativeTime(s -> s.getMetrics().networkTime());
+  public int cumulativeSpawnsNetworkTimeInMs() {
+    return getCumulativeInt(s -> s.getMetrics().networkTimeInMs());
   }
 
   /**
    * Returns the cumulative spawns fetch time for the {@link Action}.
    *
-   * @return the cumulative measurement, or null in case of execution errors or when the measurement
+   * @return the cumulative measurement, or zero in case of execution errors or when the measurement
    *     is not implemented for the current platform
    */
-  @Nullable
-  public Duration cumulativeSpawnsFetchTime() {
-    return getCumulativeTime(s -> s.getMetrics().fetchTime());
+  public int cumulativeSpawnsFetchTimeInMs() {
+    return getCumulativeInt(s -> s.getMetrics().fetchTimeInMs());
   }
 
   /**
    * Returns the cumulative spawns queue time for the {@link Action}.
    *
-   * @return the cumulative measurement, or null in case of execution errors or when the measurement
+   * @return the cumulative measurement, or zero in case of execution errors or when the measurement
    *     is not implemented for the current platform
    */
-  @Nullable
-  public Duration cumulativeSpawnsQueueTime() {
-    return getCumulativeTime(s -> s.getMetrics().queueTime());
+  public int cumulativeSpawnsQueueTimeInMs() {
+    return getCumulativeInt(s -> s.getMetrics().queueTimeInMs());
   }
 
   /**
    * Returns the cumulative spawns setup time for the {@link Action}.
    *
-   * @return the cumulative measurement, or null in case of execution errors or when the measurement
+   * @return the cumulative measurement, or zero in case of execution errors or when the measurement
    *     is not implemented for the current platform
    */
-  @Nullable
-  public Duration cumulativeSpawnsSetupTime() {
-    return getCumulativeTime(s -> s.getMetrics().setupTime());
+  public int cumulativeSpawnsSetupTimeInMs() {
+    return getCumulativeInt(s -> s.getMetrics().setupTimeInMs());
   }
 
   /**
    * Returns the cumulative spawns upload time for the {@link Action}.
    *
-   * @return the cumulative measurement, or null in case of execution errors or when the measurement
+   * @return the cumulative measurement, or zero in case of execution errors or when the measurement
    *     is not implemented for the current platform
    */
-  @Nullable
-  public Duration cumulativeSpawnsUploadTime() {
-    return getCumulativeTime(s -> s.getMetrics().uploadTime());
+  public int cumulativeSpawnsUploadTimeInMs() {
+    return getCumulativeInt(s -> s.getMetrics().uploadTimeInMs());
   }
 
   /**
    * Returns the cumulative spawns execution wall time for the {@link Action}.
    *
-   * @return the cumulative measurement, or null in case of execution errors or when the measurement
+   * @return the cumulative measurement, or zero in case of execution errors or when the measurement
    *     is not implemented for the current platform
    */
-  @Nullable
-  public Duration cumulativeExecutionWallTime() {
-    return getCumulativeTime(s -> s.getMetrics().executionWallTime());
+  public int cumulativeExecutionWallTimeInMs() {
+    return getCumulativeInt(s -> s.getMetrics().executionWallTimeInMs());
   }
 
   /**
    * Returns the cumulative spawns process output time for the {@link Action}.
    *
-   * @return the cumulative measurement, or null in case of execution errors or when the measurement
+   * @return the cumulative measurement, or zero in case of execution errors or when the measurement
    *     is not implemented for the current platform
    */
-  @Nullable
-  public Duration cumulativeProcessOutputTime() {
-    return getCumulativeTime(s -> s.getMetrics().processOutputsTime());
+  public int cumulativeProcessOutputTimeInMs() {
+    return getCumulativeInt(s -> s.getMetrics().processOutputsTimeInMs());
   }
 
   /**
    * Returns the cumulative spawns retry time for the {@link Action}.
    *
-   * @return the cumulative measurement, or null in case of execution errors or when the measurement
+   * @return the cumulative measurement, or zero in case of execution errors or when the measurement
    *     is not implemented for the current platform
    */
-  @Nullable
-  public Duration cumulativeRetryTime() {
-    return getCumulativeTime(s -> s.getMetrics().retryTime());
+  public int cumulativeRetryTimeInMs() {
+    return getCumulativeInt(s -> s.getMetrics().retryTimeInMs());
   }
 
   /**
@@ -292,24 +265,15 @@
   /**
    * Returns the cumulative command execution CPU time for the {@link Action}.
    *
-   * @return the cumulative measurement, or null in case of execution errors or when the measurement
+   * @return the cumulative measurement, or zero in case of execution errors or when the measurement
    *     is not implemented for the current platform
    */
-  @Nullable
-  public Duration cumulativeCommandExecutionCpuTime() {
-    Duration userTime = cumulativeCommandExecutionUserTime();
-    Duration systemTime = cumulativeCommandExecutionSystemTime();
+  public int cumulativeCommandExecutionCpuTimeInMs() {
+    int userTime = cumulativeCommandExecutionUserTimeInMs();
+    int systemTime = cumulativeCommandExecutionSystemTimeInMs();
 
-    if (userTime == null && systemTime == null) {
-      return null;
-    } else if (userTime != null && systemTime == null) {
-      return userTime;
-    } else if (userTime == null && systemTime != null) {
-      return systemTime;
-    } else {
-      checkState(userTime != null && systemTime != null);
-      return userTime.plus(systemTime);
-    }
+    // If userTime or systemTime is nondefined (=0), then it will not change a result
+    return userTime + systemTime;
   }
 
   /** Creates an ActionResult given a list of SpawnResults. */
diff --git a/src/main/java/com/google/devtools/build/lib/actions/AggregatedSpawnMetrics.java b/src/main/java/com/google/devtools/build/lib/actions/AggregatedSpawnMetrics.java
index 7555ecc..c93915a 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/AggregatedSpawnMetrics.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/AggregatedSpawnMetrics.java
@@ -26,6 +26,7 @@
 import javax.annotation.Nullable;
 
 /** Metrics aggregated per execution kind. */
+@SuppressWarnings("GoodTime") // Use ints instead of Durations to improve build time (cl/505728570)
 public final class AggregatedSpawnMetrics {
 
   public static final AggregatedSpawnMetrics EMPTY = new AggregatedSpawnMetrics(ImmutableMap.of());
@@ -131,10 +132,10 @@
    * <p>Example: {@code getTotalDuration(SpawnMetrics::queueTime)} will give the total queue time
    * across all execution kinds.
    */
-  public Duration getTotalDuration(Function<SpawnMetrics, Duration> extract) {
-    Duration result = Duration.ZERO;
+  public int getTotalDuration(Function<SpawnMetrics, Integer> extract) {
+    int result = 0;
     for (SpawnMetrics metric : metricsMap.values()) {
-      result = result.plus(extract.apply(metric));
+      result += extract.apply(metric);
     }
     return result;
   }
@@ -156,7 +157,9 @@
   public String toString(Duration total, boolean summary) {
     // For now keep compatibility with the old output and only report the remote execution.
     // TODO(michalt): Change this once the local and worker executions populate more metrics.
-    return SpawnMetrics.ExecKind.REMOTE + " " + getRemoteMetrics().toString(total, summary);
+    return SpawnMetrics.ExecKind.REMOTE
+        + " "
+        + getRemoteMetrics().toString((int) total.toMillis(), summary);
   }
 
   /** Builder for {@link AggregatedSpawnMetrics}. */
diff --git a/src/main/java/com/google/devtools/build/lib/actions/SpawnMetrics.java b/src/main/java/com/google/devtools/build/lib/actions/SpawnMetrics.java
index f3f5e94..0f7392c 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/SpawnMetrics.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/SpawnMetrics.java
@@ -16,7 +16,6 @@
 import com.google.common.base.Joiner;
 import com.google.common.base.Preconditions;
 import com.google.errorprone.annotations.CanIgnoreReturnValue;
-import java.time.Duration;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -24,6 +23,7 @@
 import java.util.Map;
 
 /** Timing, size, and memory statistics for a Spawn execution. */
+@SuppressWarnings("GoodTime") // Use ints instead of Durations to improve build time (cl/505728570)
 public final class SpawnMetrics {
 
   /** Indicates whether the metrics correspond to the remote, local or worker execution. */
@@ -52,21 +52,25 @@
   /** Any non important stats < than 10% will not be shown in the summary. */
   private static final double STATS_SHOW_THRESHOLD = 0.10;
 
-  public static SpawnMetrics forLocalExecution(Duration wallTime) {
-    return Builder.forLocalExec().setTotalTime(wallTime).setExecutionWallTime(wallTime).build();
+  public static SpawnMetrics forLocalExecution(int wallTimeInMs) {
+    return Builder.forLocalExec()
+        .setTotalTimeInMs(wallTimeInMs)
+        .setExecutionWallTimeInMs(wallTimeInMs)
+        .build();
   }
 
   private final ExecKind execKind;
-  private final Duration totalTime;
-  private final Duration parseTime;
-  private final Duration fetchTime;
-  private final Duration queueTime;
-  private final Duration uploadTime;
-  private final Duration setupTime;
-  private final Duration executionWallTime;
-  private final Duration processOutputsTime;
-  private final Duration networkTime;
-  private final Map<Integer, Duration> retryTime;
+  private final int totalTimeInMs;
+  private final int parseTimeInMs;
+  private final int fetchTimeInMs;
+  private final int queueTimeInMs;
+  private final int uploadTimeInMs;
+  private final int setupTimeInMs;
+  private final int executionWallTimeInMs;
+  private final int processOutputsTimeInMs;
+  private final int networkTimeInMs;
+  // error code to duration in ms
+  private final Map<Integer, Integer> retryTimeInMs;
   private final long inputBytes;
   private final long inputFiles;
   private final long memoryEstimateBytes;
@@ -75,20 +79,20 @@
   private final long outputBytesLimit;
   private final long outputFilesLimit;
   private final long memoryBytesLimit;
-  private final Duration timeLimit;
+  private final int timeLimitInMs;
 
   private SpawnMetrics(Builder builder) {
     this.execKind = builder.execKind;
-    this.totalTime = builder.totalTime;
-    this.parseTime = builder.parseTime;
-    this.networkTime = builder.networkTime;
-    this.fetchTime = builder.fetchTime;
-    this.queueTime = builder.queueTime;
-    this.setupTime = builder.setupTime;
-    this.uploadTime = builder.uploadTime;
-    this.executionWallTime = builder.executionWallTime;
-    this.retryTime = builder.retryTime;
-    this.processOutputsTime = builder.processOutputsTime;
+    this.totalTimeInMs = builder.totalTimeInMs;
+    this.parseTimeInMs = builder.parseTimeInMs;
+    this.networkTimeInMs = builder.networkTimeInMs;
+    this.fetchTimeInMs = builder.fetchTimeInMs;
+    this.queueTimeInMs = builder.queueTimeInMs;
+    this.setupTimeInMs = builder.setupTimeInMs;
+    this.uploadTimeInMs = builder.uploadTimeInMs;
+    this.executionWallTimeInMs = builder.executionWallTimeInMs;
+    this.retryTimeInMs = builder.retryTimeInMs;
+    this.processOutputsTimeInMs = builder.processOutputsTimeInMs;
     this.inputBytes = builder.inputBytes;
     this.inputFiles = builder.inputFiles;
     this.memoryEstimateBytes = builder.memoryEstimateBytes;
@@ -97,7 +101,7 @@
     this.outputBytesLimit = builder.outputBytesLimit;
     this.outputFilesLimit = builder.outputFilesLimit;
     this.memoryBytesLimit = builder.memoryBytesLimit;
-    this.timeLimit = builder.timeLimit;
+    this.timeLimitInMs = builder.timeLimitInMs;
   }
 
   /** The kind of execution the metrics refer to (remote/local/worker). */
@@ -107,91 +111,96 @@
 
   /** Returns true if {@link #totalTime()} is zero. */
   public boolean isEmpty() {
-    return totalTime.isZero();
+    return totalTimeInMs == 0;
   }
 
   /**
-   * Total (measured locally) wall time spent running a spawn. This should be at least as large as
-   * all the other times summed together.
+   * Total (measured locally) wall time in milliseconds spent running a spawn. This should be at
+   * least as large as all the other times summed together.
    */
-  public Duration totalTime() {
-    return totalTime;
+  public int totalTimeInMs() {
+    return totalTimeInMs;
   }
 
   /**
-   * Total time spent getting on network. This includes time getting network-side errors and the
-   * time of the round-trip, found by taking the difference of wall time here and the server time
-   * reported by the RPC. This is 0 for locally executed spawns.
+   * Total time in milliseconds spent getting on network. This includes time getting network-side
+   * errors and the time of the round-trip, found by taking the difference of wall time here and the
+   * server time reported by the RPC. This is 0 for locally executed spawns.
    */
-  public Duration networkTime() {
-    return networkTime;
+  public int networkTimeInMs() {
+    return networkTimeInMs;
   }
 
-  /** Total time waiting in queues. Includes queue time for any failed attempts. */
-  public Duration queueTime() {
-    return queueTime;
-  }
-
-  /** The time spent transferring files to the backends. This is 0 for locally executed spawns. */
-  public Duration uploadTime() {
-    return uploadTime;
+  /** Total time in milliseconds waiting in queues. Includes queue time for any failed attempts. */
+  public int queueTimeInMs() {
+    return queueTimeInMs;
   }
 
   /**
-   * The time required to setup the environment in which the spawn is run. This may be 0 for locally
-   * executed spawns, or may include time to setup a sandbox or other environment. Does not include
-   * failed attempts.
+   * The time in milliseconds spent transferring files to the backends. This is 0 for locally
+   * executed spawns.
    */
-  public Duration setupTime() {
-    return setupTime;
+  public int uploadTimeInMs() {
+    return uploadTimeInMs;
+  }
+
+  /**
+   * The time in milliseconds required to setup the environment in which the spawn is run. This may
+   * be 0 for locally executed spawns, or may include time to setup a sandbox or other environment.
+   * Does not include failed attempts.
+   */
+  public int setupTimeInMs() {
+    return setupTimeInMs;
   }
 
   /** Time spent running the subprocess. */
-  public Duration executionWallTime() {
-    return executionWallTime;
+  public int executionWallTimeInMs() {
+    return executionWallTimeInMs;
   }
 
   /**
-   * The time taken to convert the spawn into a network request, e.g., collecting runfiles, and
-   * digests for all input files.
+   * The time in milliseconds taken to convert the spawn into a network request, e.g., collecting
+   * runfiles, and digests for all input files.
    */
-  public Duration parseTime() {
-    return parseTime;
+  public int parseTimeInMs() {
+    return parseTimeInMs;
   }
 
-  /** Total time spent fetching remote outputs. */
-  public Duration fetchTime() {
-    return fetchTime;
+  /** Total time in milliseconds spent fetching remote outputs. */
+  public int fetchTimeInMs() {
+    return fetchTimeInMs;
   }
 
   /** Time spent in previous failed attempts. Does not include queue time. */
-  public Duration retryTime() {
-    return retryTime.values().stream().reduce(Duration.ZERO, Duration::plus);
+  public int retryTimeInMs() {
+    return retryTimeInMs.values().stream().reduce(0, Integer::sum);
   }
 
   /** Time spent in previous failed attempts, keyed by error code. Does not include queue time. */
-  public Map<Integer, Duration> retryTimeByError() {
-    return retryTime;
+  public Map<Integer, Integer> retryTimeByError() {
+    return retryTimeInMs;
   }
 
-
   /** Time spend by the execution framework on processing outputs. */
-  public Duration processOutputsTime() {
-    return processOutputsTime;
+  public int processOutputsTimeInMs() {
+    return processOutputsTimeInMs;
   }
 
-  /** Any time that is not measured by a more specific component, out of {@code totalTime()}. */
-  public Duration otherTime() {
-    return totalTime
-        .minus(parseTime)
-        .minus(networkTime)
-        .minus(queueTime)
-        .minus(uploadTime)
-        .minus(setupTime)
-        .minus(executionWallTime)
-        .minus(fetchTime)
-        .minus(retryTime())
-        .minus(processOutputsTime);
+  /**
+   * Any time in milliseconds that is not measured by a more specific component, out of {@code
+   * totalTime()}.
+   */
+  public int otherTimeInMs() {
+    return totalTimeInMs
+        - parseTimeInMs
+        - networkTimeInMs
+        - queueTimeInMs
+        - uploadTimeInMs
+        - setupTimeInMs
+        - executionWallTimeInMs
+        - fetchTimeInMs
+        - retryTimeInMs()
+        - processOutputsTimeInMs;
   }
 
   /** Total size in bytes of inputs or 0 if unavailable. */
@@ -234,33 +243,33 @@
     return memoryBytesLimit;
   }
 
-  /** Time limit or 0 if unavailable. */
-  public Duration timeLimit() {
-    return timeLimit;
+  /** Time limit in milliseconds or 0 if unavailable. */
+  public int timeLimitInMs() {
+    return timeLimitInMs;
   }
 
   /**
    * Generates a String representation of the stats.
    *
-   * @param total total time used to compute the percentages
+   * @param total total time in milliseconds used to compute the percentages
    * @param summary whether to exclude input file count and sizes, and memory estimates
    */
-  public String toString(Duration total, boolean summary) {
+  public String toString(int total, boolean summary) {
     StringBuilder sb = new StringBuilder();
     sb.append("(");
-    sb.append(prettyPercentage(totalTime, total));
+    sb.append(prettyPercentage(totalTimeInMs, total));
     sb.append(" of the time): [");
     List<String> stats = new ArrayList<>(8);
-    addStatToString(stats, "parse", !summary, parseTime, total);
-    addStatToString(stats, "queue", true, queueTime, total);
-    addStatToString(stats, "network", !summary, networkTime, total);
-    addStatToString(stats, "upload", !summary, uploadTime, total);
-    addStatToString(stats, "setup", true, setupTime, total);
-    addStatToString(stats, "process", true, executionWallTime, total);
-    addStatToString(stats, "fetch", !summary, fetchTime, total);
-    addStatToString(stats, "retry", !summary, retryTime(), total);
-    addStatToString(stats, "processOutputs", !summary, processOutputsTime, total);
-    addStatToString(stats, "other", !summary, otherTime(), total);
+    addStatToString(stats, "parse", !summary, parseTimeInMs, total);
+    addStatToString(stats, "queue", true, queueTimeInMs, total);
+    addStatToString(stats, "network", !summary, networkTimeInMs, total);
+    addStatToString(stats, "upload", !summary, uploadTimeInMs, total);
+    addStatToString(stats, "setup", true, setupTimeInMs, total);
+    addStatToString(stats, "process", true, executionWallTimeInMs, total);
+    addStatToString(stats, "fetch", !summary, fetchTimeInMs, total);
+    addStatToString(stats, "retry", !summary, retryTimeInMs(), total);
+    addStatToString(stats, "processOutputs", !summary, processOutputsTimeInMs, total);
+    addStatToString(stats, "other", !summary, otherTimeInMs(), total);
     if (!summary) {
       stats.add("input files: " + inputFiles);
       stats.add("input bytes: " + inputBytes);
@@ -270,7 +279,7 @@
       stats.add("output files limit: " + outputFilesLimit);
       stats.add("output bytes limit: " + outputBytesLimit);
       stats.add("memory limit: " + memoryBytesLimit);
-      stats.add("time limit: " + timeLimit.getSeconds() + " seconds");
+      stats.add("time limit: " + timeLimitInMs / 1000 + " seconds");
     }
     Joiner.on(", ").appendTo(sb, stats);
     sb.append("]");
@@ -282,15 +291,14 @@
    * forceShow} is set to false it will only show if it is above certain threshold.
    */
   private static void addStatToString(
-      List<String> strings, String name, boolean forceShow, Duration time, Duration totalTime) {
+      List<String> strings, String name, boolean forceShow, int time, int totalTime) {
     if (forceShow || isAboveThreshold(time, totalTime)) {
       strings.add(name + ": " + prettyPercentage(time, totalTime));
     }
   }
 
-  private static boolean isAboveThreshold(Duration time, Duration totalTime) {
-    return totalTime.toMillis() > 0
-        && (((float) time.toMillis() / totalTime.toMillis()) >= STATS_SHOW_THRESHOLD);
+  private static boolean isAboveThreshold(int time, int totalTime) {
+    return totalTime > 0 && (((float) time / totalTime) >= STATS_SHOW_THRESHOLD);
   }
 
   /**
@@ -298,28 +306,28 @@
    *
    * @return formatted percentage string or "N/A" if result is undefined
    */
-  private static String prettyPercentage(Duration duration, Duration total) {
+  private static String prettyPercentage(int duration, int total) {
     // Duration.toMillis() != 0 does not imply !Duration.isZero() (due to truncation).
-    if (total.toMillis() == 0) {
+    if (total == 0) {
       // Return "not available" string if total is 0 and result is undefined.
       return "N/A";
     }
-    return String.format(Locale.US, "%.2f%%", duration.toMillis() * 100.0 / total.toMillis());
+    return String.format(Locale.US, "%.2f%%", duration * 100.0 / total);
   }
 
   /** Builder class for SpawnMetrics. */
   public static class Builder {
     private ExecKind execKind = null;
-    private Duration totalTime = Duration.ZERO;
-    private Duration parseTime = Duration.ZERO;
-    private Duration networkTime = Duration.ZERO;
-    private Duration fetchTime = Duration.ZERO;
-    private Duration queueTime = Duration.ZERO;
-    private Duration setupTime = Duration.ZERO;
-    private Duration uploadTime = Duration.ZERO;
-    private Duration executionWallTime = Duration.ZERO;
-    private Duration processOutputsTime = Duration.ZERO;
-    private Map<Integer, Duration> retryTime = new HashMap<>();
+    private int totalTimeInMs = 0;
+    private int parseTimeInMs = 0;
+    private int networkTimeInMs = 0;
+    private int fetchTimeInMs = 0;
+    private int queueTimeInMs = 0;
+    private int setupTimeInMs = 0;
+    private int uploadTimeInMs = 0;
+    private int executionWallTimeInMs = 0;
+    private int processOutputsTimeInMs = 0;
+    private Map<Integer, Integer> retryTimeInMs = new HashMap<>();
     private long inputBytes = 0;
     private long inputFiles = 0;
     private long memoryEstimateBytes = 0;
@@ -328,7 +336,7 @@
     private long outputBytesLimit = 0;
     private long outputFilesLimit = 0;
     private long memoryBytesLimit = 0;
-    private Duration timeLimit = Duration.ZERO;
+    private int timeLimitInMs = 0;
 
     public static Builder forLocalExec() {
       return forExec(ExecKind.LOCAL);
@@ -367,75 +375,75 @@
     }
 
     @CanIgnoreReturnValue
-    public Builder setTotalTime(Duration totalTime) {
-      this.totalTime = totalTime;
+    public Builder setTotalTimeInMs(int totalTimeInMs) {
+      this.totalTimeInMs = totalTimeInMs;
       return this;
     }
 
     @CanIgnoreReturnValue
-    public Builder setParseTime(Duration parseTime) {
-      this.parseTime = parseTime;
+    public Builder setParseTimeInMs(int parseTimeInMs) {
+      this.parseTimeInMs = parseTimeInMs;
       return this;
     }
 
     @CanIgnoreReturnValue
-    public Builder setNetworkTime(Duration networkTime) {
-      this.networkTime = networkTime;
+    public Builder setNetworkTimeInMs(int networkTimeInMs) {
+      this.networkTimeInMs = networkTimeInMs;
       return this;
     }
 
     @CanIgnoreReturnValue
-    public Builder setFetchTime(Duration fetchTime) {
-      this.fetchTime = fetchTime;
+    public Builder setFetchTimeInMs(int fetchTimeInMs) {
+      this.fetchTimeInMs = fetchTimeInMs;
       return this;
     }
 
     @CanIgnoreReturnValue
-    public Builder setQueueTime(Duration queueTime) {
-      this.queueTime = queueTime;
+    public Builder setQueueTimeInMs(int queueTimeInMs) {
+      this.queueTimeInMs = queueTimeInMs;
       return this;
     }
 
     @CanIgnoreReturnValue
-    public Builder setSetupTime(Duration setupTime) {
-      this.setupTime = setupTime;
+    public Builder setSetupTimeInMs(int setupTimeInMs) {
+      this.setupTimeInMs = setupTimeInMs;
       return this;
     }
 
     @CanIgnoreReturnValue
-    public Builder addSetupTime(Duration setupTime) {
-      this.setupTime = this.setupTime.plus(setupTime);
+    public Builder addSetupTimeInMs(int setupTimeInMs) {
+      this.setupTimeInMs = this.setupTimeInMs + setupTimeInMs;
       return this;
     }
 
     @CanIgnoreReturnValue
-    public Builder setUploadTime(Duration uploadTime) {
-      this.uploadTime = uploadTime;
+    public Builder setUploadTimeInMs(int uploadTimeInMs) {
+      this.uploadTimeInMs = uploadTimeInMs;
       return this;
     }
 
     @CanIgnoreReturnValue
-    public Builder setExecutionWallTime(Duration executionWallTime) {
-      this.executionWallTime = executionWallTime;
+    public Builder setExecutionWallTimeInMs(int executionWallTimeInMs) {
+      this.executionWallTimeInMs = executionWallTimeInMs;
       return this;
     }
 
     @CanIgnoreReturnValue
-    public Builder addRetryTime(int errorCode, Duration retryTime) {
-      Duration d = this.retryTime.getOrDefault(errorCode, Duration.ZERO);
-      this.retryTime.put(errorCode, d.plus(retryTime));
+    public Builder addRetryTimeInMs(int errorCode, int retryTimeInMs) {
+      Integer t = this.retryTimeInMs.getOrDefault(errorCode, 0);
+      this.retryTimeInMs.put(errorCode, t + retryTimeInMs);
       return this;
     }
 
     @CanIgnoreReturnValue
-    public Builder setRetryTime(Map<Integer, Duration> retryTime) {
-      this.retryTime = new HashMap<>(retryTime);
+    public Builder setRetryTimeInMs(Map<Integer, Integer> retryTimeInMs) {
+      this.retryTimeInMs = new HashMap<>(retryTimeInMs);
       return this;
     }
 
     @CanIgnoreReturnValue
-    public Builder setProcessOutputsTime(Duration processOutputsTime) {
-      this.processOutputsTime = processOutputsTime;
+    public Builder setProcessOutputsTimeInMs(int processOutputsTimeInMs) {
+      this.processOutputsTimeInMs = processOutputsTimeInMs;
       return this;
     }
 
@@ -488,25 +496,25 @@
     }
 
     @CanIgnoreReturnValue
-    public Builder setTimeLimit(Duration timeLimit) {
-      this.timeLimit = timeLimit;
+    public Builder setTimeLimitInMs(int timeLimitInMs) {
+      this.timeLimitInMs = timeLimitInMs;
       return this;
     }
 
     @CanIgnoreReturnValue
     public Builder addDurations(SpawnMetrics metric) {
-      totalTime = totalTime.plus(metric.totalTime());
-      parseTime = parseTime.plus(metric.parseTime());
-      networkTime = networkTime.plus(metric.networkTime());
-      fetchTime = fetchTime.plus(metric.fetchTime());
-      queueTime = queueTime.plus(metric.queueTime());
-      uploadTime = uploadTime.plus(metric.uploadTime());
-      setupTime = setupTime.plus(metric.setupTime());
-      executionWallTime = executionWallTime.plus(metric.executionWallTime());
-      for (Map.Entry<Integer, Duration> entry : metric.retryTime.entrySet()) {
-        addRetryTime(entry.getKey().intValue(), entry.getValue());
+      totalTimeInMs += metric.totalTimeInMs();
+      parseTimeInMs += metric.parseTimeInMs();
+      networkTimeInMs += metric.networkTimeInMs();
+      fetchTimeInMs += metric.fetchTimeInMs();
+      queueTimeInMs += metric.queueTimeInMs();
+      uploadTimeInMs += metric.uploadTimeInMs();
+      setupTimeInMs += metric.setupTimeInMs();
+      executionWallTimeInMs += metric.executionWallTimeInMs();
+      for (Map.Entry<Integer, Integer> entry : metric.retryTimeInMs.entrySet()) {
+        addRetryTimeInMs(entry.getKey().intValue(), entry.getValue());
       }
-      processOutputsTime = processOutputsTime.plus(metric.processOutputsTime());
+      processOutputsTimeInMs += metric.processOutputsTimeInMs();
       return this;
     }
 
@@ -520,7 +528,7 @@
       outputFilesLimit += metric.outputFilesLimit();
       outputBytesLimit += metric.outputBytesLimit();
       memoryBytesLimit += metric.memoryLimit();
-      timeLimit = timeLimit.plus(metric.timeLimit());
+      timeLimitInMs += metric.timeLimitInMs();
       return this;
     }
 
@@ -534,8 +542,7 @@
       outputFilesLimit = Long.max(outputFilesLimit, metric.outputFilesLimit());
       outputBytesLimit = Long.max(outputBytesLimit, metric.outputBytesLimit());
       memoryBytesLimit = Long.max(memoryBytesLimit, metric.memoryLimit());
-      timeLimit =
-          Duration.ofSeconds(Long.max(timeLimit.getSeconds(), metric.timeLimit().getSeconds()));
+      timeLimitInMs = Integer.max(timeLimitInMs, metric.timeLimitInMs());
       return this;
     }
   }
diff --git a/src/main/java/com/google/devtools/build/lib/actions/SpawnResult.java b/src/main/java/com/google/devtools/build/lib/actions/SpawnResult.java
index b25c0f0..70c5068 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/SpawnResult.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/SpawnResult.java
@@ -27,12 +27,12 @@
 import com.google.errorprone.annotations.CanIgnoreReturnValue;
 import com.google.protobuf.ByteString;
 import java.io.InputStream;
-import java.time.Duration;
 import java.time.Instant;
 import java.util.Locale;
 import javax.annotation.Nullable;
 
 /** The result of a {@link Spawn}'s execution. */
+@SuppressWarnings("GoodTime") // Use ints instead of Durations to improve build time (cl/505728570)
 public interface SpawnResult {
 
   int POSIX_TIMEOUT_EXIT_CODE = /*SIGNAL_BASE=*/ 128 + /*SIGALRM=*/ 14;
@@ -183,29 +183,26 @@
   /**
    * Returns the wall time taken by the {@link Spawn}'s execution.
    *
-   * @return the measurement, or null in case of execution errors or when the measurement is not
+   * @return the measurement, or 0 in case of execution errors or when the measurement is not
    *     implemented for the current platform
    */
-  @Nullable
-  Duration getWallTime();
+  int getWallTimeInMs();
 
   /**
    * Returns the user time taken by the {@link Spawn}'s execution.
    *
-   * @return the measurement, or null in case of execution errors or when the measurement is not
+   * @return the measurement, or 0 in case of execution errors or when the measurement is not
    *     implemented for the current platform
    */
-  @Nullable
-  Duration getUserTime();
+  int getUserTimeInMs();
 
   /**
    * Returns the system time taken by the {@link Spawn}'s execution.
    *
-   * @return the measurement, or null in case of execution errors or when the measurement is not
+   * @return the measurement, or 0 in case of execution errors or when the measurement is not
    *     implemented for the current platform
    */
-  @Nullable
-  Duration getSystemTime();
+  int getSystemTimeInMs();
 
   /**
    * Returns the number of block output operations during the {@link Spawn}'s execution.
@@ -308,9 +305,9 @@
     private final String runnerSubtype;
     private final SpawnMetrics spawnMetrics;
     @Nullable private final Instant startTime;
-    @Nullable private final Duration wallTime;
-    @Nullable private final Duration userTime;
-    @Nullable private final Duration systemTime;
+    private final int wallTimeInMs;
+    private final int userTimeInMs;
+    private final int systemTimeInMs;
     @Nullable private final Long numBlockOutputOperations;
     @Nullable private final Long numBlockInputOperations;
     @Nullable private final Long numInvoluntaryContextSwitches;
@@ -336,12 +333,11 @@
       this.spawnMetrics =
           builder.spawnMetrics != null
               ? builder.spawnMetrics
-              : SpawnMetrics.forLocalExecution(
-                  builder.wallTime == null ? Duration.ZERO : builder.wallTime);
+              : SpawnMetrics.forLocalExecution(builder.wallTimeInMs);
       this.startTime = builder.startTime;
-      this.wallTime = builder.wallTime;
-      this.userTime = builder.userTime;
-      this.systemTime = builder.systemTime;
+      this.wallTimeInMs = builder.wallTimeInMs;
+      this.userTimeInMs = builder.userTimeInMs;
+      this.systemTimeInMs = builder.systemTimeInMs;
       this.numBlockOutputOperations = builder.numBlockOutputOperations;
       this.numBlockInputOperations = builder.numBlockInputOperations;
       this.numInvoluntaryContextSwitches = builder.numInvoluntaryContextSwitches;
@@ -397,18 +393,18 @@
     }
 
     @Override
-    public Duration getWallTime() {
-      return wallTime;
+    public int getWallTimeInMs() {
+      return wallTimeInMs;
     }
 
     @Override
-    public Duration getUserTime() {
-      return userTime;
+    public int getUserTimeInMs() {
+      return userTimeInMs;
     }
 
     @Override
-    public Duration getSystemTime() {
-      return systemTime;
+    public int getSystemTimeInMs() {
+      return systemTimeInMs;
     }
 
     @Override
@@ -452,12 +448,13 @@
       String explanation = Strings.isNullOrEmpty(message) ? "" : ": " + message;
 
       if (status() == Status.TIMEOUT) {
-        if (getWallTime() != null) {
+        // 0 wall time means no measurement
+        if (getWallTimeInMs() != 0) {
           explanation +=
               String.format(
                   Locale.US,
                   " (failed due to timeout after %.2f seconds.)",
-                  getWallTime().toMillis() / 1000.0);
+                  getWallTimeInMs() / 1000.0);
         } else {
           explanation += " (failed due to timeout.)";
         }
@@ -506,9 +503,9 @@
     private String runnerSubtype = "";
     private SpawnMetrics spawnMetrics;
     private Instant startTime;
-    private Duration wallTime;
-    private Duration userTime;
-    private Duration systemTime;
+    private int wallTimeInMs;
+    private int userTimeInMs;
+    private int systemTimeInMs;
     private Long numBlockOutputOperations;
     private Long numBlockInputOperations;
     private Long numInvoluntaryContextSwitches;
@@ -605,20 +602,20 @@
     }
 
     @CanIgnoreReturnValue
-    public Builder setWallTime(Duration wallTime) {
-      this.wallTime = wallTime;
+    public Builder setWallTimeInMs(int wallTimeInMs) {
+      this.wallTimeInMs = wallTimeInMs;
       return this;
     }
 
     @CanIgnoreReturnValue
-    public Builder setUserTime(Duration userTime) {
-      this.userTime = userTime;
+    public Builder setUserTimeInMs(int userTimeInMs) {
+      this.userTimeInMs = userTimeInMs;
       return this;
     }
 
     @CanIgnoreReturnValue
-    public Builder setSystemTime(Duration systemTime) {
-      this.systemTime = systemTime;
+    public Builder setSystemTimeInMs(int systemTimeInMs) {
+      this.systemTimeInMs = systemTimeInMs;
       return this;
     }
 
diff --git a/src/main/java/com/google/devtools/build/lib/exec/SpawnLogContext.java b/src/main/java/com/google/devtools/build/lib/exec/SpawnLogContext.java
index 186b844..2b7a75c 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/SpawnLogContext.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/SpawnLogContext.java
@@ -167,7 +167,7 @@
       builder.setProgressMessage(progressMessage);
     }
     builder.setMnemonic(spawn.getMnemonic());
-    builder.setWalltime(durationToProto(result.getMetrics().executionWallTime()));
+    builder.setWalltime(millisToProto(result.getMetrics().executionWallTimeInMs()));
 
     if (spawn.getTargetLabel() != null) {
       builder.setTargetLabel(spawn.getTargetLabel());
@@ -176,35 +176,35 @@
     if (executionOptions != null && executionOptions.executionLogSpawnMetrics) {
       SpawnMetrics metrics = result.getMetrics();
       Protos.SpawnMetrics.Builder metricsBuilder = builder.getMetricsBuilder();
-      if (!metrics.totalTime().isZero()) {
-        metricsBuilder.setTotalTime(durationToProto(metrics.totalTime()));
+      if (metrics.totalTimeInMs() != 0L) {
+        metricsBuilder.setTotalTime(millisToProto(metrics.totalTimeInMs()));
       }
-      if (!metrics.parseTime().isZero()) {
-        metricsBuilder.setParseTime(durationToProto(metrics.parseTime()));
+      if (metrics.parseTimeInMs() != 0L) {
+        metricsBuilder.setParseTime(millisToProto(metrics.parseTimeInMs()));
       }
-      if (!metrics.networkTime().isZero()) {
-        metricsBuilder.setNetworkTime(durationToProto(metrics.networkTime()));
+      if (metrics.networkTimeInMs() != 0L) {
+        metricsBuilder.setNetworkTime(millisToProto(metrics.networkTimeInMs()));
       }
-      if (!metrics.fetchTime().isZero()) {
-        metricsBuilder.setFetchTime(durationToProto(metrics.fetchTime()));
+      if (metrics.fetchTimeInMs() != 0L) {
+        metricsBuilder.setFetchTime(millisToProto(metrics.fetchTimeInMs()));
       }
-      if (!metrics.queueTime().isZero()) {
-        metricsBuilder.setQueueTime(durationToProto(metrics.queueTime()));
+      if (metrics.queueTimeInMs() != 0L) {
+        metricsBuilder.setQueueTime(millisToProto(metrics.queueTimeInMs()));
       }
-      if (!metrics.setupTime().isZero()) {
-        metricsBuilder.setSetupTime(durationToProto(metrics.setupTime()));
+      if (metrics.setupTimeInMs() != 0L) {
+        metricsBuilder.setSetupTime(millisToProto(metrics.setupTimeInMs()));
       }
-      if (!metrics.uploadTime().isZero()) {
-        metricsBuilder.setUploadTime(durationToProto(metrics.uploadTime()));
+      if (metrics.uploadTimeInMs() != 0L) {
+        metricsBuilder.setUploadTime(millisToProto(metrics.uploadTimeInMs()));
       }
-      if (!metrics.executionWallTime().isZero()) {
-        metricsBuilder.setExecutionWallTime(durationToProto(metrics.executionWallTime()));
+      if (metrics.executionWallTimeInMs() != 0L) {
+        metricsBuilder.setExecutionWallTime(millisToProto(metrics.executionWallTimeInMs()));
       }
-      if (!metrics.processOutputsTime().isZero()) {
-        metricsBuilder.setProcessOutputsTime(durationToProto(metrics.processOutputsTime()));
+      if (metrics.processOutputsTimeInMs() != 0L) {
+        metricsBuilder.setProcessOutputsTime(millisToProto(metrics.processOutputsTimeInMs()));
       }
-      if (!metrics.retryTime().isZero()) {
-        metricsBuilder.setRetryTime(durationToProto(metrics.retryTime()));
+      if (metrics.retryTimeInMs() != 0L) {
+        metricsBuilder.setRetryTime(millisToProto(metrics.retryTimeInMs()));
       }
       metricsBuilder.setInputBytes(metrics.inputBytes());
       metricsBuilder.setInputFiles(metrics.inputFiles());
@@ -214,16 +214,16 @@
       metricsBuilder.setOutputBytesLimit(metrics.outputBytesLimit());
       metricsBuilder.setOutputFilesLimit(metrics.outputFilesLimit());
       metricsBuilder.setMemoryBytesLimit(metrics.memoryLimit());
-      if (!metrics.timeLimit().isZero()) {
-        metricsBuilder.setTimeLimit(durationToProto(metrics.timeLimit()));
+      if (metrics.timeLimitInMs() != 0L) {
+        metricsBuilder.setTimeLimit(millisToProto(metrics.timeLimitInMs()));
       }
     }
 
     executionLog.write(builder.build());
   }
 
-  private static com.google.protobuf.Duration durationToProto(Duration d) {
-    return Durations.fromNanos(d.toNanos());
+  private static com.google.protobuf.Duration millisToProto(int t) {
+    return Durations.fromMillis(t);
   }
 
   public void close() throws IOException {
diff --git a/src/main/java/com/google/devtools/build/lib/exec/StandaloneTestStrategy.java b/src/main/java/com/google/devtools/build/lib/exec/StandaloneTestStrategy.java
index 0bde6ba7..a12e94d 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/StandaloneTestStrategy.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/StandaloneTestStrategy.java
@@ -59,12 +59,12 @@
 import com.google.devtools.build.lib.view.test.TestStatus.BlazeTestStatus;
 import com.google.devtools.build.lib.view.test.TestStatus.TestCase;
 import com.google.devtools.build.lib.view.test.TestStatus.TestResultData;
+import com.google.protobuf.Duration;
 import com.google.protobuf.util.Durations;
 import java.io.Closeable;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.time.Duration;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
@@ -375,54 +375,54 @@
     executionInfo.setTimingBreakdown(
         BuildEventStreamProtos.TestResult.ExecutionInfo.TimingBreakdown.newBuilder()
             .setName("totalTime")
-            .setTime(toProtoDuration(sm.totalTime()))
+            .setTime(toProtoDuration(sm.totalTimeInMs()))
             .addChild(
                 BuildEventStreamProtos.TestResult.ExecutionInfo.TimingBreakdown.newBuilder()
                     .setName("parseTime")
-                    .setTime(toProtoDuration(sm.parseTime()))
+                    .setTime(toProtoDuration(sm.parseTimeInMs()))
                     .build())
             .addChild(
                 BuildEventStreamProtos.TestResult.ExecutionInfo.TimingBreakdown.newBuilder()
                     .setName("fetchTime")
-                    .setTime(toProtoDuration(sm.fetchTime()))
+                    .setTime(toProtoDuration(sm.fetchTimeInMs()))
                     .build())
             .addChild(
                 BuildEventStreamProtos.TestResult.ExecutionInfo.TimingBreakdown.newBuilder()
                     .setName("queueTime")
-                    .setTime(toProtoDuration(sm.queueTime()))
+                    .setTime(toProtoDuration(sm.queueTimeInMs()))
                     .build())
             .addChild(
                 BuildEventStreamProtos.TestResult.ExecutionInfo.TimingBreakdown.newBuilder()
                     .setName("uploadTime")
-                    .setTime(toProtoDuration(sm.uploadTime()))
+                    .setTime(toProtoDuration(sm.uploadTimeInMs()))
                     .build())
             .addChild(
                 BuildEventStreamProtos.TestResult.ExecutionInfo.TimingBreakdown.newBuilder()
                     .setName("setupTime")
-                    .setTime(toProtoDuration(sm.setupTime()))
+                    .setTime(toProtoDuration(sm.setupTimeInMs()))
                     .build())
             .addChild(
                 BuildEventStreamProtos.TestResult.ExecutionInfo.TimingBreakdown.newBuilder()
                     .setName("executionWallTime")
-                    .setTime(toProtoDuration(sm.executionWallTime()))
+                    .setTime(toProtoDuration(sm.executionWallTimeInMs()))
                     .build())
             .addChild(
                 BuildEventStreamProtos.TestResult.ExecutionInfo.TimingBreakdown.newBuilder()
                     .setName("processOutputsTime")
-                    .setTime(toProtoDuration(sm.processOutputsTime()))
+                    .setTime(toProtoDuration(sm.processOutputsTimeInMs()))
                     .build())
             .addChild(
                 BuildEventStreamProtos.TestResult.ExecutionInfo.TimingBreakdown.newBuilder()
                     .setName("networkTime")
-                    .setTime(toProtoDuration(sm.networkTime()))
+                    .setTime(toProtoDuration(sm.networkTimeInMs()))
                     .build())
             .build());
 
     return executionInfo.build();
   }
 
-  private static com.google.protobuf.Duration toProtoDuration(Duration d) {
-    return Durations.fromNanos(d.toNanos());
+  private static Duration toProtoDuration(int timeInMs) {
+    return Durations.fromMillis(timeInMs);
   }
 
   /**
@@ -436,8 +436,7 @@
             action.getTestXmlGeneratorScript().getExecPath().getCallablePathString(),
             action.getTestLog().getExecPathString(),
             action.getXmlOutputPath().getPathString(),
-            Long.toString(
-                (result.getWallTime() == null ? Duration.ZERO : result.getWallTime()).getSeconds()),
+            Integer.toString(result.getWallTimeInMs() / 1000),
             Integer.toString(result.exitCode()));
     ImmutableMap.Builder<String, String> envBuilder = ImmutableMap.builder();
     // "PATH" and "TEST_BINARY" are also required, they should always be set in testEnv.
@@ -693,10 +692,7 @@
     // set. We fall back to the time measured here for backwards compatibility.
     long durationMillis = endTimeMillis - startTimeMillis;
     durationMillis =
-        (primaryResult.getWallTime() != null
-                ? primaryResult.getWallTime()
-                : Duration.ofMillis(durationMillis))
-            .toMillis();
+        (primaryResult.getWallTimeInMs() != 0 ? primaryResult.getWallTimeInMs() : durationMillis);
 
     testResultDataBuilder
         .setStartTimeMillisEpoch(startTimeMillis)
diff --git a/src/main/java/com/google/devtools/build/lib/exec/local/LocalSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/exec/local/LocalSpawnRunner.java
index 0a89c0b..aed4133 100644
--- a/src/main/java/com/google/devtools/build/lib/exec/local/LocalSpawnRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/exec/local/LocalSpawnRunner.java
@@ -144,7 +144,7 @@
     if (Spawns.shouldPrefetchInputsForLocalExecution(spawn)) {
       context.prefetchInputsAndWait();
     }
-    spawnMetrics.addSetupTime(setupTimeStopwatch.elapsed());
+    spawnMetrics.addSetupTimeInMs((int) setupTimeStopwatch.elapsed().toMillis());
 
     try (SilentCloseable c =
         Profiler.instance()
@@ -160,7 +160,7 @@
               context.speculating()
                   ? ResourcePriority.DYNAMIC_STANDALONE
                   : ResourcePriority.LOCAL)) {
-        spawnMetrics.setQueueTime(queueStopwatch.elapsed());
+        spawnMetrics.setQueueTimeInMs((int) queueStopwatch.elapsed().toMillis());
         context.report(SpawnExecutingEvent.create(getName()));
         if (!localExecutionOptions.localLockfreeOutput) {
           // Without local-lockfree, we grab the lock before running the action, so we can't
@@ -255,7 +255,8 @@
               result.exitCode(),
               attempts);
           Thread.sleep(attempts * 1000L);
-          spawnMetrics.addRetryTime(result.exitCode(), rertyStopwatch.elapsed());
+          spawnMetrics.addRetryTimeInMs(
+              result.exitCode(), (int) rertyStopwatch.elapsed().toMillis());
           attempts++;
         }
       }
@@ -353,7 +354,7 @@
                         + localExecutionOptions.allowedLocalAction.regexPattern()
                         + "\n")
                     .getBytes(UTF_8));
-        spawnMetrics.setTotalTime(totalTimeStopwatch.elapsed());
+        spawnMetrics.setTotalTimeInMs((int) totalTimeStopwatch.elapsed().toMillis());
         return spawnResultBuilder
             .setStatus(Status.EXECUTION_DENIED)
             .setExitCode(LOCAL_EXEC_ERROR)
@@ -427,7 +428,7 @@
           args = ImmutableList.copyOf(newArgs);
         }
         subprocessBuilder.setArgv(args);
-        spawnMetrics.addSetupTime(setupTimeStopwatch.elapsed());
+        spawnMetrics.addSetupTimeInMs((int) setupTimeStopwatch.elapsed().toMillis());
 
         spawnResultBuilder.setStartTime(Instant.now());
         Stopwatch executionStopwatch = Stopwatch.createStarted();
@@ -460,7 +461,7 @@
               .write(
                   ("Action failed to execute: java.io.IOException: " + msg + "\n").getBytes(UTF_8));
           outErr.getErrorStream().flush();
-          spawnMetrics.setTotalTime(totalTimeStopwatch.elapsed());
+          spawnMetrics.setTotalTimeInMs((int) totalTimeStopwatch.elapsed().toMillis());
           return spawnResultBuilder
               .setStatus(Status.EXECUTION_FAILED)
               .setExitCode(LOCAL_EXEC_ERROR)
@@ -472,7 +473,7 @@
         setState(State.SUCCESS);
         // TODO(b/62588075): Calculate wall time inside commands instead?
         Duration wallTime = executionStopwatch.elapsed();
-        spawnMetrics.setExecutionWallTime(wallTime);
+        spawnMetrics.setExecutionWallTimeInMs((int) wallTime.toMillis());
 
         boolean wasTimeout =
             terminationStatus.timedOut()
@@ -481,7 +482,10 @@
             wasTimeout ? SpawnResult.POSIX_TIMEOUT_EXIT_CODE : terminationStatus.getRawExitCode();
         Status status =
             wasTimeout ? Status.TIMEOUT : (exitCode == 0 ? Status.SUCCESS : Status.NON_ZERO_EXIT);
-        spawnResultBuilder.setStatus(status).setExitCode(exitCode).setWallTime(wallTime);
+        spawnResultBuilder
+            .setStatus(status)
+            .setExitCode(exitCode)
+            .setWallTimeInMs((int) wallTime.toMillis());
         if (status != Status.SUCCESS) {
           spawnResultBuilder.setFailureDetail(makeFailureDetail(exitCode, status, actionType));
         }
@@ -489,8 +493,10 @@
           ExecutionStatistics.getResourceUsage(statisticsPath)
               .ifPresent(
                   resourceUsage -> {
-                    spawnResultBuilder.setUserTime(resourceUsage.getUserExecutionTime());
-                    spawnResultBuilder.setSystemTime(resourceUsage.getSystemExecutionTime());
+                    spawnResultBuilder.setUserTimeInMs(
+                        (int) resourceUsage.getUserExecutionTime().toMillis());
+                    spawnResultBuilder.setSystemTimeInMs(
+                        (int) resourceUsage.getSystemExecutionTime().toMillis());
                     spawnResultBuilder.setNumBlockOutputOperations(
                         resourceUsage.getBlockOutputOperations());
                     spawnResultBuilder.setNumBlockInputOperations(
@@ -507,7 +513,7 @@
                     }
                   });
         }
-        spawnMetrics.setTotalTime(totalTimeStopwatch.elapsed());
+        spawnMetrics.setTotalTimeInMs((int) totalTimeStopwatch.elapsed().toMillis());
         spawnResultBuilder.setSpawnMetrics(spawnMetrics.build());
         return spawnResultBuilder.build();
       } finally {
diff --git a/src/main/java/com/google/devtools/build/lib/remote/RemoteExecutionService.java b/src/main/java/com/google/devtools/build/lib/remote/RemoteExecutionService.java
index 44a5d25..b07ccaa 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/RemoteExecutionService.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/RemoteExecutionService.java
@@ -1276,7 +1276,7 @@
               action.getSpawnExecutionContext().getFileOutErr(),
               spawnResult.exitCode(),
               spawnResult.getStartTime(),
-              spawnResult.getWallTime());
+              spawnResult.getWallTimeInMs());
         });
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnCache.java b/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnCache.java
index 589d68b..66bd917 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnCache.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnCache.java
@@ -118,9 +118,9 @@
           fetchTime.stop();
           totalTime.stop();
           spawnMetrics
-              .setFetchTime(fetchTime.elapsed())
-              .setTotalTime(totalTime.elapsed())
-              .setNetworkTime(action.getNetworkTime().getDuration());
+              .setFetchTimeInMs((int) fetchTime.elapsed().toMillis())
+              .setTotalTimeInMs((int) totalTime.elapsed().toMillis())
+              .setNetworkTimeInMs((int) action.getNetworkTime().getDuration().toMillis());
           SpawnResult spawnResult =
               createSpawnResult(
                   action.getActionKey(),
diff --git a/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnRunner.java
index 1329421..0874592 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/RemoteSpawnRunner.java
@@ -193,7 +193,7 @@
 
     maybeWriteParamFilesLocally(spawn);
 
-    spawnMetrics.setParseTime(totalTime.elapsed());
+    spawnMetrics.setParseTimeInMs((int) totalTime.elapsed().toMillis());
 
     Profiler prof = Profiler.instance();
     try {
@@ -252,10 +252,12 @@
               // subtract network time consumed here to ensure wall clock during upload is not
               // double
               // counted, and metrics time computation does not exceed total time
-              spawnMetrics.setUploadTime(
-                  uploadTime
-                      .elapsed()
-                      .minus(action.getNetworkTime().getDuration().minus(networkTimeStart)));
+              spawnMetrics.setUploadTimeInMs(
+                  (int)
+                      uploadTime
+                          .elapsed()
+                          .minus(action.getNetworkTime().getDuration().minus(networkTimeStart))
+                          .toMillis());
             }
 
             context.report(SPAWN_SCHEDULING_EVENT);
@@ -363,33 +365,32 @@
     // contributions for a phase or phases.
     if (!executionMetadata.getWorker().isEmpty()) {
       // Accumulate queueTime from any previous attempts
-      Duration remoteQueueTime =
-          spawnMetrics
-              .build()
-              .queueTime()
-              .plus(
+      int remoteQueueTimeInMs =
+          spawnMetrics.build().queueTimeInMs()
+              + (int)
                   between(
-                      executionMetadata.getQueuedTimestamp(),
-                      executionMetadata.getWorkerStartTimestamp()));
-      spawnMetrics.setQueueTime(remoteQueueTime);
+                          executionMetadata.getQueuedTimestamp(),
+                          executionMetadata.getWorkerStartTimestamp())
+                      .toMillis();
+      spawnMetrics.setQueueTimeInMs(remoteQueueTimeInMs);
       // setup time does not include failed attempts
       Duration setupTime =
           between(
               executionMetadata.getWorkerStartTimestamp(),
               executionMetadata.getExecutionStartTimestamp());
-      spawnMetrics.setSetupTime(setupTime);
+      spawnMetrics.setSetupTimeInMs((int) setupTime.toMillis());
       // execution time is unspecified for failures
       Duration executionWallTime =
           between(
               executionMetadata.getExecutionStartTimestamp(),
               executionMetadata.getExecutionCompletedTimestamp());
-      spawnMetrics.setExecutionWallTime(executionWallTime);
+      spawnMetrics.setExecutionWallTimeInMs((int) executionWallTime.toMillis());
       // remoteProcessOutputs time is unspecified for failures
       Duration remoteProcessOutputsTime =
           between(
               executionMetadata.getOutputUploadStartTimestamp(),
               executionMetadata.getOutputUploadCompletedTimestamp());
-      spawnMetrics.setProcessOutputsTime(remoteProcessOutputsTime);
+      spawnMetrics.setProcessOutputsTimeInMs((int) remoteProcessOutputsTime.toMillis());
     }
   }
 
@@ -425,9 +426,10 @@
         result.getExecutionMetadata().getExecutionStartTimestamp(),
         result.getExecutionMetadata().getExecutionCompletedTimestamp(),
         spawnMetrics
-            .setFetchTime(fetchTime.elapsed().minus(networkTimeEnd.minus(networkTimeStart)))
-            .setTotalTime(totalTime.elapsed())
-            .setNetworkTime(networkTimeEnd)
+            .setFetchTimeInMs(
+                (int) fetchTime.elapsed().minus(networkTimeEnd.minus(networkTimeStart)).toMillis())
+            .setTotalTimeInMs((int) totalTime.elapsed().toMillis())
+            .setNetworkTimeInMs((int) networkTimeEnd.toMillis())
             .build(),
         spawn.getMnemonic());
   }
diff --git a/src/main/java/com/google/devtools/build/lib/remote/UploadManifest.java b/src/main/java/com/google/devtools/build/lib/remote/UploadManifest.java
index 9db92c0..941a3e5 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/UploadManifest.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/UploadManifest.java
@@ -62,7 +62,6 @@
 import io.reactivex.rxjava3.core.Single;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
-import java.time.Duration;
 import java.time.Instant;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -102,7 +101,7 @@
       FileOutErr outErr,
       int exitCode,
       Instant startTime,
-      Duration wallTime)
+      int wallTimeInMs)
       throws ExecException, IOException {
     ActionResult.Builder result = ActionResult.newBuilder();
     result.setExitCode(exitCode);
@@ -127,9 +126,10 @@
       result.setStdoutDigest(manifest.getStdoutDigest());
     }
 
-    if (startTime != null && wallTime != null) {
+    // if wallTime is zero, than it's not set
+    if (startTime != null && wallTimeInMs != 0) {
       Timestamp startTimestamp = instantToTimestamp(startTime);
-      Timestamp completedTimestamp = instantToTimestamp(startTime.plus(wallTime));
+      Timestamp completedTimestamp = instantToTimestamp(startTime.plusMillis(wallTimeInMs));
       result
           .getExecutionMetadataBuilder()
           .setWorkerStartTimestamp(startTimestamp)
diff --git a/src/main/java/com/google/devtools/build/lib/remote/util/Utils.java b/src/main/java/com/google/devtools/build/lib/remote/util/Utils.java
index 384c367..c1caf79 100644
--- a/src/main/java/com/google/devtools/build/lib/remote/util/Utils.java
+++ b/src/main/java/com/google/devtools/build/lib/remote/util/Utils.java
@@ -157,10 +157,12 @@
             .setRunnerName(cacheHit ? runnerName + " cache hit" : runnerName)
             .setCacheHit(cacheHit)
             .setStartTime(timestampToInstant(executionStartTimestamp))
-            .setWallTime(
-                java.time.Duration.between(
-                    timestampToInstant(executionStartTimestamp),
-                    timestampToInstant(executionCompletedTimestamp)))
+            .setWallTimeInMs(
+                (int)
+                    java.time.Duration.between(
+                            timestampToInstant(executionStartTimestamp),
+                            timestampToInstant(executionCompletedTimestamp))
+                        .toMillis())
             .setSpawnMetrics(spawnMetrics)
             .setRemote(true)
             .setDigest(
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/AggregatedCriticalPath.java b/src/main/java/com/google/devtools/build/lib/runtime/AggregatedCriticalPath.java
index 7d4fbb3..bd38d02 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/AggregatedCriticalPath.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/AggregatedCriticalPath.java
@@ -24,26 +24,27 @@
  * Aggregates all the critical path components in one object. This allows us to easily access the
  * components data and have a proper toString().
  */
+@SuppressWarnings("GoodTime") // Use ints instead of Durations to improve build time (cl/505728570)
 public class AggregatedCriticalPath {
   public static final AggregatedCriticalPath EMPTY =
-      new AggregatedCriticalPath(Duration.ZERO, AggregatedSpawnMetrics.EMPTY, ImmutableList.of());
+      new AggregatedCriticalPath(0, AggregatedSpawnMetrics.EMPTY, ImmutableList.of());
 
-  private final Duration totalTime;
+  private final int totalTimeInMs;
   private final AggregatedSpawnMetrics aggregatedSpawnMetrics;
   private final ImmutableList<CriticalPathComponent> criticalPathComponents;
 
   public AggregatedCriticalPath(
-      Duration totalTime,
+      int totalTimeInMs,
       AggregatedSpawnMetrics aggregatedSpawnMetrics,
       ImmutableList<CriticalPathComponent> criticalPathComponents) {
-    this.totalTime = totalTime;
+    this.totalTimeInMs = totalTimeInMs;
     this.aggregatedSpawnMetrics = aggregatedSpawnMetrics;
     this.criticalPathComponents = criticalPathComponents;
   }
 
   /** Total wall time spent running the critical path actions. */
-  public Duration totalTime() {
-    return totalTime;
+  public int totalTimeInMs() {
+    return totalTimeInMs;
   }
 
   public AggregatedSpawnMetrics getSpawnMetrics() {
@@ -56,15 +57,14 @@
   }
 
   public String getNewStringSummary() {
-    Duration executionWallTime =
-        aggregatedSpawnMetrics.getTotalDuration(SpawnMetrics::executionWallTime);
-    Duration overheadTime =
-        aggregatedSpawnMetrics.getTotalDuration(SpawnMetrics::totalTime).minus(executionWallTime);
+    int executionWallTimeInMs =
+        aggregatedSpawnMetrics.getTotalDuration(SpawnMetrics::executionWallTimeInMs);
+    int overheadTimeInMs =
+        aggregatedSpawnMetrics.getTotalDuration(SpawnMetrics::totalTimeInMs)
+            - executionWallTimeInMs;
     return String.format(
         "Execution critical path %.2fs (setup %.2fs, action wall time %.2fs)",
-        totalTime.toMillis() / 1000.0,
-        overheadTime.toMillis() / 1000.0,
-        executionWallTime.toMillis() / 1000.0);
+        totalTimeInMs / 1000.0, overheadTimeInMs / 1000.0, executionWallTimeInMs / 1000.0);
   }
 
   @Override
@@ -74,11 +74,11 @@
 
   private String toString(boolean summary, boolean remote) {
     StringBuilder sb = new StringBuilder("Critical Path: ");
-    sb.append(String.format("%.2f", totalTime.toMillis() / 1000.0));
+    sb.append(String.format("%.2f", totalTimeInMs / 1000.0));
     sb.append("s");
     if (remote) {
       sb.append(", ");
-      sb.append(getSpawnMetrics().toString(totalTime(), summary));
+      sb.append(getSpawnMetrics().toString(Duration.ofMillis(totalTimeInMs), summary));
     }
     if (summary || criticalPathComponents.isEmpty()) {
       return sb.toString();
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/CriticalPathComponent.java b/src/main/java/com/google/devtools/build/lib/runtime/CriticalPathComponent.java
index e3600f1..2924ffb 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/CriticalPathComponent.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/CriticalPathComponent.java
@@ -52,7 +52,7 @@
   private SpawnMetrics phaseMaxMetrics = EMPTY_PLACEHOLDER_METRICS;
 
   private AggregatedSpawnMetrics totalSpawnMetrics = AggregatedSpawnMetrics.EMPTY;
-  private Duration longestRunningTotalDuration = Duration.ZERO;
+  private int longestRunningTotalDurationInMs = 0;
   private boolean phaseChange;
 
   /** Name of the runner used for the spawn. */
@@ -188,14 +188,14 @@
       }
       this.phaseMaxMetrics = metrics;
       this.phaseChange = false;
-    } else if (metrics.totalTime().compareTo(this.phaseMaxMetrics.totalTime()) > 0) {
+    } else if (metrics.totalTimeInMs() > phaseMaxMetrics.totalTimeInMs()) {
       this.phaseMaxMetrics = metrics;
     }
 
-    if (runnerName != null && metrics.totalTime().compareTo(this.longestRunningTotalDuration) > 0) {
+    if (runnerName != null && metrics.totalTimeInMs() > this.longestRunningTotalDurationInMs) {
       this.longestPhaseSpawnRunnerName = runnerName;
       this.longestPhaseSpawnRunnerSubtype = runnerSubtype;
-      this.longestRunningTotalDuration = metrics.totalTime();
+      this.longestRunningTotalDurationInMs = metrics.totalTimeInMs();
     }
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/CriticalPathComputer.java b/src/main/java/com/google/devtools/build/lib/runtime/CriticalPathComputer.java
index 3491b33..ca2a008 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/CriticalPathComputer.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/CriticalPathComputer.java
@@ -126,7 +126,9 @@
     }
 
     return new AggregatedCriticalPath(
-        criticalPath.getAggregatedElapsedTime(), metricsBuilder.build(), components.build());
+        (int) criticalPath.getAggregatedElapsedTime().toMillis(),
+        metricsBuilder.build(),
+        components.build());
   }
 
   public Map<Artifact, CriticalPathComponent> getCriticalPathComponentsMap() {
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/ExecutionGraphModule.java b/src/main/java/com/google/devtools/build/lib/runtime/ExecutionGraphModule.java
index f4136eb..b53fbb9 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/ExecutionGraphModule.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/ExecutionGraphModule.java
@@ -423,7 +423,7 @@
 
       SpawnMetrics metrics = spawnResult.getMetrics();
       spawnResult = null;
-      long totalMillis = metrics.totalTime().toMillis();
+      long totalMillis = metrics.totalTimeInMs();
 
       long discoverInputsMillis = 0;
       ActionInput firstOutput = getFirstOutput(spawn.getResourceOwner(), spawn.getOutputFiles());
@@ -439,20 +439,20 @@
           ExecutionGraph.Metrics.newBuilder()
               .setStartTimestampMillis(startMillis)
               .setDurationMillis((int) totalMillis)
-              .setFetchMillis((int) metrics.fetchTime().toMillis())
+              .setFetchMillis(metrics.fetchTimeInMs())
               .setDiscoverInputsMillis((int) discoverInputsMillis)
-              .setParseMillis((int) metrics.parseTime().toMillis())
-              .setProcessMillis((int) metrics.executionWallTime().toMillis())
-              .setQueueMillis((int) metrics.queueTime().toMillis())
-              .setRetryMillis((int) metrics.retryTime().toMillis())
-              .setSetupMillis((int) metrics.setupTime().toMillis())
-              .setUploadMillis((int) metrics.uploadTime().toMillis())
-              .setNetworkMillis((int) metrics.networkTime().toMillis())
-              .setOtherMillis((int) metrics.otherTime().toMillis())
-              .setProcessOutputsMillis((int) metrics.processOutputsTime().toMillis());
+              .setParseMillis(metrics.parseTimeInMs())
+              .setProcessMillis(metrics.executionWallTimeInMs())
+              .setQueueMillis(metrics.queueTimeInMs())
+              .setRetryMillis(metrics.retryTimeInMs())
+              .setSetupMillis(metrics.setupTimeInMs())
+              .setUploadMillis(metrics.uploadTimeInMs())
+              .setNetworkMillis(metrics.networkTimeInMs())
+              .setOtherMillis(metrics.otherTimeInMs())
+              .setProcessOutputsMillis(metrics.processOutputsTimeInMs());
 
-      for (Map.Entry<Integer, Duration> entry : metrics.retryTimeByError().entrySet()) {
-        metricsBuilder.putRetryMillisByError(entry.getKey(), (int) entry.getValue().toMillis());
+      for (Map.Entry<Integer, Integer> entry : metrics.retryTimeByError().entrySet()) {
+        metricsBuilder.putRetryMillisByError(entry.getKey(), entry.getValue());
       }
       metrics = null;
       // maybeAddEdges can take a while, so do it last and try to give up references to any objects
@@ -646,7 +646,7 @@
     void enqueue(DiscoveredInputsEvent event) {
       // The other times from SpawnMetrics are not needed. The only instance of
       // DiscoveredInputsEvent sets only total and parse time, and to the same value.
-      var totalTime = event.getMetrics().totalTime();
+      var totalTime = Duration.ofMillis(event.getMetrics().totalTimeInMs());
       var firstOutput = getFirstOutput(event.getAction(), event.getAction().getOutputs());
       var sum = outputToDiscoverInputsTime.get(firstOutput);
       if (sum != null) {
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/SpawnStats.java b/src/main/java/com/google/devtools/build/lib/runtime/SpawnStats.java
index b93a1a5..fd53f1a 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/SpawnStats.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/SpawnStats.java
@@ -41,7 +41,7 @@
   public void countActionResult(ActionResult actionResult) {
     for (SpawnResult r : actionResult.spawnResults()) {
       countRunnerName(r.getRunnerName());
-      totalWallTimeMillis.addAndGet(r.getMetrics().executionWallTime().toMillis());
+      totalWallTimeMillis.addAndGet(r.getMetrics().executionWallTimeInMs());
     }
   }
 
diff --git a/src/main/java/com/google/devtools/build/lib/sandbox/AbstractSandboxSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/sandbox/AbstractSandboxSpawnRunner.java
index 54e8e61..9397583 100644
--- a/src/main/java/com/google/devtools/build/lib/sandbox/AbstractSandboxSpawnRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/sandbox/AbstractSandboxSpawnRunner.java
@@ -269,7 +269,7 @@
             .setStatus(status)
             .setExitCode(exitCode)
             .setStartTime(startTime)
-            .setWallTime(wallTime)
+            .setWallTimeInMs((int) wallTime.toMillis())
             .setFailureMessage(failureMessage);
 
     if (failureDetail != null) {
@@ -281,8 +281,10 @@
       ExecutionStatistics.getResourceUsage(statisticsPath)
           .ifPresent(
               resourceUsage -> {
-                spawnResultBuilder.setUserTime(resourceUsage.getUserExecutionTime());
-                spawnResultBuilder.setSystemTime(resourceUsage.getSystemExecutionTime());
+                spawnResultBuilder.setUserTimeInMs(
+                    (int) resourceUsage.getUserExecutionTime().toMillis());
+                spawnResultBuilder.setSystemTimeInMs(
+                    (int) resourceUsage.getSystemExecutionTime().toMillis());
                 spawnResultBuilder.setNumBlockOutputOperations(
                     resourceUsage.getBlockOutputOperations());
                 spawnResultBuilder.setNumBlockInputOperations(
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java b/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java
index 8c13763..addc401 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/ActionExecutionFunction.java
@@ -817,8 +817,8 @@
           .post(
               new DiscoveredInputsEvent(
                   SpawnMetrics.Builder.forOtherExec()
-                      .setParseTime(discoveredInputsDuration)
-                      .setTotalTime(discoveredInputsDuration)
+                      .setParseTimeInMs((int) discoveredInputsDuration.toMillis())
+                      .setTotalTimeInMs((int) discoveredInputsDuration.toMillis())
                       .build(),
                   action,
                   actionStartTime));
diff --git a/src/main/java/com/google/devtools/build/lib/worker/WorkerSpawnRunner.java b/src/main/java/com/google/devtools/build/lib/worker/WorkerSpawnRunner.java
index 6bfbabe..b4a6b11 100644
--- a/src/main/java/com/google/devtools/build/lib/worker/WorkerSpawnRunner.java
+++ b/src/main/java/com/google/devtools/build/lib/worker/WorkerSpawnRunner.java
@@ -221,8 +221,8 @@
             .setExitCode(exitCode)
             .setStatus(exitCode == 0 ? Status.SUCCESS : Status.NON_ZERO_EXIT)
             .setStartTime(startTime)
-            .setWallTime(wallTime)
-            .setSpawnMetrics(spawnMetrics.setTotalTime(wallTime).build());
+            .setWallTimeInMs((int) wallTime.toMillis())
+            .setSpawnMetrics(spawnMetrics.setTotalTimeInMs((int) wallTime.toMillis()).build());
     if (exitCode != 0) {
       builder.setFailureDetail(
           FailureDetail.newBuilder()
@@ -386,7 +386,7 @@
       }
     }
     Duration setupInputsTime = setupInputsStopwatch.elapsed();
-    spawnMetrics.setSetupTime(setupInputsTime);
+    spawnMetrics.setSetupTimeInMs((int) setupInputsTime.toMillis());
 
     Stopwatch queueStopwatch = Stopwatch.createStarted();
     ResourceSet resourceSet =
@@ -411,7 +411,7 @@
           createWorkRequest(spawn, context, flagFiles, virtualInputDigests, inputFileCache, key);
 
       // We acquired a worker and resources -- mark that as queuing time.
-      spawnMetrics.setQueueTime(queueStopwatch.elapsed());
+      spawnMetrics.setQueueTimeInMs((int) queueStopwatch.elapsed().toMillis());
       response =
           executeRequest(
               spawn, context, inputFiles, outputs, workerOwner, key, request, spawnMetrics, handle);
@@ -437,7 +437,8 @@
           context.lockOutputFiles(response.getExitCode(), response.getOutput(), null);
           hasOutputFileLock = true;
           workerOwner.getWorker().finishExecution(execRoot, outputs);
-          spawnMetrics.setProcessOutputsTime(processOutputsStopwatch.elapsed());
+          spawnMetrics.setProcessOutputsTimeInMs(
+              (int) processOutputsStopwatch.elapsed().toMillis());
         } else {
           throw createUserExecException(
               "The response finished successfully, but worker is taken by finishAsync",
@@ -519,7 +520,7 @@
       Stopwatch prepareExecutionStopwatch = Stopwatch.createStarted();
       worker.prepareExecution(inputFiles, outputs, key.getWorkerFilesWithDigests().keySet());
       initializeMetrics(key, worker);
-      spawnMetrics.addSetupTime(prepareExecutionStopwatch.elapsed());
+      spawnMetrics.addSetupTimeInMs((int) prepareExecutionStopwatch.elapsed().toMillis());
     } catch (IOException e) {
       restoreInterrupt(e);
       String message =
@@ -580,7 +581,7 @@
       }
     }
 
-    spawnMetrics.setExecutionWallTime(executionStopwatch.elapsed());
+    spawnMetrics.setExecutionWallTimeInMs((int) executionStopwatch.elapsed().toMillis());
 
     return response;
   }
diff --git a/src/test/java/com/google/devtools/build/lib/actions/ActionResultTest.java b/src/test/java/com/google/devtools/build/lib/actions/ActionResultTest.java
index ddcc606..5071f58 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/ActionResultTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/ActionResultTest.java
@@ -16,7 +16,6 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.common.collect.ImmutableList;
-import java.time.Duration;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -30,10 +29,10 @@
   public void testCumulativeCommandExecutionTime_noSpawnResults() {
     List<SpawnResult> spawnResults = ImmutableList.of();
     ActionResult actionResult = ActionResult.create(spawnResults);
-    assertThat(actionResult.cumulativeCommandExecutionWallTime()).isNull();
-    assertThat(actionResult.cumulativeCommandExecutionCpuTime()).isNull();
-    assertThat(actionResult.cumulativeCommandExecutionUserTime()).isNull();
-    assertThat(actionResult.cumulativeCommandExecutionSystemTime()).isNull();
+    assertThat(actionResult.cumulativeCommandExecutionWallTimeInMs()).isEqualTo(0);
+    assertThat(actionResult.cumulativeCommandExecutionCpuTimeInMs()).isEqualTo(0);
+    assertThat(actionResult.cumulativeCommandExecutionUserTimeInMs()).isEqualTo(0);
+    assertThat(actionResult.cumulativeCommandExecutionSystemTimeInMs()).isEqualTo(0);
     assertThat(actionResult.cumulativeCommandExecutionBlockOutputOperations()).isNull();
     assertThat(actionResult.cumulativeCommandExecutionBlockInputOperations()).isNull();
     assertThat(actionResult.cumulativeCommandExecutionInvoluntaryContextSwitches()).isNull();
@@ -43,9 +42,9 @@
   public void testCumulativeCommandExecutionTime_oneSpawnResult() {
     SpawnResult spawnResult =
         new SpawnResult.Builder()
-            .setWallTime(Duration.ofMillis(1984))
-            .setUserTime(Duration.ofMillis(225))
-            .setSystemTime(Duration.ofMillis(42))
+            .setWallTimeInMs(1984)
+            .setUserTimeInMs(225)
+            .setSystemTimeInMs(42)
             .setNumBlockOutputOperations(10)
             .setNumBlockInputOperations(20)
             .setNumInvoluntaryContextSwitches(30)
@@ -55,25 +54,23 @@
     List<SpawnResult> spawnResults = ImmutableList.of(spawnResult);
     ActionResult actionResult = ActionResult.create(spawnResults);
 
-    assertThat(actionResult.cumulativeCommandExecutionWallTime())
-        .isEqualTo(Duration.ofMillis(1984));
-    assertThat(actionResult.cumulativeCommandExecutionCpuTime()).isEqualTo(Duration.ofMillis(267));
-    assertThat(actionResult.cumulativeCommandExecutionUserTime()).isEqualTo(Duration.ofMillis(225));
+    assertThat(actionResult.cumulativeCommandExecutionWallTimeInMs()).isEqualTo(1984);
+    assertThat(actionResult.cumulativeCommandExecutionCpuTimeInMs()).isEqualTo(267);
+    assertThat(actionResult.cumulativeCommandExecutionUserTimeInMs()).isEqualTo(225);
 
-    assertThat(actionResult.cumulativeCommandExecutionSystemTime())
-        .isEqualTo(Duration.ofMillis(42));
-    assertThat(actionResult.cumulativeCommandExecutionBlockOutputOperations()).isEqualTo(10L);
-    assertThat(actionResult.cumulativeCommandExecutionBlockInputOperations()).isEqualTo(20L);
-    assertThat(actionResult.cumulativeCommandExecutionInvoluntaryContextSwitches()).isEqualTo(30L);
+    assertThat(actionResult.cumulativeCommandExecutionSystemTimeInMs()).isEqualTo(42);
+    assertThat(actionResult.cumulativeCommandExecutionBlockOutputOperations()).isEqualTo(10);
+    assertThat(actionResult.cumulativeCommandExecutionBlockInputOperations()).isEqualTo(20);
+    assertThat(actionResult.cumulativeCommandExecutionInvoluntaryContextSwitches()).isEqualTo(30);
   }
 
   @Test
   public void testCumulativeCommandExecutionTime_manySpawnResults() {
     SpawnResult spawnResult1 =
         new SpawnResult.Builder()
-            .setWallTime(Duration.ofMillis(1979))
-            .setUserTime(Duration.ofMillis(1))
-            .setSystemTime(Duration.ofMillis(33))
+            .setWallTimeInMs(1979)
+            .setUserTimeInMs(1)
+            .setSystemTimeInMs(33)
             .setNumBlockOutputOperations(10)
             .setNumBlockInputOperations(20)
             .setNumInvoluntaryContextSwitches(30)
@@ -82,9 +79,9 @@
             .build();
     SpawnResult spawnResult2 =
         new SpawnResult.Builder()
-            .setWallTime(Duration.ofMillis(4))
-            .setUserTime(Duration.ofMillis(1))
-            .setSystemTime(Duration.ofMillis(7))
+            .setWallTimeInMs(4)
+            .setUserTimeInMs(1)
+            .setSystemTimeInMs(7)
             .setNumBlockOutputOperations(100)
             .setNumBlockInputOperations(200)
             .setNumInvoluntaryContextSwitches(300)
@@ -93,9 +90,9 @@
             .build();
     SpawnResult spawnResult3 =
         new SpawnResult.Builder()
-            .setWallTime(Duration.ofMillis(1))
-            .setUserTime(Duration.ofMillis(2))
-            .setSystemTime(Duration.ofMillis(2))
+            .setWallTimeInMs(1)
+            .setUserTimeInMs(2)
+            .setSystemTimeInMs(2)
             .setNumBlockOutputOperations(1000)
             .setNumBlockInputOperations(2000)
             .setNumInvoluntaryContextSwitches(3000)
@@ -105,13 +102,11 @@
     List<SpawnResult> spawnResults = ImmutableList.of(spawnResult1, spawnResult2, spawnResult3);
     ActionResult actionResult = ActionResult.create(spawnResults);
 
-    assertThat(actionResult.cumulativeCommandExecutionWallTime())
-        .isEqualTo(Duration.ofMillis(1984));
-    assertThat(actionResult.cumulativeCommandExecutionCpuTime()).isEqualTo(Duration.ofMillis(46));
-    assertThat(actionResult.cumulativeCommandExecutionUserTime()).isEqualTo(Duration.ofMillis(4));
+    assertThat(actionResult.cumulativeCommandExecutionWallTimeInMs()).isEqualTo(1984L);
+    assertThat(actionResult.cumulativeCommandExecutionCpuTimeInMs()).isEqualTo(46L);
+    assertThat(actionResult.cumulativeCommandExecutionUserTimeInMs()).isEqualTo(4L);
 
-    assertThat(actionResult.cumulativeCommandExecutionSystemTime())
-        .isEqualTo(Duration.ofMillis(42));
+    assertThat(actionResult.cumulativeCommandExecutionSystemTimeInMs()).isEqualTo(42L);
     assertThat(actionResult.cumulativeCommandExecutionBlockOutputOperations()).isEqualTo(1110L);
     assertThat(actionResult.cumulativeCommandExecutionBlockInputOperations()).isEqualTo(2220L);
 
@@ -138,10 +133,10 @@
             .build();
     List<SpawnResult> spawnResults = ImmutableList.of(spawnResult1, spawnResult2, spawnResult3);
     ActionResult actionResult = ActionResult.create(spawnResults);
-    assertThat(actionResult.cumulativeCommandExecutionWallTime()).isNull();
-    assertThat(actionResult.cumulativeCommandExecutionCpuTime()).isNull();
-    assertThat(actionResult.cumulativeCommandExecutionUserTime()).isNull();
-    assertThat(actionResult.cumulativeCommandExecutionSystemTime()).isNull();
+    assertThat(actionResult.cumulativeCommandExecutionWallTimeInMs()).isEqualTo(0);
+    assertThat(actionResult.cumulativeCommandExecutionCpuTimeInMs()).isEqualTo(0);
+    assertThat(actionResult.cumulativeCommandExecutionUserTimeInMs()).isEqualTo(0);
+    assertThat(actionResult.cumulativeCommandExecutionSystemTimeInMs()).isEqualTo(0);
     assertThat(actionResult.cumulativeCommandExecutionBlockOutputOperations()).isNull();
     assertThat(actionResult.cumulativeCommandExecutionBlockInputOperations()).isNull();
     assertThat(actionResult.cumulativeCommandExecutionInvoluntaryContextSwitches()).isNull();
@@ -151,53 +146,52 @@
   public void testCumulativeCommandExecutionTime_manySpawnResults_butOnlyUserTime() {
     SpawnResult spawnResult1 =
         new SpawnResult.Builder()
-            .setUserTime(Duration.ofMillis(2))
+            .setUserTimeInMs(2)
             .setStatus(SpawnResult.Status.SUCCESS)
             .setRunnerName("test")
             .build();
     SpawnResult spawnResult2 =
         new SpawnResult.Builder()
-            .setUserTime(Duration.ofMillis(3))
+            .setUserTimeInMs(3)
             .setStatus(SpawnResult.Status.SUCCESS)
             .setRunnerName("test")
             .build();
     SpawnResult spawnResult3 =
         new SpawnResult.Builder()
-            .setUserTime(Duration.ofMillis(4))
+            .setUserTimeInMs(4)
             .setStatus(SpawnResult.Status.SUCCESS)
             .setRunnerName("test")
             .build();
     List<SpawnResult> spawnResults = ImmutableList.of(spawnResult1, spawnResult2, spawnResult3);
     ActionResult actionResult = ActionResult.create(spawnResults);
-    assertThat(actionResult.cumulativeCommandExecutionCpuTime()).isEqualTo(Duration.ofMillis(9));
-    assertThat(actionResult.cumulativeCommandExecutionUserTime()).isEqualTo(Duration.ofMillis(9));
+    assertThat(actionResult.cumulativeCommandExecutionCpuTimeInMs()).isEqualTo(9L);
+    assertThat(actionResult.cumulativeCommandExecutionUserTimeInMs()).isEqualTo(9L);
   }
 
   @Test
   public void testCumulativeCommandExecutionTime_manySpawnResults_butOnlySystemTime() {
     SpawnResult spawnResult1 =
         new SpawnResult.Builder()
-            .setSystemTime(Duration.ofMillis(33))
+            .setSystemTimeInMs(33)
             .setStatus(SpawnResult.Status.SUCCESS)
             .setRunnerName("test")
             .build();
     SpawnResult spawnResult2 =
         new SpawnResult.Builder()
-            .setSystemTime(Duration.ofMillis(7))
+            .setSystemTimeInMs(7)
             .setStatus(SpawnResult.Status.SUCCESS)
             .setRunnerName("test")
             .build();
     SpawnResult spawnResult3 =
         new SpawnResult.Builder()
-            .setSystemTime(Duration.ofMillis(2))
+            .setSystemTimeInMs(2)
             .setStatus(SpawnResult.Status.SUCCESS)
             .setRunnerName("test")
             .build();
     List<SpawnResult> spawnResults = ImmutableList.of(spawnResult1, spawnResult2, spawnResult3);
     ActionResult actionResult = ActionResult.create(spawnResults);
-    assertThat(actionResult.cumulativeCommandExecutionCpuTime()).isEqualTo(Duration.ofMillis(42));
+    assertThat(actionResult.cumulativeCommandExecutionCpuTimeInMs()).isEqualTo(42L);
 
-    assertThat(actionResult.cumulativeCommandExecutionSystemTime())
-        .isEqualTo(Duration.ofMillis(42));
+    assertThat(actionResult.cumulativeCommandExecutionSystemTimeInMs()).isEqualTo(42L);
   }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/actions/AggregatedSpawnMetricsTest.java b/src/test/java/com/google/devtools/build/lib/actions/AggregatedSpawnMetricsTest.java
index 5505605..a608a5f 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/AggregatedSpawnMetricsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/AggregatedSpawnMetricsTest.java
@@ -28,16 +28,16 @@
   public void sumAllMetrics() throws Exception {
     SpawnMetrics metrics1 =
         SpawnMetrics.Builder.forRemoteExec()
-            .setTotalTime(Duration.ofSeconds(1))
-            .setExecutionWallTime(Duration.ofSeconds(2))
+            .setTotalTimeInMs(1 * 1000)
+            .setExecutionWallTimeInMs(2 * 1000)
             .setInputBytes(10)
             .setInputFiles(20)
             .setMemoryEstimateBytes(30)
             .build();
     SpawnMetrics metrics2 =
         SpawnMetrics.Builder.forRemoteExec()
-            .setTotalTime(Duration.ofSeconds(10))
-            .setExecutionWallTime(Duration.ofSeconds(20))
+            .setTotalTimeInMs(10 * 1000)
+            .setExecutionWallTimeInMs(20 * 1000)
             .setInputBytes(100)
             .setInputFiles(200)
             .setMemoryEstimateBytes(300)
@@ -47,8 +47,8 @@
     aggregated = aggregated.sumAllMetrics(metrics1);
     aggregated = aggregated.sumAllMetrics(metrics2);
 
-    assertThat(aggregated.getRemoteMetrics().totalTime()).isEqualTo(Duration.ofSeconds(11));
-    assertThat(aggregated.getRemoteMetrics().executionWallTime()).isEqualTo(Duration.ofSeconds(22));
+    assertThat(aggregated.getRemoteMetrics().totalTimeInMs()).isEqualTo(11 * 1000);
+    assertThat(aggregated.getRemoteMetrics().executionWallTimeInMs()).isEqualTo(22 * 1000);
     assertThat(aggregated.getRemoteMetrics().inputBytes()).isEqualTo(110);
     assertThat(aggregated.getRemoteMetrics().inputFiles()).isEqualTo(220);
     assertThat(aggregated.getRemoteMetrics().memoryEstimate()).isEqualTo(330);
@@ -58,16 +58,16 @@
   public void sumDurationMetricsMaxOther() throws Exception {
     SpawnMetrics metrics1 =
         SpawnMetrics.Builder.forRemoteExec()
-            .setTotalTime(Duration.ofSeconds(1))
-            .setExecutionWallTime(Duration.ofSeconds(2))
+            .setTotalTimeInMs(1 * 1000)
+            .setExecutionWallTimeInMs(2 * 1000)
             .setInputBytes(10)
             .setInputFiles(20)
             .setMemoryEstimateBytes(30)
             .build();
     SpawnMetrics metrics2 =
         SpawnMetrics.Builder.forRemoteExec()
-            .setTotalTime(Duration.ofSeconds(10))
-            .setExecutionWallTime(Duration.ofSeconds(20))
+            .setTotalTimeInMs(10 * 1000)
+            .setExecutionWallTimeInMs(20 * 1000)
             .setInputBytes(100)
             .setInputFiles(200)
             .setMemoryEstimateBytes(300)
@@ -77,8 +77,8 @@
     aggregated = aggregated.sumDurationsMaxOther(metrics1);
     aggregated = aggregated.sumDurationsMaxOther(metrics2);
 
-    assertThat(aggregated.getRemoteMetrics().totalTime()).isEqualTo(Duration.ofSeconds(11));
-    assertThat(aggregated.getRemoteMetrics().executionWallTime()).isEqualTo(Duration.ofSeconds(22));
+    assertThat(aggregated.getRemoteMetrics().totalTimeInMs()).isEqualTo(11 * 1000);
+    assertThat(aggregated.getRemoteMetrics().executionWallTimeInMs()).isEqualTo(22 * 1000);
     assertThat(aggregated.getRemoteMetrics().inputBytes()).isEqualTo(100);
     assertThat(aggregated.getRemoteMetrics().inputFiles()).isEqualTo(200);
     assertThat(aggregated.getRemoteMetrics().memoryEstimate()).isEqualTo(300);
@@ -86,44 +86,41 @@
 
   @Test
   public void aggregatingMetrics_preservesExecKind() throws Exception {
-    SpawnMetrics metrics1 =
-        SpawnMetrics.Builder.forLocalExec().setTotalTime(Duration.ofSeconds(1)).build();
-    SpawnMetrics metrics2 =
-        SpawnMetrics.Builder.forRemoteExec().setTotalTime(Duration.ofSeconds(2)).build();
-    SpawnMetrics metrics3 =
-        SpawnMetrics.Builder.forWorkerExec().setTotalTime(Duration.ofSeconds(3)).build();
+    SpawnMetrics metrics1 = SpawnMetrics.Builder.forLocalExec().setTotalTimeInMs(1 * 1000).build();
+    SpawnMetrics metrics2 = SpawnMetrics.Builder.forRemoteExec().setTotalTimeInMs(2 * 1000).build();
+    SpawnMetrics metrics3 = SpawnMetrics.Builder.forWorkerExec().setTotalTimeInMs(3 * 1000).build();
 
     AggregatedSpawnMetrics aggregated = AggregatedSpawnMetrics.EMPTY;
     aggregated = aggregated.sumAllMetrics(metrics1);
     aggregated = aggregated.sumDurationsMaxOther(metrics2);
     aggregated = aggregated.sumDurationsMaxOther(metrics3);
 
-    assertThat(aggregated.getMetrics(SpawnMetrics.ExecKind.LOCAL).totalTime())
-        .isEqualTo(Duration.ofSeconds(1));
-    assertThat(aggregated.getMetrics(SpawnMetrics.ExecKind.REMOTE).totalTime())
-        .isEqualTo(Duration.ofSeconds(2));
-    assertThat(aggregated.getMetrics(SpawnMetrics.ExecKind.WORKER).totalTime())
-        .isEqualTo(Duration.ofSeconds(3));
+    assertThat(aggregated.getMetrics(SpawnMetrics.ExecKind.LOCAL).totalTimeInMs())
+        .isEqualTo(1 * 1000L);
+    assertThat(aggregated.getMetrics(SpawnMetrics.ExecKind.REMOTE).totalTimeInMs())
+        .isEqualTo(2 * 1000L);
+    assertThat(aggregated.getMetrics(SpawnMetrics.ExecKind.WORKER).totalTimeInMs())
+        .isEqualTo(3 * 1000L);
   }
 
   @Test
   public void toString_printsOnlyRemote() throws Exception {
     SpawnMetrics metrics1 =
         SpawnMetrics.Builder.forLocalExec()
-            .setTotalTime(Duration.ofSeconds(1))
-            .setExecutionWallTime(Duration.ofSeconds(1))
+            .setTotalTimeInMs(1 * 1000)
+            .setExecutionWallTimeInMs(1 * 1000)
             .build();
     SpawnMetrics metrics2 =
         SpawnMetrics.Builder.forRemoteExec()
-            .setTotalTime(Duration.ofSeconds(2))
-            .setNetworkTime(Duration.ofSeconds(1))
-            .setExecutionWallTime(Duration.ofSeconds(1))
+            .setTotalTimeInMs(2 * 1000)
+            .setNetworkTimeInMs(1 * 1000)
+            .setExecutionWallTimeInMs(1 * 1000)
             .build();
     SpawnMetrics metrics3 =
         SpawnMetrics.Builder.forWorkerExec()
-            .setTotalTime(Duration.ofSeconds(3))
-            .setQueueTime(Duration.ofSeconds(1))
-            .setExecutionWallTime(Duration.ofSeconds(2))
+            .setTotalTimeInMs(3 * 1000)
+            .setQueueTimeInMs(1 * 1000)
+            .setExecutionWallTimeInMs(2 * 1000)
             .build();
 
     AggregatedSpawnMetrics aggregated =
diff --git a/src/test/java/com/google/devtools/build/lib/actions/SpawnMetricsTest.java b/src/test/java/com/google/devtools/build/lib/actions/SpawnMetricsTest.java
index d44fe32..6f7a78f 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/SpawnMetricsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/SpawnMetricsTest.java
@@ -15,7 +15,6 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import java.time.Duration;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -28,8 +27,8 @@
   public void builder_addDurationsNonDurations() throws Exception {
     SpawnMetrics metrics1 =
         SpawnMetrics.Builder.forRemoteExec()
-            .setTotalTime(Duration.ofSeconds(1))
-            .setExecutionWallTime(Duration.ofSeconds(2))
+            .setTotalTimeInMs(1 * 1000)
+            .setExecutionWallTimeInMs(2 * 1000)
             .setInputBytes(10)
             .setInputFiles(20)
             .setMemoryEstimateBytes(30)
@@ -38,12 +37,12 @@
             .setOutputBytesLimit(50)
             .setOutputFilesLimit(60)
             .setMemoryBytesLimit(70)
-            .setTimeLimit(Duration.ofSeconds(80))
+            .setTimeLimitInMs(80 * 1000)
             .build();
     SpawnMetrics metrics2 =
         SpawnMetrics.Builder.forRemoteExec()
-            .setTotalTime(Duration.ofSeconds(10))
-            .setExecutionWallTime(Duration.ofSeconds(20))
+            .setTotalTimeInMs(10 * 1000)
+            .setExecutionWallTimeInMs(20 * 1000)
             .setInputBytes(100)
             .setInputFiles(200)
             .setMemoryEstimateBytes(300)
@@ -52,7 +51,7 @@
             .setOutputBytesLimit(500)
             .setOutputFilesLimit(600)
             .setMemoryBytesLimit(700)
-            .setTimeLimit(Duration.ofSeconds(800))
+            .setTimeLimitInMs(800 * 1000)
             .build();
 
     SpawnMetrics result =
@@ -63,8 +62,8 @@
             .addNonDurations(metrics2)
             .build();
 
-    assertThat(result.totalTime()).isEqualTo(Duration.ofSeconds(11));
-    assertThat(result.executionWallTime()).isEqualTo(Duration.ofSeconds(22));
+    assertThat(result.totalTimeInMs()).isEqualTo(11 * 1000);
+    assertThat(result.executionWallTimeInMs()).isEqualTo(22 * 1000);
     assertThat(result.inputBytes()).isEqualTo(110);
     assertThat(result.inputFiles()).isEqualTo(220);
     assertThat(result.memoryEstimate()).isEqualTo(330);
@@ -73,15 +72,15 @@
     assertThat(result.outputBytesLimit()).isEqualTo(550);
     assertThat(result.outputFilesLimit()).isEqualTo(660);
     assertThat(result.memoryLimit()).isEqualTo(770);
-    assertThat(result.timeLimit()).isEqualTo(Duration.ofSeconds(880));
+    assertThat(result.timeLimitInMs()).isEqualTo(880 * 1000);
   }
 
   @Test
   public void builder_addDurationsMaxNonDurations() throws Exception {
     SpawnMetrics metrics1 =
         SpawnMetrics.Builder.forRemoteExec()
-            .setTotalTime(Duration.ofSeconds(1))
-            .setExecutionWallTime(Duration.ofSeconds(2))
+            .setTotalTimeInMs(1 * 1000)
+            .setExecutionWallTimeInMs(2 * 1000)
             .setInputBytes(10)
             .setInputFiles(20)
             .setMemoryEstimateBytes(30)
@@ -90,12 +89,12 @@
             .setOutputBytesLimit(50)
             .setOutputFilesLimit(60)
             .setMemoryBytesLimit(70)
-            .setTimeLimit(Duration.ofSeconds(80))
+            .setTimeLimitInMs(80 * 1000)
             .build();
     SpawnMetrics metrics2 =
         SpawnMetrics.Builder.forRemoteExec()
-            .setTotalTime(Duration.ofSeconds(10))
-            .setExecutionWallTime(Duration.ofSeconds(20))
+            .setTotalTimeInMs(10 * 1000)
+            .setExecutionWallTimeInMs(20 * 1000)
             .setInputBytes(100)
             .setInputFiles(200)
             .setMemoryEstimateBytes(300)
@@ -104,7 +103,7 @@
             .setOutputBytesLimit(500)
             .setOutputFilesLimit(600)
             .setMemoryBytesLimit(700)
-            .setTimeLimit(Duration.ofSeconds(800))
+            .setTimeLimitInMs(800 * 1000)
             .build();
 
     SpawnMetrics result =
@@ -115,8 +114,8 @@
             .maxNonDurations(metrics2)
             .build();
 
-    assertThat(result.totalTime()).isEqualTo(Duration.ofSeconds(11));
-    assertThat(result.executionWallTime()).isEqualTo(Duration.ofSeconds(22));
+    assertThat(result.totalTimeInMs()).isEqualTo(11 * 1000);
+    assertThat(result.executionWallTimeInMs()).isEqualTo(22 * 1000);
     assertThat(result.inputBytes()).isEqualTo(100);
     assertThat(result.inputFiles()).isEqualTo(200);
     assertThat(result.memoryEstimate()).isEqualTo(300);
@@ -125,6 +124,6 @@
     assertThat(result.outputBytesLimit()).isEqualTo(500);
     assertThat(result.outputFilesLimit()).isEqualTo(600);
     assertThat(result.memoryLimit()).isEqualTo(700);
-    assertThat(result.timeLimit()).isEqualTo(Duration.ofSeconds(800));
+    assertThat(result.timeLimitInMs()).isEqualTo(800 * 1000);
   }
 }
diff --git a/src/test/java/com/google/devtools/build/lib/actions/SpawnResultTest.java b/src/test/java/com/google/devtools/build/lib/actions/SpawnResultTest.java
index 46fc034..df9e2b8 100644
--- a/src/test/java/com/google/devtools/build/lib/actions/SpawnResultTest.java
+++ b/src/test/java/com/google/devtools/build/lib/actions/SpawnResultTest.java
@@ -23,7 +23,6 @@
 import com.google.devtools.build.lib.vfs.Path;
 import com.google.devtools.build.lib.vfs.util.FileSystems;
 import com.google.protobuf.ByteString;
-import java.time.Duration;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
@@ -39,7 +38,7 @@
     SpawnResult r =
         new SpawnResult.Builder()
             .setStatus(SpawnResult.Status.TIMEOUT)
-            .setWallTime(Duration.ofSeconds(5))
+            .setWallTimeInMs(5 * 1000)
             .setExitCode(SpawnResult.POSIX_TIMEOUT_EXIT_CODE)
             .setFailureDetail(
                 FailureDetail.newBuilder()
diff --git a/src/test/java/com/google/devtools/build/lib/exec/StandaloneTestStrategyTest.java b/src/test/java/com/google/devtools/build/lib/exec/StandaloneTestStrategyTest.java
index 6b9956f..da97104 100644
--- a/src/test/java/com/google/devtools/build/lib/exec/StandaloneTestStrategyTest.java
+++ b/src/test/java/com/google/devtools/build/lib/exec/StandaloneTestStrategyTest.java
@@ -75,7 +75,6 @@
 import com.google.devtools.common.options.Options;
 import java.io.IOException;
 import java.io.OutputStream;
-import java.time.Duration;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.stream.Collectors;
@@ -286,7 +285,7 @@
     SpawnResult expectedSpawnResult =
         new SpawnResult.Builder()
             .setStatus(Status.SUCCESS)
-            .setWallTime(Duration.ofMillis(10))
+            .setWallTimeInMs(10)
             .setRunnerName("test")
             .build();
     when(spawnStrategy.exec(any(), any())).thenReturn(ImmutableList.of(expectedSpawnResult));
@@ -347,13 +346,13 @@
             .setStatus(Status.NON_ZERO_EXIT)
             .setExitCode(1)
             .setFailureDetail(NON_ZERO_EXIT_DETAILS)
-            .setWallTime(Duration.ofMillis(10))
+            .setWallTimeInMs(10)
             .setRunnerName("test")
             .build();
     SpawnResult passSpawnResult =
         new SpawnResult.Builder()
             .setStatus(Status.SUCCESS)
-            .setWallTime(Duration.ofMillis(15))
+            .setWallTimeInMs(15)
             .setRunnerName("test")
             .build();
     when(spawnStrategy.exec(any(), any()))
@@ -420,7 +419,7 @@
     SpawnResult expectedSpawnResult =
         new SpawnResult.Builder()
             .setStatus(Status.SUCCESS)
-            .setWallTime(Duration.ofMillis(10))
+            .setWallTimeInMs(10)
             .setRunnerName("remote")
             .setExecutorHostname("a-remote-host")
             .build();
@@ -479,7 +478,7 @@
         new SpawnResult.Builder()
             .setStatus(Status.SUCCESS)
             .setCacheHit(true)
-            .setWallTime(Duration.ofMillis(10))
+            .setWallTimeInMs(10)
             .setRunnerName("remote cache")
             .build();
     when(spawnStrategy.exec(any(), any())).thenReturn(ImmutableList.of(expectedSpawnResult));
diff --git a/src/test/java/com/google/devtools/build/lib/exec/local/LocalSpawnRunnerTest.java b/src/test/java/com/google/devtools/build/lib/exec/local/LocalSpawnRunnerTest.java
index 89f5755..13ce020 100644
--- a/src/test/java/com/google/devtools/build/lib/exec/local/LocalSpawnRunnerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/exec/local/LocalSpawnRunnerTest.java
@@ -621,9 +621,9 @@
     assertThat(result.status()).isEqualTo(SpawnResult.Status.EXECUTION_FAILED);
     assertThat(result.exitCode()).isEqualTo(-1);
     assertThat(result.setupSuccess()).isFalse();
-    assertThat(result.getWallTime()).isNull();
-    assertThat(result.getUserTime()).isNull();
-    assertThat(result.getSystemTime()).isNull();
+    assertThat(result.getWallTimeInMs()).isEqualTo(0);
+    assertThat(result.getUserTimeInMs()).isEqualTo(0);
+    assertThat(result.getSystemTimeInMs()).isEqualTo(0);
     assertThat(result.getExecutorHostName()).isEqualTo(NetUtil.getCachedShortHostName());
 
     assertThat(FileSystemUtils.readContent(fs.getPath("/out/stderr"), UTF_8))
@@ -653,9 +653,9 @@
     assertThat(reply.status()).isEqualTo(SpawnResult.Status.EXECUTION_DENIED);
     assertThat(reply.exitCode()).isEqualTo(-1);
     assertThat(reply.setupSuccess()).isFalse();
-    assertThat(reply.getWallTime()).isNull();
-    assertThat(reply.getUserTime()).isNull();
-    assertThat(reply.getSystemTime()).isNull();
+    assertThat(reply.getWallTimeInMs()).isEqualTo(0);
+    assertThat(reply.getUserTimeInMs()).isEqualTo(0);
+    assertThat(reply.getSystemTimeInMs()).isEqualTo(0);
     assertThat(reply.getExecutorHostName()).isEqualTo(NetUtil.getCachedShortHostName());
 
     // TODO(ulfjack): Maybe we should only lock after checking?
@@ -954,17 +954,17 @@
     LocalExecutionOptions options = Options.getDefaults(LocalExecutionOptions.class);
     options.collectLocalExecutionStatistics = true;
 
-    Duration minimumWallTimeToSpend = Duration.ofSeconds(10);
+    int minimumWallTimeToSpendInMs = 10 * 1000;
 
-    Duration minimumUserTimeToSpend = minimumWallTimeToSpend;
+    int minimumUserTimeToSpendInMs = minimumWallTimeToSpendInMs;
     // Under normal loads we should be able to use a much lower bound for maxUserTime, but be
     // generous here in case of hardware issues.
-    Duration maximumUserTimeToSpend = minimumUserTimeToSpend.plus(Duration.ofSeconds(20));
+    int maximumUserTimeToSpendInMs = minimumUserTimeToSpendInMs + 20 * 1000;
 
-    Duration minimumSystemTimeToSpend = Duration.ZERO;
+    int minimumSystemTimeToSpendInMs = 0;
     // Under normal loads we should be able to use a much lower bound for maxSysTime, but be
     // generous here in case of hardware issues.
-    Duration maximumSystemTimeToSpend = minimumSystemTimeToSpend.plus(Duration.ofSeconds(20));
+    int maximumSystemTimeToSpendInMs = minimumSystemTimeToSpendInMs + 20 * 1000;
 
     Path execRoot = getTemporaryExecRoot(fs);
     Path embeddedBinaries = getTemporaryEmbeddedBin(fs);
@@ -989,8 +989,8 @@
     Spawn spawn =
         new SpawnBuilder(
                 cpuTimeSpenderPath.getPathString(),
-                String.valueOf(minimumUserTimeToSpend.getSeconds()),
-                String.valueOf(minimumSystemTimeToSpend.getSeconds()))
+                String.valueOf(minimumUserTimeToSpendInMs / 1000L),
+                String.valueOf(minimumSystemTimeToSpendInMs / 1000L))
             .build();
 
     FileOutErr fileOutErr = new FileOutErr(fs.getPath("/dev/null"), fs.getPath("/dev/null"));
@@ -1003,15 +1003,15 @@
     assertThat(spawnResult.setupSuccess()).isTrue();
     assertThat(spawnResult.getExecutorHostName()).isEqualTo(NetUtil.getCachedShortHostName());
 
-    assertThat(spawnResult.getWallTime()).isNotNull();
-    assertThat(spawnResult.getWallTime()).isAtLeast(minimumWallTimeToSpend);
+    assertThat(spawnResult.getWallTimeInMs()).isNotNull();
+    assertThat(spawnResult.getWallTimeInMs()).isAtLeast(minimumWallTimeToSpendInMs);
     // Under heavy starvation, max wall time could be anything, so don't check it here.
-    assertThat(spawnResult.getUserTime()).isNotNull();
-    assertThat(spawnResult.getUserTime()).isAtLeast(minimumUserTimeToSpend);
-    assertThat(spawnResult.getUserTime()).isAtMost(maximumUserTimeToSpend);
-    assertThat(spawnResult.getSystemTime()).isNotNull();
-    assertThat(spawnResult.getSystemTime()).isAtLeast(minimumSystemTimeToSpend);
-    assertThat(spawnResult.getSystemTime()).isAtMost(maximumSystemTimeToSpend);
+    assertThat(spawnResult.getUserTimeInMs()).isNotNull();
+    assertThat(spawnResult.getUserTimeInMs()).isAtLeast(minimumUserTimeToSpendInMs);
+    assertThat(spawnResult.getUserTimeInMs()).isAtMost(maximumUserTimeToSpendInMs);
+    assertThat(spawnResult.getSystemTimeInMs()).isNotNull();
+    assertThat(spawnResult.getSystemTimeInMs()).isAtLeast(minimumSystemTimeToSpendInMs);
+    assertThat(spawnResult.getSystemTimeInMs()).isAtMost(maximumSystemTimeToSpendInMs);
     assertThat(spawnResult.getNumBlockOutputOperations()).isAtLeast(0L);
     assertThat(spawnResult.getNumBlockInputOperations()).isAtLeast(0L);
     assertThat(spawnResult.getNumInvoluntaryContextSwitches()).isAtLeast(0L);
@@ -1027,10 +1027,10 @@
     LocalExecutionOptions options = Options.getDefaults(LocalExecutionOptions.class);
     options.collectLocalExecutionStatistics = false;
 
-    Duration minimumWallTimeToSpend = Duration.ofSeconds(1);
+    int minimumWallTimeToSpendInMs = 1 * 1000;
 
-    Duration minimumUserTimeToSpend = minimumWallTimeToSpend;
-    Duration minimumSystemTimeToSpend = Duration.ZERO;
+    int minimumUserTimeToSpendInMs = minimumWallTimeToSpendInMs;
+    int minimumSystemTimeToSpendInMs = 0;
 
     Path execRoot = getTemporaryExecRoot(fs);
     Path embeddedBinaries = getTemporaryEmbeddedBin(fs);
@@ -1055,8 +1055,8 @@
     Spawn spawn =
         new SpawnBuilder(
                 cpuTimeSpenderPath.getPathString(),
-                String.valueOf(minimumUserTimeToSpend.getSeconds()),
-                String.valueOf(minimumSystemTimeToSpend.getSeconds()))
+                String.valueOf(minimumUserTimeToSpendInMs / 1000L),
+                String.valueOf(minimumSystemTimeToSpendInMs / 1000L))
             .build();
 
     FileOutErr fileOutErr = new FileOutErr(fs.getPath("/dev/null"), fs.getPath("/dev/null"));
@@ -1069,11 +1069,11 @@
     assertThat(spawnResult.setupSuccess()).isTrue();
     assertThat(spawnResult.getExecutorHostName()).isEqualTo(NetUtil.getCachedShortHostName());
 
-    assertThat(spawnResult.getWallTime()).isNotNull();
-    assertThat(spawnResult.getWallTime()).isAtLeast(minimumWallTimeToSpend);
+    assertThat(spawnResult.getWallTimeInMs()).isNotNull();
+    assertThat(spawnResult.getWallTimeInMs()).isAtLeast(minimumWallTimeToSpendInMs);
     // Under heavy starvation, max wall time could be anything, so don't check it here.
-    assertThat(spawnResult.getUserTime()).isNull();
-    assertThat(spawnResult.getSystemTime()).isNull();
+    assertThat(spawnResult.getUserTimeInMs()).isEqualTo(0);
+    assertThat(spawnResult.getSystemTimeInMs()).isEqualTo(0);
     assertThat(spawnResult.getNumBlockOutputOperations()).isNull();
     assertThat(spawnResult.getNumBlockInputOperations()).isNull();
     assertThat(spawnResult.getNumInvoluntaryContextSwitches()).isNull();
diff --git a/src/test/java/com/google/devtools/build/lib/remote/GrpcCacheClientTest.java b/src/test/java/com/google/devtools/build/lib/remote/GrpcCacheClientTest.java
index 4a6f918..643cd7c 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/GrpcCacheClientTest.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/GrpcCacheClientTest.java
@@ -507,7 +507,7 @@
             outErr,
             /* exitCode= */ 0,
             /* startTime= */ null,
-            /* wallTime= */ null);
+            /* wallTimeInMs= */ 0);
     return uploadManifest.upload(context, remoteCache, NullEventHandler.INSTANCE);
   }
 
diff --git a/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnRunnerTest.java b/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnRunnerTest.java
index 16eec4c..a6db5fb 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnRunnerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnRunnerTest.java
@@ -1227,10 +1227,10 @@
   public void accountingAddsDurationsForStages() {
     SpawnMetrics.Builder builder =
         SpawnMetrics.Builder.forRemoteExec()
-            .setQueueTime(Duration.ofSeconds(1))
-            .setSetupTime(Duration.ofSeconds(2))
-            .setExecutionWallTime(Duration.ofSeconds(2))
-            .setProcessOutputsTime(Duration.ofSeconds(2));
+            .setQueueTimeInMs(1 * 1000)
+            .setSetupTimeInMs(2 * 1000)
+            .setExecutionWallTimeInMs(2 * 1000)
+            .setProcessOutputsTimeInMs(2 * 1000);
     Timestamp queued = Timestamp.getDefaultInstance();
     com.google.protobuf.Duration oneSecond = Durations.fromMillis(1000);
     Timestamp workerStart = Timestamps.add(queued, oneSecond);
@@ -1251,13 +1251,13 @@
     RemoteSpawnRunner.spawnMetricsAccounting(builder, executedMetadata);
     SpawnMetrics spawnMetrics = builder.build();
     // remote queue time is accumulated
-    assertThat(spawnMetrics.queueTime()).isEqualTo(Duration.ofSeconds(2));
+    assertThat(spawnMetrics.queueTimeInMs()).isEqualTo(2 * 1000L);
     // setup time is substituted
-    assertThat(spawnMetrics.setupTime()).isEqualTo(Duration.ofSeconds(1));
+    assertThat(spawnMetrics.setupTimeInMs()).isEqualTo(1 * 1000L);
     // execution time is unspecified, assume substituted
-    assertThat(spawnMetrics.executionWallTime()).isEqualTo(Duration.ofSeconds(1));
+    assertThat(spawnMetrics.executionWallTimeInMs()).isEqualTo(1 * 1000L);
     // ProcessOutputs time is unspecified, assume substituted
-    assertThat(spawnMetrics.processOutputsTime()).isEqualTo(Duration.ofSeconds(1));
+    assertThat(spawnMetrics.processOutputsTimeInMs()).isEqualTo(1 * 1000L);
   }
 
   @Test
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/ExecutionGraphModuleTest.java b/src/test/java/com/google/devtools/build/lib/runtime/ExecutionGraphModuleTest.java
index 3c9685d..fa71005 100644
--- a/src/test/java/com/google/devtools/build/lib/runtime/ExecutionGraphModuleTest.java
+++ b/src/test/java/com/google/devtools/build/lib/runtime/ExecutionGraphModuleTest.java
@@ -64,7 +64,6 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.time.Duration;
 import java.time.Instant;
 import java.util.UUID;
 import org.junit.Before;
@@ -122,9 +121,9 @@
             .setExitCode(0)
             .setSpawnMetrics(
                 SpawnMetrics.Builder.forLocalExec()
-                    .setTotalTime(Duration.ofMillis(1234L))
-                    .setExecutionWallTime(Duration.ofMillis(2345L))
-                    .setProcessOutputsTime(Duration.ofMillis(3456L))
+                    .setTotalTimeInMs(1234)
+                    .setExecutionWallTimeInMs(2345)
+                    .setProcessOutputsTimeInMs(3456)
                     .build())
             .build();
     startLogging(eventBus, uuid, buffer, DependencyInfo.NONE);
@@ -167,20 +166,17 @@
             .setExitCode(0)
             .setSpawnMetrics(
                 SpawnMetrics.Builder.forLocalExec()
-                    .setTotalTime(Duration.ofMillis(1234L))
-                    .setExecutionWallTime(Duration.ofMillis(2345L))
-                    .setProcessOutputsTime(Duration.ofMillis(3456L))
-                    .setParseTime(Duration.ofMillis(2000))
+                    .setTotalTimeInMs(1234)
+                    .setExecutionWallTimeInMs(2345)
+                    .setProcessOutputsTimeInMs(3456)
+                    .setParseTimeInMs(2000)
                     .build())
             .build();
     startLogging(eventBus, uuid, buffer, DependencyInfo.NONE);
     Instant startTimeInstant = Instant.ofEpochMilli(999888777L);
     module.discoverInputs(
         new DiscoveredInputsEvent(
-            SpawnMetrics.Builder.forOtherExec()
-                .setParseTime(Duration.ofMillis(987))
-                .setTotalTime(Duration.ofMillis(987))
-                .build(),
+            SpawnMetrics.Builder.forOtherExec().setParseTimeInMs(987).setTotalTimeInMs(987).build(),
             new ActionsTestUtil.NullAction(createOutputArtifact("output/foo/out")),
             0));
     module.spawnExecuted(new SpawnExecutedEvent(spawn, result, startTimeInstant));
@@ -243,9 +239,9 @@
             .setExitCode(0)
             .setSpawnMetrics(
                 SpawnMetrics.Builder.forLocalExec()
-                    .setTotalTime(Duration.ofMillis(1234L))
-                    .setExecutionWallTime(Duration.ofMillis(2345L))
-                    .setProcessOutputsTime(Duration.ofMillis(3456L))
+                    .setTotalTimeInMs(1234)
+                    .setExecutionWallTimeInMs(2345)
+                    .setProcessOutputsTimeInMs(3456)
                     .build())
             .build();
     startLogging(eventBus, uuid, buffer, DependencyInfo.ALL);
@@ -386,9 +382,9 @@
             .setExitCode(0)
             .setSpawnMetrics(
                 SpawnMetrics.Builder.forLocalExec()
-                    .setTotalTime(Duration.ofMillis(1234L))
-                    .setExecutionWallTime(Duration.ofMillis(2345L))
-                    .setProcessOutputsTime(Duration.ofMillis(3456L))
+                    .setTotalTimeInMs(1234)
+                    .setExecutionWallTimeInMs(2345)
+                    .setProcessOutputsTimeInMs(3456)
                     .build())
             .build();
     startLogging(eventBus, uuid, buffer, DependencyInfo.NONE);
@@ -413,7 +409,7 @@
     module.spawnExecuted(
         new SpawnExecutedEvent(
             new SpawnBuilder().withOwnerPrimaryOutput(createOutputArtifact("foo/out")).build(),
-            createRemoteSpawnResult(Duration.ofMillis(200)),
+            createRemoteSpawnResult(200),
             Instant.ofEpochMilli(100)));
     module.actionComplete(
         new ActionCompletionEvent(
@@ -446,7 +442,7 @@
     module.spawnExecuted(
         new SpawnExecutedEvent(
             new SpawnBuilder().withOwnerPrimaryOutput(createOutputArtifact("foo/out")).build(),
-            createRemoteSpawnResult(Duration.ofMillis(200)),
+            createRemoteSpawnResult(200),
             Instant.ofEpochMilli(100)));
     var action = new ActionsTestUtil.NullAction(createOutputArtifact("bar/out"));
     module.actionComplete(new ActionCompletionEvent(0, action, null));
@@ -475,8 +471,8 @@
   public void multipleSpawnsWithSameOutput_recordsBothSpawnsWithRetry() throws Exception {
     var buffer = new ByteArrayOutputStream();
     startLogging(eventBus, UUID.randomUUID(), buffer, DependencyInfo.ALL);
-    SpawnResult localResult = createLocalSpawnResult(Duration.ofMillis(100));
-    SpawnResult remoteResult = createRemoteSpawnResult(Duration.ofMillis(200));
+    SpawnResult localResult = createLocalSpawnResult(100);
+    SpawnResult remoteResult = createRemoteSpawnResult(200);
     Spawn spawn =
         new SpawnBuilder().withOwnerPrimaryOutput(createOutputArtifact("foo/out")).build();
 
@@ -548,8 +544,8 @@
         UUID.randomUUID(),
         buffer,
         DependencyInfo.ALL);
-    SpawnResult localResult = createLocalSpawnResult(Duration.ofMillis(100));
-    SpawnResult remoteResult = createRemoteSpawnResult(Duration.ofMillis(200));
+    SpawnResult localResult = createLocalSpawnResult(100);
+    SpawnResult remoteResult = createRemoteSpawnResult(200);
     Spawn spawn =
         new SpawnBuilder().withOwnerPrimaryOutput(createOutputArtifact("foo/out")).build();
 
@@ -593,8 +589,8 @@
         UUID.randomUUID(),
         buffer,
         DependencyInfo.ALL);
-    SpawnResult localResult = createLocalSpawnResult(Duration.ofMillis(100));
-    SpawnResult remoteResult = createRemoteSpawnResult(Duration.ofMillis(200));
+    SpawnResult localResult = createLocalSpawnResult(100);
+    SpawnResult remoteResult = createRemoteSpawnResult(200);
     Artifact input = createOutputArtifact("foo/input");
     Spawn spawn = new SpawnBuilder().withOwnerPrimaryOutput(input).build();
     Spawn dependentSpawn =
@@ -602,7 +598,7 @@
             .withOwnerPrimaryOutput(createOutputArtifact("foo/output"))
             .withInput(input)
             .build();
-    SpawnResult dependentResult = createRemoteSpawnResult(Duration.ofMillis(300));
+    SpawnResult dependentResult = createRemoteSpawnResult(300);
 
     module.spawnExecuted(new SpawnExecutedEvent(spawn, localResult, Instant.EPOCH));
     module.spawnExecuted(new SpawnExecutedEvent(spawn, remoteResult, Instant.ofEpochMilli(10)));
@@ -666,21 +662,23 @@
         artifactRoot, artifactRoot.getExecPath().getRelative(rootRelativePath));
   }
 
-  private SpawnResult createLocalSpawnResult(Duration totalTime) {
+  private SpawnResult createLocalSpawnResult(int totalTimeInMs) {
     return new SpawnResult.Builder()
         .setRunnerName("local")
         .setStatus(Status.SUCCESS)
         .setExitCode(0)
-        .setSpawnMetrics(SpawnMetrics.Builder.forLocalExec().setTotalTime(totalTime).build())
+        .setSpawnMetrics(
+            SpawnMetrics.Builder.forLocalExec().setTotalTimeInMs(totalTimeInMs).build())
         .build();
   }
 
-  private SpawnResult createRemoteSpawnResult(Duration totalTime) {
+  private SpawnResult createRemoteSpawnResult(int totalTimeInMs) {
     return new SpawnResult.Builder()
         .setRunnerName("remote")
         .setStatus(Status.SUCCESS)
         .setExitCode(0)
-        .setSpawnMetrics(SpawnMetrics.Builder.forRemoteExec().setTotalTime(totalTime).build())
+        .setSpawnMetrics(
+            SpawnMetrics.Builder.forRemoteExec().setTotalTimeInMs(totalTimeInMs).build())
         .build();
   }
 
diff --git a/src/test/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedSpawnRunnerTest.java b/src/test/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedSpawnRunnerTest.java
index e9b8c85b..9dccffc 100644
--- a/src/test/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedSpawnRunnerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/sandbox/ProcessWrapperSandboxedSpawnRunnerTest.java
@@ -80,7 +80,7 @@
     assertThat(spawnResult.status()).isEqualTo(SpawnResult.Status.SUCCESS);
     assertThat(spawnResult.exitCode()).isEqualTo(0);
     assertThat(spawnResult.setupSuccess()).isTrue();
-    assertThat(spawnResult.getWallTime()).isNotNull();
+    assertThat(spawnResult.getWallTimeInMs()).isNotNull();
   }
 
   @Test
@@ -88,15 +88,15 @@
     // TODO(b/62588075) Currently no process-wrapper or execution statistics support in Windows.
     assumeTrue(OS.getCurrent() != OS.WINDOWS);
 
-    Duration minimumWallTimeToSpend = Duration.ofSeconds(10);
+    int minimumWallTimeToSpend = 10 * 1000;
     // Because of e.g. interference, wall time taken may be much larger than CPU time used.
-    Duration maximumWallTimeToSpend = Duration.ofSeconds(40);
+    int maximumWallTimeToSpend = 40 * 1000;
 
-    Duration minimumUserTimeToSpend = minimumWallTimeToSpend;
-    Duration maximumUserTimeToSpend = minimumUserTimeToSpend.plusSeconds(2);
+    int minimumUserTimeToSpend = minimumWallTimeToSpend;
+    int maximumUserTimeToSpend = minimumUserTimeToSpend + 2 * 1000;
 
-    Duration minimumSystemTimeToSpend = Duration.ZERO;
-    Duration maximumSystemTimeToSpend = minimumSystemTimeToSpend.plusSeconds(2);
+    int minimumSystemTimeToSpend = 0;
+    int maximumSystemTimeToSpend = minimumSystemTimeToSpend + 2 * 1000;
 
     CommandEnvironment commandEnvironment = runtimeWrapper.newCommand();
     commandEnvironment.setWorkspaceName("workspace");
@@ -127,8 +127,8 @@
     Spawn spawn =
         new SpawnBuilder(
                 cpuTimeSpenderPath.getPathString(),
-                String.valueOf(minimumUserTimeToSpend.getSeconds()),
-                String.valueOf(minimumSystemTimeToSpend.getSeconds()))
+                String.valueOf(minimumUserTimeToSpend / 1000),
+                String.valueOf(minimumSystemTimeToSpend / 1000))
             .build();
 
     FileOutErr fileOutErr =
@@ -142,15 +142,15 @@
     assertThat(spawnResult.exitCode()).isEqualTo(0);
     assertThat(spawnResult.setupSuccess()).isTrue();
 
-    assertThat(spawnResult.getWallTime()).isNotNull();
-    assertThat(spawnResult.getWallTime()).isAtLeast(minimumWallTimeToSpend);
-    assertThat(spawnResult.getWallTime()).isAtMost(maximumWallTimeToSpend);
-    assertThat(spawnResult.getUserTime()).isNotNull();
-    assertThat(spawnResult.getUserTime()).isAtLeast(minimumUserTimeToSpend);
-    assertThat(spawnResult.getUserTime()).isAtMost(maximumUserTimeToSpend);
-    assertThat(spawnResult.getSystemTime()).isNotNull();
-    assertThat(spawnResult.getSystemTime()).isAtLeast(minimumSystemTimeToSpend);
-    assertThat(spawnResult.getSystemTime()).isAtMost(maximumSystemTimeToSpend);
+    assertThat(spawnResult.getWallTimeInMs()).isNotNull();
+    assertThat(spawnResult.getWallTimeInMs()).isAtLeast(minimumWallTimeToSpend);
+    assertThat(spawnResult.getWallTimeInMs()).isAtMost(maximumWallTimeToSpend);
+    assertThat(spawnResult.getUserTimeInMs()).isNotNull();
+    assertThat(spawnResult.getUserTimeInMs()).isAtLeast(minimumUserTimeToSpend);
+    assertThat(spawnResult.getUserTimeInMs()).isAtMost(maximumUserTimeToSpend);
+    assertThat(spawnResult.getSystemTimeInMs()).isNotNull();
+    assertThat(spawnResult.getSystemTimeInMs()).isAtLeast(minimumSystemTimeToSpend);
+    assertThat(spawnResult.getSystemTimeInMs()).isAtMost(maximumSystemTimeToSpend);
     assertThat(spawnResult.getNumBlockOutputOperations()).isAtLeast(0L);
     assertThat(spawnResult.getNumBlockInputOperations()).isAtLeast(0L);
     assertThat(spawnResult.getNumInvoluntaryContextSwitches()).isAtLeast(0L);
@@ -161,12 +161,12 @@
     // TODO(b/62588075) Currently no process-wrapper support in Windows.
     assumeTrue(OS.getCurrent() != OS.WINDOWS);
 
-    Duration minimumWallTimeToSpend = Duration.ofSeconds(10);
+    int minimumWallTimeToSpend = 10 * 1000;
     // Because of e.g. interference, wall time taken may be much larger than CPU time used.
-    Duration maximumWallTimeToSpend = Duration.ofSeconds(40);
+    int maximumWallTimeToSpend = 40 * 1000;
 
-    Duration minimumUserTimeToSpend = minimumWallTimeToSpend;
-    Duration minimumSystemTimeToSpend = Duration.ZERO;
+    int minimumUserTimeToSpend = minimumWallTimeToSpend;
+    int minimumSystemTimeToSpend = 0;
 
     CommandEnvironment commandEnvironment =
         getCommandEnvironmentWithExecutionStatisticsOptionDisabled("workspace");
@@ -198,8 +198,8 @@
     Spawn spawn =
         new SpawnBuilder(
                 cpuTimeSpenderPath.getPathString(),
-                String.valueOf(minimumUserTimeToSpend.getSeconds()),
-                String.valueOf(minimumSystemTimeToSpend.getSeconds()))
+                String.valueOf(minimumUserTimeToSpend / 1000),
+                String.valueOf(minimumSystemTimeToSpend / 1000))
             .build();
 
     FileOutErr fileOutErr =
@@ -213,11 +213,11 @@
     assertThat(spawnResult.exitCode()).isEqualTo(0);
     assertThat(spawnResult.setupSuccess()).isTrue();
 
-    assertThat(spawnResult.getWallTime()).isNotNull();
-    assertThat(spawnResult.getWallTime()).isAtLeast(minimumWallTimeToSpend);
-    assertThat(spawnResult.getWallTime()).isAtMost(maximumWallTimeToSpend);
-    assertThat(spawnResult.getUserTime()).isNull();
-    assertThat(spawnResult.getSystemTime()).isNull();
+    assertThat(spawnResult.getWallTimeInMs()).isNotNull();
+    assertThat(spawnResult.getWallTimeInMs()).isAtLeast(minimumWallTimeToSpend);
+    assertThat(spawnResult.getWallTimeInMs()).isAtMost(maximumWallTimeToSpend);
+    assertThat(spawnResult.getUserTimeInMs()).isEqualTo(0);
+    assertThat(spawnResult.getSystemTimeInMs()).isEqualTo(0);
     assertThat(spawnResult.getNumBlockOutputOperations()).isNull();
     assertThat(spawnResult.getNumBlockInputOperations()).isNull();
     assertThat(spawnResult.getNumInvoluntaryContextSwitches()).isNull();
diff --git a/src/tools/remote/src/main/java/com/google/devtools/build/remote/worker/ExecutionServer.java b/src/tools/remote/src/main/java/com/google/devtools/build/remote/worker/ExecutionServer.java
index b2149d7..7baded7 100644
--- a/src/tools/remote/src/main/java/com/google/devtools/build/remote/worker/ExecutionServer.java
+++ b/src/tools/remote/src/main/java/com/google/devtools/build/remote/worker/ExecutionServer.java
@@ -365,7 +365,7 @@
                 outErr,
                 exitCode,
                 startTime,
-                wallTime);
+                (int) wallTime.toMillis());
         result = manifest.upload(context, cache, NullEventHandler.INSTANCE);
       } catch (ExecException e) {
         if (errStatus == null) {