Add `--gc_churning_threshold_if_multiple_top_level_targets` option which overrides `--gc_churning_threshold` if there are multiple top level targets.
PiperOrigin-RevId: 781121537
Change-Id: Icbfce5c11b8a71f112040d620b86d0db892948ed
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/GcChurningDetector.java b/src/main/java/com/google/devtools/build/lib/runtime/GcChurningDetector.java
index 5912e80..b1c5336 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/GcChurningDetector.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/GcChurningDetector.java
@@ -45,7 +45,8 @@
private static final GoogleLogger logger = GoogleLogger.forEnclosingClass();
private static final Duration MIN_INVOCATION_WALL_TIME_DURATION = Duration.ofMinutes(1);
- private final int thresholdPercentage;
+ private volatile int thresholdPercentage;
+ private final int thresholdPercentageIfMultipleTopLevelTargets;
private Duration cumulativeFullGcDuration = Duration.ZERO;
private final Clock clock;
private final Instant start;
@@ -53,8 +54,14 @@
private final BugReporter bugReporter;
@VisibleForTesting
- GcChurningDetector(int thresholdPercentage, Clock clock, BugReporter bugReporter) {
+ GcChurningDetector(
+ int thresholdPercentage,
+ int thresholdPercentageIfMultipleTopLevelTargets,
+ Clock clock,
+ BugReporter bugReporter) {
this.thresholdPercentage = thresholdPercentage;
+ this.thresholdPercentageIfMultipleTopLevelTargets =
+ thresholdPercentageIfMultipleTopLevelTargets;
this.clock = clock;
this.start = clock.now();
this.bugReporter = bugReporter;
@@ -62,7 +69,19 @@
static GcChurningDetector createForCommand(MemoryPressureOptions options) {
return new GcChurningDetector(
- options.gcChurningThreshold, BlazeClock.instance(), BugReporter.defaultInstance());
+ options.gcChurningThreshold,
+ options.gcChurningThresholdIfMultipleTopLevelTargets.orElse(options.gcChurningThreshold),
+ BlazeClock.instance(),
+ BugReporter.defaultInstance());
+ }
+
+ void targetParsingComplete(int numTopLevelTargets) {
+ if (numTopLevelTargets > 1) {
+ thresholdPercentage = thresholdPercentageIfMultipleTopLevelTargets;
+ logger.atInfo().log(
+ "Switched to thresholdPercentage of %s because there were %s top-level targets",
+ thresholdPercentage, numTopLevelTargets);
+ }
}
// This is called from MemoryPressureListener on a single memory-pressure-listener-0 thread, so it
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/MemoryPressureListener.java b/src/main/java/com/google/devtools/build/lib/runtime/MemoryPressureListener.java
index a08e598..9ae7be9 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/MemoryPressureListener.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/MemoryPressureListener.java
@@ -173,6 +173,13 @@
this.gcChurnDetector.set(gcChurningDetector);
}
+ void targetParsingComplete(int numTopLevelTargets) {
+ GcChurningDetector gcChurningDetector = gcChurnDetector.get();
+ if (gcChurningDetector != null) {
+ gcChurningDetector.targetParsingComplete(numTopLevelTargets);
+ }
+ }
+
void populateStats(MemoryPressureStats.Builder memoryPressureStatsBuilder) {
GcChurningDetector gcChurningDetector = gcChurnDetector.get();
if (gcChurningDetector != null) {
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/MemoryPressureModule.java b/src/main/java/com/google/devtools/build/lib/runtime/MemoryPressureModule.java
index 69d32b6..7fbdfe7 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/MemoryPressureModule.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/MemoryPressureModule.java
@@ -17,6 +17,7 @@
import com.google.common.collect.ImmutableList;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
+import com.google.devtools.build.lib.pkgcache.TargetParsingCompleteEvent;
import com.google.devtools.build.lib.runtime.MemoryPressure.MemoryPressureStats;
import com.google.devtools.build.lib.skyframe.HighWaterMarkLimiter;
import com.google.devtools.common.options.OptionsBase;
@@ -53,6 +54,11 @@
eventBus.register(highWaterMarkLimiter);
}
+ @Subscribe
+ public void targetParsingComplete(TargetParsingCompleteEvent event) {
+ memoryPressureListener.targetParsingComplete(event.getTargets().size());
+ }
+
@Override
public void afterCommand() {
postStats();
diff --git a/src/main/java/com/google/devtools/build/lib/runtime/MemoryPressureOptions.java b/src/main/java/com/google/devtools/build/lib/runtime/MemoryPressureOptions.java
index c257851..3964ce0 100644
--- a/src/main/java/com/google/devtools/build/lib/runtime/MemoryPressureOptions.java
+++ b/src/main/java/com/google/devtools/build/lib/runtime/MemoryPressureOptions.java
@@ -17,6 +17,7 @@
import com.google.devtools.common.options.Converter;
import com.google.devtools.common.options.Converters.CommaSeparatedOptionListConverter;
import com.google.devtools.common.options.Converters.DurationConverter;
+import com.google.devtools.common.options.Converters.OptionalPercentageConverter;
import com.google.devtools.common.options.Converters.PercentageConverter;
import com.google.devtools.common.options.Converters.RangeConverter;
import com.google.devtools.common.options.Converters.RegexPatternConverter;
@@ -27,6 +28,7 @@
import com.google.devtools.common.options.OptionsParsingException;
import com.google.devtools.common.options.RegexPatternOption;
import java.time.Duration;
+import java.util.OptionalInt;
/** Options for responding to memory pressure. */
public final class MemoryPressureOptions extends OptionsBase {
@@ -119,6 +121,21 @@
+ " never give up for this reason.")
public int gcChurningThreshold;
+ @Option(
+ name = "gc_churning_threshold_if_multiple_top_level_targets",
+ defaultValue = OptionalPercentageConverter.UNSET,
+ documentationCategory = OptionDocumentationCategory.EXECUTION_STRATEGY,
+ effectTags = {OptionEffectTag.HOST_MACHINE_RESOURCE_OPTIMIZATIONS},
+ converter = OptionalPercentageConverter.class,
+ help =
+ "If set to a value in [0, 100] and this is a command that takes top-level targets (e.g."
+ + " build but not query) and there are multiple such top-level targets, overrides"
+ + " --gc_churning_threshold. Useful to configure more aggressive OOMing behavior"
+ + " (i.e. a lower value than --gc_churning_threshold) when they are multiple"
+ + " top-level targets so that the invoker of Bazel can split and retry while still"
+ + " having less aggressive behavior when there is a single top-level target.")
+ public OptionalInt gcChurningThresholdIfMultipleTopLevelTargets;
+
// NOTE: 2024-06-11, this matches both known patterns of:
// jdk.internal.vm.Filler[Array|Element[]] but does not match the thread
// related jdk.internal.vm entries which we do not currently want to count.
diff --git a/src/main/java/com/google/devtools/common/options/Converters.java b/src/main/java/com/google/devtools/common/options/Converters.java
index 4bfc8c5..f20b1fb 100644
--- a/src/main/java/com/google/devtools/common/options/Converters.java
+++ b/src/main/java/com/google/devtools/common/options/Converters.java
@@ -27,6 +27,7 @@
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.OptionalInt;
import java.util.logging.Level;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -700,6 +701,24 @@
}
}
+ /** Same as {@link PercentageConverter} but also supports being unset. */
+ public static class OptionalPercentageConverter extends Converter.Contextless<OptionalInt> {
+ public static final String UNSET = "-1";
+ private static final PercentageConverter PERCENTAGE_CONVERTER = new PercentageConverter();
+
+ @Override
+ public String getTypeDescription() {
+ return "an integer";
+ }
+
+ @Override
+ public OptionalInt convert(String input) throws OptionsParsingException {
+ return input.equals(UNSET)
+ ? OptionalInt.empty()
+ : OptionalInt.of(PERCENTAGE_CONVERTER.convert(input));
+ }
+ }
+
/**
* A {@link Converter} for {@link com.github.benmanes.caffeine.cache.CaffeineSpec}. The spec may
* be empty, in which case this converter returns null.
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/GcChurningDetectorTest.java b/src/test/java/com/google/devtools/build/lib/runtime/GcChurningDetectorTest.java
index 534239b..d6e1fd6 100644
--- a/src/test/java/com/google/devtools/build/lib/runtime/GcChurningDetectorTest.java
+++ b/src/test/java/com/google/devtools/build/lib/runtime/GcChurningDetectorTest.java
@@ -44,7 +44,11 @@
ManualClock fakeClock = new ManualClock();
GcChurningDetector underTest =
- new GcChurningDetector(/* thresholdPercentage= */ 100, fakeClock, mockBugReporter);
+ new GcChurningDetector(
+ /* thresholdPercentage= */ 100,
+ /* thresholdPercentageIfMultipleTopLevelTargets= */ 100,
+ fakeClock,
+ mockBugReporter);
fakeClock.advance(Duration.ofMillis(50L));
underTest.handle(fullGcEvent(Duration.ofMillis(10L)));
@@ -78,7 +82,11 @@
ManualClock fakeClock = new ManualClock();
GcChurningDetector underTest =
- new GcChurningDetector(/* thresholdPercentage= */ 100, fakeClock, mockBugReporter);
+ new GcChurningDetector(
+ /* thresholdPercentage= */ 100,
+ /* thresholdPercentageIfMultipleTopLevelTargets= */ 100,
+ fakeClock,
+ mockBugReporter);
fakeClock.advance(Duration.ofNanos(456L));
underTest.handle(fullGcEvent(Duration.ofNanos(123L)));
@@ -105,7 +113,11 @@
ManualClock fakeClock = new ManualClock();
GcChurningDetector underTest =
- new GcChurningDetector(/* thresholdPercentage= */ 50, fakeClock, mockBugReporter);
+ new GcChurningDetector(
+ /* thresholdPercentage= */ 50,
+ /* thresholdPercentageIfMultipleTopLevelTargets= */ 50,
+ fakeClock,
+ mockBugReporter);
fakeClock.advance(Duration.ofMinutes(3L));
underTest.handle(fullGcEvent(Duration.ofMinutes(1L)));
@@ -121,7 +133,11 @@
ManualClock fakeClock = new ManualClock();
GcChurningDetector underTest =
- new GcChurningDetector(/* thresholdPercentage= */ 50, fakeClock, mockBugReporter);
+ new GcChurningDetector(
+ /* thresholdPercentage= */ 50,
+ /* thresholdPercentageIfMultipleTopLevelTargets= */ 50,
+ fakeClock,
+ mockBugReporter);
fakeClock.advance(Duration.ofSeconds(30L));
underTest.handle(fullGcEvent(Duration.ofSeconds(15L)));
@@ -136,6 +152,48 @@
verifyOom();
}
+ @Test
+ public void thresholdPercentageIfMultipleTopLevelTargets_onlySingleTarget() {
+ ManualClock fakeClock = new ManualClock();
+
+ GcChurningDetector underTest =
+ new GcChurningDetector(
+ /* thresholdPercentage= */ 100,
+ /* thresholdPercentageIfMultipleTopLevelTargets= */ 50,
+ fakeClock,
+ mockBugReporter);
+
+ fakeClock.advance(Duration.ofSeconds(60L));
+ underTest.handle(fullGcEvent(Duration.ofSeconds(30L)));
+ verifyNoOom();
+
+ underTest.targetParsingComplete(1);
+ fakeClock.advance(Duration.ofSeconds(30L));
+ underTest.handle(fullGcEvent(Duration.ofSeconds(20L)));
+ verifyNoOom();
+ }
+
+ @Test
+ public void thresholdPercentageIfMultipleTopLevelTargets() {
+ ManualClock fakeClock = new ManualClock();
+
+ GcChurningDetector underTest =
+ new GcChurningDetector(
+ /* thresholdPercentage= */ 100,
+ /* thresholdPercentageIfMultipleTopLevelTargets= */ 50,
+ fakeClock,
+ mockBugReporter);
+
+ fakeClock.advance(Duration.ofSeconds(60L));
+ underTest.handle(fullGcEvent(Duration.ofSeconds(40L)));
+ verifyNoOom();
+
+ underTest.targetParsingComplete(2);
+ fakeClock.advance(Duration.ofSeconds(30L));
+ underTest.handle(fullGcEvent(Duration.ofSeconds(20L)));
+ verifyOom();
+ }
+
private void verifyNoOom() {
verifyNoInteractions(mockBugReporter);
}
diff --git a/src/test/java/com/google/devtools/build/lib/runtime/MemoryPressureListenerTest.java b/src/test/java/com/google/devtools/build/lib/runtime/MemoryPressureListenerTest.java
index 4e59593..4bd422d 100644
--- a/src/test/java/com/google/devtools/build/lib/runtime/MemoryPressureListenerTest.java
+++ b/src/test/java/com/google/devtools/build/lib/runtime/MemoryPressureListenerTest.java
@@ -319,4 +319,17 @@
verify(mockGcThrashingDetector).handle(eq(event));
verify(mockGcChurningDetector).handle(eq(event));
}
+
+ @Test
+ public void forwardsTargetParsingComplete() {
+ MemoryPressureListener underTest =
+ MemoryPressureListener.createFromBeans(
+ ImmutableList.of(mockUselessBean, mockBean), directExecutor());
+
+ GcChurningDetector mockGcChurningDetector = mock(GcChurningDetector.class);
+ underTest.initForInvocation(eventBus, mock(GcThrashingDetector.class), mockGcChurningDetector);
+ underTest.targetParsingComplete(42);
+
+ verify(mockGcChurningDetector).targetParsingComplete(eq(42));
+ }
}