All Downloads are FREE. Search and download functionalities are using the official Maven repository.

it.jnrpe.plugin.tomcat.CheckTomcat Maven / Gradle / Ivy

The newest version!
/*******************************************************************************
 * Copyright (c) 2007, 2014 Massimiliano Ziccardi
 *
 * 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 it.jnrpe.plugin.tomcat;

import it.jnrpe.ICommandLine;
import it.jnrpe.JNRPELogger;
import it.jnrpe.ReturnValue;
import it.jnrpe.Status;
import it.jnrpe.plugins.Metric;
import it.jnrpe.plugins.MetricBuilder;
import it.jnrpe.plugins.MetricGatheringException;
import it.jnrpe.plugins.PluginBase;
import it.jnrpe.plugins.annotations.Option;
import it.jnrpe.plugins.annotations.Plugin;
import it.jnrpe.plugins.annotations.PluginOptions;
import it.jnrpe.utils.BadThresholdException;
import it.jnrpe.utils.thresholds.Prefixes;
import it.jnrpe.utils.thresholds.ReturnValueBuilder;
import it.jnrpe.utils.thresholds.ThresholdsEvaluatorBuilder;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Plugin(name = "CHECK_TOMCAT", description = "Checks the tomcat /manager/status page for excessive memory usage or an excessive number of threads in use.\n"
        + "This plugin does a HTTP GET of the tomcat status page:\n"
        + "\n"
        + "  /manager/status?XML=true\n"
        + "\n"
        + "  It checks the resulting XML for:\n"
        + "\n"
        + "  * Low free memory\n"
        + "  * Excessive thread usage\n"
        + "\n"
        + "    In order to use this, you must provide a username and password that has a 'manager-gui' role in the\n"
        + "    CATALINA_HOME/conf/tomcat-users.xml file.\n")
@PluginOptions({
        @Option(shortName = "h", longName = "hostname", description = "Hostname or IP address", required = true, hasArgs = true, argName = "hostname", optionalArgs = false, option = "hostname"),
        @Option(shortName = "p", longName = "port", description = "Port number; default is 8080", required = false, hasArgs = true, argName = "port", optionalArgs = false, option = "port"),
        @Option(shortName = "l", longName = "username", description = "Username for authentication", required = true, hasArgs = true, argName = "username", optionalArgs = false, option = "username"),
        @Option(shortName = "a", longName = "password", description = "Password for authentication", required = true, hasArgs = true, argName = "password", optionalArgs = false, option = "password"),
        @Option(shortName = "m", longName = "memory", description = "Check memory", required = false, hasArgs = false, argName = "memory", optionalArgs = false, option = "memory"),
        @Option(shortName = "t", longName = "threads", description = "Check threads", required = false, hasArgs = false, argName = "threads", optionalArgs = false, option = "threads"),
        @Option(shortName = "P", longName = "percent", description = "Check the usage as percen", required = false, hasArgs = false, argName = "percent", optionalArgs = false, option = "percent"),
        
        @Option(shortName = "w", longName = "warning", description = "Warning threshold value for threads or memory (in MB). Must be used with either the 'memory' or 'threads' option.", required = false, hasArgs = true, argName = "warning", optionalArgs = false, option = "warning"),
        @Option(shortName = "c", longName = "critical", description = "Critical threshold value for threads or memory (in MB). Must be used with either the 'memory' or 'threads' option.", required = false, hasArgs = true, argName = "critical", optionalArgs = false, option = "critical"),

        @Option(shortName = "r", longName = "th", description = "Configure a threshold. Format : metric={metric},ok={range},warn={range},crit={range},unit={unit},prefix={SI prefix}", required = false, hasArgs = true, argName = "thresholds", optionalArgs = false, option = "thresholds"),
        @Option(shortName = "S", longName = "ssl", description = "Use ssl", required = false, hasArgs = false, argName = "ssl", optionalArgs = false, option = "ssl"),
        @Option(shortName = "T", longName = "timeout", description = "Connection timeout in seconds. Default is 10.", required = false, hasArgs = true, argName = "timeout", optionalArgs = false, option = "timeout"), 
})
public class CheckTomcat extends PluginBase {

    /**
     * The dataprovider object. Used to retrieve the information from the 
     * Tomcat application server.
     */
    private IAppServerDataProvider dataProvider = new TomcatDataProvider();

    /**
     * The logger object.
     */
    protected final JNRPELogger LOG = new JNRPELogger(this);

    /**
     * Default Tomcat http port.
     */
    private static final String DEFAULT_PORT = "8080";

    /**
     * Default Tomcat manager URL.
     */
    private static final String DEFAULT_URI = "/manager/status?XML=true";

    /**
     * Default timeout.
     */
    private static final String DEFAULT_TIMEOUT = "10";

    @Override
    protected String getPluginName() {
        return "CHECK_TOMCAT";
    }

    /**
     * Computes the percent value of the passed in value.
     * 
     * @param value the value to be converted to a percent value.
     * @param maxValue the maximum value that value can
     * assume
     * 
     * @return the value as percent
     */
    private BigDecimal toPercent(BigDecimal value, BigDecimal maxValue) {
        
        return value.divide(maxValue, 4, RoundingMode.HALF_UP).multiply(new BigDecimal(100));
    }
    
    /**
     * Computes the percent value of the passed in value.
     * 
     * @param value the value to be converted to a percent value.
     * @param maxValue the maximum value that value can
     * assume
     * 
     * @return the value as percent
     */
    private BigDecimal toPercent(long value, long maxValue) {
        return toPercent(new BigDecimal(value), new BigDecimal(maxValue));
    }
    
    /**
     * Gathers the metrics about the Tomcat JVM memory usage
     * 
     * @return the gathered metrics
     * @throws MetricGatheringException
     */
    private Collection gatherMemoryMetric() throws MetricGatheringException {

        List res = new ArrayList();

        MemoryData jvmMemory = dataProvider.getJVMMemoryUsage();
        
        final BigDecimal freeMemory = Prefixes.RAW.convert(jvmMemory.getFreeMemory(), Prefixes.mega);
        
        BigDecimal percent = toPercent(jvmMemory.getFreeMemory(), jvmMemory.getTotalMemory());
        
        res.add(MetricBuilder.forMetric("memory")
                .withMessage("Memory : {0,number,#.##}mb", freeMemory.doubleValue())
                .withValue(jvmMemory.getFreeMemory(), "#.##")
                .withMinValue(0, "#.##")
                .withMaxValue(jvmMemory.getTotalMemory(), "#.##").build());
        
        LOG.debug(getContext(), "Created metric : memory");
        
        res.add(MetricBuilder.forMetric("memory%")
                .withMessage("Used Memory : {0,number,#.##}%", percent.doubleValue())
                .withValue(percent, "#.##")
                .withMinValue(0, "#")
                .withMaxValue(100, "#").build());

        LOG.debug(getContext(), "Created metric : memory%");
        
        for (MemoryPoolData mpd : dataProvider.getMemoryPoolData()) {
            res.add(MetricBuilder
                    .forMetric(mpd.getPoolName() + "-memoryPool")
                    .withMessage("Memory Pool - usage init: {0}, usage committed: {1}, usage max: {2}, usage used: {3}", 
                            mpd.getUsageInit(),
                            mpd.getUsageCommitted(), 
                            mpd.getUsageMax(), 
                            mpd.getUsageUsed())
                     .withValue(mpd.getUsageUsed(), "#.##")
                     .withMinValue(0, "#.##")
                    .withMaxValue(mpd.getUsageMax(), "#.##").build());
            
            LOG.debug(getContext(), "Created metric : " + mpd.getPoolName() + "-memoryPool");
            
            BigDecimal memoryPoolPercent = toPercent(mpd.getUsageUsed(), mpd.getUsageMax());
            
            res.add(MetricBuilder
                    .forMetric(mpd.getPoolName() + "-memoryPool%")
                    .withMessage("Memory Pool - usage {0,number,#.##}%", memoryPoolPercent)
                    .withValue(memoryPoolPercent, "#.##")
                    .withMinValue(0, "#")
                    .withMaxValue(mpd.getUsageMax(), "#").build());
            LOG.debug(getContext(), "Created metric : " + mpd.getPoolName() + "-memoryPool%");;
        }

        return res;
    }

    /**
     * Gathers the metrics about the thread usage of each connector.
     * The metric name will have the following format: [connector-name]-threadInfo.
     * This way user will be able to configure checks against a single connector.
     * 
     * @return the metrics
     * @throws MetricGatheringException
     */
    private Collection gatherConnectorMetrics() throws MetricGatheringException {

        List res = new ArrayList();

        for (ThreadData td : dataProvider.getThreadData()) {

            final String metricName = td.getConnectorName().replace("\"", "") + "-threadInfo";
            final String percentMetricName = td.getConnectorName().replace("\"", "") + "-threadInfo%";

            res.add(MetricBuilder
                    .forMetric(metricName)
                    .withMessage("{0} - thread count: {1}, current thread busy: {2}, max thread: {3}", metricName, td.getCurrentThreadCount(),
                            td.getCurrentThreadBusy(), td.getMaxThreadCount())
                    .withValue(td.getCurrentThreadBusy()).withMinValue(0)
                    .withMaxValue(td.getMaxThreadCount()).build());
            
            final BigDecimal threadUsagePercent = toPercent(td.getCurrentThreadBusy(), td.getMaxThreadCount());
            
            res.add(MetricBuilder
                    .forMetric(percentMetricName)
                    .withMessage("{0} - thread usage: {1,number,#.##}%", metricName, threadUsagePercent)
                    .withValue(threadUsagePercent, "#.##")
                    .withMinValue(0, "#")
                    .withMaxValue(td.getMaxThreadCount(), "#")
                    .build());
            
            LOG.debug(getContext(), "Created metric : " + metricName);
        }

        return res;
    }

    @Override
    protected Collection gatherMetrics(ICommandLine cl) throws MetricGatheringException {

        Collection metrics = new ArrayList();
        metrics.addAll(gatherConnectorMetrics());
        metrics.addAll(gatherMemoryMetric());

        return metrics;
    }

    /**
     * Internally used to initialize the Tomcat connector.
     * 
     * @param cl the received command line
     * @throws MetricGatheringException
     */
    private void init(ICommandLine cl) throws MetricGatheringException {
        String username = cl.getOptionValue("username");
        String password = cl.getOptionValue("password");
        int timeout = Integer.parseInt(cl.getOptionValue("timeout", DEFAULT_TIMEOUT));
        String hostname = cl.getOptionValue("hostname");

        String port = cl.getOptionValue("port", DEFAULT_PORT);
        String uri = cl.getOptionValue("uri", DEFAULT_URI);
        try {
            this.dataProvider.init(hostname, uri, Integer.parseInt(port), username, password, cl.hasOption("ssl"), timeout);
        } catch (Exception e) {
            throw new MetricGatheringException(e.getMessage(), Status.UNKNOWN, e);
        }
    }
    
    /**
     * Return true if the received metricName is about a memory 
     * metric.
     * 
     * @param metricName the metric to be checked
     * 
     * @return true if the received metricName is about a memory 
     * metric
     */
    private boolean isMemory(String metricName) {
        String tmp = metricName.toLowerCase();
        return tmp.equals("memory") || tmp.equals("memory%");
    }
    
    /**
     * Return true if the received metricName is about a threads 
     * metric.
     * 
     * @param metricName the metric to be checked
     * 
     * @return true if the received metricName is about a threads 
     * metric
     */
    private boolean isThread(String metricName) {
        String tmp = metricName.toLowerCase();
        return tmp.endsWith("-threadInfo") || tmp.endsWith("-threadInfo%");
    }
    
    /**
     * Return true if the received metricName is about a percent 
     * metric.
     * 
     * @param metricName the metric to be checked
     * 
     * @return true if the received metricName is about a percent 
     * metric
     */
    private boolean isPercent(String metricName) {
        return metricName.endsWith("%");
    }
    
    /**
     * Returns the prefix to be used to create the metrics.
     * 
     * @param cl the received command line
     * 
     * @return the prefix to be used. Memory metrics are produced as 
     * {@link Prefixes#mega}, while percent metrics are {@link Prefixes#RAW}.
     */
    private Prefixes getPrefix(ICommandLine cl) {
        
        if (cl.hasOption("memory") && !cl.hasOption("percent")) {
            return Prefixes.mega;
        }
        
        return Prefixes.RAW;
    }
    
    /**
     * Update the return value of this plugin.
     * 
     * @param oldRet
     * @param ret
     * @return
     */
    private ReturnValue updateRet(ReturnValue oldRet, ReturnValue ret) {
        if (oldRet == null) {
            return ret;
        }
        
        if (ret.getStatus().getSeverity() > oldRet.getStatus().getSeverity()) {
            return ret;
        }
        
        return oldRet;
    }
    
    @Override
    public ReturnValue execute(ICommandLine cl) throws BadThresholdException {
        try{
            init(cl);
            
            // If we are not using the new threshold format, we have to manually handle the 
            // range checks.
            if (!cl.hasOption("th")) {
                String criticalThreshold = cl.getOptionValue("critical");
                String warningThreshold = cl.getOptionValue("warning");
                
                Collection metrics;
                
                if (cl.hasOption("memory")) {
                    metrics = gatherMemoryMetric();
                } else if (cl.hasOption("threads")) {
                    metrics = gatherConnectorMetrics();
                } else {
                    return ReturnValueBuilder
                            .forPlugin(getPluginName())
                            .withStatus(Status.OK)
                            .create();
                }
                
                ReturnValue ret = null;

                for (Metric metric : metrics) {
                    
                    if (cl.hasOption("memory") && !isMemory(metric.getMetricName())) {
                        continue;
                    }
                    
                    if (cl.hasOption("thread") && !isThread(metric.getMetricName())) {
                        continue;
                    }
                    
                    if (cl.hasOption("percent") && !isPercent(metric.getMetricName())) {
                        continue;
                    }
                    
                    ret = updateRet(ret, ReturnValueBuilder.forPlugin(
                            getPluginName(),
                            new ThresholdsEvaluatorBuilder()
                                .withLegacyThreshold(metric.getMetricName(), null, warningThreshold, criticalThreshold, getPrefix(cl))
                                .create())
                            .withValue(metric)
                            .create());
                }
                
                if (ret != null) {
                    return ret;
                }
                
                return ReturnValueBuilder.forPlugin(this.getPluginName())
                        .withForcedMessage("No metrics gathered")
                        .withStatus(Status.UNKNOWN).create();
                
            } else {
                // For the new threshold format, we don't have anything to do
                return super.execute(cl);
            }
        } catch (MetricGatheringException mge) {
            LOG.info(getContext(), "Plugin execution failed : " + mge.getMessage(), mge);
            return ReturnValueBuilder.forPlugin(getPluginName()).withForcedMessage(mge.getMessage()).withStatus(mge.getStatus()).create();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy