net.grinder.engine.agent.PropertyBuilder.java Source code

Java tutorial

Introduction

Here is the source code for net.grinder.engine.agent.PropertyBuilder.java

Source

/* 
 * 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);
    }
}