io.s4.meter.common.EventGenerator.java Source code

Java tutorial

Introduction

Here is the source code for io.s4.meter.common.EventGenerator.java

Source

/*
 * Copyright (c) 2011 Yahoo! Inc. All rights reserved.
 * 
 * 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. See accompanying LICENSE file. 
 */
package io.s4.meter.common;

import io.s4.client.Driver;
import io.s4.client.Message;
import io.s4.client.ReadMode;

import java.io.IOException;
import java.io.Serializable;
import org.apache.log4j.Logger;
import org.json.JSONException;
import org.json.JSONObject;

/**
 * 
 * Generates events in JSON format using pluggable event generation logic
 * provided by the application developer.
 * 
 * This class was designed primarily to implement distributed performance
 * testing of the S4 cluster. Instances can be created by a central process and
 * sent to remote generator containers where they are de-serialized and started.
 * 
 * By design, event generators are disposable objects. That is, event generators
 * are created with an immutable configuration used once to generate events, and
 * discarded. Create a new instance of <code>EventGenerator</code> every time a
 * new event stream is needed.
 * 
 * This is a base class that needs to be extended by the application developer.
 * The concrete class is required to implement the <code>init</code> and
 * <code>getDocument</code> methods.
 * 
 * @author Leo Neumeyer
 */
@SuppressWarnings("serial")
public abstract class EventGenerator implements Serializable {

    private static Logger logger = Logger.getLogger(EventGenerator.class);

    final private String hostname;
    final private String port;
    final private String s4StreamName;
    final private String s4EventClassName;
    final private long eventPeriod;
    final private long numEvents;

    transient private Driver driver;
    transient private long startTime;
    transient private long time;
    transient private long eventCount;
    transient private int modulus;
    transient private boolean isInterrupted;
    transient private boolean isStarted;

    /**
     * Instances can only be created using this constructor. No setter methods
     * are provided.
     * 
     * @param hostname
     *            the hostname of the S4 client adaptor server.
     * @param port
     *            the port of the S4 client adaptor server.
     * @param s4StreamName
     *            the stream name used in the S4 application that will process
     *            the incoming events.
     * @param s4EventClassName
     *            the name of the event class to which the JSON events must be
     *            converted.
     * @param eventRate
     *            the target event rate. May not be achieved if sufficient
     *            resources are not available.
     * @param numEvents
     *            the total number of events that will be generated.
     * 
     * 
     */
    protected EventGenerator(String hostname, String port, String s4StreamName, String s4EventClassName,
            float eventRate, long numEvents) {
        super();
        this.port = port;
        this.hostname = hostname;
        this.s4StreamName = s4StreamName;
        this.s4EventClassName = s4EventClassName;
        this.eventPeriod = (long) (1000f / eventRate);
        this.numEvents = numEvents;
    }

    /*
     * This method is called when the object is deserialized and can be used to
     * initialized transient fields.
     */
    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {

        in.defaultReadObject();
        initInternal();
        logger.info("Initialized event generator.");

    }

    private void initInternal() {

        logger.info("Initializing S4 driver for EventGenerator.");
        isStarted = true;
        isStarted = false;
        isInterrupted = false;
        eventCount = 0;
        modulus = (int) (10000f / (float) eventPeriod); // Every 10 secs.
        setDriver(new Driver(hostname, Integer.parseInt(port)));
        driver.setReadMode(ReadMode.None);

        if (logger.isDebugEnabled())
            driver.setDebug(false);
        else
            driver.setDebug(true);

        try {
            if (!driver.init()) {
                logger.error("Driver initialization failed.");
                System.exit(1);
            }

            if (!driver.connect()) {
                logger.error("Driver initialization failed.");
                System.exit(1);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * Starts the event generator in the remote host. Calls the
     * <code>init</code> method in the concrete class, sends
     * <code>numEvents</code> events using the S4 driver, and closes the
     * connection to the S4 driver.
     * 
     * @throws Exception
     */
    public void start() throws Exception {

        if (isStarted) {
            logger.error("Event generator can only be started once. Create a new instance to start a new stream.");
            throw new Exception();
        }
        isStarted = true;

        /*
         * Initialize the concrete class lazily to make sure all fields are set
         * after serialization.
         */
        init();

        /* We use time in milliseconds to control the event rate. */
        time = System.currentTimeMillis();
        startTime = time;

        /* Let's send the events to the adaptor. */
        for (long i = 0; i < numEvents; i++) {
            send(i);
        }

        /* Close driver connection. */
        close();
    }

    /**
     * @param docId
     *            unique ID for this document
     * @throws InterruptedException
     * @throws JSONException
     */
    private void send(long eventID) throws InterruptedException, JSONException {

        String avgRate;
        StringBuilder docId = new StringBuilder();
        docId.append(hostname).append("-").append(driver.hashCode()).append("-").append(eventID);
        JSONObject jsonDoc = getDocument(docId.toString());
        time = System.currentTimeMillis();
        long delta = (time - startTime) - (eventPeriod * eventCount);
        if (delta < 0) {

            /*
             * Wait if we are transmitting faster than the target rate.
             */
            Thread.sleep(-delta);
        }

        Message m = new Message(s4StreamName, s4EventClassName, jsonDoc.toString());

        if (isInterrupted) {
            close();
            throw new InterruptedException();
        }

        try {
            driver.send(m);
        } catch (IOException e) {

            logger.error("Unable not send a message using the S4 driver.", e);

            avgRate = String.format("%8.2f", ((float) (eventCount * 1000) / (float) (time - startTime)));
            logger.error("Event count: " + String.format("%10d", eventCount) + " time: "
                    + String.format("%8d", (time - startTime) / 1000) + " avg rate: " + avgRate + "   "
                    + jsonDoc.toString() + " " + s4StreamName + " " + s4EventClassName);
        }

        if (logger.isTraceEnabled() && (eventCount % modulus) == 0) {

            if (eventCount > 0)
                avgRate = String.format("%8.2f", ((float) (eventCount * 1000) / (float) (time - startTime)));
            else
                avgRate = "--------";

            logger.trace("count: " + String.format("%10d", eventCount) + " time: "
                    + String.format("%8d", (time - startTime) / 1000) + " avg rate: " + avgRate + "   "
                    + jsonDoc.toString() + " " + s4StreamName + " " + s4EventClassName);
        }
        eventCount++;
    }

    /**
     * Stops the event generation process when a process is active and safely
     * closes the connection to the S4 client adaptor. Does nothing otherwise.
     */
    public void stop() {

        isInterrupted = true;

    }

    /**
     * Closes the connection to the S4 client adaptor.
     */
    public void close() {
        try {
            if (driver != null)
                driver.disconnect();
        } catch (Exception e) {
            logger.error("Error when trying to disconnect driver.");
        }
    }

    /**
     * @return the hostname of the S4 client adaptor.
     */
    public String getHostname() {
        return hostname;
    }

    /**
     * @return the port of the S4 client adaptor.
     */
    public String getPort() {
        return port;
    }

    /**
     * @return the S4 client driver.
     */
    public Driver getDriver() {
        return driver;
    }

    /**
     * @param driver
     *            the S4 client driver.
     */
    final private void setDriver(Driver driver) {
        this.driver = driver;
    }

    /**
     * @return the eventRate.
     */
    public float getEventRate() {
        if (time - startTime > 0)
            return (float) eventCount / (float) ((time - startTime) * 1000);
        else
            return 0.0f;
    }

    /**
     * @return the eventCount
     */
    public float getEventCount() {
        return eventCount;
    }

    /**
     * Implements the event generation logic.
     * 
     * @param eventID
     *            the eventID is a positive number between <code>0</code> and
     *            <code>numEvents</code>.
     * @return JSONObject the document to be sent to the S4 client adaptor.
     * @throws JSONException
     */
    abstract protected JSONObject getDocument(String docId) throws JSONException;

    /**
     * Implements initialization logic for the <code>getDocument</code> method.
     */
    abstract protected void init();
}