Sun GlassFish Enterprise Server v3 Prelude Add-On Component Development Guide

Chapter 5 Adding Monitoring Capabilities

Monitoring is the process of reviewing the statistics of a system to improve performance or solve problems. By monitoring the state of components and services that are deployed in the Enterprise Server, system administrators can identify performance bottlenecks, predict failures, perform root cause analysis, and ensure that everything is functioning as expected. Monitoring data can also be useful in performance tuning and capacity planning.

An add-on component typically generates statistics that the Enterprise Server can gather at run time. Adding monitoring capabilities enables an add-on component to provide statistics to Enterprise Server in the same way as components that are supplied in Enterprise Server distributions. As a result, system administrators can use the same administrative interfaces to monitor statistics from any installed Enterprise Server component, regardless of the origin of the component.

The following topics are addressed here:

Defining Statistics That Are to Be Monitored

At runtime, your add-on component might perform operations that affect the behavior and performance of your system. For example, your component might start a thread of control, receive a request from a service, or request a connection from a connection pool. Monitoring the statistics that are related to these operations helps a system administrator maintain the system.

To provide statistics to Enterprise Server, your component must define events for the operations that generate these statistics. At runtime, your component must send these events when performing the operations for which the events are defined. For example, to enable the number of received requests to be monitored, a component must send a “request received” event each time that the component receives a request.

A statistic can correspond to single event or to multiple events.

Defining statistics that are to be monitored involves the following tasks:

Defining an Event Provider

An event provider defines the types of events for the operations that generate statistics for an add-on component.

To define an event provider, write a JavaTM language interface that defines the types of events for the component. In the interface, define one method for each type of event that is related to the component.


Note –

You are not required to implement the event provider interface. After you register the event provider, Enterprise Server generates the implementation class at runtime for you. For more information, see Registering an Event Provider.


Identifying the Event Type

If you overload a method in your implementation, annotate each form of the method with the @org.glassfish.flashlight.provider.annotations.ProbeName annotation to uniquely identify the event type. Set the value element of the @ProbeName annotation to the name of the event type.


Note –

If you do not annotate a method, the name of the event type is the method name. Therefore, you are not required to annotate methods that are not overloaded.


Specifying Event Parameters

To enable methods in an event listener to select a subset of values, annotate each parameter in the method signature with the org.glassfish.flashlight.provider.annotations.ProbeParam annotation. Set the value element of the @ProbeParam annotation to the name of the parameter.

Example of Defining an Event Provider


Example 5–1 Defining an Event Provider

This example shows the definition of the TxManager interface. This interface defines events for the start and end of transactions that are performed by a transaction manager.

The methods in this interface are as follows:

onTxBegin

This method sends an event to indicate the start of a transaction. The name of the event type that is associated with this method is begin. A parameter that is named txId is passed to the method.

onCompletion

This method sends an event to indicate the end of a transaction. The name of the event type that is associated with this method is the method name. A parameter that is named outcome is passed to the method.

import org.glassfish.flashlight.provider.annotations.ProbeName;
import org.glassfish.flashlight.provider.annotations.ProbeParam;

  public interface TxManager {
    
    @ProbeName("begin")
    public void onTxBegin(
        @ProbeParam("{txId}") String txId
    );

    public void onCompletion(
        @ProbeParam("{outcome}") boolean outcome
    );
 }

Registering an Event Provider

Registering an event provider generates a class that implements the event provider interface. Enterprise Server provides the org.glassfish.flashlight.provider.ProbeProviderFactory factory class that generates the event provider class at runtime. To generate the class, Enterprise Server uses the ASM framework for manipulating and analyzing Java byte codes.

By default, a nonoperational implementation of the methods is created. If monitoring is not enabled, which means that no listeners are registered, the methods do not consume any computing resources, such as memory or processor cycles.


Note –

The ProbeProviderFactory.getProbeProvider method is an unstable interface and is subject to change.


To register an event provider, invoke the ProbeProviderFactory.getProbeProvider method in the class that represents your add-on component. In the invocation of the ProbeProviderFactory.getProbeProvider method, pass the following information as parameters to the method:

component-name

Your choice of name for the add-on component that is to send the event.

provider-name

Your choice of name for the provider.

application-name

Your choice of name for the application that the add-on component represents. The application-name can be null.

event-provider-class

The compiled class that is to implement your event provider interface. For example, if your event provider interface is named TxManager, specify the class as TxManager.class.


Example 5–2 Registering an Event Provider

This example shows the code for registering the event provider interface TxManager for the add-on component that is represented by the class TransactionManagerImpl. The definition of the TxManager interface is shown in Example 5–1. The component name is tx and the provider name is TxManager. No application name is specified.

...
import org.glassfish.flashlight.provider.ProbeProviderFactory;
...
public class TransactionManagerImpl {
...
    @Inject
    protected ProbeProviderFactory probeProviderFactory;
...
     TxManager txProvider = probeProviderFactory.getProbeProvider(
         "tx", "TxManager", null, TxManager.class);
...
}

Sending an Event

At runtime, your add-on component might perform an operation that generates statistics. To provide statistics about the operation to Enterprise Server, your component must send an event of the correct type when performing the operation.

To send an event, invoke the method of your event provider class that is defined for the type of the event. Ensure that the method is invoked when your component performs the operation for which the event was defined. One way to meet this requirement is to invoke the method for sending the event in the body of the method for performing the operation.


Example 5–3 Sending an Event

This example shows the code for invoking the onTxBegin method to send an event of type begin. This event indicates that a component is about to begin a transaction. To ensure that the event is sent at the correct time, the onTxBegin method is invoked in the body of the begin method, which starts a transaction.

The declaration of the onTxBegin method in the event provider interface is shown in Example 5–1.

The creation of the txProvider object is shown in Example 5–2.

...
public class TransactionManagerImpl {
...
     public void begin() {
        String txId = createTransactionId();
        ....
        txProvider.onTxBegin(txId); //emit
      }
...
}

Updating the Monitorable Object Tree

A monitorable object is a component, subcomponent, or service that can be monitored. Enterprise Server uses a tree structure to track monitorable objects.

Because the tree is dynamic, the tree changes as components of the Enterprise Server instance are added, modified, or removed. Objects are also added to or removed from the tree in response to configuration changes. For example, if monitoring for a component is turned off, the component's monitorable object is removed from the tree.

To enable system administrators to access statistics for all components in the same way, you must provide statistics for an add-on component by updating the monitorable object tree. Statistics for the add-on component are then available through the Enterprise Server administrative commands get(1), list(1), and set(1). These commands locate an object in the tree through the object's dotted name.

For more information about the tree structure of monitorable objects, see How the Monitoring Tree Structure Works in Sun GlassFish Enterprise Server v3 Prelude Administration Guide.

To make an add-on component a monitorable object, you must add the add-on component to the monitorable object tree.

To update the statistics for an add-on component, you must add the statistics to the monitorable object tree, and create event listeners to gather statistics from events that represent these statistics. At runtime, these listeners must update monitorable objects with statistics that these events contain. The events are sent by event provider classes. For information about how to create event provider classes and send events, see Defining Statistics That Are to Be Monitored.

Updating the monitorable object tree involves the following tasks:

Creating Event Listeners

An event listener gathers statistics from events that an event provider sends. To enable an add-on component to gather statistics from events, create listeners to receive events from the event provider. The listener can receive events from the add-on component in which the listener is created and from other components.

To create an event listener, write a Java class to represent the listener. The listener can be any Java object.

Ensure that the class that you write meets these requirements:

A listener is called in the same thread as the event method. As a result, the listener can use thread locals. If the monitored system allows access to thread locals, the listener can access thread locals of the monitored system.


Note –

A listener that is not registered to listen for events is never called by the framework. Therefore, unregistered listeners do not consume any computing resources, such as memory or processor cycles.


Subscribing to Events From Event Provider Classes

To receive events from event provider classes, a listener must subscribe to the events. Subscribing to events also specifies the provider and the type of events that the listener will receive.

To subscribe to events from event provider classes, write one method in your listener class to process each type of event. To specify the provider and the type of event, annotate the method with the org.glassfish.flashlight.client.ProbeListener annotation. In the @ProbeListener annotation, specify the provider and the type as follows:

"component-name:provider-name:app-name:event-type"

Note –

The @ProbeListener annotation is an unstable interface and is subject to change.


component-name

The name of add-on component that is to send the event. This parameter must match the parameter that is defined when the event provider is registered. See Registering an Event Provider.

provider-name

The name of the provider. This parameter must match the parameter that is defined when the event provider is registered. See Registering an Event Provider.

application-name

The name of the application that the add-on component represents. This parameter must match the parameter that is defined when the event provider is registered. See Registering an Event Provider.

event-type

The type of the event. This type is defined in the event provider interface. For more information, see Identifying the Event Type.

In the method body, provide the code to update monitoring statistics in response to the event.


Example 5–4 Subscribing to Events From Event Provider Classes

This example shows the code for subscribing to events of type begin from the tx component. The provider of the component is TxManager. The body of the begin method contains code to increase the transaction count txcount by 1 each time that an event is received.

The definition of the begin event type is shown in Example 5–1.

The code for sending begin events is shown in Example 5–3.

...
import org.glassfish.flashlight.client.ProbeListener;
...
public class TxListener {
    AtomicInteger txCount = ....;

    @ProbeListner("tx:TxManager::begin")
    public void begin(String txId) {
      txCount++;
    }
  }

Registering an Event Listener

Registering an event listener enables the listener to receive callbacks from the Enterprise Server event infrastructure. The listener can then collect data from events and update monitorable objects in the object tree. These monitorable objects form the basis for monitoring statistics.

At runtime, the Enterprise Server event infrastructure registers listeners for an event provider when the event provider is started and unregisters them when the event provider is shut down. As a result, listeners have no dependencies on other components.

To register a listener, invoke the org.glassfish.flashlight.client.ProbeClientMediator.registerListener method in the class that represents your add-on component. In the method invocation, pass the listener object as a parameter.

The registerListener method returns a collection of ProbeClientMethodHandle objects. To enable a listener to turn on or turn off monitoring, pass this collection to your listener class. In your listener class, invoke methods of the ProbeClientMethodHandle objects to turn on and turn off monitoring.

For information about how to register an event provider, see Registering an Event Provider.


Example 5–5 Registering an Event Listener

This example shows the code for registering the event listener TxListener for the add-on component that is represented by the class TransactionManagerImpl.

Code for the following methods of the TxListener class is beyond the scope of this example:

...
import org.glassfish.flashlight.client.ProbeClientMediator;
...
public class TransactionManagerImpl {

    @Inject
    private ProbeClientMediator pcm;

...
    TxListener txL = new TxListener(txmNode);
    Collection<ProbeClientMethodHandle> handles = pcm.registerListener(txL);
    txL.setProbeListenerHandles(handles);
...
}

Adding Statistics for a Component to the Monitorable Object Tree

Adding statistics for a component to the monitorable object tree makes the component and its statistics monitorable objects.

Adding a statistics for a component to the monitorable object tree involves the following tasks:

Creating a TreeNode Object to Represent a Component

To a create a TreeNode object to represent a component in the monitorable object tree, invoke the static method createTreeNode of the org.glassfish.flashlight.datatree.factory.TreeNodeFactory class. Invoke this method in the class that represents your component.

In the invocation of the createTreeNode method, pass the following information as parameters to the method:

Creating Objects to Represent a Component's Statistics

Create one object to represent each statistic that you are adding to the monitorable object tree. Create the objects in your listener class or in the class that represents your add-on component. Each object must be an implementation of an interface that extends org.glassfish.flashlight.datatree.TreeNode.


Note –

The TreeNode is an unstable interface and is subject to change.


To specify the name of the node in the monitorable object tree that the object represents, invoke the object's setName method. In the invocation of the setName method, pass a string that contains the name of the node as a parameter to the method.

The object that represents a statistic must also provide methods for computing the statistic from event data.

The org.glassfish.flashlight.statistics package provides the following utility classes to gather and compute statistics data:

Average

Provides averages.

Counter

Provides a counter that a class can use to maintain count.

TimeStats

Provides timing information in seconds.

TimeStatsMillis

Provides timing information in milliseconds.

TimeStatsNanos

Provides timing information in nanoseconds.


Note –

The classes in the org.glassfish.flashlight.statistics package are unstable interfaces and are subject to change.


The org.glassfish.flashlight.statistics.factory package provides a factory class for each utility class as shown in the following table.

Utility Class 

Factory Class 

Average

AverageFactory

Counter

CounterFactory

TimeStats

TimeStatsFactory

TimeStatsMillis

TimeStatsFactory

TimeStatsNanos

TimeStatsFactory

Getting the server Node Object

The server node object is the parent of the TreeNode object that represents the component in the monitorable object tree.

To get the server node object, invoke the get method of the org.glassfish.flashlight.MonitoringRuntimeDataRegistry class. In the invocation of the get method, pass the string server as a parameter.

Adding an Object to the Tree

To add an object to the tree, invoke the addChild method of the object's parent object. In the invocation of the addChild method, pass the TreeNode object that you are adding as a parameter.

The parent object depends on whether the object represents an add-on component or a statistic:

Dotted Names for an Add-On Component's Statistics

The Enterprise Server administrative commands get(1), list(1), and set(1) locate a statistic through the dotted name of the statistic. The dotted name of a statistic for an add-on component is determined from the names of the TreeNode objects that represent the statistic and the component in the monitorable object tree as follows:

server.componenent-treenode-name.statistic-treenode-name
componenent-treenode-name

The name of the TreeNode object that represents the component in the monitorable object tree. This name is passed In the invocation of the createTreeNode method that creates the object. For more information, see Creating a TreeNode Object to Represent a Component.

statistic-treenode-name

The name of the TreeNode object that represents the statistic in the monitorable object tree. This name is passed In the invocation of the setName method. For more information, see Creating Objects to Represent a Component's Statistics.

Example of Adding Statistics for a Component to the Monitorable Object Tree


Example 5–6 Adding Statistics for a Component to the Monitorable Object Tree

This example shows the code for adding the totaltransactioncount statistic to the monitorable object tree. To add this statistic, objects are added to the monitorable object tree as follows:

...
import org.glassfish.flashlight.client.ProbeListener;
import org.glassfish.flashlight.datatree.TreeNode;
import org.glassfish.flashlight.datatree.factory.TreeNodeFactory;
import org.glassfish.flashlight.statistics.Counter;
...
public class TransactionManagerImpl {

    @Inject
    private MonitoringRuntimeDataRegistry mrdr;

    private TreeNode serverNode;
    private Counter totalTxCount = CounterFactory.createCount();

    public void init (){
        ...
        TreeNode txNode = TreeNodeFactory.createTreeNode("tx", null, "transactions");
        TreeNode serverNode = getServerNode();
        serverNode.addChild(txNode);
        ...
        totalTxCount.setName("totaltransactioncount");
        txNode.addChild(totalTxCount);
        ...
    }
...
    private TreeNode getServerNode() {
        TreeNode serverNode = null;
        if (mrdr.get("server") != null) {
            serverNode = mrdr.get("server");
        } else {
            serverNode = TreeNodeFactory.createTreeNode("server", null, "server");
            mrdr.add("server", serverNode);
        }
        return serverNode;
    }
...
}

Example of Adding Monitoring Capabilities

This example shows a component that monitors the number of requests that a container receives. The following table provides a cross-reference to the listing of each class or interface in the example.

Class or Interface 

Listing 

ModuleProbeProvider

Example 5–7

ModuleBootStrap

Example 5–8

ModuleStatsTelemetry

Example 5–9

Module

Example 5–10

ModuleMBean

Example 5–11


Example 5–7 Event Provider Interface

This example illustrates how to define an event provider as explained in Defining an Event Provider.

The example shows the definition of the ModuleProbeProvider interface. The event provider sends events when the request count is increased by 1 or decreased by 1.

This interface declares the following methods:

The name of each method is also the name of the event type that is associated with the method.

A parameter that is named count is passed to each method.

package com.example.count.monitoring;

import org.glassfish.flashlight.provider.annotations.ProbeParam;

/**
 * Monitoring count eventse
 * Provider interface for module specific probe events.
 *
 */
public interface ModuleProbeProvider {

    /**
     * Emits probe event whenever the count is incremented
     */
    public void moduleCountIncrementEvent(
        @ProbeParam("count") Integer count
    );

    /**
     * Emits probe event whenever the count is decremented
     */
    public void moduleCountDecrementEvent(
        @ProbeParam("count") Integer count
    );

}


Example 5–8 Bootstrap Class

This example illustrates how to perform the following tasks:

package com.example.count.monitoring;

import java.lang.management.ManagementFactory;
import org.jvnet.hk2.component.PostConstruct;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import org.jvnet.hk2.annotations.Inject;
import org.jvnet.hk2.annotations.Service;
import org.jvnet.hk2.annotations.Scoped;
import org.jvnet.hk2.component.Singleton;
import org.glassfish.flashlight.MonitoringRuntimeDataRegistry;
import org.glassfish.internal.api.Init;
import org.glassfish.flashlight.provider.ProbeProviderFactory;
import org.glassfish.flashlight.client.ProbeClientMediator;
import org.glassfish.flashlight.client.ProbeClientMethodHandle;
import java.util.Collection;
import org.glassfish.flashlight.datatree.TreeNode;
import org.glassfish.flashlight.datatree.factory.TreeNodeFactory;

/**
 * Monitoring Count Example
 * Bootstrap object for registering probe provider and listener
 *
 */
@Service
@Scoped(Singleton.class)
public class ModuleBootStrap implements Init, PostConstruct {

    @Inject
    private MonitoringRuntimeDataRegistry mrdr;

    @Inject
    protected ProbeProviderFactory probeProviderFactory;

    @Inject
    private ProbeClientMediator pcm;

    private TreeNode serverNode;

    public void postConstruct() {
        try {
            MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
            ObjectName name = new ObjectName("count.example.monitoring:name=countMBean");
            Module mbean = new Module();
            mbs.registerMBean(mbean, name);

            ModuleProbeProvider mpp = probeProviderFactory.getProbeProvider(
                    "count", "example", "countapp", ModuleProbeProvider.class);
            mbean.setProbeProvider(mpp);

            TreeNode serverNode = getServerNode();
            TreeNode countNode = TreeNodeFactory.createTreeNode("count", null, "example");
            serverNode.addChild(countNode);

            ModuleStatsTelemetry modTM = new ModuleStatsTelemetry(countNode);
            Collection<ProbeClientMethodHandle> handles = pcm.registerListener(modTM);
            modTM.setProbeListenerHandles(handles);
        } catch (Exception e) {
            System.out.println("Caught exception in postconstruct");
            e.printStackTrace();
        }
    }

    private TreeNode getServerNode() {
        TreeNode serverNode = null;
        if (mrdr.get("server") != null) {
            serverNode = mrdr.get("server");
        } else {
            serverNode = TreeNodeFactory.createTreeNode("server", null, "server");
            mrdr.add("server", serverNode);
        }
        return serverNode;
    }
}


Example 5–9 Listener Class

This example shows how to perform the following tasks:

The example also shows the code for processing the events and updating the monitoring statistics. To enable the listener to update statistics in the monitorable object tree, the parent of statistics object in the tree is passed in the constructor of this class.

package com.example.count.monitoring;

import java.util.Collection;
import org.glassfish.flashlight.client.ProbeClientMethodHandle;
import org.glassfish.flashlight.statistics.*;
import org.glassfish.flashlight.statistics.factory.CounterFactory;
import org.glassfish.flashlight.datatree.TreeNode;
import org.glassfish.flashlight.datatree.factory.*;
import org.glassfish.flashlight.client.ProbeListener;
import org.glassfish.flashlight.provider.annotations.ProbeParam;
import org.glassfish.flashlight.provider.annotations.*;

/**
 * Monitoring counter example
 * Telemtry object which listens to probe events and updates
 * the monitoring stats
 *
 */
public class ModuleStatsTelemetry{

    private Counter k = CounterFactory.createCount();
    private Collection<ProbeClientMethodHandle> handles;
   
    
    public ModuleStatsTelemetry(TreeNode parent) {
        k.setName("countMBeanCount");
        parent.addChild(k);
    }


    @ProbeListener("count:example:countapp:moduleCountIncrementEvent")
    public void moduleCountIncrementEvent(
        @ProbeParam("count") Integer count) {
        k.increment();
    }

    @ProbeListener("count:example:countapp:moduleCountDecrementEvent")
    public void moduleCountDecrementEvent(
        @ProbeParam("count") Integer count) {
        k.decrement();
    }


    public void setProbeListenerHandles(Collection<ProbeClientMethodHandle> handles) {
        this.handles = handles;
        // by default, the handles are enabled
        // following template is provided to enable/disable
        /*
        for (ProbeClientMethodHandle handle : handles) {
               handle.enable();
        }
        */
    }
    
}


Example 5–10 MBean Interface

This example defines the interface for a simple standard MBean that has methods to increase and decrease a counter by 1.

package com.example.count.monitoring;

/**
 * Monitoring counter example
 * ModuleMBean interface 
 *
 */
public interface ModuleMBean {
    public Integer getCount() ;
    public void incrementCount() ;
    public void decrementCount() ;
}


Example 5–11 MBean Implementation

This example illustrates how to send an event as explained in Sending an Event. The example shows code for sending events as follows:

The methods incrementCount and decrementCount are invoked by an entity that is beyond the scope of this example, for example, JConsole.

package com.example.count.monitoring;

/**
 * Monitoring counter example
 * ModuleMBean implementation 
 *
 */
public class Module implements ModuleMBean {
    private int k = 0;
    private ModuleProbeProvider mpp = null;

    public Integer getCount() {
        return k;
    }

    public void incrementCount() {
        k++;
        if (mpp != null) {
            mpp.moduleCountIncrementEvent(k);
        }
    }

    public void decrementCount() {
        k--;
        if (mpp != null) {
            mpp.moduleCountDecrementEvent(k);
        }
    }

    void setProbeProvider(ModuleProbeProvider mpp) {
        this.mpp = mpp;
    }

}