org.addsimplicity.anicetus.TelemetryContext.java Source code

Java tutorial

Introduction

Here is the source code for org.addsimplicity.anicetus.TelemetryContext.java

Source

/**
 * Copyright 2008-2009 Dan Pritchett
 * 
 * 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 org.addsimplicity.anicetus;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Stack;

import org.addsimplicity.anicetus.entity.CompletionStatus;
import org.addsimplicity.anicetus.entity.ExecInfo;
import org.addsimplicity.anicetus.entity.GlobalInfo;
import org.addsimplicity.anicetus.entity.SubTypedInfo;
import org.addsimplicity.anicetus.entity.TelemetryEvent;
import org.addsimplicity.anicetus.entity.TelemetrySession;
import org.addsimplicity.anicetus.entity.TelemetryState;
import org.addsimplicity.anicetus.entity.TelemetryTransaction;
import org.addsimplicity.anicetus.io.DeliveryAdapter;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

/**
 * The TelemetryContext establishes the execution container for all telemetry.
 * The execution context is defined by the system, process, and potentially the
 * thread of execution for the session. A session is a logical concept that
 * represents a unit of activity performed by the application.
 * 
 * A session provides a container for other telemetry artifacts. Transactions
 * are one of the artifacts that are also containers for other artifacts. The
 * TelemetryContext provides a convenience method for creating other artifacts
 * that will be parented to the current container (either session or
 * transaction).
 * 
 * The TelemetryContext is responsible for sending telemetry to the bus. By
 * default, a session is sent anytime it ends. Applications may also send beacon
 * telemetry (i.e. state and events) directly to the telemetry bus using the
 * context convenience method.
 * 
 * The lifecycle of the session is controlled by startSession and endSession.
 * These methods are automatically called from the Spring container if Spring is
 * used to manage the lifecycle of the context.
 * 
 * @author Dan Pritchett (driveawedge@yahoo.com)
 * 
 */
public class TelemetryContext implements InitializingBean, DisposableBean {
    private final Stack<ExecInfo> m_context = new Stack<ExecInfo>();
    private DeliveryAdapter m_deliveryAdapter;
    private String m_operationName;
    private int m_processIdentifier = -1;
    private String m_reportingNode;
    private TelemetrySession m_session;

    /**
     * Called after the Spring framework sets all properties. This method will
     * start the session.
     * 
     * @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
     */
    public void afterPropertiesSet() throws Exception {
        startSession();
    }

    /**
     * The beginTransaction method starts a new transaction as a child of the
     * current session or transaction.
     * 
     * @param resourceId
     *          The application defined resource identifier.
     * @return an initialized transaction.
     */
    public TelemetryTransaction beginTransaction(String resourceId) {
        final TelemetryTransaction trans = new TelemetryTransaction(m_context.peek());
        trans.setResourceId(resourceId);
        setReporting(trans);

        m_context.push(trans);

        return trans;
    }

    /**
     * The destroy method is called by the Spring framework when the context is
     * being disposed. An open session will be closed and published before the
     * bean is disposed.
     * 
     * @see org.springframework.beans.factory.DisposableBean#destroy()
     */
    public void destroy() throws Exception {
        if (m_context.size() > 0) {
            endSession();
        }
    }

    /**
     * The current session is ended. Any open transactions are closed with their
     * completion status set to unknown. The session is published on the telemetry
     * bus.
     */
    public void endSession() {
        closeDanglingTrans();
        m_session.complete();
        m_context.pop();

        m_deliveryAdapter.sendTelemetry(m_session);
        startSession();
    }

    /**
     * The current transaction is closed. The completion status will be set to
     * unknown if it has not already been set.
     */
    public void endTransaction() {
        if (m_context.size() <= 1) {
            return; // Something is unbalanced but do we throw exceptions in reporting
            // flows?
        }

        final TelemetryTransaction trans = (TelemetryTransaction) m_context.pop();
        if (trans.getStatus() == null) {
            trans.setStatus(CompletionStatus.Unknown);
        }

        trans.complete();
    }

    /**
     * Return the currently set delivery adapter.
     * 
     * @return the current delivery adapter.
     */
    public DeliveryAdapter getDeliveryAdapter() {
        return m_deliveryAdapter;
    }

    /**
     * Return the operation name. The operation name is set on the session when it
     * is created.
     * 
     * @return the operation name.
     */
    public String getOperationName() {
        return m_operationName;
    }

    /**
     * Return the processor identifier. The process identifier is extracted from
     * the system property anicetus.processid. The process identifier is used in
     * conjunction with the thread identifier to construct the session execution
     * context.
     * 
     * @return the processor identifier.
     */
    public int getProcessIdentifier() {
        return m_processIdentifier;
    }

    /**
     * Return the reporting node. The reporting node is set to the host name of
     * the default interface on the system where the application is run. The
     * reporting node is set on the session.
     * 
     * @return the reporting node.
     */
    public String getReportingNode() {
        return m_reportingNode;
    }

    /**
     * Return the current active session.
     * 
     * @return the current active session.
     */
    public TelemetrySession getSession() {
        return m_session;
    }

    /**
     * Create an event, child of the current context (session or transaction).
     * 
     * @param type
     *          The application defined type of the event.
     * @return the newly created event.
     */
    public SubTypedInfo newEvent(String type) {
        final SubTypedInfo evt = new TelemetryEvent(m_context.peek());
        evt.setType(type);
        setReporting(evt);

        return evt;
    }

    /**
     * Create a state, child of the current context (session or transaction).
     * 
     * @return the newly created state.
     */
    public TelemetryState newState() {
        final TelemetryState state = new TelemetryState(m_context.peek());
        setReporting(state);

        return state;
    }

    /**
     * Return the current execution context (session or transaction).
     * 
     * @return the session or transaction context in effect.
     */
    public ExecInfo peekTransaction() {
        return m_context.peek();
    }

    /**
     * Pop the current transaction off the top of the stack. The session will not
     * be popped from the stack.
     * 
     * @return the current transaction or null if no transactions are left.
     */
    public ExecInfo popTransaction() {
        return m_context.size() > 1 ? m_context.pop() : null;
    }

    /**
     * Push a transaction to the top of the execution context. The standard
     * reporting information will be added to the transaction.
     * 
     * @param transaction
     *          The transaction to push.
     */
    public void pushTransaction(ExecInfo transaction) {
        setReporting(transaction);

        m_context.push(transaction);
    }

    /**
     * Send a telemetry beacon that is independent of the current execution
     * context. The beacon will be sent immediately. The basic information about
     * the current application environment will be set.
     * 
     * @param beacon
     *          The beacon to be sent.
     */
    public void sendBeacon(GlobalInfo beacon) {
        fillBaseInfo(beacon);
        m_deliveryAdapter.sendTelemetry(beacon);
    }

    /**
     * Set the delivery adpater that will be used to publish events on the
     * telemetry bus.
     * 
     * @param deliveryAdapter
     *          The delivery adapter instance.
     */
    public void setDeliveryAdapter(DeliveryAdapter deliveryAdapter) {
        m_deliveryAdapter = deliveryAdapter;
    }

    /**
     * Set the operation name for this context. This will be set on sessions when
     * they are created.
     * 
     * @param operationName
     */
    public void setOperationName(String operationName) {
        m_operationName = operationName;
    }

    /**
     * Set the process identifier for this context. The process identifier should
     * be the operating system identifier for the JVM process.
     * 
     * @param processIdentifier
     *          The JVM process identifier.
     */
    public void setProcessIdentifier(int processIdentifier) {
        m_processIdentifier = processIdentifier;
    }

    /**
     * Set the node identifier for this context. This is typically the host name
     * or IP address.
     * 
     * @param reportingNode
     *          The node network identifier.
     */
    public void setReportingNode(String reportingNode) {
        m_reportingNode = reportingNode;
    }

    /**
     * Start a new session. The session is filled with the current execution
     * environment information.
     */
    public void startSession() {
        m_session = createSession();
        m_context.clear();
        m_context.push(m_session);
        m_session.setOperationName(m_operationName);
        sniffHost();
        m_session.setReportingNode(m_reportingNode);
        sniffProcessId();
        m_session.setExecutionContext(makeExecContext());
    }

    private void closeDanglingTrans() {
        while (m_context.size() > 1) {
            endTransaction();
        }
    }

    private void fillBaseInfo(GlobalInfo info) {
        if (info.getReportingNode() == null) {
            info.setReportingNode(m_reportingNode);
        }

        if (info.getExecutionContext() == null) {
            info.setExecutionContext(makeExecContext());
        }
    }

    private String makeExecContext() {
        return (m_processIdentifier >= 0 ? m_processIdentifier : "UNKNOWN") + "." + Thread.currentThread().getId();
    }

    private void setReporting(GlobalInfo target) {
        target.setReportingNode(m_context.peek().getReportingNode());
    }

    private void sniffHost() {
        if (m_reportingNode == null) {
            try {
                final InetAddress local = InetAddress.getLocalHost();
                m_reportingNode = local.getCanonicalHostName();
            } catch (final UnknownHostException e) {
                // Not sure what else to do. Just local host it.
                //
                m_reportingNode = "127.0.0.1";
            }
        }
    }

    private void sniffProcessId() {
        if (m_processIdentifier < 0) {
            final String pid = System.getProperty("anicetus.processid");
            if (pid != null) {
                try {
                    m_processIdentifier = Integer.parseInt(pid);
                } catch (final NumberFormatException e) {
                    // Set it to zero. This will cause something other than 'UNKNOWN'
                    // which is a hint
                    // they set the property to an unparseable string.
                    //
                    m_processIdentifier = 0;
                }
            }
        }
    }

    protected TelemetrySession createSession() {
        return new TelemetrySession();
    }
}