it.jnrpe.plugin.CheckProcs.java Source code

Java tutorial

Introduction

Here is the source code for it.jnrpe.plugin.CheckProcs.java

Source

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

import it.jnrpe.ICommandLine;
import it.jnrpe.ReturnValue;
import it.jnrpe.Status;
import it.jnrpe.plugin.utils.ShellUtils;
import it.jnrpe.plugins.Metric;
import it.jnrpe.plugins.MetricBuilder;
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.ThresholdUtil;
import it.jnrpe.utils.TimeUnit;

import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.regex.Pattern;

import org.apache.commons.io.IOUtils;

/**
 * Checks system processes and does check against threshold metrics.
 * 
 * @author Frederico Campos
 * 
 */
@Plugin(name = "CHECK_PROCS", description = "Checks system processes and does check against metrics. Default metrics is number of processes.")
@PluginOptions({

        @Option(shortName = "w", longName = "warning", description = "Warning value if metric is out of range", required = false, hasArgs = true, argName = "warning", optionalArgs = false, option = "warning"),
        @Option(shortName = "c", longName = "critical", description = "Critical value if metric is out of range", required = false, hasArgs = true, argName = "critical", optionalArgs = false, option = "critical"),

        @Option(shortName = "m", longName = "metric", description = "Metric type. Valid values are: PROCS - number of processes"
                + "; VSZ - virtual memory size (unix only); RSS - resident set memory size (unix only); MEM - memory usage in KB (Windows only); CPU - CPU percentage; "
                + "ELAPSED - elapsed time in seconds (unix only)", required = false, hasArgs = true, argName = "metric", optionalArgs = false, option = "metric"),

        @Option(shortName = "a", longName = "argument-array", description = "Only scan for processes with args that contain STRING. (unix only). Use instead of ereg-argument-array.", required = false, hasArgs = true, argName = "argument-array", optionalArgs = false, option = "argument-array"),
        @Option(shortName = "e", longName = "ereg-argument-array", description = "Only scan for processes with args that contain the regex. (unix only). Use instead of argument-array.", required = false, hasArgs = true, argName = "ereg-argument-array", optionalArgs = false, option = "ereg-argument-array"),

        @Option(shortName = "p", longName = "ppid", description = "Only scan for children of the parent process ID indicated (unix only).", required = false, hasArgs = true, argName = "ppid", optionalArgs = false, option = "ppid"),

        @Option(shortName = "z", longName = "vsz", description = "Only scan for processes with VSZ higher than indicated (unix only).", required = false, hasArgs = true, argName = "vsz", optionalArgs = false, option = "vsz"),
        @Option(shortName = "r", longName = "rss", description = "Only scan for processes with RSS higher than indicated (unix only).", required = false, hasArgs = true, argName = "rss", optionalArgs = false, option = "rss"),
        @Option(shortName = "M", longName = "memory", description = "Only scan for processes with memory usage higher than indicated (windows only).", required = false, hasArgs = true, argName = "memory", optionalArgs = false, option = "memory"),

        @Option(shortName = "C", longName = "command", description = "Only scan for exact matches of COMMAND (without path).", required = false, hasArgs = true, argName = "command", optionalArgs = false, option = "command"),
        @Option(shortName = "u", longName = "user", description = "Only scan for exact matches of USER", required = false, hasArgs = true, argName = "user", optionalArgs = false, option = "user") })
public class CheckProcs extends PluginBase {

    private final static int SECONDS_IN_MINUTE = 60;
    private final static int SECONDS_IN_HOUR = SECONDS_IN_MINUTE * 60;
    private final static int SECONDS_IN_DAY = SECONDS_IN_HOUR * 24;

    private final static String[] DEFAULT_WINDOWS_CMD = new String[] { "cmd", "/C", "tasklist /FO CSV /V" };

    private final static String[] DEFAULT_UNIX_CMD = new String[] { "/bin/ps", "-eo",
            "comm,pid,ppid,user,c,rss,vsz,time,args" };

    private final static String METRIC_PROCS = "PROCS";

    private final static String METRIC_RSS = "RSS";

    private final static String METRIC_VSZ = "VSZ";

    private final static String METRIC_CPU = "CPU";

    private final static String METRIC_ELAPSED = "ELAPSED";

    private final static String METRIC_MEMORY = "MEMORY";

    private final static String FILTER_COMMAND = "command";

    private final static String FILTER_PPID = "ppid";

    private final static String FILTER_VSZ = "vsz";

    private final static String FILTER_RSS = "rss";

    private final static String FILTER_USER = "user";

    private final static String FILTER_ARG_ARRAY = "argument-array";

    private final static String FILTER_EREG_ARG_ARRAY = "ereg-argument-array";

    private final static String FILTER_MEMORY = "memory";

    private final static String[] FILTERS = new String[] { FILTER_COMMAND, FILTER_PPID, FILTER_VSZ, FILTER_RSS,
            FILTER_USER, FILTER_ARG_ARRAY, FILTER_EREG_ARG_ARRAY };

    private final static String[] UNIX_ONLY = new String[] { FILTER_ARG_ARRAY, FILTER_RSS, FILTER_PPID, FILTER_VSZ,
            FILTER_EREG_ARG_ARRAY,

    };

    // private final static String UNIX_TMP_FILE = "/tmp/checkprocs.out";

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

    /**
     * Execute the plugin
     * 
     * @param cl
     * @return
     * @throws BadThresholdException
     */
    @Override
    public ReturnValue execute(final ICommandLine cl) throws BadThresholdException {
        boolean windows = ShellUtils.isWindows();
        try {
            String metric = cl.getOptionValue("metric");
            if (metric == null) {
                metric = METRIC_PROCS;
            }
            metric = metric.toUpperCase();

            validateArguments(cl, windows, metric);

            String output = exec(!windows);

            List<Map<String, String>> result = windows ? parseWindowsOutput(output) : parseUnixOutput(output);
            return analyze(result, cl, metric);
        } catch (Exception e) {
            e.printStackTrace();
            throw new BadThresholdException(e);
        }
    }

    /**
     * Checks command line arguments for operating system specific filters and
     * metrics
     * 
     * @param output
     * @param cl
     * @param metric
     * @return
     */
    private void validateArguments(ICommandLine cl, boolean windows, String metric) throws Exception {
        if (windows) {
            if (METRIC_VSZ.equals(metric) || METRIC_RSS.equals(metric) || METRIC_ELAPSED.endsWith(metric)) {
                throw new Exception("Metric " + metric + " not supported in Wndows.");
            } else {
                for (String opt : UNIX_ONLY) {
                    if (cl.getOptionValue("opt") != null) {
                        throw new Exception("Option " + opt + " is not supported in Windows.");
                    }
                }
            }
        } else {
            if (METRIC_MEMORY.equals(metric)) {
                throw new Exception("Metric " + metric + " not supported in unix.");
            }
        }
    }

    /**
     * Analyze output and gather metrics
     * 
     * @param output
     * @param cl
     * @param metric
     * @return
     */
    private ReturnValue analyze(List<Map<String, String>> output, ICommandLine cl, String metric) {
        Map<String, String> filterAndValue = getFilterAndValue(cl);
        output = applyFilters(output, filterAndValue);
        String message = getMessage(filterAndValue);
        String critical = cl.getOptionValue("critical");
        String warning = cl.getOptionValue("warning");

        ReturnValue retVal = null;
        try {
            if (METRIC_PROCS.equals(metric)) {
                retVal = analyzeProcMetrics(output, cl, critical, warning, message);
            } else {
                retVal = analyzeMetrics(output, cl, critical, warning, message, metric);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return retVal;
    }

    private ReturnValue analyzeProcMetrics(List<Map<String, String>> output, ICommandLine cl, String critical,
            String warning, String message) throws Exception {
        int size = output.size();
        Metric sizeMetric = MetricBuilder.forMetric("size").withMinValue(0).withValue(size).build();
        if (critical != null) {
            if (ThresholdUtil.isValueInRange(critical, sizeMetric)) {
                return new ReturnValue(Status.CRITICAL, "PROCS CRITICAL: " + message + " " + size + " processes.");
            }
        }
        if (warning != null) {
            if (ThresholdUtil.isValueInRange(warning, sizeMetric)) {
                return new ReturnValue(Status.WARNING, "PROCS WARNING: " + message + " " + size + " processes.");
            }
        }
        return new ReturnValue(Status.OK, "PROCS OK: " + message + " " + size + " processes.");
    }

    /**
     * Analyze process cpu thresholds
     * 
     * @param output
     * @param cl
     * @param critical
     * @param warning
     * @param message
     * @return
     * @throws Exception
     */
    private ReturnValue analyzeMetrics(List<Map<String, String>> output, ICommandLine cl, String critical,
            String warning, String message, String metric) throws Exception {

        if (critical != null) {
            int checkCritical = compareMetric(output, critical, metric.toUpperCase());
            if (checkCritical > 0) {
                return new ReturnValue(Status.CRITICAL, metric.toUpperCase() + " CRITCAL: " + message + " "
                        + (output.size() - checkCritical) + " critical out of " + output.size() + " processes.");
            }
        }
        if (warning != null) {
            int checkWarning = compareMetric(output, warning, metric);
            if (checkWarning > 0) {
                return new ReturnValue(Status.WARNING, metric.toUpperCase() + " WARNING: " + message + " "
                        + (output.size() - checkWarning) + " warning out of " + output.size() + " processes.");
            }
        }
        return new ReturnValue(Status.OK,
                metric.toUpperCase() + " OK: " + message + " " + output.size() + " processes.");
    }

    @SuppressWarnings("deprecation")
    private int compareMetric(List<Map<String, String>> output, String value, String metric)
            throws BadThresholdException {
        List<Map<String, String>> list = new ArrayList<Map<String, String>>();
        for (Map<String, String> values : output) {
            int procValue = Integer.parseInt(values.get(metric.toLowerCase()));

            Metric metricObj = MetricBuilder.forMetric(metric).withValue(procValue).build();

            if (ThresholdUtil.isValueInRange(value, metricObj)) {
                list.add(values);
            }
        }
        return list.size();
    }

    /**
     * Get parameter list in return message
     * 
     * @param filterAndValue
     * @return
     */
    private String getMessage(Map<String, String> filterAndValue) {

        String msgString = "";
        if (!filterAndValue.isEmpty()) {
            StringBuilder msg = new StringBuilder();
            msg.append("with ");

            for (Entry<String, String> entry : filterAndValue.entrySet()) {
                msg.append(entry.getKey()).append(" = ").append(entry.getValue()).append(", ");
            }

            msgString = msg.toString().trim();
            if (msgString.endsWith(", ")) {
                msgString = msgString.substring(0, msgString.length() - 2);
            }
        }
        return msgString;
    }

    /**
     * Execute a system command and return the output.
     * 
     * @param command
     * @return String
     */
    private String exec(boolean unix) throws Exception {
        String output = null;
        InputStream input = null;
        String[] command = null;
        if (unix) {
            // write output to tmp file
            command = DEFAULT_UNIX_CMD;
            Process p = Runtime.getRuntime().exec(command);
            input = p.getInputStream();

            ByteArrayOutputStream bout = new ByteArrayOutputStream();
            IOUtils.copy(input, bout);
            output = getFormattedOutput(new String(bout.toByteArray(), "UTF-8"));
            input.close();

        } else {
            command = DEFAULT_WINDOWS_CMD;
            output = ShellUtils.executeSystemCommandAndGetOutput(command, "CP437");
        }

        return output;
    }

    /**
     * Comma separate the command output
     */
    private String getFormattedOutput(String output) {
        String out = "";
        StringBuilder lines = new StringBuilder();
        String[] splittedLines = output.split("\n");
        for (int i = 0; i < splittedLines.length; i++) {
            if (i == 0) {
                continue;
            }
            String splittedLine = splittedLines[i];
            if (splittedLine.contains("<defunct>")) {
                continue;
            }

            String line = splittedLine.replaceAll("\\s+", ",");

            lines.append(line).append('\n');
        }
        return lines.toString();
    }

    /**
     * Parse command output for windows environment.
     * 
     * @param output
     * @return List<Map<String,String>>
     */

    private List<Map<String, String>> parseWindowsOutput(String output) {
        List<Map<String, String>> info = new ArrayList<Map<String, String>>();
        String[] lines = output.split("\n");

        int totalRunTime = 0;
        String cpu = METRIC_CPU.toLowerCase();
        int count = 0;
        for (String l : lines) {
            if (count == 0) {
                count++;
                continue;
            }

            Map<String, String> values = new HashMap<String, String>();
            String[] line = l.replaceAll("\"", "").split(",");
            values.put(FILTER_COMMAND, line[0]);
            values.put("pid", line[1]);
            values.put(FILTER_MEMORY, String.valueOf(convertToMemoryInt(line[4])));
            values.put(FILTER_USER, line[6]);
            int seconds = 0;
            if (!ShellUtils.isWindowsIdleProc(line[0])) {
                seconds = convertToSeconds(line[7].trim());
            }
            totalRunTime += seconds;
            values.put(cpu, Integer.toString(seconds));
            info.add(values);
        }

        for (Map<String, String> map : info) {
            int secs = Integer.parseInt(map.get(cpu));
            LOG.debug(getContext(), "secs " + secs);
            double perc = ((double) secs / (double) totalRunTime) * 100.0;
            map.put(cpu, Integer.toString((int) perc));
        }

        LOG.debug(getContext(), String.valueOf(info));
        return info;
    }

    /**
     * Parse command output for unix environment.
     * 
     * @param output
     * @return List<Map<String,String>>
     */
    private List<Map<String, String>> parseUnixOutput(String output) {
        List<Map<String, String>> info = new ArrayList<Map<String, String>>();
        output = output.replaceAll("\"", "");
        String[] lines = output.split("\n");
        for (String l : lines) {
            if (l.startsWith("PID")) {
                continue;
            }

            String[] line = l.split(",", 9);

            // FIXME : the ps command itself should be skipped
            // if (line[8].contains(DEFAULT_UNIX_CMD[2])) {
            // // continue;
            // }

            Map<String, String> values = new HashMap<String, String>();
            values.put(FILTER_COMMAND, line[0].trim());
            values.put("pid", line[1].trim());
            values.put("ppid", line[2].trim());
            values.put(FILTER_USER, line[3].trim());
            values.put(METRIC_CPU.toLowerCase(), line[4].trim());
            values.put(METRIC_RSS.toLowerCase(), line[5].trim());
            values.put(METRIC_VSZ.toLowerCase(), line[6].trim());
            values.put(METRIC_ELAPSED.toLowerCase(), String.valueOf(convertToSeconds(line[7].trim())));
            values.put(FILTER_ARG_ARRAY, line[8]);

            info.add(values);
        }
        return info;
    }

    private int convertToMemoryInt(String mem) {
        mem = mem.replaceAll("[^0-9]", "");

        return Integer.parseInt(mem);
    }

    /**
     * Apply filters to processes output
     * 
     * @param values
     * @param filterAndValue
     * @return
     */
    private List<Map<String, String>> applyFilters(List<Map<String, String>> values,
            Map<String, String> filterAndValue) {
        if (filterAndValue == null || filterAndValue.isEmpty()) {
            return values;
        }
        List<Map<String, String>> filtered = new ArrayList<Map<String, String>>();
        for (Map<String, String> map : values) {
            boolean matchesAll = true;
            for (Entry<String, String> entry : filterAndValue.entrySet()) {
                String filter = entry.getKey();
                String filterValue = entry.getValue();
                if (filter.contains(FILTER_COMMAND) || filter.contains(FILTER_USER)
                        || FILTER_ARG_ARRAY.equals(filter) || filter.contains(FILTER_PPID)) {
                    if (!map.get(filter).contains(filterValue)) {
                        matchesAll = false;
                        break;
                    }
                } else if (filter.contains(FILTER_EREG_ARG_ARRAY)
                        && !Pattern.matches(filterValue, map.get(FILTER_ARG_ARRAY))) {
                    matchesAll = false;
                    break;
                } else if (filter.contains(FILTER_VSZ) || filter.contains(FILTER_RSS)
                        || filter.contains(FILTER_MEMORY)) {
                    int filterval = Integer.parseInt(filterValue);
                    int value = Integer.parseInt(map.get(filter));
                    if (value < filterval) {
                        matchesAll = false;
                        break;
                    }
                }
            }
            if (matchesAll) {
                filtered.add(map);
            }
        }
        return filtered;
    }

    private Map<String, String> getFilterAndValue(ICommandLine cl) {
        Map<String, String> map = new HashMap<String, String>();
        for (String filter : FILTERS) {
            if (cl.getOptionValue(filter) != null) {
                map.put(filter, cl.getOptionValue(filter));
            }
        }
        return map;
    }

    /**
     * Convert date in format DD-HH:MM:SS to seconds.
     * 
     * @param input
     * @return
     */
    private int convertToSeconds(final String input) {
        int days = 0;
        int hours = 0;
        int minutes = 0;
        int seconds = 0;

        String remainingTokens = input;

        if (input.indexOf('-') != -1) {
            String[] parts = remainingTokens.split("-");
            days = Integer.parseInt(parts[0]);
            remainingTokens = parts[1];
        }

        String[] timeParts = remainingTokens.split(":");

        for (int i = timeParts.length - 1, partType = 0; i >= 0; i--, partType++) {
            switch (partType) {
            case 0: // Seconds
                seconds = Integer.parseInt(timeParts[i]);
                break;
            case 1: // Minutes
                minutes = Integer.parseInt(timeParts[i]);
                break;
            case 2: // Hours
                hours = Integer.parseInt(timeParts[i]);
                break;
            default: // bad input
                break;
            }
        }

        return (int) (TimeUnit.DAY.convert(days, TimeUnit.SECOND) + TimeUnit.HOUR.convert(hours, TimeUnit.SECOND)
                + TimeUnit.MINUTE.convert(minutes, TimeUnit.SECOND) + seconds);
    }
}