net.grinder.AgentController.java Source code

Java tutorial

Introduction

Here is the source code for net.grinder.AgentController.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;

import net.grinder.AgentDaemon.AgentShutDownListener;
import net.grinder.common.GrinderException;
import net.grinder.common.GrinderProperties;
import net.grinder.communication.*;
import net.grinder.engine.agent.Agent;
import net.grinder.engine.common.AgentControllerConnectorFactory;
import net.grinder.engine.communication.AgentControllerServerListener;
import net.grinder.engine.communication.AgentDownloadGrinderMessage;
import net.grinder.engine.communication.AgentUpdateGrinderMessage;
import net.grinder.engine.communication.LogReportGrinderMessage;
import net.grinder.engine.controller.AgentControllerIdentityImplementation;
import net.grinder.message.console.AgentControllerProcessReportMessage;
import net.grinder.message.console.AgentControllerState;
import net.grinder.messages.agent.StartGrinderMessage;
import net.grinder.messages.console.AgentAddress;
import net.grinder.util.LogCompressUtils;
import net.grinder.util.NetworkUtils;
import net.grinder.util.thread.Condition;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.ArrayUtils;
import org.ngrinder.common.constants.AgentConstants;
import org.ngrinder.infra.AgentConfig;
import org.ngrinder.monitor.collector.SystemDataCollector;
import org.ngrinder.monitor.controller.model.SystemDataModel;
import org.ngrinder.monitor.share.domain.SystemInfo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FilenameFilter;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;

import static org.ngrinder.common.constants.InternalConstants.PROP_INTERNAL_NGRINDER_VERSION;
import static org.ngrinder.common.util.NoOp.noOp;
import static org.ngrinder.common.util.Preconditions.checkNotNull;

/**
 * Agent Controller which handles agent start and stop.
 *
 * @author JunHo Yoon
 * @since 3.0
 */
public class AgentController implements Agent, AgentConstants {

    private static final Logger LOGGER = LoggerFactory.getLogger("agent controller");
    private final AgentConfig agentConfig;

    private Timer m_timer;
    @SuppressWarnings("FieldCanBeLocal")
    private final Condition m_eventSynchronization = new Condition();
    private final AgentControllerIdentityImplementation m_agentIdentity;
    private final AgentControllerServerListener m_agentControllerServerListener;
    private FanOutStreamSender m_fanOutStreamSender;
    private final AgentControllerConnectorFactory m_connectorFactory = new AgentControllerConnectorFactory(
            ConnectionType.AGENT);
    private final Condition m_eventSyncCondition;
    private volatile AgentControllerState m_state = AgentControllerState.STARTED;

    private SystemDataCollector agentSystemDataCollector = new SystemDataCollector();

    private int m_connectionPort = 0;

    private static SystemDataModel emptySystemDataModel = new SystemDataModel();

    private AgentUpdateHandler agentUpdateHandler;

    private int retryCount = 0;

    private String version;

    /**
     * Constructor.
     *
     * @param eventSyncCondition event sync condition to wait until agent start to run.
     */
    public AgentController(Condition eventSyncCondition, AgentConfig agentConfig) throws GrinderException {
        this.m_eventSyncCondition = eventSyncCondition;

        this.agentConfig = agentConfig;
        this.version = agentConfig.getInternalProperties().getProperty(PROP_INTERNAL_NGRINDER_VERSION);
        this.m_agentControllerServerListener = new AgentControllerServerListener(m_eventSynchronization, LOGGER);
        // Set it with the default name
        this.m_agentIdentity = new AgentControllerIdentityImplementation(agentConfig.getAgentHostID(),
                NetworkUtils.DEFAULT_LOCAL_HOST_ADDRESS);
        this.m_agentIdentity.setRegion(agentConfig.getRegion());
        this.agentSystemDataCollector = new SystemDataCollector();
        this.agentSystemDataCollector.setAgentHome(agentConfig.getHome().getDirectory());
        this.agentSystemDataCollector.refresh();
    }

    /**
     * Run the agent controller.
     *
     * @throws GrinderException occurs when the test execution is failed.
     */
    @SuppressWarnings("ConstantConditions")
    public void run() throws GrinderException {
        synchronized (m_eventSyncCondition) {
            m_eventSyncCondition.notifyAll();
        }

        StartGrinderMessage startMessage = null;
        ConsoleCommunication consoleCommunication = null;
        m_fanOutStreamSender = new FanOutStreamSender(GrinderConstants.AGENT_CONTROLLER_FANOUT_STREAM_THREAD_COUNT);
        m_timer = new Timer(false);
        AgentDaemon agent = new AgentDaemon(
                checkNotNull(agentConfig, "agent.conf should be provided before agent daemon start."));
        try {
            while (true) {
                do {
                    if (consoleCommunication == null) {
                        final Connector connector = m_connectorFactory.create(agentConfig.getControllerIP(),
                                agentConfig.getControllerPort());
                        try {
                            consoleCommunication = new ConsoleCommunication(connector);
                            consoleCommunication.start();
                            LOGGER.info("Connected to agent controller server at {}",
                                    connector.getEndpointAsString());
                        } catch (CommunicationException e) {
                            LOGGER.error("Error while connecting to agent controller server at {}",
                                    connector.getEndpointAsString());
                            return;
                        }
                    }

                    if (consoleCommunication != null && startMessage == null) {
                        if (m_state == AgentControllerState.UPDATING) {
                            m_agentControllerServerListener.waitForMessage();
                            break;
                        } else {
                            LOGGER.info("Waiting for agent controller server signal");
                            m_state = AgentControllerState.READY;
                            m_agentControllerServerListener.waitForMessage();
                            if (m_agentControllerServerListener.received(AgentControllerServerListener.START)) {
                                startMessage = m_agentControllerServerListener.getLastStartGrinderMessage();
                                LOGGER.info("Agent start message is received from controller {}", startMessage);
                                continue;
                            } else {
                                break; // Another message, check at end of outer
                                // while loop.
                            }
                        }
                    }

                    if (startMessage != null) {
                        m_agentIdentity.setNumber(startMessage.getAgentNumber());
                    }
                } while (checkNotNull(startMessage).getProperties() == null);

                // Here the agent run code goes..
                if (startMessage != null) {
                    final String testId = startMessage.getProperties().getProperty("grinder.test.id", "unknown");
                    LOGGER.info("Starting agent... for {}", testId);
                    m_state = AgentControllerState.BUSY;
                    m_connectionPort = startMessage.getProperties().getInt(GrinderProperties.CONSOLE_PORT, 0);
                    agent.run(startMessage.getProperties());

                    final ConsoleCommunication conCom = consoleCommunication;
                    agent.resetListeners();
                    agent.addListener(new AgentShutDownListener() {
                        @Override
                        public void shutdownAgent() {
                            LOGGER.info("Send log for {}", testId);
                            sendLog(conCom, testId);
                            m_state = AgentControllerState.READY;
                            m_connectionPort = 0;
                        }
                    });
                }
                // Ignore any pending start messages.
                m_agentControllerServerListener.discardMessages(AgentControllerServerListener.START);

                if (!m_agentControllerServerListener.received(AgentControllerServerListener.ANY)) {
                    // We've got here naturally, without a console signal.
                    LOGGER.info("Agent is started. Waiting for agent controller signal");
                    m_agentControllerServerListener.waitForMessage();
                }

                if (m_agentControllerServerListener.received(AgentControllerServerListener.START)) {
                    startMessage = m_agentControllerServerListener.getLastStartGrinderMessage();
                } else if (m_agentControllerServerListener.received(AgentControllerServerListener.STOP)) {
                    agent.shutdown();
                    startMessage = null;
                    m_connectionPort = 0;
                    m_agentControllerServerListener.discardMessages(AgentControllerServerListener.STOP);
                } else if (m_agentControllerServerListener.received(AgentControllerServerListener.SHUTDOWN)) {
                    m_connectionPort = 0;
                    break;
                } else if (m_agentControllerServerListener.received(AgentControllerServerListener.AGENT_UPDATE)) {
                    // Do update agent by downloading new version.
                    startMessage = null;
                    m_connectionPort = 0;
                    m_state = AgentControllerState.UPDATING;
                    final AgentUpdateGrinderMessage message = m_agentControllerServerListener
                            .getLastAgentUpdateGrinderMessage();
                    m_agentControllerServerListener.discardMessages(AgentControllerServerListener.AGENT_UPDATE);
                    AgentDownloadGrinderMessage agentDownloadGrinderMessage = new AgentDownloadGrinderMessage(
                            message.getVersion());
                    try {
                        // If it's initial message
                        if (agentUpdateHandler == null && message.getNext() == 0) {
                            IOUtils.closeQuietly(agentUpdateHandler);
                            agentUpdateHandler = new AgentUpdateHandler(agentConfig, message);
                        } else if (agentUpdateHandler != null) {
                            if (message.isValid()) {
                                retryCount = 0;
                                agentUpdateHandler.update(message);
                                agentDownloadGrinderMessage.setNext(message.getNext());
                            } else if (retryCount <= AgentDownloadGrinderMessage.MAX_RETRY_COUNT) {
                                retryCount++;
                                agentDownloadGrinderMessage.setNext(message.getOffset());
                            } else {
                                throw new CommunicationException(
                                        "Error while getting the agent package from " + "controller");
                            }
                        } else {
                            throw new CommunicationException(
                                    "Error while getting the agent package from controller");
                        }
                        if (consoleCommunication != null) {
                            consoleCommunication.sendMessage(agentDownloadGrinderMessage);
                        } else {
                            break;
                        }

                    } catch (IllegalArgumentException ex) {
                        IOUtils.closeQuietly(agentUpdateHandler);
                        agentUpdateHandler = null;
                        retryCount = 0;
                        LOGGER.info("same or old agent version {} is sent for update. skip this.",
                                message.getVersion());
                        m_state = AgentControllerState.READY;
                    } catch (Exception e) {
                        retryCount = 0;
                        IOUtils.closeQuietly(agentUpdateHandler);
                        agentUpdateHandler = null;
                        LOGGER.error("While updating agent, the exception occurred.", e);
                        m_state = AgentControllerState.READY;
                    }

                } else {
                    // ConsoleListener.RESET or natural death.
                    startMessage = null;
                }
            }

        } finally {
            m_connectionPort = 0;
            // Abnormal state.
            agent.shutdown();
            m_state = AgentControllerState.FINISHED;
            shutdownConsoleCommunication(consoleCommunication);
            m_timer.cancel();
        }
    }

    private void sendLog(ConsoleCommunication consoleCommunication, String testId) {
        File logFolder = new File(agentConfig.getHome().getLogDirectory(), testId);
        if (!logFolder.exists()) {
            return;
        }
        File[] logFiles = logFolder.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return (name.endsWith(".log"));
            }
        });

        if (logFiles == null || ArrayUtils.isEmpty(logFiles)) {
            LOGGER.error("No log exists under {}", logFolder.getAbsolutePath());
            return;
        }
        Arrays.sort(logFiles);
        // Take only one file... if agent.send.all.logs is not set.
        if (!agentConfig.getAgentProperties().getPropertyBoolean(PROP_AGENT_ALL_LOGS)) {
            logFiles = new File[] { logFiles[0] };
        }
        final byte[] compressedLog = LogCompressUtils.compress(logFiles, Charset.defaultCharset(),
                Charset.forName("UTF-8"));
        consoleCommunication
                .sendMessage(new LogReportGrinderMessage(testId, compressedLog, new AgentAddress(m_agentIdentity)));
        // Delete logs to clean up
        if (!agentConfig.getAgentProperties().getPropertyBoolean(PROP_AGENT_KEEP_LOGS)) {
            LOGGER.info("Clean up the perftest logs");
            FileUtils.deleteQuietly(logFolder);
        }
    }

    private void shutdownConsoleCommunication(ConsoleCommunication consoleCommunication) {
        sendCurrentState(consoleCommunication);
        if (consoleCommunication != null) {
            consoleCommunication.shutdown();
            //noinspection UnusedAssignment
            consoleCommunication = null;
        }
        m_agentControllerServerListener.discardMessages(AgentControllerServerListener.ANY);
    }

    private void sendCurrentState(ConsoleCommunication consoleCommunication) {
        if (consoleCommunication != null) {
            try {
                consoleCommunication.sendCurrentState();
            } catch (CommunicationException e) {
                LOGGER.error("Error while sending current state : {}.", e.getMessage());
                LOGGER.debug("The error detail is ", e);
            }
        }
    }

    /**
     * Clean up resources.
     */
    public void shutdown() {
        if (m_timer != null) {
            m_timer.cancel();
        }
        if (m_fanOutStreamSender != null) {
            m_fanOutStreamSender.shutdown();
        }
        m_agentControllerServerListener.shutdown();
        LOGGER.info("Agent controller shuts down");
    }

    /**
     * Get current System performance.
     *
     * @return {@link SystemDataModel} instance
     */
    public SystemDataModel getSystemDataModel() {
        try {
            SystemInfo systemInfo = agentSystemDataCollector.execute();
            return new SystemDataModel(systemInfo, this.version);
        } catch (Exception e) {
            LOGGER.error("Error while getting system data model : {} ", e.getMessage());
            LOGGER.debug("The error detail is ", e);
            return emptySystemDataModel;
        }
    }

    public AgentConfig getAgentConfig() {
        return agentConfig;
    }

    public final class ConsoleCommunication {
        private final ClientSender m_sender;
        private final TimerTask m_reportRunningTask;
        private final MessagePump m_messagePump;

        public ConsoleCommunication(Connector connector) throws CommunicationException {
            final ClientReceiver receiver = ClientReceiver.connect(connector, new AgentAddress(m_agentIdentity));
            m_sender = ClientSender.connect(receiver);

            m_sender.send(new AgentControllerProcessReportMessage(AgentControllerState.STARTED,
                    getSystemDataModel(), m_connectionPort, version));
            final MessageDispatchSender messageDispatcher = new MessageDispatchSender();
            m_agentControllerServerListener.registerMessageHandlers(messageDispatcher);

            m_messagePump = new MessagePump(receiver, messageDispatcher, 1);

            m_reportRunningTask = new TimerTask() {
                public void run() {
                    try {
                        sendCurrentState();
                    } catch (CommunicationException e) {
                        cancel();
                        LOGGER.error("Error while sending current state:" + e.getMessage());
                        LOGGER.debug("The error detail is", e);
                    }
                }
            };
        }

        public void sendMessage(Message message) {
            try {
                m_sender.send(message);
            } catch (CommunicationException e) {
                LOGGER.error("{}. This error is not critical if it doesn't occur much.", e.getMessage());
            }
        }

        public void sendCurrentState() throws CommunicationException {
            sendMessage(new AgentControllerProcessReportMessage(m_state, getSystemDataModel(), m_connectionPort,
                    version));
        }

        public void start() {
            m_messagePump.start();
            m_timer.schedule(m_reportRunningTask, 0, GrinderConstants.AGENT_CONTROLLER_HEARTBEAT_INTERVAL);
        }

        public void shutdown() {
            m_reportRunningTask.cancel();
            try {
                m_sender.send(
                        new AgentControllerProcessReportMessage(AgentControllerState.FINISHED, null, 0, version));
            } catch (CommunicationException e) {
                // Fall through
                // Ignore - peer has probably shut down.
                noOp();
            } finally {
                m_messagePump.shutdown();
            }
        }

    }
}