edu.wisc.jmeter.MonitorListener.java Source code

Java tutorial

Introduction

Here is the source code for edu.wisc.jmeter.MonitorListener.java

Source

/**
 * Copyright (c) 2000-2009, Jasig, Inc.
 * See license distributed with this file and available online at
 * https://www.ja-sig.org/svn/jasig-parent/tags/rel-10/license-header.txt
 */

package edu.wisc.jmeter;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.regex.Pattern;

import org.apache.commons.io.IOUtils;
import org.apache.jmeter.assertions.AssertionResult;
import org.apache.jmeter.engine.event.LoopIterationEvent;
import org.apache.jmeter.gui.UnsharedComponent;
import org.apache.jmeter.samplers.SampleEvent;
import org.apache.jmeter.samplers.SampleListener;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.testbeans.TestBean;
import org.apache.jmeter.testelement.AbstractTestElement;
import org.apache.jmeter.testelement.TestListener;
import org.apache.jmeter.threads.JMeterContext;
import org.apache.jmeter.threads.JMeterContextService;
import org.apache.jmeter.threads.JMeterVariables;
import org.apache.jmeter.visualizers.Visualizer;
import org.apache.jorphan.logging.LoggingManager;
import org.apache.log.Logger;
import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.mail.MailException;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.JavaMailSenderImpl;

import edu.wisc.jmeter.dao.ErrorHandlingMonitorDao;
import edu.wisc.jmeter.dao.JdbcMonitorDao;
import edu.wisc.jmeter.dao.MonitorDao;

/**
 * @author Eric Dalquist
 * @version $Revision: 1.11 $
 */
public class MonitorListener extends AbstractTestElement
        implements SampleListener, TestListener, TestBean, Visualizer, UnsharedComponent {
    private static final long serialVersionUID = 1L;
    private static final Logger log = LoggingManager.getLoggerForClass();

    public static final int DEFAULT_PURGE_OLD_FAILURE = 60 * 24 * 7; //default to 1 week
    public static final int DEFAULT_PURGE_OLD_STATUS = 60 * 24; //default to 1 day

    private final SimpleDateFormat RESPONSE_FILE_DATE_FORMAT = new SimpleDateFormat("yyyyMMdd.HHmmss.SSS");

    private String statusVar; //Name of the variable used to communicate server status
    private Pattern statusSamplePattern; //Regex pattern used to identifiy samples of server status flags
    private Pattern monitoredSamplePattern; //Regex pattern used to identifiy samples to be monitored

    private String logLocation; //Failed responses are saved here

    //Email Notification Settings
    private String notificationVar; //Name of the variable used to communicate if notification should be performed
    private int failureThreshold; //Number of failures needed to mark a machine as down
    private int backoffDuration; //Minutes for spacing between notifications, exponential backoff is used
    private String smtpHost;
    private String emailTo;
    private String emailFrom;

    // Database logging settings
    private String jdbcDriver;
    private String jdbcUrl;
    private String jdbcUser;
    private String jdbcPass;

    // Purging times in minutes
    private int purgeOldFailure = DEFAULT_PURGE_OLD_FAILURE;
    private int purgeOldStatus = DEFAULT_PURGE_OLD_STATUS;

    private DataSource connectionPool;
    private JdbcMonitorDao jdbcMonitorDao;
    private MonitorDao monitorDao;
    private JavaMailSender javaMailSender;

    public MonitorListener() {
        log.info("Created MonitorListener");
    }

    @Override
    public Object clone() {
        log.info("Cloned MonitorListener");
        final MonitorListener clone = (MonitorListener) super.clone();

        clone.connectionPool = connectionPool;
        clone.monitorDao = monitorDao;
        clone.javaMailSender = javaMailSender;

        return clone;
    }

    public String getStatusSamplePattern() {
        if (statusSamplePattern == null) {
            return null;
        }
        return statusSamplePattern.pattern();
    }

    public void setStatusSamplePattern(String statusSamplePattern) {
        if (statusSamplePattern == null) {
            this.statusSamplePattern = null;
        }
        this.statusSamplePattern = Pattern.compile(statusSamplePattern);
    }

    public String getMonitoredSamplePattern() {
        if (monitoredSamplePattern == null) {
            return null;
        }
        return monitoredSamplePattern.pattern();
    }

    public void setMonitoredSamplePattern(String monitoredSamplePattern) {
        if (monitoredSamplePattern == null) {
            this.monitoredSamplePattern = null;
        }
        this.monitoredSamplePattern = Pattern.compile(monitoredSamplePattern);
    }

    public String getStatusVar() {
        return statusVar;
    }

    public void setStatusVar(String statusVar) {
        this.statusVar = statusVar;
    }

    public String getLogLocation() {
        return logLocation;
    }

    public void setLogLocation(String logLocation) {
        this.logLocation = logLocation;
    }

    public String getNotificationVar() {
        return notificationVar;
    }

    public void setNotificationVar(String notificationVar) {
        this.notificationVar = notificationVar;
    }

    public int getFailureThreshold() {
        return failureThreshold;
    }

    public void setFailureThreshold(int failureThreshold) {
        this.failureThreshold = failureThreshold;
    }

    public int getBackoffDuration() {
        return backoffDuration;
    }

    public void setBackoffDuration(int backoffDuration) {
        this.backoffDuration = backoffDuration;
    }

    public String getSmtpHost() {
        return smtpHost;
    }

    public void setSmtpHost(String smtpHost) {
        this.smtpHost = smtpHost;
    }

    public String getEmailTo() {
        return emailTo;
    }

    public void setEmailTo(String emailTo) {
        this.emailTo = emailTo;
    }

    public String getEmailFrom() {
        return emailFrom;
    }

    public void setEmailFrom(String emailFrom) {
        this.emailFrom = emailFrom;
    }

    public String getJdbcDriver() {
        return jdbcDriver;
    }

    public void setJdbcDriver(String jdbcDriver) {
        this.jdbcDriver = jdbcDriver;
    }

    public String getJdbcUrl() {
        return jdbcUrl;
    }

    public void setJdbcUrl(String jdbcUrl) {
        this.jdbcUrl = jdbcUrl;
    }

    public String getJdbcUser() {
        return jdbcUser;
    }

    public void setJdbcUser(String jdbcUser) {
        this.jdbcUser = jdbcUser;
    }

    public String getJdbcPass() {
        return jdbcPass;
    }

    public void setJdbcPass(String jdbcPass) {
        this.jdbcPass = jdbcPass;
    }

    public int getPurgeOldFailure() {
        return purgeOldFailure;
    }

    public void setPurgeOldFailure(int purgeOldFailures) {
        this.purgeOldFailure = purgeOldFailures;
    }

    public int getPurgeOldStatus() {
        return purgeOldStatus;
    }

    public void setPurgeOldStatus(int purgeOldStatus) {
        this.purgeOldStatus = purgeOldStatus;
    }

    @Override
    public void testStarted() {
        this.connectionPool = new DataSource();
        this.connectionPool.setDriverClassName(this.jdbcDriver);
        this.connectionPool.setUrl(this.jdbcUrl);
        this.connectionPool.setUsername(this.jdbcUser);
        this.connectionPool.setPassword(this.jdbcPass);
        this.connectionPool.setValidationQuery("SELECT 1 FROM DUAL");
        this.connectionPool.setTestOnBorrow(true);
        this.connectionPool.setTestWhileIdle(true);
        this.connectionPool.setJdbcInterceptors("ConnectionState(useEquals=true);ResetAbandonedTimer");

        log.info("Created DB pool for: {" + this.jdbcDriver + ", " + this.jdbcUrl + ", " + this.jdbcUser + "}");

        this.jdbcMonitorDao = new JdbcMonitorDao(this.connectionPool, this.purgeOldFailure, this.purgeOldStatus);
        try {
            this.jdbcMonitorDao.afterPropertiesSet();
            this.monitorDao = new ErrorHandlingMonitorDao(this.jdbcMonitorDao);
        } catch (Exception e) {
            throw new RuntimeException("Failed to initialize JdbcMonitorDao", e);
        }
        log.info("Created JdbcMonitorDao");

        final JavaMailSenderImpl mailSender = new JavaMailSenderImpl();
        mailSender.setHost(this.smtpHost);
        this.javaMailSender = mailSender;
        log.info("Created JavaMailSender for: {" + this.smtpHost + "}");
    }

    @Override
    public void testIterationStart(LoopIterationEvent event) {
        final JMeterContext jmctx = JMeterContextService.getContext();
        final JMeterVariables vars = jmctx.getVariables();

        IterationInfo iterationInfo = (IterationInfo) vars.getObject(IterationInfo.class.getName());
        if (iterationInfo == null) {
            iterationInfo = new IterationInfo();
            vars.putObject(IterationInfo.class.getName(), iterationInfo);
        }

        final Date previousStart = iterationInfo.getPreviousStart();
        if (previousStart != null) {
            for (final String host : iterationInfo.getPreviousVisitedHosts()) {
                this.monitorDao.purgeRequestLog(host, previousStart);
            }
        }

        iterationInfo.iterate();
    }

    @Override
    public void sampleOccurred(SampleEvent e) {
        final JMeterContext jmctx = JMeterContextService.getContext();
        final JMeterVariables vars = jmctx.getVariables();

        final SampleResult result = e.getResult();

        final String statusStr = vars.get(this.statusVar);
        final Status checkStatus;
        if (statusStr != null) {
            checkStatus = Status.valueOf(statusStr);
        } else {
            checkStatus = Status.UNKOWN;
        }

        switch (checkStatus) {
        case UP: {
            this.checkLastSample(result, vars, checkStatus);
        }
            break;
        case OUT_UP: {
            this.checkLastSample(result, vars, checkStatus);
        }
            break;
        case OUT_DOWN: {
            final String sampleLabel = result.getSampleLabel();
            if (this.statusSamplePattern != null && !this.statusSamplePattern.matcher(sampleLabel).matches()) {
                //Request is not status sample, ignore it
                return;
            }

            //Out of cluster and tomcat down, set the status if not already set
            final String hostName = this.getSampleTargetHost(result);
            final HostStatus hostStatus = this.monitorDao.getHostStatus(hostName);
            if (Status.OUT_DOWN != hostStatus.getStatus()) {
                hostStatus.setStatus(Status.OUT_DOWN);
                this.monitorDao.storeHostStatus(hostStatus);
            }
        }
            break;
        default: {
            this.checkLastSample(result, vars, checkStatus);
        }
        }
    }

    @Override
    public void sampleStarted(SampleEvent e) {
    }

    @Override
    public void sampleStopped(SampleEvent e) {
    }

    @Override
    public void add(SampleResult sample) {
    }

    @Override
    public boolean isStats() {
        return false;
    }

    @Override
    public void testEnded() {
        final DataSource pool = this.connectionPool;
        this.connectionPool = null;
        if (pool != null) {
            pool.close();
            log.info("Closed data pool");
        }

        try {
            this.jdbcMonitorDao.destroy();
        } catch (Exception e) {
            log.info("Failed to close monitor dao", e);
        }
        this.monitorDao = null;

        this.javaMailSender = null;
    }

    @Override
    public void testStarted(String host) {
        this.testStarted();
    }

    @Override
    public void testEnded(String host) {
        this.testEnded();
    }

    private void checkLastSample(SampleResult result, JMeterVariables vars, Status checkStatus) {
        final String sampleLabel = result.getSampleLabel();
        if (this.monitoredSamplePattern != null && !this.monitoredSamplePattern.matcher(sampleLabel).matches()) {
            //Request is not monitored, ignore it
            return;
        }

        final Date sampleEndTime = new Date(result.getEndTime());
        final boolean lastSampleOk = result.isSuccessful();

        final String hostName = this.getSampleTargetHost(result);
        this.trackHost(vars, hostName);
        final HostStatus hostStatus = this.monitorDao.getHostStatus(hostName);

        int messageCount = hostStatus.getMessageCount();
        if (lastSampleOk) {
            if (hostStatus.getFailureCount() > 0) {
                final String messageSubject = buildMessageSubject(hostName, Status.UP, 0, 0);
                final String messageBody = buildMessageBody(sampleEndTime, hostName, sampleLabel, Status.UP, 0, 0,
                        null);
                Notification sentEmail = Notification.FALSE;

                //Only send up message if down message has been sent
                if (messageCount > 0) {
                    hostStatus.setLastMessageSent(new Date());
                    if (notifyForHost(vars)) {
                        sendEmail(sampleEndTime, messageSubject, messageBody, hostName, Status.UP);
                        sentEmail = Notification.TRUE;
                    } else {
                        sentEmail = Notification.DISABLED;
                    }
                }

                hostStatus.setFailureCount(0);
                hostStatus.setMessageCount(0);
                hostStatus.setStatus(checkStatus.isOut() ? Status.OUT_UP : Status.UP);

                //Log the clearing of the failure to the DB
                this.monitorDao.logFailureAndStatus(hostStatus, sampleLabel, sampleEndTime, hostStatus.getStatus(),
                        messageSubject, messageBody, sentEmail);
            } else if ((checkStatus.isOut() && hostStatus.getStatus() != Status.OUT_UP)
                    || (!checkStatus.isOut() && hostStatus.getStatus() != Status.UP)) {

                final Status oldStatus = hostStatus.getStatus();

                //Update the HostStatus with the correct status
                hostStatus.setStatus(checkStatus.isOut() ? Status.OUT_UP : Status.UP);
                this.monitorDao.storeHostStatus(hostStatus);

                log.info("Switching HostStatus.status from " + oldStatus + " to " + hostStatus.getStatus() + " for "
                        + sampleLabel);
            }
        } else {
            //Failed request, increment the counter
            final int failureCount = hostStatus.incrementFailureCount();

            //Handle the server being checked while still coming up
            hostStatus.setStatus(checkStatus.isOut() ? Status.OUT_DOWN : Status.DOWN);

            final String errorMessages = getErrorMessages(result);

            //Setup default messages
            String messageSubject = buildMessageSubject(hostName, hostStatus.getStatus(), messageCount,
                    failureCount);
            String messageBody = buildMessageBody(sampleEndTime, hostName, sampleLabel, hostStatus.getStatus(),
                    messageCount, failureCount, errorMessages);
            Notification sentEmail = Notification.FALSE;

            //Don't email when server is out
            //Ignore single failure counts to avoid making noise about transient failures
            if (!checkStatus.isOut() && failureCount >= failureThreshold) {
                //If not the first message use an exponential roll off send messages after a certain ammount of time
                Date refDate = sampleEndTime;
                if (messageCount > 0) {
                    int minutesToMessage = (int) Math.pow(2, Math.max(messageCount - 1, 0)) * backoffDuration;
                    Calendar refCal = Calendar.getInstance();
                    refCal.setTime(hostStatus.getLastMessageSent());
                    refCal.add(Calendar.MINUTE, minutesToMessage);
                    refDate = refCal.getTime();
                }

                //If a message hasn't been sent yet or if enough time has passed since the last failure message
                if (messageCount == 0 || sampleEndTime.after(refDate)) {
                    //Count last-sent and count even if message sending is disabled to keep both paths of behavior very similar
                    hostStatus.setLastMessageSent(new Date());
                    messageCount = hostStatus.incrementMessageCount();

                    if (notifyForHost(vars)) {
                        //Update messages with new messageCount
                        messageSubject = buildMessageSubject(hostName, hostStatus.getStatus(), messageCount,
                                failureCount);
                        messageBody = buildMessageBody(sampleEndTime, hostName, sampleLabel, hostStatus.getStatus(),
                                messageCount, failureCount, errorMessages);

                        sendEmail(sampleEndTime, messageSubject, messageBody, hostName, hostStatus.getStatus());
                        sentEmail = Notification.TRUE;
                    } else {
                        sentEmail = Notification.DISABLED;
                    }
                }
            }

            //Log the failure to the DB
            this.monitorDao.logFailureAndStatus(hostStatus, sampleLabel, sampleEndTime, hostStatus.getStatus(),
                    messageSubject, messageBody, sentEmail);

            //Save the data for every failure, post processing so we get updated counts
            final String userId = vars.get("userId");
            saveResponseToFile(result, sampleEndTime, userId, hostName, errorMessages, failureCount, messageCount);
        }

        this.monitorDao.logRequestAndStatus(hostStatus, sampleLabel, sampleEndTime, result.getTime(), lastSampleOk);
    }

    /**
     * Generate the email subject string
     */
    private String buildMessageSubject(String hostName, Status status, int messageCount, int failureCount) {
        final StringBuilder subject = new StringBuilder("myUwMonitor: ");

        subject.append(hostName).append(" ").append(status);

        if (Status.DOWN.equals(status)) {
            subject.append(" (fc=").append(failureCount).append(", mc=").append(messageCount).append(")");
        }

        return subject.toString();
    }

    /**
     * Generate the email body string
     */
    private String buildMessageBody(Date sampleEndTime, String hostName, String label, Status status,
            int messageCount, int failureCount, String errorMessages) {
        final StringBuilder body = new StringBuilder();
        body.append(sampleEndTime).append(": myUwMonitor: ").append(hostName).append(" (").append(label)
                .append(" )");

        if (status.isUp()) {
            body.append(" ").append(status);
        } else {
            body.append(" (failureCount=").append(failureCount).append(", messageCount=").append(messageCount)
                    .append(")\n").append(errorMessages);
        }

        return body.toString();
    }

    /**
     * Executes a shell script to send an email.
     */
    private void sendEmail(Date now, String subject, String body, String host, Status status) {
        log("Sending email (" + status + "): " + subject + " - " + body);

        final SimpleMailMessage message = new SimpleMailMessage();
        message.setTo(emailTo);
        message.setFrom(emailFrom);
        message.setSubject(subject);
        message.setText(body);

        try {
            this.javaMailSender.send(message);
        } catch (MailException me) {
            log("Failed to send email", me);
        }
    }

    /**
     * Builds a String of messages from the errors that occured on the request.
     */
    private String getErrorMessages(SampleResult sampleResult) {
        final StringBuilder failureMessage = new StringBuilder();

        if (!sampleResult.getResponseCode().equals("200")) {
            failureMessage.append("Response code was '");
            failureMessage.append(sampleResult.getResponseCode());
            failureMessage.append("' - '");
            failureMessage.append(sampleResult.getResponseMessage());
            failureMessage.append("', ");
        } else {
            final AssertionResult[] assertionResults = sampleResult.getAssertionResults();
            for (final AssertionResult assertionResult : assertionResults) {
                if (assertionResult.isError() || assertionResult.isFailure()) {
                    failureMessage.append("Assertion ");
                    if (assertionResult.isError()) {
                        failureMessage.append("error");
                    } else {
                        failureMessage.append("failure");
                    }
                    failureMessage.append(": '");

                    failureMessage.append(assertionResult.getFailureMessage());
                    failureMessage.append("', ");
                }
            }
        }

        //Remove the trailing , if the string is long enough
        int messageLength = failureMessage.length();
        if (messageLength > 2) {
            failureMessage.delete(messageLength - 2, messageLength);
        }

        return failureMessage.toString();
    }

    /**
     * Saves data from the last response to a file
     */
    private void saveResponseToFile(SampleResult sampleResult, Date now, String userId, String hostName,
            String errorMessages, int errorCount, int messageCount) {
        final String formatedDate;
        synchronized (RESPONSE_FILE_DATE_FORMAT) {
            formatedDate = RESPONSE_FILE_DATE_FORMAT.format(now);
        }
        final File responseFile = new File(logLocation, formatedDate + "." + hostName + ".response");

        PrintStream ps = null;
        try {
            ps = new PrintStream(responseFile);

            final String respHeaders = sampleResult.getResponseHeaders();
            final String respData = sampleResult.getResponseDataAsString();

            ps.println("Sampler Label: " + sampleResult.getSampleLabel());
            ps.println("Portal User: " + userId);
            ps.println("Consecutive Error Count: " + errorCount);
            ps.println("Sent Message Count: " + messageCount);
            ps.println("Error Messages: " + errorMessages);
            ps.println("--------------------------------------------------------------------------------");
            ps.print(respHeaders);
            ps.println("--------------------------------------------------------------------------------");
            ps.print(respData);
            ps.flush();

            log("Saved response to: " + responseFile);
        } catch (FileNotFoundException fnfe) {
            log("Failed to save response headers and body to file", fnfe);
        } finally {
            IOUtils.closeQuietly(ps);
        }
    }

    /**
     * Gets the host targeted by the sampler
     */
    private String getSampleTargetHost(SampleResult result) {
        return result.getURL().getHost();
    }

    private boolean notifyForHost(JMeterVariables vars) {
        final String notificationStr = vars.get(this.notificationVar);
        return Boolean.parseBoolean(notificationStr);
    }

    private void trackHost(JMeterVariables vars, String hostname) {
        final IterationInfo iterationInfo = (IterationInfo) vars.getObject(IterationInfo.class.getName());
        iterationInfo.addHost(hostname);
    }

    private void log(String msg) {
        System.out.println(msg);
        log.error(msg);
    }

    private void log(String msg, Throwable t) {
        System.out.println(msg + t);
        log.error(msg, t);
    }

}