Collect action cache hits, misses, and reasons for the misses.
As a bonus, this brings in a bunch of new unit tests for the
ActionCacheChecker.
RELNOTES: None.
PiperOrigin-RevId: 170059577
diff --git a/src/main/java/com/google/devtools/build/lib/actions/ActionCacheChecker.java b/src/main/java/com/google/devtools/build/lib/actions/ActionCacheChecker.java
index 1a6c4c8..fc64c75 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/ActionCacheChecker.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/ActionCacheChecker.java
@@ -24,6 +24,7 @@
import com.google.devtools.build.lib.actions.cache.DigestUtils;
import com.google.devtools.build.lib.actions.cache.Metadata;
import com.google.devtools.build.lib.actions.cache.MetadataHandler;
+import com.google.devtools.build.lib.actions.cache.Protos.ActionCacheStatistics.MissReason;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.events.EventKind;
@@ -285,32 +286,38 @@
if (unconditionalExecution(action)) {
Preconditions.checkState(action.isVolatile());
reportUnconditionalExecution(handler, action);
- return true; // must execute - unconditional execution is requested.
+ actionCache.accountMiss(MissReason.UNCONDITIONAL_EXECUTION);
+ return true;
}
if (entry == null) {
reportNewAction(handler, action);
- return true; // must execute -- no cache entry (e.g. first build)
+ actionCache.accountMiss(MissReason.NOT_CACHED);
+ return true;
}
if (entry.isCorrupted()) {
reportCorruptedCacheEntry(handler, action);
- return true; // cache entry is corrupted - must execute
+ actionCache.accountMiss(MissReason.CORRUPTED_CACHE_ENTRY);
+ return true;
} else if (validateArtifacts(entry, action, actionInputs, metadataHandler, true)) {
reportChanged(handler, action);
- return true; // files have changed
+ actionCache.accountMiss(MissReason.DIFFERENT_FILES);
+ return true;
} else if (!entry.getActionKey().equals(action.getKey())) {
reportCommand(handler, action);
- return true; // must execute -- action key is different
+ actionCache.accountMiss(MissReason.DIFFERENT_ACTION_KEY);
+ return true;
}
Map<String, String> usedClientEnv = computeUsedClientEnv(action, clientEnv);
if (!entry.getUsedClientEnvDigest().equals(DigestUtils.fromEnv(usedClientEnv))) {
reportClientEnv(handler, action, usedClientEnv);
- return true; // different values taken from the environment -- must execute
+ actionCache.accountMiss(MissReason.DIFFERENT_ENVIRONMENT);
+ return true;
}
-
entry.getFileDigest();
- return false; // cache hit
+ actionCache.accountHit();
+ return false;
}
private static Metadata getMetadataOrConstant(MetadataHandler metadataHandler, Artifact artifact)
@@ -460,13 +467,16 @@
if (entry != null) {
if (entry.isCorrupted()) {
reportCorruptedCacheEntry(handler, action);
+ actionCache.accountMiss(MissReason.CORRUPTED_CACHE_ENTRY);
changed = true;
} else if (validateArtifacts(entry, action, action.getInputs(), metadataHandler, false)) {
reportChanged(handler, action);
+ actionCache.accountMiss(MissReason.DIFFERENT_FILES);
changed = true;
}
} else {
reportChangedDeps(handler, action);
+ actionCache.accountMiss(MissReason.DIFFERENT_DEPS);
changed = true;
}
if (changed) {
@@ -482,6 +492,8 @@
metadataHandler.setDigestForVirtualArtifact(middleman, entry.getFileDigest());
if (changed) {
actionCache.put(cacheKey, entry);
+ } else {
+ actionCache.accountHit();
}
}
diff --git a/src/main/java/com/google/devtools/build/lib/actions/BUILD b/src/main/java/com/google/devtools/build/lib/actions/BUILD
index db44f36..f481e5f 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/actions/BUILD
@@ -35,6 +35,7 @@
"//src/main/java/com/google/devtools/build/skyframe",
"//src/main/java/com/google/devtools/build/skyframe:skyframe-objects",
"//src/main/java/com/google/devtools/common/options",
+ "//src/main/protobuf:action_cache_java_proto",
"//src/main/protobuf:extra_actions_base_java_proto",
"//third_party:auto_value",
"//third_party:guava",
diff --git a/src/main/java/com/google/devtools/build/lib/actions/cache/ActionCache.java b/src/main/java/com/google/devtools/build/lib/actions/cache/ActionCache.java
index 59eb31f..fd1756a0 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/cache/ActionCache.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/cache/ActionCache.java
@@ -15,7 +15,10 @@
package com.google.devtools.build.lib.actions.cache;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
+import com.google.devtools.build.lib.actions.cache.Protos.ActionCacheStatistics;
+import com.google.devtools.build.lib.actions.cache.Protos.ActionCacheStatistics.MissReason;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible;
import com.google.devtools.build.lib.util.Preconditions;
import com.google.devtools.build.lib.vfs.PathFragment;
@@ -67,6 +70,10 @@
* will continue to return same result regardless of internal data transformations).
*/
final class Entry {
+ /** Unique instance to represent a corrupted cache entry. */
+ public static final ActionCache.Entry CORRUPTED =
+ new ActionCache.Entry(null, ImmutableMap.<String, String>of(), false);
+
private final String actionKey;
@Nullable
// Null iff the corresponding action does not do input discovery.
@@ -141,7 +148,7 @@
* Returns true if this cache entry is corrupted and should be ignored.
*/
public boolean isCorrupted() {
- return actionKey == null;
+ return this == CORRUPTED;
}
/**
@@ -194,4 +201,22 @@
* Dumps action cache content into the given PrintStream.
*/
void dump(PrintStream out);
+
+ /** Accounts one cache hit. */
+ void accountHit();
+
+ /** Accounts one cache miss for the given reason. */
+ void accountMiss(MissReason reason);
+
+ /**
+ * Populates the given builder with statistics.
+ *
+ * <p>The extracted values are not guaranteed to be a consistent snapshot of the metrics tracked
+ * by the action cache. Therefore, even if it is safe to call this function at any point in time,
+ * this should only be called once there are no actions running.
+ */
+ void mergeIntoActionCacheStatistics(ActionCacheStatistics.Builder builder);
+
+ /** Resets the current statistics to zero. */
+ void resetStatistics();
}
diff --git a/src/main/java/com/google/devtools/build/lib/actions/cache/CompactPersistentActionCache.java b/src/main/java/com/google/devtools/build/lib/actions/cache/CompactPersistentActionCache.java
index 70015af..52cca10 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/cache/CompactPersistentActionCache.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/cache/CompactPersistentActionCache.java
@@ -16,7 +16,8 @@
import static java.nio.charset.StandardCharsets.ISO_8859_1;
import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.actions.cache.Protos.ActionCacheStatistics;
+import com.google.devtools.build.lib.actions.cache.Protos.ActionCacheStatistics.MissReason;
import com.google.devtools.build.lib.clock.Clock;
import com.google.devtools.build.lib.concurrent.ThreadSafety.ConditionallyThreadSafe;
import com.google.devtools.build.lib.profiler.AutoProfiler;
@@ -35,9 +36,11 @@
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.Collection;
+import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
/**
@@ -154,8 +157,9 @@
private final PersistentMap<Integer, byte[]> map;
private final PersistentStringIndexer indexer;
- static final ActionCache.Entry CORRUPTED =
- new ActionCache.Entry(null, ImmutableMap.<String, String>of(), false);
+
+ private final AtomicInteger hits = new AtomicInteger();
+ private final Map<MissReason, AtomicInteger> misses = new EnumMap<>(MissReason.class);
public CompactPersistentActionCache(Path cacheRoot, Clock clock) throws IOException {
Path cacheFile = cacheFile(cacheRoot);
@@ -187,6 +191,15 @@
throw new IOException("Failed action cache referential integrity check: " + integrityError);
}
}
+
+ for (MissReason reason : MissReason.values()) {
+ if (reason == MissReason.UNRECOGNIZED) {
+ // The presence of this enum value is a protobuf artifact and confuses our metrics
+ // externalization code below. Just skip it.
+ continue;
+ }
+ misses.put(reason, new AtomicInteger(0));
+ }
}
/**
@@ -254,7 +267,7 @@
return data != null ? CompactPersistentActionCache.decode(indexer, data) : null;
} catch (IOException e) {
// return entry marked as corrupted.
- return CORRUPTED;
+ return ActionCache.Entry.CORRUPTED;
}
}
@@ -420,7 +433,7 @@
if (source.remaining() > 0) {
throw new IOException("serialized entry data has not been fully decoded");
}
- return new Entry(
+ return new ActionCache.Entry(
actionKey,
usedClientEnvDigest,
count == NO_INPUT_DISCOVERY_COUNT ? null : builder.build(),
@@ -429,4 +442,38 @@
throw new IOException("encoded entry data is incomplete", e);
}
}
+
+ @Override
+ public void accountHit() {
+ hits.incrementAndGet();
+ }
+
+ @Override
+ public void accountMiss(MissReason reason) {
+ AtomicInteger counter = misses.get(reason);
+ Preconditions.checkNotNull(counter, "Miss reason %s was not registered in the misses map "
+ + "during cache construction", reason);
+ counter.incrementAndGet();
+ }
+
+ @Override
+ public void mergeIntoActionCacheStatistics(ActionCacheStatistics.Builder builder) {
+ builder.setHits(hits.get());
+
+ int totalMisses = 0;
+ for (Map.Entry<MissReason, AtomicInteger> entry : misses.entrySet()) {
+ int count = entry.getValue().get();
+ builder.addMissDetailsBuilder().setReason(entry.getKey()).setCount(count);
+ totalMisses += count;
+ }
+ builder.setMisses(totalMisses);
+ }
+
+ @Override
+ public void resetStatistics() {
+ hits.set(0);
+ for (Map.Entry<MissReason, AtomicInteger> entry : misses.entrySet()) {
+ entry.getValue().set(0);
+ }
+ }
}
diff --git a/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java b/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java
index 1285961..af7659b 100644
--- a/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java
+++ b/src/main/java/com/google/devtools/build/lib/buildtool/ExecutionTool.java
@@ -380,6 +380,7 @@
request.getBuildOptions().getSymlinkPrefix(productName), productName);
ActionCache actionCache = getActionCache();
+ actionCache.resetStatistics();
SkyframeExecutor skyframeExecutor = env.getSkyframeExecutor();
Builder builder = createBuilder(
request, actionCache, skyframeExecutor, modifiedOutputFiles);
@@ -734,6 +735,7 @@
*/
private void saveActionCache(ActionCache actionCache) {
ActionCacheStatistics.Builder builder = ActionCacheStatistics.newBuilder();
+ actionCache.mergeIntoActionCacheStatistics(builder);
AutoProfiler p =
AutoProfiler.profiledAndLogged("Saving action cache", ProfilerTask.INFO, logger);