This page provides examples that describe how you can implement common use cases for the Agent API. See the javadoc at https://sdkdocs.appdynamics.com/java-agent-api/v20.6/.

Common Use Cases

The following are the common use cases for the Agent API.

Start and End a Synchronous Business Transaction

The following example shows code that starts a Business Transaction called 'Checkout' whenever the method checkout() is called. The Business Transaction ends when the method returns. Encapsulating the method body in a try or finally block ensures that you end the Business Transaction even if the method itself throws an exception or otherwise terminates without reaching the end.

public String checkout(List<ItemOrders> orders) {
   Transaction transaction = null;
   try {
        transaction = AppdynamicsAgent.startTransaction("Checkout", null, EntryTypes.POJO, false);
 
        /*******************
         * Method Body Here
         *******************/
    } finally {
        if (transaction != null) {
           transaction.end();
        }
    }
}
CODE

Alternatively, you can use try-with-resources pattern:

public String checkout(List<ItemOrders> orders) {
   try (Transaction transaction = AppdynamicsAgent.startTransaction("Checkout", null, EntryTypes.POJO, false)) {
        /*******************
         * Method Body Here
         *******************/
    }
}
CODE

In this case, the Business Transaction ends when the try block closes.

Start and End an Asynchronous Business Transaction

This example shows code that starts a Business Transaction called 'CheckoutAsync' whenever the method checkoutAsync() is called. The originating segment created for the Business Transaction ends when the method endSegment() is called on the Business Transaction, or when it is closed if used in a try-with-resources construct. Encapsulating the method body in a try/finally block ensures that we end the segment even if the method itself throws an exception or otherwise terminates without reaching the end.

//The thread where the Business Transaction starts
public String checkoutAsync(List<ItemOrders> orders) {
   Transaction transaction = null;
   try {
        transaction = AppdynamicsAgent.startTransaction("CheckoutAsync", null, EntryTypes.POJO, true);
		//mark handoff to link this segment with the end segment
		transaction.markHandoff(commonPayload);

        /*******************
         * Method Body Here
         *******************/
    } finally {
        if (transaction != null) {
           transaction.endSegment();
        }
    }
}
CODE


Alternatively, the try-with-resources pattern is supported:

//The thread where the Business Transaction starts
public String checkoutAsync(List<ItemOrders> orders) {
   try (Transaction transaction = AppdynamicsAgent.startTransaction("CheckoutAsync", null, EntryTypes.POJO, true)) {
		//mark handoff to link this segment with the end segment
		transaction.markHandoff(commonPayload);

        /*******************
         * Method Body Here
         *******************/
    }
}
CODE


This ends the segment in the thread where the Business Transaction was started when the try block closes. The Business Transaction itself needs to be ended in the method where the asynchronous Business Transaction ends:

//The thread where the Business Transaction ends
public String checkoutAsyncEnd(List<ItemOrders> orders, Transaction transaction, Object commonPayload) {
   //link to the originating segment
   Transaction transactionSegment = AppdynamicsAgent.startSegment(commonPayload);

   /*******************
   * Method Body Here
   *******************/
   if (transactionSegment != null) {
      transactionSegment.endSegment();
   }
   if (transaction != null) {
      transaction.end();
   }
}
CODE

Set the Transaction Name After a Start

The following example shows how to set the transaction name after it has been started. This is useful if you want to split transactions based on events that happen in your application:

Transaction name can only be set after a start on the originating segment, and if no async hand-offs or exits have been made.

public String checkout(List<ItemOrders> orders) {
   AppdynamicsAgent.startTransaction("Checkout", null, EntryTypes.POJO, false);
   /*******************
    * Method Body Here
    *******************/
   if (orders.isEmpty()) {
   	   AppdynamicsAgent.setCurrentTransactionName("Empty Cart");
   }
   AppdynamicsAgent.getTransaction().end();
}
CODE

Start a Transaction Using OOTB Servlet Naming Rules

The following example shows how to leverage automatic servlet naming rules to name a transaction:

public String checkout(List<ItemOrders> orders) {
	Map<String, String> headers = new HashMap<String, String>();
	headers.put("header1", "headerValue1");
	Map<String, String[]> parameters = new HashMap<String, String[]>();
	Map<String, Object> cookies = new HashMap<String, Object>();
	cookies.put("chip", "ahoy");
	ServletContext.ServletContextBuilder contextBuilder = new ServletContext.ServletContextBuilder()
        .withURL(new URL("https://www.yourstore.com/endpoint/to/checkout"))
        .withParameters(parameters)
        .withHeaders(headers)
        .withCookies(cookies)
        .withSessionAttributes(new HashMap<String, Object>())
        .withRequestMethod("GET")
        .withHostValue("hostValue")
        .withHostOriginatingAddress("hostOriginatingAddress");
	Transaction t = AppdynamicsAgent.startServletTransaction(contextBuilder.build(), EntryTypes.HTTP, null, false);
   /*******************
    * Method Body Here
    *******************/
   t.end();
}
CODE

Define an Exit Call

For an inventoryServer.verifyQuantities(orders), which makes a request to another process, you can monitor that request as an Exit Call. This helps to continue monitoring the Business Transaction through the call to the downstream server and identify the time spent in the remote service. You can do this by modifying the method as follows:

public void verifyQuantities(List<ItemOrders> orders) {
    ExitCall exitCall = null;
    try {
        exitCall = AppdynamicsAgent.getTransaction().startExitCall("Quantity Check", "Inventory Server", ExitTypes.HTTP, false);
         
       /*******************
        * Method Body
        *******************/
    } finally {
        if (exitCall != null) {
            exitCall.end();
        }
    }
}
CODE

Alternatively, if you want to use HTTP Backend detection rules, you can use the startHttpExitCall method: 

public void verifyQuantities(List<ItemOrders> orders) { 
    ExitCall exitCall = null; 
    try { 
         Map<String, String> identifyingProps = new HashMap<String, String>(); 
         URL url = new URL("https://www.appdynamics.com/solutions/microservices/"); 
           exitCall = AppdynamicsAgent.getTransaction().startExitCall(identifyingProps, url, false); 
           /******************* 
           * Method Body 
           *******************/ 
     } finally { 
        if (exitCall != null) { 
            exitCall.end();
        } 
     } 
}
CODE

The above code modifications define the Exit Call that manifests it as a remote service in the controller. To tag and follow the request into an instrumented downstream tier, add a correlation header:

public void verifyQuantities(List<ItemOrders> orders) {
    ExitCall exitCall = null;
    try {
        exitCall = AppdynamicsAgent.getTransaction().startExitCall("Quantity Check", "Inventory Server", ExitTypes.HTTP, false);
 
        // Generate the appdynamics correlation header
        String correlationHeader = exitCall.getCorrelationHeader();
 
        // ... Method code including request creation
 
        // Modify the request/payload to include the correlation header
        inventoryRequest.addHeader(AppdynamicsAgent.TRANSACTION_CORRELATION_HEADER, correlationHeader);
     
    } finally {
        if (exitCall != null) {
            exitCall.end();
        }
    }
}
CODE

The following example shows how to instrument an asynchronous exit:

public class StartEndAsyncExitCall {

    private void makeAsyncExitTransaction() throws Exception {
        System.out.println("Starting transaction");
        try (Transaction transaction = AppdynamicsAgent.startTransaction("StartEndAsyncExitCall", null, EntryTypes.POJO, true)) {
            Thread.sleep(1000);
            new TransactionEndingThread(transaction).start();
            System.out.println("Starting exitcall");
            ExitCall exitCall = null;
            exitCall = transaction.startExitCall("Custom", "Custom", ExitTypes.CUSTOM, true);
            new ExitCallEndingThread(exitCall).start();
        }
    }

    public class TransactionEndingThread extends Thread {
        Transaction transaction = null;

        TransactionEndingThread(Transaction transaction) {
            this.transaction = transaction;
        }

        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            transaction.end();
            System.out.println("Ending transaction");
        }
    }

    public class ExitCallEndingThread extends Thread {
        ExitCall exitCall = null;

        ExitCallEndingThread(ExitCall exitCall) {
            this.exitCall = exitCall;
        }

        public void run() {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            exitCall.end();
            System.out.println("Ending exitcall");
        }
    }
}
CODE

Define an Asynchronous Thread Handoff

If your checkout method also does a thread handoff and executes some business logic that you want to monitor in a separate thread, register the worker thread with the Business Transaction:

public String checkout(List<ItemOrders> orders) {
    Transaction transaction = null;
    try {
        transaction = AppdynamicsAgent.startTransaction("Checkout", null, EntryTypes.POJO, false);
 
        // ... Method code
 
        // Custom thread handoff using custom queue
        asyncTaskQueue.add(task);
         
    } finally {
        if (transaction != null) {
           transaction.end();
        }
    }
}
CODE


To instrument this, modify the add method to mark a thread handoff and then start a new segment where the thread begins running:

public class AsyncTaskQueue {
    public void add(Task task) {
        AppdynamicsAgent.getTransaction().markHandoff(task);
         
        /*******************
         * Method Body
         *******************/
}
CODE
public class Task {
    public void run() {
        Transaction transaction = null;
        try {
            transaction = AppdynamicsAgent.startSegment(this);
 
            /*******************
             * Method Body
             *******************/
        } finally {
            if (transaction != null) {
                transaction.endSegment();
            }
        }
    }
 
    public void cancel() {
        AppdynamicsAgent.cancelHandoff(this);
 
        /*******************
         * Method Body
         *******************/
    }
}
CODE


The task object is used by the agent to link the segments. Correlating thread segments using the agent API requires that the agent is running in the Executor mode.

Add Data to Snapshot or Analytics

In many instances, there are values of interest in the code that are helpful to add to the snapshot. This aids in the root cause diagnosis of issues or to send to the AppDynamics Business Transaction analytics to help answer real-time business-oriented questions about your application. Data reported using this API appears in the same way as it had been collected with a Method Invocation Data collector.

To report a total checkout amount to Business Transaction analytics and have it presented in the APM snapshots, use the following code:

private static final Set<DataScope> dataScopeSet = new HashSet(Arrays.asList(DataScope.ANALYTICS, DataScope.SNAPSHOTS));
 
public String checkout(List<ItemOrders> orders) {
    Transaction transaction = null;
    try {
        transaction = AppdynamicsAgent.startTransaction("Checkout", null, EntryTypes.POJO, false);
 
        // ... Method code
 
        double shoppingCartTotal = total(orders);
        transaction.collectData("cart total", Double.toString(shoppingCartTotal), dataScopeSet);
         
    } finally {
        if (transaction != null) {
           transaction.end();
        }
    }
}
CODE

Define a Custom Metric or Event

It can also be useful to report a value as a custom metric:

public String checkout(List<ItemOrders> orders) {
 
    // ... Method code
 
    double shoppingCartTotal = total(orders);
    AppdynamicsAgent.getMetricPublisher().reportSumMetric("Cart Total", (long) shoppingCartTotal);
 
}
CODE

It is recommended to provide a metric path along with the metric name in the code with format: Server|Component:<TierName>|Custom Metrics|<MetricName>, while publishing the metric.


Reporting custom metrics and events is possible irrespective of the Business Transaction context.