blob: 2152c9388acde79d07af6fd2b512dabdd37291c4 [file] [log] [blame]
// Copyright 2018 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.server;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import com.google.devtools.build.lib.clock.BlazeClock;
import com.google.devtools.build.lib.testutil.ManualClock;
import com.google.devtools.build.lib.testutil.TestUtils;
import com.google.devtools.build.lib.unix.ProcMeminfoParser;
import com.google.devtools.build.lib.util.OS;
import io.grpc.Server;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/** Tests for {@link ServerWatcherRunnable}. */
@RunWith(JUnit4.class)
public class ServerWatcherRunnableTest {
private ManualClock clock;
private Server mockServer;
@Before
public final void setManualClock() {
clock = new ManualClock();
mockServer = mock(Server.class);
BlazeClock.setClock(clock);
}
@Test
public void testBasicIdleCheck() throws Exception {
CommandManager mockCommands = mock(CommandManager.class);
ServerWatcherRunnable underTest =
new ServerWatcherRunnable(
mockServer, /*maxIdleSeconds=*/ 10, /*shutdownOnLowSysMem=*/ false, mockCommands);
Thread thread = new Thread(underTest);
when(mockCommands.isEmpty()).thenReturn(true);
AtomicInteger checkIdleCounter = new AtomicInteger();
doAnswer(
invocation -> {
checkIdleCounter.incrementAndGet();
verify(mockServer, never()).shutdown();
clock.advanceMillis(Duration.ofSeconds(5).toMillis());
return null;
})
.when(mockCommands)
.waitForChange(anyLong());
thread.start();
thread.join(TestUtils.WAIT_TIMEOUT_MILLISECONDS);
verify(mockServer).shutdown();
assertThat(checkIdleCounter.get()).isEqualTo(2);
}
@Test
public void runLowAbsoluteHighPercentageMemoryCheck() throws Exception {
if (!usingLinux()) {
return;
}
assertThat(doesIdleLowMemoryCheckShutdown(/*freeRamKb=*/ 5000, /*totalRamKb=*/ 10000))
.isFalse();
}
@Test
public void runHighAbsoluteLowPercentageMemoryCheck() throws Exception {
if (!usingLinux()) {
return;
}
assertThat(doesIdleLowMemoryCheckShutdown(/*freeRamKb=*/ 1L << 21, /*totalRamKb=*/ 1L << 30))
.isFalse();
}
@Test
public void runLowAsboluteLowPercentageMemoryCheck() throws Exception {
if (!usingLinux()) {
return;
}
assertThat(doesIdleLowMemoryCheckShutdown(/*freeRamKb=*/ 5000, /*totalRamKb=*/ 1000000))
.isTrue();
}
@Test
public void testshutdownOnLowSysMemDisabled() throws Exception {
if (!usingLinux()) {
return;
}
assertThat(
doesIdleLowMemoryCheckShutdown(
/*freeRamKb=*/ 5000, /*totalRamKb=*/ 1000000, /*shutdownOnLowSysMem=*/ false))
.isFalse();
}
private boolean doesIdleLowMemoryCheckShutdown(long freeRamKb, long totalRamKb) throws Exception {
return doesIdleLowMemoryCheckShutdown(freeRamKb, totalRamKb, /*shutdownOnLowSysMem=*/ true);
}
private boolean doesIdleLowMemoryCheckShutdown(
long freeRamKb, long totalRamKb, boolean shutdownOnLowSysMem) throws Exception {
CommandManager mockCommandManager = mock(CommandManager.class);
ProcMeminfoParser mockParser = mock(ProcMeminfoParser.class);
ServerWatcherRunnable underTest =
new ServerWatcherRunnable(
mockServer,
// Shut down after an hour if we see no memory issues.
/*maxIdleSeconds=*/ Duration.ofHours(1).getSeconds(),
shutdownOnLowSysMem,
mockCommandManager,
() -> mockParser);
Thread thread = new Thread(underTest);
when(mockCommandManager.isEmpty()).thenReturn(true);
AtomicInteger serverWatcherLoopCounter = new AtomicInteger();
when(mockParser.getFreeRamKb()).thenReturn(freeRamKb);
when(mockParser.getTotalKb()).thenReturn(totalRamKb);
doAnswer(
invocation -> {
serverWatcherLoopCounter.incrementAndGet();
clock.advanceMillis(Duration.ofMinutes(1).toMillis());
return null;
})
.when(mockCommandManager)
.waitForChange(Duration.ofSeconds(5).toMillis());
thread.start();
thread.join(TestUtils.WAIT_TIMEOUT_MILLISECONDS);
verify(mockServer).shutdown();
// If we shut down due to memory pressure, it will only be after 5 minutes of being idle.
return serverWatcherLoopCounter.get() == 5;
}
private boolean usingLinux() {
return OS.getCurrent() == OS.LINUX;
}
}