/* * 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 net.grinder.engine.agent; import net.grinder.common.GrinderProperties; import net.grinder.util.Directory; import net.grinder.util.NetworkUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.math.NumberUtils; import org.hyperic.sigar.Sigar; import org.hyperic.sigar.SigarException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.FilenameFilter; import java.net.InetAddress; import java.util.List; import static org.ngrinder.common.util.Preconditions.checkNotEmpty; import static org.ngrinder.common.util.Preconditions.checkNotNull; /** * Class which is responsible to build custom jvm arguments. * <p/> * This class aware of security. So it produces the appropriate JVM arguments * which works at security env. * * @author JunHo Yoon * @since 3.0 */ public class PropertyBuilder { private static final Logger LOGGER = LoggerFactory.getLogger(ProcessBuilder.class); private final GrinderProperties properties; private final Directory baseDirectory; private final String hostName; private final boolean securityEnabled; private final String hostString; private final boolean server; private final boolean useXmxLimit; private final String additionalJavaOpt; private boolean enableLocalDNS; /** * Constructor with null additional java opt value. * * @param properties {@link GrinderProperties} * @param baseDirectory base directory which the script executes. * @param securityEnabled true if security enable mode * @param hostString hostString * @param hostName current host name * @param server server mode * @param useXmxLimit true if 1G limit should be enabled * @param enableLocalDNS true if the local dns should be enabled. * @param additionalJavaOpt additional java option to be provided when invoking agent * process */ public PropertyBuilder(GrinderProperties properties, Directory baseDirectory, boolean securityEnabled, String hostString, String hostName, boolean server, boolean useXmxLimit, boolean enableLocalDNS, String additionalJavaOpt) { this.enableLocalDNS = enableLocalDNS; this.properties = checkNotNull(properties); this.baseDirectory = checkNotNull(baseDirectory); this.securityEnabled = securityEnabled; this.hostString = hostString; this.hostName = checkNotEmpty(hostName); this.server = server; this.useXmxLimit = useXmxLimit; this.additionalJavaOpt = additionalJavaOpt; } /** * Constructor with null additional java opt value. * * @param properties {@link GrinderProperties} * @param baseDirectory base directory which the script executes. * @param securityEnabled true if security enable mode * @param hostString hostString * @param hostName current host name * @param server server mode * @param useXmxLimit true if 1G limit should be enabled * @param additionalJavaOpt additional java option to be provided when invoking agent * process */ public PropertyBuilder(GrinderProperties properties, Directory baseDirectory, boolean securityEnabled, String hostString, String hostName, boolean server, boolean useXmxLimit, String additionalJavaOpt) { this(properties, baseDirectory, securityEnabled, hostString, hostName, server, useXmxLimit, true, additionalJavaOpt); } /** * Constructor with null additional java opt value. * * @param properties {@link GrinderProperties} * @param baseDirectory base directory which the script executes. * @param securityEnabled true if security enable mode * @param hostString hostString * @param hostName current host name * @param server server mode * @param useXmxLimit true if 1G limit should be enabled */ public PropertyBuilder(GrinderProperties properties, Directory baseDirectory, boolean securityEnabled, String hostString, String hostName, boolean server, boolean useXmxLimit) { this(properties, baseDirectory, securityEnabled, hostString, hostName, server, useXmxLimit, null); } /** * Constructor. * * @param properties {@link GrinderProperties} * @param baseDirectory base directory which the script executes. * @param securityEnabled true if security enable mode * @param hostString hostString * @param hostName current host name * @param server server mode */ public PropertyBuilder(GrinderProperties properties, Directory baseDirectory, boolean securityEnabled, String hostString, String hostName, boolean server) { this(properties, baseDirectory, securityEnabled, hostString, hostName, server, true); } /** * Constructor. * * @param properties {@link GrinderProperties} * @param baseDirectory base directory which the script executes. * @param securityEnabled true if security enable mode * @param hostString hostString * @param hostName current host name */ public PropertyBuilder(GrinderProperties properties, Directory baseDirectory, boolean securityEnabled, String hostString, String hostName) { this(properties, baseDirectory, securityEnabled, hostString, hostName, false); } /** * Build JVM Arguments. * * @return generated jvm arguments */ public String buildJVMArgument() { return addMemorySettings(new StringBuilder(buildJVMArgumentWithoutMemory())).toString(); } /** * Build JVM Arguments. * * @return generated jvm arguments */ public String buildJVMArgumentWithoutMemory() { StringBuilder jvmArguments = new StringBuilder(); if (securityEnabled) { jvmArguments = addSecurityManager(jvmArguments); jvmArguments = addCurrentAgentPath(jvmArguments); jvmArguments = addConsoleIP(jvmArguments); jvmArguments = addDnsIP(jvmArguments); } else { jvmArguments.append(properties.getProperty("grinder.jvm.arguments", "")); jvmArguments = addNativeLibraryPath(jvmArguments); } jvmArguments = addParam(jvmArguments, properties.getProperty("grinder.param", "")); jvmArguments = addPythonPathJvmArgument(jvmArguments); jvmArguments = addCustomDns(jvmArguments); if (server) { jvmArguments = addServerMode(jvmArguments); } if (StringUtils.isNotBlank(additionalJavaOpt)) { jvmArguments = addAdditionalJavaOpt(jvmArguments); } return jvmArguments.toString(); } private StringBuilder addParam(StringBuilder jvmArguments, String param) { if (StringUtils.isEmpty(param)) { return jvmArguments; } return jvmArguments.append(" -Dparam=").append(param).append(" "); } private StringBuilder addAdditionalJavaOpt(StringBuilder jvmArguments) { return jvmArguments.append(" ").append(additionalJavaOpt).append(" "); } private StringBuilder addNativeLibraryPath(StringBuilder jvmArguments) { return jvmArguments.append(" -Djna.library.path=").append(new File(baseDirectory.getFile(), "/lib")) .append(" "); } protected static final long MIN_PER_PROCESS_MEM_SIZE = 50 * 1024 * 1024; protected static final long DEFAULT_XMX_SIZE = 500 * 1024 * 1024; protected static final long DEFAULT_MAX_XMX_SIZE = 1024 * 1024 * 1024; protected StringBuilder addMemorySettings(StringBuilder jvmArguments) { String processCountStr = properties.getProperty("grinder.processes", "1"); // For compatibility, try both. int reservedMemoryUnit = properties.getInt("grinder.reserved.memory", 0); if (reservedMemoryUnit == 0) { reservedMemoryUnit = properties.getInt("grinder.memory.reserved", 300); } int reservedMemory = Math.max(reservedMemoryUnit, 0) * 1024 * 1024; int processCount = NumberUtils.toInt(processCountStr, 1); long desirableXmx; // make 500M as default. long permGen = 32 * 1024 * 1024; try { // Make a free memory room size of reservedMemory. long free = new Sigar().getMem().getActualFree() - reservedMemory; long perProcessTotalMemory = Math.max(free / processCount, MIN_PER_PROCESS_MEM_SIZE); desirableXmx = (long) (perProcessTotalMemory * 0.5); permGen = Math.min(Math.max((long) (perProcessTotalMemory * 0.2), 50L * 1024 * 1024), 128 * 1024 * 1024); if (this.useXmxLimit) { desirableXmx = Math.min(DEFAULT_MAX_XMX_SIZE, desirableXmx); } } catch (UnsatisfiedLinkError e) { LOGGER.error("Sigar lib link error: {}", e.getMessage()); desirableXmx = DEFAULT_XMX_SIZE; } catch (SigarException e) { LOGGER.error("Error occurred while calculating memory size : {}", e.getMessage()); desirableXmx = DEFAULT_XMX_SIZE; } jvmArguments.append(" -Xms").append(getMemorySize(desirableXmx)).append("m -Xmx").append(getMemorySize(desirableXmx)).append("m "); jvmArguments.append(" -XX:PermSize=") .append(properties.getInt("grinder.memory.permsize", getMemorySize(permGen))).append("m "); jvmArguments.append(" -XX:MaxPermSize=") .append(properties.getInt("grinder.memory.maxpermsize", getMemorySize(permGen))).append("m "); return jvmArguments; } private int getMemorySize(long memoryInByte) { return (int) (memoryInByte / (1024 * 1024)); } protected StringBuilder addServerMode(StringBuilder jvmArguments) { return jvmArguments.append(" -server "); } protected StringBuilder addSecurityManager(StringBuilder jvmArguments) { return jvmArguments.append(" -Djava.security.manager=org.ngrinder.sm.NGrinderSecurityManager "); } private String getPath(File file, boolean useAbsolutePath) { return useAbsolutePath ? FilenameUtils.normalize(file.getAbsolutePath()) : file.getPath(); } /** * Build custom class path based on the jar files on given base path. * * @param useAbsolutePath true if the class path entries should be represented as * absolute path * @return classpath string */ @SuppressWarnings("ResultOfMethodCallIgnored") public String buildCustomClassPath(final boolean useAbsolutePath) { File baseFile = baseDirectory.getFile(); File libFolder = new File(baseFile, "lib"); final StringBuffer customClassPath = new StringBuffer(); customClassPath.append(getPath(baseFile, useAbsolutePath)); if (libFolder.exists()) { customClassPath.append(File.pathSeparator).append(getPath(new File(baseFile, "lib"), useAbsolutePath)); libFolder.list(new FilenameFilter() { @Override public boolean accept(File dir, String name) { if (name.endsWith(".jar")) { customClassPath.append(File.pathSeparator) .append(getPath(new File(dir, name), useAbsolutePath)); } return true; } }); } return customClassPath.toString(); } /** * Rebase class path from relative path to absolute path. * * @param classPath class path * @return converted path. */ public String rebaseCustomClassPath(String classPath) { StringBuilder newClassPath = new StringBuilder(); boolean isFirst = true; for (String each : StringUtils.split(classPath, ";:")) { File file = new File(baseDirectory.getFile(), each); if (!isFirst) { newClassPath.append(File.pathSeparator); } isFirst = false; newClassPath.append(FilenameUtils.normalize(file.getAbsolutePath())); } return newClassPath.toString(); } @SuppressWarnings("ResultOfMethodCallIgnored") private StringBuilder addPythonPathJvmArgument(StringBuilder jvmArguments) { jvmArguments.append(" -Dpython.path="); jvmArguments.append(new File(baseDirectory.getFile(), "lib").getAbsolutePath()); String pythonPath = System.getenv().get("PYTHONPATH"); if (pythonPath != null) { jvmArguments.append(File.pathSeparator).append(pythonPath); } String pythonHome = System.getenv().get("PYTHONHOME"); if (pythonHome != null) { jvmArguments.append(" -Dpython.home="); jvmArguments.append(pythonHome); } jvmArguments.append(" "); File jythonCache = new File(FileUtils.getTempDirectory(), "jython"); jythonCache.mkdirs(); jvmArguments.append(" -Dpython.cachedir=").append(jythonCache.getAbsolutePath()).append(" "); return jvmArguments; } private StringBuilder addCurrentAgentPath(StringBuilder jvmArguments) { return jvmArguments.append(" -Dngrinder.exec.path=").append(baseDirectory.getFile()).append(" "); } private StringBuilder addConsoleIP(StringBuilder jvmArguments) { return jvmArguments.append(" -Dngrinder.console.ip=") .append(properties.getProperty(GrinderProperties.CONSOLE_HOST, "127.0.0.1")).append(" "); } StringBuilder addDnsIP(StringBuilder jvmArguments) { try { List<?> dnsServers = NetworkUtils.getDnsServers(); if (!dnsServers.isEmpty()) { return jvmArguments.append(" -Dngrinder.dns.ip=").append(StringUtils.join(dnsServers, ",")).append(" "); } } catch (Exception e) { LOGGER.error("Error while adding DNS IPs for the security mode. This might be occurred by not using " + "Oracle JDK : {}", e.getMessage()); } return jvmArguments; } private StringBuilder addCustomDns(StringBuilder jvmArguments) { jvmArguments.append(" -Dngrinder.etc.hosts=").append(hostName).append(":127.0.0.1,localhost:127.0.0.1"); if (StringUtils.isNotEmpty(hostString)) { jvmArguments.append(",").append(rebaseHostString(hostString)); } if (enableLocalDNS) { jvmArguments.append(" -Dsun.net.spi.nameservice.provider.1=dns,LocalManagedDns "); } return jvmArguments; } /** * Rebase Host String.. add the missing ip addresses if only host is * provided.. * * @param hostString host string * @return completed host string. */ public String rebaseHostString(String hostString) { String[] split = StringUtils.split(hostString, ","); StringBuilder newHostString = new StringBuilder(); boolean first = true; for (String pair : split) { if (!first) { newHostString.append(","); } first = false; if (pair.startsWith(":")) { newHostString.append(pair); } else if (pair.contains(":")) { newHostString.append(pair); } else if (securityEnabled) { // When the security mode is enabled, we should provide all IPs boolean eachFirst = true; for (InetAddress each : NetworkUtils.getIpsFromHost(pair)) { if (!eachFirst) { newHostString.append(","); } newHostString.append(pair).append(":").append(each.getHostAddress()); eachFirst = false; } } } return newHostString.toString(); } void addProperties(String key, String value) { this.properties.put(key, value); } }