Skip to content

Transactions

Jason Keller edited this page Jun 29, 2021 · 1 revision

Java agent transactions

Starting a transaction

These are the options for starting a transaction with the Java agent.

Trace annotation

Trace annotation documentation

Example

package com.example;

import com.newrelic.api.agent.Trace;

public class MyClass {
    public static void main(String[] args) {
        while (true) {
            myMethod();
        }
    }

    @Trace(dispatcher = true)
    public static void myMethod() {
        System.out.println("Hello, World");
    }
}

XML instrumentation

XML instrumentation doumentation

Example

<?xml version="1.0" encoding="UTF-8"?>
<extension xmlns="https://newrelic.com/docs/java/xsd/v1.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="newrelic-extension extension.xsd"
    name="customExtension" version="1.0">
  <instrumentation metricPrefix="Example">
    <pointcut transactionStartPoint="true">
      <className>com.example.MyClass</className>
      <method>
        <name>myMethod</name>
      </method>
    </pointcut>
  </instrumentation>
</extension>

Custom Instrumentation Editor (CIE)

CIE documentation

This generates XML but does so using an online UI that allows you to select methods to instrument from thread profiles. The XML is sent down to the agent which stores it in memory.

Internal APIs

Using internal Transaction API to get and start a transaction: com.newrelic.agent.Transaction.getTransaction(true)

    /**
     * Get this thread's reference to a transaction, creating it if this thread has no Transaction reference and
     * creation is requested and allowable.
     *
     * @param createIfNotExists true to request creation of the object
     * @return the transaction. Return null if createIfNotExists is false and the transaction has not previously been
     * created, or if the current thread is an Agent thread.
     */
    public static Transaction getTransaction(boolean createIfNotExists) {
    }

Using internal AgentBridge API to get and start a transaction: AgentBridge.getAgent().getTransaction(true)

    /**
     * Get the transaction stored in a thread local.
     *
     * @param createIfNotExists if true, create a transaction if needed.
     * @return the transaction in the thread local or possibly null if createIfExists == false
     */
    Transaction getTransaction(boolean createIfNotExists);

Components of a transaction

Transaction

Transaction represents a single execution path of code.

/**
 * Represents a single transaction in the instrumented application. A transaction is a single top-level activity, such
 * as servicing a single web request or a large-grained unit of background work.<br/>
 * <br/>
 * Transactions are composed of one or more per-task work units, each of which is represented by a TransactionActivity
 * object. The Transaction and the TransactionActivity are each referenced by a thread local variable.<br/>
 * <br/>
 * Transactions are created by instrumented method invocations. Creation of the Transaction always creates a
 * TransactionActivity. If an instrumented thread declares itself asynchronous, its Transaction is discarded and its
 * TransactionActivity becomes associated with the top-level Transaction that originated it.<br/>
 * <br/>
 * TransactionActivities are closed when the last tracer on their call stack is popped off. Transactions are closed when
 * their last child TransactionActivity is finished and no further activities are pending.<br/>
 * <br/>
 * This class is thread-safe.
 */
public class Transaction {
}

TransactionActivity

TransactionActivity represents all of the activity on a specific thread.

/**
 * This class tracks the state of a single asynchronous activity within a transaction. An instance of this class may be
 * associated with e.g. a thread, the execution of a task on pool thread, or the execution of a "coroutine" on a thread.<br>
 * <br>
 * This class is not threadsafe, but is typically referenced by two distinct threads during its lifetime. First, it
 * holds state for its associated thread (or task, etc.) until its root tracer completes. It may then be queued
 * (indirectly through its owning transaction) for harvest, during which it is referenced by the harvest thread. These
 * two usages never overlap in time. Memory visibility is assured because the transaction will pass through a
 * synchronized object (e.g. atomic or concurrent collection) that has the effect of inserting a full barrier between
 * the async activity's last write and the harvest thread's first read.
 */
public class TransactionActivity {
}

Tracer

A Tracer records information about a method invocation. There are a number of Tracer implementations for different use cases (e.g. DefaultTracer, DefaultSqlTracer, and UltraLightTracer).

/**
 * A tracer records information about a method invocation - primarily the start and stop time of the invocation. A
 * tracer instance is associated with a single method invocation.
 *
 * Tracers are created by {@link TracerFactory} instances.
 */
public interface Tracer extends TimedItem, ExitTracer, ErrorTracer {
}

Segment

Segment is similar to a TracedMethod but represents an arbitrary piece of time rather than the time it takes for a method to execute.

/**
 * <p>
 * Represents a timed unit of work. Like a {@link TracedMethod}, reports a single metric,
 * generates a single segment in a transaction trace, and can be reported as an external call. Unlike a TracedMethod,
 * a Segment's timed duration may encompass arbitrary application work; it is not limited to a single method or thread.
 * </p>
 * <p>
 * Timing begins when the instance is created via {@link Transaction#startSegment} and ends when the {@link #end()}
 * or {@link #ignore()} method is called. These calls can be issued from distinct application threads. If a Segment is
 * not explicitly ignored or ended it will be timed out according to the <code>segment_timeout</code> value which is
 * user configurable in the yaml file or by a Java system property.
 * </p>
 * <p>
 * A {@link Segment} will show up in the Transaction Breakdown table, as well as the Transaction Trace page in APM.
 * </p>
 */
public interface Segment extends AttributeHolder {
}

TracedMethod

TracedMethod is extended by Tracer and represents the time it takes for a method to execute.

/**
 * Represents a single instance of the timing mechanism associated with a method that is instrumented using the
 * {@link Trace} annotation.
 * 
 * @see Agent#getTracedMethod()
 */
public interface TracedMethod extends AttributeHolder {
}

Token

A Token is used to link asynchronous units of work to the originating Transaction.

/**
 * Tokens are passed between threads to link asynchronous units of work to the originating {@link Transaction}
 * associated with the token. This results in two or more pieces of work being tied together into a single
 * {@link Transaction}. A token is created by a call to {@link Transaction#getToken()}.
 */
public interface Token {
}

Transaction hierarchy

A Transaction is created on the initiating thread and has a TransactionActivity representing the activity on each thread. Each TransactionActivity has a Tracer for each TracedMethod that was invoked on its associated thread. A TransactionActivity occurring on a thread other than the initiating thread is linked to the Transaction using a Token passed from the initiating thread.

Transactions

Lifecycle of a transaction

The lifcycle of a Transaction is managed by the TransactionService.

Initialization

  1. Methods annotated with @Trace are processed by the TraceMatchVisitor
  2. InstrumentationImpl.createTracer starts the process of creating a Transaction.
  3. com.newrelic.agent.Transaction.getTransaction(true) is used to create a Transaction. This code path is the only correct way to create a Transaction.
  4. com.newrelic.agent.Transaction.postConstruct() creates a TransactionActivity for the initiating thread of the Transaction.
  5. The Transaction is stored in the ThreadLocal for easy access.
  6. InstrumentationImpl.startTracer creates the Tracer and records the start time.
  7. TransactionActivity.tracerStarted saves the Tracer on the TransactionActivity call stack, ensuring that the intial Tracer is saved as the root Tracer.
  8. com.newrelic.agent.Transaction.activityStarted starts the TransactionActivity for the thread.
  9. com.newrelic.agent.Transaction.startTransactionIfBeginning starts the Transaction and notifies the TransactionService of that.

Processing and finalization

  1. DefaultTracer.finish is called when a method completes.
  2. DefaultTracer.performFinishWork stops timing for the Tracer, notifies the parent Tracer (if one exists) of the child Tracer's finish, and records response time and external metrics representing the method execution.
  3. TransactionActivity.tracerFinished records that a Tracer has finished and removes it from its call stack.
  4. TransactionActivity.finished is called when the root Tracer is successfully popped off the call stack, as we know that it should be the last Tracer to be dealt with.
  5. com.newrelic.agent.Transaction.activityFinished closes the TransactionActivity and tracks each finished child TransactionActivity.
  6. com.newrelic.agent.Transaction.finishTransaction is called when all child TransactionActivities have been finished. It then finalizes the Transaction name and any statistic calculations. It also notifies the TransactionService that the Transaction has finished.
  7. OtherDispatcher.transactionFinished records some final metrics and Transaction attributes.
  8. TransactionService.transactionFinished calls doProcessTransaction which notifies all implementations of TransactionListener, ExtendedTransactionListener, and TransactionStatsListener that the Transaction has been finished and that they should do something based off of that. One such TransactionListener that gets notified is the TransactionEventsService which creates a TransactionEvent for the Transaction in dispatcherTransactionFinished. Another such TransactionListener that gets notified is the SpanEventsServiceImpl which creates a SpanEvent for every Tracer of the Transaction in dispatcherTransactionFinished.
  9. com.newrelic.agent.Transaction.activityFinished closes and removes the Transaction from the ThreadLocal.

Java agent bytecode weaving

Given the original code, the Java agent intercepts the MyClass class file while it is being loaded, determines that it is annotated with @Trace, weaves new bytecode to inject New Relic APIs into the class file, and then returns the class back to the class loader to finish being loaded. Below are examples of the original code versus the weaved code.

Original code

package com.example;

import com.newrelic.api.agent.Trace;

public class MyClass {
    public static void main(String[] args) {
        while (true) {
            myMethod();
        }
    }

    @Trace(dispatcher = true)
    public static void myMethod() {
        System.out.println("Hello, World");
    }
}

Code weaved with New Relic APIs

/*
 * Decompiled with CFR 0.151.
 *
 * Could not load the following classes:
 *  com.newrelic.agent.bridge.AgentBridge
 *  com.newrelic.agent.bridge.ExitTracer
 *  com.newrelic.agent.instrumentation.InstrumentationType
 *  com.newrelic.agent.instrumentation.InstrumentedClass
 *  com.newrelic.agent.instrumentation.InstrumentedMethod
 *  com.newrelic.api.agent.Trace
 */
package com.example;

import com.newrelic.agent.bridge.AgentBridge;
import com.newrelic.agent.bridge.ExitTracer;
import com.newrelic.agent.instrumentation.InstrumentationType;
import com.newrelic.agent.instrumentation.InstrumentedClass;
import com.newrelic.agent.instrumentation.InstrumentedMethod;
import com.newrelic.api.agent.Trace;

@InstrumentedClass
public class MyClass {
    public static void main(String[] args) {
        while (true) {
            MyClass.myMethod();
        }
    }

    @InstrumentedMethod(dispatcher=true, instrumentationNames={"MyClass.java"}, instrumentationTypes={InstrumentationType.TraceAnnotation})
    @Trace(dispatcher=true)
    public static void myMethod() {
        ExitTracer exitTracer = null;
        try {
            try {
                exitTracer = AgentBridge.instrumentation.createTracer(null, 5, null, 30);
            }
            catch (Throwable throwable) {}
            System.out.println("Hello, World");
            if (exitTracer != null) {
                exitTracer.finish(177, null);
            }
            return;
        }
        catch (Throwable throwable) {
            if (exitTracer != null) {
                throwable = throwable;
                exitTracer.finish(throwable);
            }
            throw throwable;
        }
    }
}