Skip to content

Commit 3dc6951

Browse files
kkresscopybara-github
authored andcommitted
Change --memory_profile_stable_heap_parameters to accept more than one GC specification
Currently memory_profile_stable_heap_parameters expects 2 ints and runs that many GCs with pauses between them 2nd param. This CL doesn't change that, but allows any arbitrary number of pairs to be provided that will run the same logic for each pair. This allows experimenting with forcing longer pauses on that thread before doing the quick GCs that allow for cleaner memory measurement. PiperOrigin-RevId: 485646588 Change-Id: Iff4f17cdaae409854f99397b4271bb5f87c4c404
1 parent 38561bc commit 3dc6951

File tree

6 files changed

+149
-37
lines changed

6 files changed

+149
-37
lines changed

src/main/java/com/google/devtools/build/lib/profiler/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ java_library(
3333
"//src/main/java/com/google/devtools/build/lib/concurrent",
3434
"//src/main/java/com/google/devtools/build/lib/unix:procmeminfo_parser",
3535
"//src/main/java/com/google/devtools/build/lib/util:os",
36+
"//src/main/java/com/google/devtools/build/lib/util:pair",
3637
"//src/main/java/com/google/devtools/build/lib/worker:worker_metric",
3738
"//src/main/java/com/google/devtools/common/options",
3839
"//third_party:auto_value",

src/main/java/com/google/devtools/build/lib/profiler/MemoryProfiler.java

Lines changed: 56 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,16 @@
1818
import com.google.common.annotations.VisibleForTesting;
1919
import com.google.common.base.MoreObjects;
2020
import com.google.common.base.Splitter;
21+
import com.google.devtools.build.lib.util.Pair;
2122
import com.google.devtools.common.options.OptionsParsingException;
2223
import java.io.OutputStream;
2324
import java.io.PrintStream;
2425
import java.lang.management.ManagementFactory;
2526
import java.lang.management.MemoryMXBean;
2627
import java.lang.management.MemoryUsage;
2728
import java.time.Duration;
28-
import java.util.Iterator;
29+
import java.util.ArrayList;
30+
import java.util.List;
2931
import java.util.NoSuchElementException;
3032
import javax.annotation.Nullable;
3133

@@ -111,14 +113,29 @@ synchronized HeapAndNonHeap prepareBeanAndGetLocalMinUsage(
111113
bean.gc();
112114
MemoryUsage minHeapUsed = bean.getHeapMemoryUsage();
113115
MemoryUsage minNonHeapUsed = bean.getNonHeapMemoryUsage();
116+
114117
if (nextPhase == ProfilePhase.FINISH && memoryProfileStableHeapParameters != null) {
115-
for (int i = 1; i < memoryProfileStableHeapParameters.numTimesToDoGc; i++) {
116-
sleeper.sleep(memoryProfileStableHeapParameters.timeToSleepBetweenGcs);
117-
bean.gc();
118-
MemoryUsage currentHeapUsed = bean.getHeapMemoryUsage();
119-
if (currentHeapUsed.getUsed() < minHeapUsed.getUsed()) {
120-
minHeapUsed = currentHeapUsed;
121-
minNonHeapUsed = bean.getNonHeapMemoryUsage();
118+
for (int j = 0; j < memoryProfileStableHeapParameters.gcSpecs.size(); j++) {
119+
Pair<Integer, Duration> spec = memoryProfileStableHeapParameters.gcSpecs.get(j);
120+
121+
int numTimesToDoGc = spec.first;
122+
Duration timeToSleepBetweenGcs = spec.second;
123+
124+
for (int i = 0; i < numTimesToDoGc; i++) {
125+
// We want to skip the first cycle for the first spec, since we ran a
126+
// GC at the top of this function, but all the rest should get their
127+
// proper runs
128+
if (j == 0 && i == 0) {
129+
continue;
130+
}
131+
132+
sleeper.sleep(timeToSleepBetweenGcs);
133+
bean.gc();
134+
MemoryUsage currentHeapUsed = bean.getHeapMemoryUsage();
135+
if (currentHeapUsed.getUsed() < minHeapUsed.getUsed()) {
136+
minHeapUsed = currentHeapUsed;
137+
minNonHeapUsed = bean.getNonHeapMemoryUsage();
138+
}
122139
}
123140
}
124141
}
@@ -130,12 +147,10 @@ synchronized HeapAndNonHeap prepareBeanAndGetLocalMinUsage(
130147
* build.
131148
*/
132149
public static class MemoryProfileStableHeapParameters {
133-
private final int numTimesToDoGc;
134-
private final Duration timeToSleepBetweenGcs;
150+
private final ArrayList<Pair<Integer, Duration>> gcSpecs;
135151

136-
private MemoryProfileStableHeapParameters(int numTimesToDoGc, Duration timeToSleepBetweenGcs) {
137-
this.numTimesToDoGc = numTimesToDoGc;
138-
this.timeToSleepBetweenGcs = timeToSleepBetweenGcs;
152+
private MemoryProfileStableHeapParameters(ArrayList<Pair<Integer, Duration>> gcSpecs) {
153+
this.gcSpecs = gcSpecs;
139154
}
140155

141156
/** Converter for {@code MemoryProfileStableHeapParameters} option. */
@@ -147,40 +162,48 @@ public static class Converter
147162
@Override
148163
public MemoryProfileStableHeapParameters convert(String input)
149164
throws OptionsParsingException {
150-
Iterator<String> values = SPLITTER.split(input).iterator();
165+
List<String> values = SPLITTER.splitToList(input);
166+
167+
if (values.size() % 2 != 0) {
168+
throw new OptionsParsingException(
169+
"Expected even number of comma-separated integer values");
170+
}
171+
172+
ArrayList<Pair<Integer, Duration>> gcSpecs = new ArrayList<>(values.size() / 2);
173+
151174
try {
152-
int numTimesToDoGc = Integer.parseInt(values.next());
153-
int numSecondsToSleepBetweenGcs = Integer.parseInt(values.next());
154-
if (values.hasNext()) {
155-
throw new OptionsParsingException("Expected exactly 2 comma-separated integer values");
175+
for (int i = 0; i < values.size(); i += 2) {
176+
int numTimesToDoGc = Integer.parseInt(values.get(i));
177+
int numSecondsToSleepBetweenGcs = Integer.parseInt(values.get(i + 1));
178+
179+
if (numTimesToDoGc <= 0) {
180+
throw new OptionsParsingException("Number of times to GC must be positive");
181+
}
182+
if (numSecondsToSleepBetweenGcs < 0) {
183+
throw new OptionsParsingException(
184+
"Number of seconds to sleep between GC's must be non-negative");
185+
}
186+
gcSpecs.add(Pair.of(numTimesToDoGc, Duration.ofSeconds(numSecondsToSleepBetweenGcs)));
156187
}
157-
if (numTimesToDoGc <= 0) {
158-
throw new OptionsParsingException("Number of times to GC must be positive");
159-
}
160-
if (numSecondsToSleepBetweenGcs < 0) {
161-
throw new OptionsParsingException(
162-
"Number of seconds to sleep between GC's must be non-negative");
163-
}
164-
return new MemoryProfileStableHeapParameters(
165-
numTimesToDoGc, Duration.ofSeconds(numSecondsToSleepBetweenGcs));
188+
189+
return new MemoryProfileStableHeapParameters(gcSpecs);
166190
} catch (NumberFormatException | NoSuchElementException nfe) {
167191
throw new OptionsParsingException(
168-
"Expected exactly 2 comma-separated integer values", nfe);
192+
"Expected even number of comma-separated integer values, could not parse integer in"
193+
+ " list",
194+
nfe);
169195
}
170196
}
171197

172198
@Override
173199
public String getTypeDescription() {
174-
return "two integers, separated by a comma";
200+
return "integers, separated by a comma expected in pairs";
175201
}
176202
}
177203

178204
@Override
179205
public String toString() {
180-
return MoreObjects.toStringHelper(this)
181-
.add("numTimesToDoGc", numTimesToDoGc)
182-
.add("timeToSleepBetweenGcs", timeToSleepBetweenGcs)
183-
.toString();
206+
return MoreObjects.toStringHelper(this).add("gcSpecs", gcSpecs).toString();
184207
}
185208
}
186209

src/main/java/com/google/devtools/build/lib/runtime/CommonCommandOptions.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -337,9 +337,11 @@ public String getTypeDescription() {
337337
effectTags = {OptionEffectTag.BAZEL_MONITORING},
338338
converter = MemoryProfileStableHeapParameters.Converter.class,
339339
help =
340-
"Tune memory profile's computation of stable heap at end of build. Should be two"
341-
+ " integers separated by a comma. First parameter is the number of GCs to perform."
342-
+ " Second parameter is the number of seconds to wait between GCs.")
340+
"Tune memory profile's computation of stable heap at end of build. Should be and even"
341+
+ " number of integers separated by commas. In each pair the first integer is the"
342+
+ " number of GCs to perform. The second integer in each pair is the number of"
343+
+ " seconds to wait between GCs. Ex: 2,4,4,0 would 2 GCs with a 4sec pause, followed"
344+
+ " by 4 GCs with zero second pause")
343345
public MemoryProfileStableHeapParameters memoryProfileStableHeapParameters;
344346

345347
@Option(

src/main/java/com/google/devtools/build/lib/util/BUILD

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,16 @@ java_library(
142142
],
143143
)
144144

145+
java_library(
146+
name = "pair",
147+
srcs = [
148+
"Pair.java",
149+
],
150+
deps = [
151+
"//third_party:jsr305",
152+
],
153+
)
154+
145155
java_library(
146156
name = "util",
147157
srcs = [
@@ -165,7 +175,6 @@ java_library(
165175
"OptionsUtils.java",
166176
"OrderedSetMultimap.java",
167177
"OsUtils.java",
168-
"Pair.java",
169178
"PathFragmentFilter.java",
170179
"PersistentMap.java",
171180
"RegexFilter.java",
@@ -177,6 +186,10 @@ java_library(
177186
"TimeUtilities.java",
178187
"UserUtils.java",
179188
],
189+
exports = [
190+
# vfs depends on the profiler and creates a cycle since we use Pair in profiler
191+
":pair",
192+
],
180193
deps = [
181194
":os",
182195
":shell_escaper",

src/test/java/com/google/devtools/build/lib/profiler/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ java_test(
2828
"//src/main/java/com/google/devtools/build/lib/profiler:profiler-output",
2929
"//src/main/java/com/google/devtools/build/lib/profiler:system_network_stats",
3030
"//src/main/java/com/google/devtools/build/lib/worker:worker_metric",
31+
"//src/main/java/com/google/devtools/common/options",
3132
"//src/test/java/com/google/devtools/build/lib/testutil",
3233
"//src/test/java/com/google/devtools/build/lib/testutil:TestUtils",
3334
"//third_party:guava",

src/test/java/com/google/devtools/build/lib/profiler/MemoryProfilerTest.java

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,15 @@
1515
package com.google.devtools.build.lib.profiler;
1616

1717
import static com.google.common.truth.Truth.assertThat;
18+
import static org.junit.Assert.assertThrows;
1819
import static org.mockito.Mockito.times;
1920
import static org.mockito.Mockito.verify;
2021
import static org.mockito.Mockito.when;
2122

2223
import com.google.common.io.ByteStreams;
2324
import com.google.devtools.build.lib.profiler.MemoryProfiler.MemoryProfileStableHeapParameters;
2425
import com.google.devtools.build.lib.profiler.MemoryProfiler.Sleeper;
26+
import com.google.devtools.common.options.OptionsParsingException;
2527
import java.lang.management.MemoryMXBean;
2628
import java.lang.management.MemoryUsage;
2729
import java.time.Duration;
@@ -96,6 +98,76 @@ public void profilerDoesOneGcAndNoSleepExceptInFinish() throws Exception {
9698
verify(bean, times(3)).getNonHeapMemoryUsage();
9799
}
98100

101+
@Test
102+
public void profilerHasMultiplePairs() throws Exception {
103+
MemoryProfiler profiler = MemoryProfiler.instance();
104+
profiler.setStableMemoryParameters(
105+
new MemoryProfileStableHeapParameters.Converter().convert("2,1,3,4,5,6"));
106+
profiler.start(ByteStreams.nullOutputStream());
107+
MemoryMXBean bean = Mockito.mock(MemoryMXBean.class);
108+
109+
MemoryUsage heapUsage = new MemoryUsage(0, 0, 0, 0);
110+
MemoryUsage nonHeapUsage = new MemoryUsage(5, 5, 5, 5);
111+
when(bean.getHeapMemoryUsage()).thenReturn(heapUsage);
112+
when(bean.getNonHeapMemoryUsage()).thenReturn(nonHeapUsage);
113+
114+
RecordingSleeper sleeper = new RecordingSleeper();
115+
MemoryProfiler.HeapAndNonHeap result =
116+
profiler.prepareBeanAndGetLocalMinUsage(ProfilePhase.ANALYZE, bean, sleeper);
117+
assertThat(result.getHeap()).isSameInstanceAs(heapUsage);
118+
assertThat(result.getNonHeap()).isSameInstanceAs(nonHeapUsage);
119+
assertThat(sleeper.sleeps).isEmpty();
120+
121+
verify(bean, times(1)).gc();
122+
profiler.prepareBeanAndGetLocalMinUsage(ProfilePhase.FINISH, bean, sleeper);
123+
// 1 for call to ANALYZE + spec'd runs
124+
verify(bean, times(1 + 2 + 3 + 5)).gc();
125+
126+
assertThat(sleeper.sleeps)
127+
.containsExactly(
128+
Duration.ofSeconds(1), // 2 * 1s, but we skip the first sleep
129+
Duration.ofSeconds(4), // 3 * 4s
130+
Duration.ofSeconds(4),
131+
Duration.ofSeconds(4),
132+
Duration.ofSeconds(6), // 5 * 6s
133+
Duration.ofSeconds(6),
134+
Duration.ofSeconds(6),
135+
Duration.ofSeconds(6),
136+
Duration.ofSeconds(6))
137+
.inOrder();
138+
}
139+
140+
@Test
141+
public void profilerHasBadInputOddValues() throws Exception {
142+
MemoryProfiler profiler = MemoryProfiler.instance();
143+
OptionsParsingException e =
144+
assertThrows(
145+
OptionsParsingException.class,
146+
() ->
147+
profiler.setStableMemoryParameters(
148+
new MemoryProfileStableHeapParameters.Converter().convert("1,10,7")));
149+
assertThat(e)
150+
.hasMessageThat()
151+
.contains("Expected even number of comma-separated integer values");
152+
}
153+
154+
@Test
155+
public void profilerHasBadInputNotInts() throws Exception {
156+
MemoryProfiler profiler = MemoryProfiler.instance();
157+
OptionsParsingException e =
158+
assertThrows(
159+
OptionsParsingException.class,
160+
() ->
161+
profiler.setStableMemoryParameters(
162+
new MemoryProfileStableHeapParameters.Converter()
163+
.convert("1,10,74,22,horse,goat")));
164+
assertThat(e)
165+
.hasMessageThat()
166+
.contains(
167+
"Expected even number of comma-separated integer values, could not parse integer in"
168+
+ " list");
169+
}
170+
99171
private static class RecordingSleeper implements Sleeper {
100172
private final List<Duration> sleeps = new ArrayList<>();
101173

0 commit comments

Comments
 (0)