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, tmp.resolve("tika-config.json"), 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();
}
@Test
public void heapInjectedForSingleClient() throws Exception {
assertEquals(List.of("-XX:MaxRAMPercentage=75"),
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(1, Runtime.getRuntime().availableProcessors() - 2);
if (slice >= 2) {
assertEquals(List.of("-XX:ActiveProcessorCount=" + slice), caps);
} else {
// Host too small for the auto-cap; injection is skipped by design.
assertTrue(caps.isEmpty(), "expected no auto-cap on tiny host: " + caps);
}
}
@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));
}
}