Skip to content

Commit

Permalink
Collect the number of bytes collected by GC and expose it via the BEP.
Browse files Browse the repository at this point in the history
Roll forward of bazelbuild@3678bb5.

Instead of collecting the cumulative number of bytes, collect it per type of GC
space.

PiperOrigin-RevId: 397926469
  • Loading branch information
meisterT authored and copybara-github committed Sep 21, 2021
1 parent daf5e26 commit 8965d25
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,16 @@ message BuildMetrics {
// Size of the peak JVM heap size in bytes post GC. Note that this reports 0
// if there was no major GC during the build.
int64 peak_post_gc_heap_size = 2;

message GarbageMetrics {
// Type of garbage collected, e.g. G1 Old Gen.
string type = 1;
// Number of bytes of garbage of the given type collected during this
// invocation.
int64 garbage_collected = 2;
}

repeated GarbageMetrics garbage_metrics = 3;
}
MemoryMetrics memory_metrics = 2;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildMetrics.BuildGraphMetrics;
import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildMetrics.CumulativeMetrics;
import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildMetrics.MemoryMetrics;
import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildMetrics.MemoryMetrics.GarbageMetrics;
import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildMetrics.PackageMetrics;
import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildMetrics.TargetMetrics;
import com.google.devtools.build.lib.buildeventstream.BuildEventStreamProtos.BuildMetrics.TimingMetrics;
Expand All @@ -49,6 +50,7 @@
import com.google.devtools.build.skyframe.SkyframeGraphStatsEvent;
import java.time.Duration;
import java.util.Comparator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
Expand Down Expand Up @@ -260,6 +262,14 @@ private MemoryMetrics createMemoryMetrics() {
// notification (which may arrive too late for this specific GC).
memoryMetrics.setPeakPostGcHeapSize(usedHeapSizePostBuild);
}

Map<String, Long> garbageStats = PostGCMemoryUseRecorder.get().getGarbageStats();
for (Map.Entry<String, Long> garbageEntry : garbageStats.entrySet()) {
GarbageMetrics.Builder garbageMetrics = GarbageMetrics.newBuilder();
garbageMetrics.setType(garbageEntry.getKey()).setGarbageCollected(garbageEntry.getValue());
memoryMetrics.addGarbageMetrics(garbageMetrics.build());
}

return memoryMetrics.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.flogger.GoogleLogger;
import com.google.devtools.build.lib.analysis.config.BuildConfiguration;
import com.google.devtools.build.lib.profiler.Profiler;
Expand All @@ -34,9 +35,11 @@
import com.google.devtools.common.options.OptionsBase;
import com.google.devtools.common.options.OptionsParsingResult;
import com.sun.management.GarbageCollectionNotificationInfo;
import com.sun.management.GcInfo;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryUsage;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import javax.annotation.concurrent.GuardedBy;
Expand Down Expand Up @@ -94,6 +97,9 @@ static PeakHeap create(long bytes, long timestampMillis) {
@GuardedBy("this")
private boolean memoryUsageReportedZero = false;

@GuardedBy("this")
private Map<String, Long> garbageStats = new HashMap<>();

@VisibleForTesting
PostGCMemoryUseRecorder(Iterable<GarbageCollectorMXBean> mxBeans) {
for (GarbageCollectorMXBean mxBean : mxBeans) {
Expand All @@ -114,9 +120,17 @@ public synchronized boolean wasMemoryUsageReportedZero() {
return memoryUsageReportedZero;
}

/**
* Returns the number of bytes garbage collected during this invocation. Broken down by GC space.
*/
public synchronized ImmutableMap<String, Long> getGarbageStats() {
return ImmutableMap.copyOf(garbageStats);
}

public synchronized void reset() {
peakHeap = Optional.empty();
memoryUsageReportedZero = false;
garbageStats = new HashMap<>();
}

private synchronized void updatePostGCHeapMemoryUsed(long used, long timestampMillis) {
Expand All @@ -139,6 +153,26 @@ public void handleNotification(Notification notification, Object handback) {

GarbageCollectionNotificationInfo info =
GarbageCollectionNotificationInfo.from((CompositeData) notification.getUserData());

Map<String, Long> gcBefore = new HashMap<>();
GcInfo gcInfo = info.getGcInfo();
for (Map.Entry<String, MemoryUsage> memoryUsage : gcInfo.getMemoryUsageBeforeGc().entrySet()) {
String kind = memoryUsage.getKey();
gcBefore.put(kind, memoryUsage.getValue().getUsed());
}
synchronized (this) {
for (Map.Entry<String, MemoryUsage> memoryUsage : gcInfo.getMemoryUsageAfterGc().entrySet()) {
String kind = memoryUsage.getKey();
long before = gcBefore.containsKey(kind) ? gcBefore.get(kind) : 0;
long diff = before - memoryUsage.getValue().getUsed();
// The difference is potentially negative when the JVM propagates objects from one GC space
// to another. Discard these cases.
if (diff > 0) {
garbageStats.compute(kind, (k, v) -> v == null ? diff : v + diff);
}
}
}

if (wasStopTheWorldGc(info)) {
long durationNs = info.getGcInfo().getDuration() * 1_000_000;
long end = Profiler.nanoTimeMaybe();
Expand Down
1 change: 1 addition & 0 deletions src/test/java/com/google/devtools/build/lib/metrics/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ java_test(
"//src/main/java/com/google/devtools/build/lib/metrics:memory-use-recorder",
"//src/test/java/com/google/devtools/build/lib/testutil",
"//third_party:guava",
"//third_party:jsr305",
"//third_party:junit4",
"//third_party:mockito",
"//third_party:truth",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import javax.management.Notification;
import javax.management.NotificationEmitter;
import javax.management.NotificationFilter;
Expand Down Expand Up @@ -99,7 +100,8 @@ public void noGcCauseEventsNotIgnored() {
GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION,
"end of major GC",
/*cause=*/ "No GC",
ImmutableMap.of("somepool", 100L));
ImmutableMap.of("somepool", 100L),
/*memUsedBefore=*/ null);

underTest.handleNotification(notificationWithNoGcCause, /*handback=*/ null);

Expand Down Expand Up @@ -192,6 +194,34 @@ public void memoryUsageReportedZeroDoesntGetSet() {
assertThat(rec.wasMemoryUsageReportedZero()).isFalse();
}

@Test
public void totalGarbageReported() {
PostGCMemoryUseRecorder rec = new PostGCMemoryUseRecorder(new ArrayList<>());
assertThat(rec.getGarbageStats()).isEmpty();

rec.handleNotification(
createMockNotification(
GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION,
"action",
"cause",
ImmutableMap.of("old", 1000L, "young", 2000L),
ImmutableMap.of("old", 5000L, "young", 10000L)),
/* handback= */ null);
assertThat(rec.getGarbageStats()).containsExactly("old", 4000L, "young", 8000L);
rec.handleNotification(
createMockNotification(
GarbageCollectionNotificationInfo.GARBAGE_COLLECTION_NOTIFICATION,
"action",
"cause",
ImmutableMap.of("young", 15000L),
ImmutableMap.of("young", 20000L)),
/* handback= */ null);
assertThat(rec.getGarbageStats()).containsExactly("old", 4000L, "young", 13000L);

rec.reset();
assertThat(rec.getGarbageStats()).isEmpty();
}

private static GarbageCollectorMXBean createMXBeanWithName(String name) {
GarbageCollectorMXBean b =
mock(
Expand All @@ -215,19 +245,32 @@ private static MemoryUsage createMockMemoryUsage(long used) {
return mu;
}

private static ImmutableMap<String, MemoryUsage> createMemoryUsageMap(Map<String, Long> memUsed) {
ImmutableMap.Builder<String, MemoryUsage> memUsageMap = ImmutableMap.builder();
for (Map.Entry<String, Long> e : memUsed.entrySet()) {
memUsageMap.put(e.getKey(), createMockMemoryUsage(e.getValue()));
}
return memUsageMap.build();
}

private Notification createMockNotification(
String type, String action, Map<String, Long> memUsed) {
return createMockNotification(type, action, "dummycause", memUsed);
return createMockNotification(type, action, "dummycause", memUsed, /* memUsedBefore= */ null);
}

private Notification createMockNotification(
String type, String action, String cause, Map<String, Long> memUsed) {
ImmutableMap.Builder<String, MemoryUsage> memUsageMap = ImmutableMap.builder();
for (Map.Entry<String, Long> e : memUsed.entrySet()) {
memUsageMap.put(e.getKey(), createMockMemoryUsage(e.getValue()));
}
String type,
String action,
String cause,
Map<String, Long> memUsed,
@Nullable Map<String, Long> memUsedBefore) {
GcInfo gcInfo = mock(GcInfo.class);
when(gcInfo.getMemoryUsageAfterGc()).thenReturn(memUsageMap.build());
ImmutableMap<String, MemoryUsage> memoryUsageMap = createMemoryUsageMap(memUsed);
when(gcInfo.getMemoryUsageAfterGc()).thenReturn(memoryUsageMap);
if (memUsedBefore != null) {
ImmutableMap<String, MemoryUsage> memoryUsageBeforeMap = createMemoryUsageMap(memUsedBefore);
when(gcInfo.getMemoryUsageBeforeGc()).thenReturn(memoryUsageBeforeMap);
}

GarbageCollectionNotificationInfo notInfo =
new GarbageCollectionNotificationInfo("DummyGCName", action, cause, gcInfo);
Expand Down

0 comments on commit 8965d25

Please sign in to comment.