Store the spawn digest in the execution log.
The digest is the unique identifier for a remote execution request and comes in handy when replaying the action locally for debugging (it dispenses with inspecting the grpc log).
PiperOrigin-RevId: 446170315
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 962db41..7205dcb 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
@@ -13,6 +13,7 @@
// limitations under the License.
package com.google.devtools.build.lib.actions;
+import com.google.auto.value.AutoValue;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.devtools.build.lib.bugreport.BugReport;
@@ -268,6 +269,23 @@
/** Whether the spawn result was obtained through remote strategy. */
boolean wasRemote();
+ /** A unique identifier for the spawn. */
+ @AutoValue
+ @Immutable
+ public abstract class Digest {
+ public abstract String getHash();
+
+ public abstract Long getSizeBytes();
+
+ public static Digest of(String hash, Long sizeBytes) {
+ return new AutoValue_SpawnResult_Digest(hash, sizeBytes);
+ }
+ }
+
+ default Optional<Digest> getDigest() {
+ return Optional.empty();
+ }
+
/** Basic implementation of {@link SpawnResult}. */
@Immutable
@ThreadSafe
@@ -294,7 +312,9 @@
// Invariant: Either both have a value or both are null.
@Nullable private final ActionInput inMemoryOutputFile;
@Nullable private final ByteString inMemoryContents;
+
private final boolean remote;
+ private final Optional<Digest> digest;
SimpleSpawnResult(Builder builder) {
this.exitCode = builder.exitCode;
@@ -320,6 +340,7 @@
this.inMemoryContents = builder.inMemoryContents;
this.actionMetadataLog = builder.actionMetadataLog;
this.remote = builder.remote;
+ this.digest = builder.digest;
}
@Override
@@ -456,6 +477,11 @@
public boolean wasRemote() {
return remote;
}
+
+ @Override
+ public Optional<Digest> getDigest() {
+ return digest;
+ }
}
/** Builder class for {@link SpawnResult}. */
@@ -482,7 +508,9 @@
// Invariant: Either both have a value or both are null.
@Nullable private ActionInput inMemoryOutputFile;
@Nullable private ByteString inMemoryContents;
+
private boolean remote;
+ private Optional<Digest> digest = Optional.empty();
public SpawnResult build() {
Preconditions.checkArgument(!runnerName.isEmpty());
@@ -617,6 +645,11 @@
this.remote = remote;
return this;
}
+
+ public Builder setDigest(Optional<Digest> digest) {
+ this.digest = digest;
+ return this;
+ }
}
/** A {@link Spawn}'s metadata name and {@link Path}. */
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 8da6e88..bd954c4 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
@@ -150,6 +150,13 @@
builder.setExitCode(result.exitCode());
builder.setRemoteCacheHit(result.isCacheHit());
builder.setRunner(result.getRunnerName());
+ if (result.getDigest().isPresent()) {
+ builder
+ .getDigestBuilder()
+ .setHash(result.getDigest().get().getHash())
+ .setSizeBytes(result.getDigest().get().getSizeBytes());
+ }
+
String progressMessage = spawn.getResourceOwner().getProgressMessage();
if (progressMessage != null) {
builder.setProgressMessage(progressMessage);
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 67d0201..03e8825 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
@@ -122,6 +122,7 @@
.setNetworkTime(action.getNetworkTime().getDuration());
SpawnResult spawnResult =
createSpawnResult(
+ action.getActionKey(),
result.getExitCode(),
/*cacheHit=*/ true,
result.cacheName(),
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 a74d6ac..01c01ae 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
@@ -422,6 +422,7 @@
// subtract network time consumed here to ensure wall clock during fetch is not double
// counted, and metrics time computation does not exceed total time
return createSpawnResult(
+ action.getActionKey(),
result.getExitCode(),
cacheHit,
cacheName,
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 c1b10e1..524b96b 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
@@ -76,6 +76,7 @@
import java.util.Collection;
import java.util.Locale;
import java.util.Map;
+import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.function.BiFunction;
@@ -143,6 +144,7 @@
/** Constructs a {@link SpawnResult}. */
public static SpawnResult createSpawnResult(
+ ActionKey actionKey,
int exitCode,
boolean cacheHit,
String runnerName,
@@ -164,7 +166,11 @@
timestampToInstant(executionStartTimestamp),
timestampToInstant(executionCompletedTimestamp)))
.setSpawnMetrics(spawnMetrics)
- .setRemote(true);
+ .setRemote(true)
+ .setDigest(
+ Optional.of(
+ SpawnResult.Digest.of(
+ actionKey.getDigest().getHash(), actionKey.getDigest().getSizeBytes())));
if (exitCode != 0) {
builder.setFailureDetail(
FailureDetail.newBuilder()
diff --git a/src/main/protobuf/spawn.proto b/src/main/protobuf/spawn.proto
index 1507add..0f55812 100644
--- a/src/main/protobuf/spawn.proto
+++ b/src/main/protobuf/spawn.proto
@@ -136,4 +136,7 @@
// Canonical label of the target that emitted this spawn, may not always be
// set.
string target_label = 18;
+
+ // A unique identifier for this Spawn.
+ Digest digest = 19;
}
diff --git a/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnCacheTest.java b/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnCacheTest.java
index 4503118..c926c23 100644
--- a/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnCacheTest.java
+++ b/src/test/java/com/google/devtools/build/lib/remote/RemoteSpawnCacheTest.java
@@ -88,11 +88,13 @@
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import java.util.SortedMap;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
@@ -263,10 +265,11 @@
// arrange
RemoteSpawnCache cache = createRemoteSpawnCache();
RemoteExecutionService service = cache.getRemoteExecutionService();
+ ArgumentCaptor<ActionKey> actionKeyCaptor = ArgumentCaptor.forClass(ActionKey.class);
ActionResult actionResult = ActionResult.getDefaultInstance();
when(remoteCache.downloadActionResult(
any(RemoteActionExecutionContext.class),
- any(ActionKey.class),
+ actionKeyCaptor.capture(),
/* inlineOutErr= */ eq(false)))
.thenAnswer(
new Answer<CachedActionResult>() {
@@ -304,6 +307,12 @@
.downloadOutputs(
any(), eq(RemoteActionResult.createFromCache(CachedActionResult.remote(actionResult))));
verify(service, never()).uploadOutputs(any(), any());
+ assertThat(result.getDigest())
+ .isEqualTo(
+ Optional.of(
+ SpawnResult.Digest.of(
+ actionKeyCaptor.getValue().getDigest().getHash(),
+ actionKeyCaptor.getValue().getDigest().getSizeBytes())));
assertThat(result.setupSuccess()).isTrue();
assertThat(result.exitCode()).isEqualTo(0);
assertThat(result.isCacheHit()).isTrue();
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 22afbf5..bee8104 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
@@ -113,6 +113,7 @@
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.List;
+import java.util.Optional;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.annotation.Nullable;
@@ -1212,6 +1213,40 @@
}
@Test
+ public void testDigest() throws Exception {
+ RemoteSpawnRunner runner = newSpawnRunner();
+ RemoteExecutionService service = runner.getRemoteExecutionService();
+
+ ExecuteResponse resp =
+ ExecuteResponse.newBuilder()
+ .setResult(ActionResult.newBuilder().setExitCode(0).build())
+ .build();
+ when(executor.executeRemotely(
+ any(RemoteActionExecutionContext.class),
+ any(ExecuteRequest.class),
+ any(OperationObserver.class)))
+ .thenReturn(resp);
+
+ Spawn spawn = newSimpleSpawn();
+ FakeSpawnExecutionContext policy = getSpawnContext(spawn);
+
+ SpawnResult res = runner.exec(spawn, policy);
+ assertThat(res.status()).isEqualTo(Status.SUCCESS);
+
+ ArgumentCaptor<RemoteAction> requestCaptor = ArgumentCaptor.forClass(RemoteAction.class);
+
+ verify(service)
+ .executeRemotely(requestCaptor.capture(), anyBoolean(), any(OperationObserver.class));
+
+ assertThat(res.getDigest())
+ .isEqualTo(
+ Optional.of(
+ SpawnResult.Digest.of(
+ requestCaptor.getValue().getActionKey().getDigest().getHash(),
+ requestCaptor.getValue().getActionKey().getDigest().getSizeBytes())));
+ }
+
+ @Test
public void accountingDisabledWithoutWorker() {
SpawnMetrics.Builder spawnMetrics = Mockito.mock(SpawnMetrics.Builder.class);
RemoteSpawnRunner.spawnMetricsAccounting(