Using A TransactionMonitor

A TransactionMonitor is a Monitor that implements the CompositeMonitor interface and can be used to gather data about a unit of work. In many cases, a TransactionMonitor is used to monitor a method call on some object in the system.

TransactionMonitors assist you in collecting the typical information needed for a unit of work: name, start time, stop time, latency, success/failure, the Throwable that is the cause of a failure, and an indication of whether it failed due to a business failure.

Sounds neat, how do I use it?

Here's some production code that uses TransactionMonitor.

public void doFoo()
        throws FooException {
    TransactionMonitor monitor = new TransactionMonitor(getClass(), "doFoo");
 
    try {
        // Do some work here
 
        monitor.set("result", result, false);
        monitor.succeeded();
    } catch (FooException e) {
        monitor.failedDueTo(e);
        throw e;
    } finally {
        monitor.done();
    }
}

There's a lot going on here, so we'll go line by line, explaining what's happening at each step.
TransactionMonitor monitor = new TransactionMonitor(getClass(), "doFoo");

This line creates a TransactionMonitor with the name <className>.doFoo. It also notes the start time in an attribute name "startTime".
monitor.set("result", result, false);

This saves an additional attribute on this monitor. You're encouraged to add additional attributes to monitors like this. Just pick a name that describes the attribute clearly; ERMA convention is to use camel case for the names. Might as well stick with that.
monitor.succeeded();

This marks this transaction as having succeeded.
monitor.failedDueTo(e);

This marks this transaction as having failed due to the Throwable provided. The toString() output of the supplied throwable will be saved in an attribute called "failureThrowable".
monitor.done();

This tells ERMA that the transaction is done. ERMA will note the end time (in an attribute called "endTime"), calculate the latency (in an attribute called "latency"), and then submit itself to the MonitoringEngine for processing.

Hey, what happens if a RuntimeException is thrown?

The TransactionMonitor follows a convention of "failed until proven successful." This means that if you don't call monitor.succeeded() the transaction will be marked as failed. This un-American behavior has the benefit of not requiring your code to catch Throwable and rethrow in order to note failure correctly. You should only catch checked exceptions and any exceptions you're interested in noting in your monitoring.

TransactionMonitor monitor = new TransactionMonitor("exceptionIndicatesFailure");
try {
    possibleRuntimeExceptionMethod();
 
    monitor.succeeded();
} finally {
    monitor.done();
}

This code will correctly mark cases where a RuntimeException was thrown as failed.

What happens if I forget to call done?

If you forget to call done, when the layer above you processes it's monitor, the MonitoringEngine will notice that it has an unprocessed monitor on the top of its stack. In that case, it will process the monitor for you.

This, however, doesn't mean that everything will look normal. Some data is collected inside TransactionMonitor.done() before processing the TransactionMonitor. When the MonitoringEngine forces the process to occur, it will not call the code that collects this information. Therefore, in the case of the TransactionMonitor, your forcibly processed monitor won't have data like endTime and latency. This is intentional, since otherwise the misstep in coding would be difficult to find.

Naming Restrictions

There are format restrictions on the names you can use for monitors and attributes. This is to preserve our sanity and simplify issues downstream.

Monitor names and attribute names must conform to this regular expression:

\[a-zA-Z\]+\[a-zA-Z_0-9\]\*

If they do not, a RuntimeException will be thrown when you exercise the application code in question.

h1. Additional Lifecycle Requirements&nbsp;

There is one more line of code you must add to your instrumentation if the spot in question will be the "topmost" usage of ERMA monitors within the current thread.

MonitoringEngine.getInstance().clearCompositeRefsForCurrentThread();

That line should occur just before the topmost monitor is constructed in your code. That call is a lifecycle signal, essentially, which lets your app tell ERMA that you expect there to be no pre-existing, higher-level monitors (called CompositeMonitor's in ERMA lingo, of which TransactionMonitor is the canonical example) at this point in the current thread.

But I Don't Like Cancer!

If you're not down with the try/catch/finally stuff check out ERMA Simplified

Or, try using AOP

Here's an example of an AOP approach using Spring-AspectJ, from orbitz-web-wl-RTI/resources/aop-config.xml:

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
 
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
 
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"
       default-autowire="no" default-lazy-init="false" default-dependency-check="none">
 
    <aop:config>
        <aop:aspect id="transactionMonitorActionAspect" ref="transactionMonitorActionAdvice">
 
            <aop:pointcut id="transactionMonitorActionPointcut"
                expression="target(org.springframework.webflow.execution.Action) and args(context)"/>
 
            <aop:around pointcut-ref="transactionMonitorActionPointcut" method="invoke"/>
 
        </aop:aspect>
    </aop:config>
 
    <bean id="transactionMonitorActionAdvice" class="com.orbitz.webframework.aop.aspectj.TransactionMonitorActionAdvice"/>
</beans>

The com.orbitz.webframework.aop.aspectj.TransactionMonitorActionAdvice code is specific to webapp monitoring, but you can easily create your own advice class. Here is the method that is applied by the config above:
public Object invoke(ProceedingJoinPoint pjp, RequestContext context) throws Throwable {
 
        TransactionMonitor monitor = new TransactionMonitor(getMonitorName(pjp, context));
 
        try {
            Object result = pjp.proceed();
            if (result != null && ClassUtils.isAssignableValue(Event.class, result)) {
                Event event = (Event) result;
                monitor.set("eventId", event.getId());
            }
            monitor.succeeded();
            return result;
        } catch (Throwable t) {
            monitor.failedDueTo(t);
            throw t;
        } finally {
            monitor.done();
        }
    }

More info is available at http://static.springframework.org/spring/docs/2.0.x/reference/aop.html.

What about performance? You may have heard that AOP adds too much overhead. But the Spring-AspectJ approach applies advice at bean loading time once on startup. Then the subsequent overhead is no different than the rest of the dynamic proxy stuff that is commonly found in the framework. So, performance shouldn't be a problem. You should still perform load tests to be sure this is true in your case, of course. As a general rule, don't apply any TransactionMonitor approach at too fine-grained a level.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License