PerClientServerManagerSizingTest.java
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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 org.apache.tika.pipes.core;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
/**
* Auto-injected fork sizing (-XX:MaxRAMPercentage, -XX:ActiveProcessorCount)
* applies uniformly, including numClients=1, unless the user set their own.
*/
public class PerClientServerManagerSizingTest {
@TempDir
Path tmp;
private List<String> commandLine(int numClients, String... forkedJvmArgs) throws Exception {
PipesConfig pipesConfig = new PipesConfig();
pipesConfig.setNumClients(numClients);
pipesConfig.setForkedJvmArgs(new ArrayList<>(Arrays.asList(forkedJvmArgs)));
PerClientServerManager manager =
new PerClientServerManager(pipesConfig, new byte[0], 0);
return Arrays.asList(manager.getCommandline(tmp));
}
private static List<String> withPrefix(List<String> args, String prefix) {
return args.stream().filter(a -> a.startsWith(prefix)).toList();
}
/** The config travels on stdin; only the port follows the main class. */
@Test
public void noConfigPathOnCommandLine() throws Exception {
List<String> args = commandLine(1);
assertEquals("org.apache.tika.pipes.core.server.PipesServer", args.get(args.size() - 2));
}
/** A lone fork is capped below the fork budget; the parent claims the JVM default on top. */
@Test
public void heapInjectedForSingleClient() throws Exception {
assertEquals(List.of("-XX:MaxRAMPercentage=60"),
withPrefix(commandLine(1), "-XX:MaxRAMPercentage"));
}
@Test
public void heapDividedAcrossClients() throws Exception {
assertEquals(List.of("-XX:MaxRAMPercentage=25"),
withPrefix(commandLine(3), "-XX:MaxRAMPercentage"));
}
@Test
public void userHeapSuppressesInjection() throws Exception {
List<String> args = commandLine(1, "-Xmx512m");
assertTrue(withPrefix(args, "-XX:MaxRAMPercentage").isEmpty(),
"user -Xmx must suppress auto MaxRAMPercentage: " + args);
assertTrue(args.contains("-Xmx512m"));
}
@Test
public void cpuCapInjectedForSingleClient() throws Exception {
List<String> caps = withPrefix(commandLine(1), "-XX:ActiveProcessorCount=");
int slice = Math.max(PerClientServerManager.MIN_AUTO_CAP_SLICE,
Math.max(1, Runtime.getRuntime().availableProcessors() - 2));
assertEquals(List.of("-XX:ActiveProcessorCount=" + slice), caps);
}
/** The floor is a clamp, not a switch: an over-provisioned host still caps every fork. */
@Test
public void cpuCapClampedWhenOverProvisioned() throws Exception {
// a thousand clients yields a fair share below the floor on any host
assertEquals(List.of("-XX:ActiveProcessorCount=" + PerClientServerManager.MIN_AUTO_CAP_SLICE),
withPrefix(commandLine(1000), "-XX:ActiveProcessorCount="));
}
@Test
public void autoCapSlice() {
assertEquals(14, PerClientServerManager.autoCapSlice(16, 1));
assertEquals(2, PerClientServerManager.autoCapSlice(16, 7));
// 10 forks on 16 cores: fair share is 1, which used to disable the cap entirely
assertEquals(2, PerClientServerManager.autoCapSlice(16, 10));
assertEquals(2, PerClientServerManager.autoCapSlice(4, 8));
assertEquals(2, PerClientServerManager.autoCapSlice(1, 1));
// numClients=0 is nonsense; treated as 1 rather than dividing by zero
assertEquals(14, PerClientServerManager.autoCapSlice(16, 0));
}
@Test
public void userCpuCapSuppressesInjection() throws Exception {
assertEquals(List.of("-XX:ActiveProcessorCount=3"),
withPrefix(commandLine(1, "-XX:ActiveProcessorCount=3"),
"-XX:ActiveProcessorCount="));
}
@Test
public void parseJvmMemArg() {
assertEquals(512L * 1024 * 1024, PerClientServerManager.parseJvmMemArg("512m"));
assertEquals(2L * 1024 * 1024 * 1024, PerClientServerManager.parseJvmMemArg("2g"));
assertEquals(1024L * 1024, PerClientServerManager.parseJvmMemArg("1024k"));
assertEquals(1L * 1024 * 1024 * 1024 * 1024, PerClientServerManager.parseJvmMemArg("1t"));
assertEquals(1000, PerClientServerManager.parseJvmMemArg("1000"));
assertEquals(-1, PerClientServerManager.parseJvmMemArg("abc"));
assertEquals(-1, PerClientServerManager.parseJvmMemArg(""));
assertEquals(-1, PerClientServerManager.parseJvmMemArg("-2g"));
assertEquals(-1, PerClientServerManager.parseJvmMemArg("999999999t"));
}
private static final long GB = 1024L * 1024 * 1024;
@Test
public void xmxOvercommitWarns() {
// 4 x 2g = 8g > 75% of 8g
assertNotNull(PerClientServerManager.heapOvercommitWarning(
List.of("-Xmx2g"), 4, 8 * GB));
// 4 x 2g = 8g <= 75% of 16g
assertNull(PerClientServerManager.heapOvercommitWarning(
List.of("-Xmx2g"), 4, 16 * GB));
// last -Xmx wins, same as the JVM
assertNull(PerClientServerManager.heapOvercommitWarning(
List.of("-Xmx8g", "-Xmx1g"), 4, 16 * GB));
// unknown total memory: no basis to warn
assertNull(PerClientServerManager.heapOvercommitWarning(
List.of("-Xmx2g"), 4, -1));
}
@Test
public void maxRamPercentageOvercommitWarns() {
// 3 x 50% = 150% of memory
assertNotNull(PerClientServerManager.heapOvercommitWarning(
List.of("-XX:MaxRAMPercentage=50"), 3, -1));
// 3 x 25% = 75% budget exactly: allowed
assertNull(PerClientServerManager.heapOvercommitWarning(
List.of("-XX:MaxRAMPercentage=25"), 3, -1));
// a fitting -Xmx overrides an overcommitted percentage, same as the JVM
assertNull(PerClientServerManager.heapOvercommitWarning(
List.of("-XX:MaxRAMPercentage=50", "-Xmx1g"), 3, 16 * GB));
}
@Test
public void noExplicitHeapNoWarning() {
assertNull(PerClientServerManager.heapOvercommitWarning(List.of(), 8, 4 * GB));
}
}