Skip to content

Commit

Permalink
Create an option to enable GcThrashingDetector.
Browse files Browse the repository at this point in the history
Also create another option so that we can experiment with and without `RetainedHeapLimiter`.

PiperOrigin-RevId: 520726326
Change-Id: I4ddfd91089132b96a9339ac60e03d634fb56c451
  • Loading branch information
justinhorvitz authored and copybara-github committed Mar 30, 2023
1 parent b284477 commit 9f93780
Show file tree
Hide file tree
Showing 6 changed files with 130 additions and 5 deletions.
5 changes: 5 additions & 0 deletions src/main/java/com/google/devtools/build/lib/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -224,14 +224,18 @@ java_library(
java_library(
name = "runtime/memory_pressure",
srcs = [
"runtime/GcThrashingDetector.java",
"runtime/MemoryPressureEvent.java",
"runtime/MemoryPressureOptions.java",
"runtime/MemoryPressureStatCollector.java",
],
deps = [
"//src/main/java/com/google/devtools/build/lib/bugreport",
"//src/main/java/com/google/devtools/build/lib/clock",
"//src/main/java/com/google/devtools/common/options",
"//src/main/protobuf:memory_pressure_java_proto",
"//third_party:auto_value",
"//third_party:flogger",
"//third_party:guava",
],
)
Expand Down Expand Up @@ -263,6 +267,7 @@ java_library(
"runtime/BlazeCommandResult.java",
"runtime/CommandDispatcher.java",
"runtime/CommandLinePathFactory.java",
"runtime/GcThrashingDetector.java",
"runtime/KeepGoingOption.java",
"runtime/LoadingPhaseThreadsOption.java",
"runtime/MemoryPressureEvent.java",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,15 @@
import static com.google.common.collect.ImmutableList.toImmutableList;

import com.google.auto.value.AutoValue;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import com.google.common.flogger.GoogleLogger;
import com.google.devtools.build.lib.bugreport.BugReporter;
import com.google.devtools.build.lib.bugreport.Crash;
import com.google.devtools.build.lib.bugreport.CrashContext;
import com.google.devtools.build.lib.clock.BlazeClock;
import com.google.devtools.build.lib.clock.Clock;
import java.time.Duration;
import java.time.Instant;
Expand Down Expand Up @@ -60,18 +64,37 @@ static Limit of(Duration period, int count) {
}
}

/**
* If enabled in {@link MemoryPressureOptions}, creates a {@link GcThrashingDetector} and
* registers it with the {@link EventBus}.
*/
public static void configureForCommand(MemoryPressureOptions options, EventBus eventBus) {
if (options.gcThrashingLimits.isEmpty() || options.oomMoreEagerlyThreshold == 100) {
return;
}

eventBus.register(
new GcThrashingDetector(
options.oomMoreEagerlyThreshold,
options.gcThrashingLimits,
BlazeClock.instance(),
BugReporter.defaultInstance()));
}

private final int threshold;
private final ImmutableList<SingleLimitTracker> trackers;
private final Clock clock;
private final BugReporter bugReporter;

@VisibleForTesting
GcThrashingDetector(int threshold, List<Limit> limits, Clock clock, BugReporter bugReporter) {
this.threshold = threshold;
this.trackers = limits.stream().map(SingleLimitTracker::new).collect(toImmutableList());
this.clock = clock;
this.bugReporter = bugReporter;
}

@Subscribe // EventBus synchronizes calls by default, making thread safety unnecessary.
void handle(MemoryPressureEvent event) {
if (event.percentTenuredSpaceUsed() < threshold) {
for (var tracker : trackers) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public void beforeCommand(CommandEnvironment env) {
MemoryPressureOptions options = env.getOptions().getOptions(MemoryPressureOptions.class);
highWaterMarkLimiter =
new HighWaterMarkLimiter(env.getSkyframeExecutor(), env.getSyscallCache(), options);

GcThrashingDetector.configureForCommand(options, eventBus);
retainedHeapLimiter.setOptions(options);

eventBus.register(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,17 @@
// limitations under the License.
package com.google.devtools.build.lib.runtime;

import com.google.common.collect.ImmutableList;
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.PercentageConverter;
import com.google.devtools.common.options.Converters.RangeConverter;
import com.google.devtools.common.options.Option;
import com.google.devtools.common.options.OptionDocumentationCategory;
import com.google.devtools.common.options.OptionEffectTag;
import com.google.devtools.common.options.OptionsBase;
import com.google.devtools.common.options.OptionsParsingException;
import java.time.Duration;

/** Options for responding to memory pressure. */
Expand Down Expand Up @@ -91,9 +96,64 @@ public final class MemoryPressureOptions extends OptionsBase {
+ " threshold is exceeded.")
public int skyframeHighWaterMarkFullGcDropsPerInvocation;

@Option(
name = "experimental_gc_thrashing_limits",
defaultValue = "",
documentationCategory = OptionDocumentationCategory.BUILD_TIME_OPTIMIZATION,
effectTags = {OptionEffectTag.HOST_MACHINE_RESOURCE_OPTIMIZATIONS},
converter = GcThrashingLimitsConverter.class,
help =
"Limits which, if reached, cause GcThrashingDetector to crash Bazel with an OOM. Each"
+ " limit is specified as <period>:<count> where period is a duration and count is a"
+ " positive integer. If more than --experimental_oom_more_eagerly_threshold percent"
+ " of tenured space (old gen heap) remains occupied after <count> consecutive full"
+ " GCs within <period>, an OOM is triggered. Multiple limits can be specified"
+ " separated by commas.")
public ImmutableList<GcThrashingDetector.Limit> gcThrashingLimits;

@Option(
name = "gc_thrashing_limits_retained_heap_limiter_mutually_exclusive",
defaultValue = "true",
documentationCategory = OptionDocumentationCategory.BUILD_TIME_OPTIMIZATION,
effectTags = {OptionEffectTag.HOST_MACHINE_RESOURCE_OPTIMIZATIONS},
help =
"If true, specifying non-empty --experimental_gc_thrashing_limits deactivates"
+ " RetainedHeapLimiter to make it mutually exclusive with GcThrashingDetector."
+ " Setting to false permits both to be active for the same command.")
public boolean gcThrashingLimitsRetainedHeapLimiterMutuallyExclusive;

static final class NonNegativeIntegerConverter extends RangeConverter {
NonNegativeIntegerConverter() {
super(0, Integer.MAX_VALUE);
}
}

static final class GcThrashingLimitsConverter
extends Converter.Contextless<ImmutableList<GcThrashingDetector.Limit>> {
private final CommaSeparatedOptionListConverter commaListConverter =
new CommaSeparatedOptionListConverter();
private final DurationConverter durationConverter = new DurationConverter();
private final RangeConverter positiveIntConverter = new RangeConverter(1, Integer.MAX_VALUE);

@Override
public ImmutableList<GcThrashingDetector.Limit> convert(String input)
throws OptionsParsingException {
ImmutableList.Builder<GcThrashingDetector.Limit> result = ImmutableList.builder();
for (String part : commaListConverter.convert(input)) {
int colonIndex = part.indexOf(':');
if (colonIndex == -1) {
throw new OptionsParsingException("Expected <period>:<count>, got " + part);
}
Duration period = durationConverter.convert(part.substring(0, colonIndex));
int count = positiveIntConverter.convert(part.substring(colonIndex + 1));
result.add(GcThrashingDetector.Limit.of(period, count));
}
return result.build();
}

@Override
public String getTypeDescription() {
return "comma separated pairs of <period>:<count>";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ final class RetainedHeapLimiter implements MemoryPressureStatCollector {
private final BugReporter bugReporter;
private final Clock clock;

private volatile MemoryPressureOptions options = Options.getDefaults(MemoryPressureOptions.class);
private volatile MemoryPressureOptions options = inactiveOptions();

private final AtomicBoolean throwingOom = new AtomicBoolean(false);
private final AtomicBoolean heapLimiterTriggeredGc = new AtomicBoolean(false);
Expand All @@ -71,7 +71,12 @@ private RetainedHeapLimiter(BugReporter bugReporter, Clock clock) {

@ThreadSafety.ThreadCompatible // Can only be called on the logical main Bazel thread.
void setOptions(MemoryPressureOptions options) {
this.options = options;
if (options.gcThrashingLimitsRetainedHeapLimiterMutuallyExclusive
&& !options.gcThrashingLimits.isEmpty()) {
this.options = inactiveOptions();
} else {
this.options = options;
}
}

// Can be called concurrently, handles concurrent calls with #setThreshold gracefully.
Expand Down Expand Up @@ -169,4 +174,10 @@ public void addStatsAndReset(MemoryPressureStats.Builder stats) {
maxConsecutiveIgnoredFullGcsOverThreshold.getAndSet(0));
consecutiveIgnoredFullGcsOverThreshold.set(0);
}

private static MemoryPressureOptions inactiveOptions() {
var options = Options.getDefaults(MemoryPressureOptions.class);
options.oomMoreEagerlyThreshold = 100;
return options;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;

import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.bugreport.BugReporter;
import com.google.devtools.build.lib.bugreport.Crash;
import com.google.devtools.build.lib.runtime.GcThrashingDetector.Limit;
import com.google.devtools.build.lib.runtime.MemoryPressure.MemoryPressureStats;
import com.google.devtools.build.lib.testutil.ManualClock;
import com.google.devtools.common.options.Options;
Expand Down Expand Up @@ -277,11 +279,35 @@ public void threshold100_noGcTriggeredEvenWithNonsenseStats() {
}

@Test
public void worksWithoutSettingOptions() {
underTest.handle(percentUsedAfterOrganicFullGc(95));
public void optionsNotSet_disabled() {
underTest.handle(percentUsedAfterOrganicFullGc(99));
assertStats(MemoryPressureStats.newBuilder().setManuallyTriggeredGcs(0));
}

@Test
public void gcThrashingLimitsSet_mutuallyExclusive_disabled() {
options.oomMoreEagerlyThreshold = 90;
options.gcThrashingLimits = ImmutableList.of(Limit.of(Duration.ofMinutes(1), 2));
options.gcThrashingLimitsRetainedHeapLimiterMutuallyExclusive = true;
underTest.setOptions(options);

underTest.handle(percentUsedAfterOrganicFullGc(99));

assertStats(MemoryPressureStats.newBuilder().setManuallyTriggeredGcs(0));
}

@Test
public void gcThrashingLimitsSet_mutuallyInclusive_enabled() {
options.oomMoreEagerlyThreshold = 90;
options.gcThrashingLimits = ImmutableList.of(Limit.of(Duration.ofMinutes(1), 2));
options.gcThrashingLimitsRetainedHeapLimiterMutuallyExclusive = false;
underTest.setOptions(options);

underTest.handle(percentUsedAfterOrganicFullGc(99));

assertStats(MemoryPressureStats.newBuilder().setManuallyTriggeredGcs(1));
}

@Test
public void statsReset() {
options.oomMoreEagerlyThreshold = 90;
Expand Down

0 comments on commit 9f93780

Please sign in to comment.