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:
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.
Counter statistics typically correspond to a single event. For example, to calculate the number of received requests, only one event is required, for example, a “request received” event. Every time that a “request received” event is sent, the number of received requests is increased by 1.
Timer statistics typically correspond to multiple events. For example, to calculate the time to process a request, two requests, for example, a “request received” event and a “request completed” event.
Defining statistics that are to be monitored involves the following tasks:
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.
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.
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.
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.
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.
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:
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.
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 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.
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:
Your choice of name for the add-on component that is to send the event.
Your choice of name for the provider.
Your choice of name for the application that the add-on component represents. The application-name can be null.
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.
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); ... }
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.
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 } ... }
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:
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:
The return value of all callback methods in the listener must be void.
Because the methods of your event provider class may be entered by multiple threads, the listener must be thread safe.
The listener must have the same restrictions as a Java Platform, Enterprise Edition (Java EE) application. For example, the listener cannot open server sockets, or create threads.
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.
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.
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"
The @ProbeListener annotation is an unstable interface and is subject to change.
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.
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.
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.
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.
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 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.
To turn on monitoring, invoke the enable method. If monitoring is turned on, a functional implementation of methods in is generated when the event provider is registered.
To turn off monitoring, invoke the disable method. If monitoring is turned off, a nonoperational implementation of methods in is generated when the event provider is registered.
For information about how to register an event provider, see Registering an Event Provider.
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:
The TxListener constructor
The setProbeListenerHandles method
... 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 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 the component in the monitorable object tree
Creating objects to represent the component's statistics in the monitorable object tree
Getting the server node object
Adding each object to the tree
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:
A string that contains the name of the node
An instance of the class that represents the component
A string that describes the category of the component
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.
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:
Provides averages.
Provides a counter that a class can use to maintain count.
Provides timing information in seconds.
Provides timing information in milliseconds.
Provides timing information in nanoseconds.
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 |
---|---|
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.
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:
If the object represents an add-on component, the parent is the server node object.
If the object represents a statistic, the parent is the TreeNode object that represents the add-on component.
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
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.
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.
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:
The component that is represented by the class TransactionManagerImpl is added as a child of the server node of the tree. The name of the node is tx. The category is transactions. The dotted name of the monitorable object that represents the component is server.tx.
The totaltransactioncount statistic is added as a child of the tx node of the tree. The dotted name of the monitorable object that represents the statistic is server.tx.totaltransactioncount.
... 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; } ... }
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 | |
ModuleBootStrap | |
ModuleStatsTelemetry | |
Module | |
ModuleMBean |
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:
moduleCountIncrementEvent
moduleCountDecrementEvent
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 ); }
This example illustrates how to perform the following tasks:
Registering an Event Provider. The example shows the code for registering the event provider and passing a reference to the event provider implementation to the MBean implementation Module.
Adding Statistics for a Component to the Monitorable Object Tree. The example shows the code for adding the component as a child of the server node of the tree.
Registering an Event Listener. The example shows the code for registering an instance of the listener class ModuleStatsTelemetry.
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; } }
This example shows how to perform the following tasks:
Creating Event Listeners. The example shows the code of the ModuleStatsTelemetry listener class.
Subscribing to Events From Event Provider Classes. This example shows the code for subscribing to the following types of events from the count component:
moduleCountIncrementEvent
moduleCountDecrementEvent
The provider of the component is example. The application name is countapp.
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(); } */ } }
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() ; }
This example illustrates how to send an event as explained in Sending an Event. The example shows code for sending events as follows:
The moduleCountIncrementEvent method is invoked in the body of the incrementCount method.
The moduleCountDecrementEvent method is invoked in the body of the decrementCount method.
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; } }