Download PDF
Download page iSDK Overview.
iSDK Overview
The instrumentation SDK (iSDK) part of the Java Agent API enables you to create custom instrumentation to apply to the code in your application at runtime, and avoid making development time changes to instrument the code.
You can use the iSDK to implement interceptors making use of the imperative Java Agent APIs and apply custom instrumentation to proprietary applications or frameworks. This custom instrumentation works with the out-of-the-box instrumentation provided by the agent.
iSDK users need to create separate custom interceptor jar files that instrument their code at application startup when deployed with the Java Agent in the sdk-plugins
directory. To remove custom instrumentation, remove the custom interceptor jar files from the agent deployment.
Use the iSDK
This procedure outlines how to use the iSDK:
- Set up your development environment.
- Set up your project.
- Implement your interceptor(s).
- Build your files.
- Deploy.
Set Up Your Development Environment
To use the iSDK, you must write code with the external dependency of the iSDK library. To do this, these tools must be available:
- A Java IDE – Eclipse or IntelliJ. The following examples are for IntelliJ usage.
- Gradle.
Set Up Your Project
To set up your project:
- In IntelliJ, create a new Gradle project for your work. The
build.gradle
file is created in the project directory that you specify. - Add the required information to your project's
build.gradle
file by following the procedure in the next section.
Install the Dependency
To use the iSDKs, do one of the following:
- Access the Agent API jar directly
- Download it from Maven Central
- Download it from the AppDynamics portal
The library version changes with each new API release, and is not tightly coupled to the version of the underlying agent.
For use with Maven Central, add the dependency to your build files in one of the following:
- Gradle:
dependencies {
compile group: 'com.appdynamics.agent', name: 'agent-api', version: '20.6.0.30246'
}
or
- Maven:
<dependency>
<groupId>com.appdynamics.agent</groupId>
<artifactId>agent-api</artifactId>
<version>20.6.0.30246</version>
</dependency>
See https://sdkdocs.appdynamics.com/java-agent-api/v20.6/ for Javadoc reference for the Agent API.
Refresh your gradle project in intellij and run "gradlew idea" if you want intellij to reference the source properly when auto-populating methods and so on.
After this setup, you must be able to use Intellij to create a class, extend an iSDK template class, and auto-populate missing methods.
Deploy the iSDK with Agent API
To leverage Agent API functionality with the iSDK, you must package it with your custom interceptor jar. To accomplish this in Grade:
Create a configuration for iSDK:
configurations { isdk }
CODEAdd the Agent-API dependency to this configuration:
dependencies { compile group: 'com.appdynamics.agent', name: 'agent-api', version: '20.6.0.30246' isdk group: 'com.appdynamics.agent', name: 'agent-api', version: '20.6.0.30246' … }
CODEInside your task for generating the interceptors jar, include the
AppdynamicsAgent
class inside the jar:jar { configurations.isdk.collect { it.isDirectory() ? from(it) : from(zipTree(it)) { include 'com/appdynamics/agent/api/AppdynamicsAgent.class' } } includeEmptyDirs = false }
CODE
To accomplish this in Maven, you must write an Ant Task to include the AppdynamicsAgent class with your jar.
Implement Your Interceptor
Class or Method Identification
For all use cases, you must identify the class and method that you wish to instrument. This is also true when configuring custom POJO instrumentation using the User Interface but the technique is much different in the SDK.
Create Custom Interceptors and Rules
Implement Your Instrumentation Logic
The iSDK provides a class called AGenericInterceptor,
which must be subclassed with the desired custom interceptor logic.
The agent examines .jar files deployed to the agent's sdk-plugins
directory and looks for classes that extend AGenericInterceptor
. These classes must implement the required abstract methods.
One key method all interceptors must implement is the initializeRules
method, which returns a list of objects that extend the IRule interface. These IRules represent where in the code you must apply a given interceptor (class name, method name, method params, and so on).
If interceptors need to perform reflection on any of the java objects used in the instrumented code, an interface, IReflector
, is provided to facilitate such reflection at runtime.
Important Methods to Implement in AGenericInterceptor
initializeRules
- Returns a list of objects that describe where in the code the interceptor must be placedonMethodBegin
- Any code in the body of this method is run before the execution of the instrumented methodonMethodEnd
- Any code in the body of this method is run after the execution of the instrumented method
AGenericInterceptors
can be combined with the Agent API to start or end transactions, add data to snapshots, and so on.
These interceptors can be applied anywhere in the code, rather than just the beginning or end of a method. Also, they collect local variable information. Specific methods in the Rule builder, such as atLineNumber
, atField
, atLocalVariable
, and so on only apply to this variety of iSDK interceptor.
- jars placed directly in the top-level "sdk-plugins" directory are also loaded.
- You can use the manifest attribute "Plugin-Classes: <comma separated list of fully qualified class names>" in your plug-in jar to specify specific classes to load as plug-ins. This improves jar loading performance, if the plug-in jar contains many classes that are not plug-ins.
Use the Rule and Rule.Builder Classes
A Rule
defines a set of classes to instrument. Your iSDK code returns a java.util.List
of Rule
objects from a method named initializeRules. Rules
are created using the builder pattern – a Rule.Builder
object is created using a match string:
Rule.Builder builder = new Rule.Builder("com.mynamespace.MyClass");
See the javadocs for all classes referenced in this section.
This is similar to what you have to do in the UI. For example, in the Add Information Point configuration screen, you must specify a name to match on – either a concrete class name, abstract class name, interface name or annotation name. The argument to the Rule.Builder
constructor is the same as what you specify into the class match text box in the image below.
You then modify the Rule.Builde
r by calling APIs on the Rule.Builder
class. Rule.Builder
supports fluent style so that your code can be compact.
You need the following:
- Class match string: Given in the constructor to
Rule.Builder
- Class match type: Set the
SDKClassMatchType
usingbuilder.setClassMatchType
- Class match string match type: Set the
SDKStringMatchType
for the method usingbuilder.classStringMatchType
- Method match string using
builder.setMethodMatchString
- Method match type: Set the
SDKStringMatchType
for the method usingbuilder.methodStringMatchType
The following is a screenshot of an initializeRules() method for creating an arbitrary interceptor. Note the following:
- The style is a mixture of fluent and iterative (lines have chained invocations, but multiple lines are still used)
- The class match is for an exact classname match to a concrete class (
SDKClassMatchType.MATCHES_CLASS
andSDKStringMatchType.EQUALS
forclassStringMatchType
) - The method match is for an exact method name string match (
SDKStringMatchType.EQUALS
formethodStringMatchType
)
Implement the Interceptor Logic
The Java code to be run when the custom interceptor is hit is provided in the onMethodBegin
and/or onMethodEnd
methods of the interceptor object.
A code example of a simple interceptor that leverages the Agent API is shown below.
public class SampleGenericInterceptor extends AGenericInterceptor {
public SampleGenericInterceptor() {
super();
}
@Override
public List<Rule> initializeRules() {
List<Rule> rules = new ArrayList<Rule>();
rules.add(new Rule.Builder("Main").methodMatchString("genericInterceptorTarget").build());
return rules;
}
public Object onMethodBegin(Object invokedObject, String className, String methodName, Object[] paramValues) {
System.out.print("In begin");
AppdynamicsAgent.startTransaction("SampleGenericInterceptorBT", null, EntryTypes.POJO, false);
Map<String, String> keyVsValue = new HashMap<String, String>();
keyVsValue.put("foo", "bar");
AppdynamicsAgent.getEventPublisher().publishEvent("an event happened", "INFO", "AGENT_STATUS", keyVsValue);
return null;
}
public void onMethodEnd(Object state, Object invokedObject, String className, String methodName,
Object[] paramValues, Throwable thrownException, Object returnValue) {
System.out.print("In end");
Transaction currentTransaction = AppdynamicsAgent.getTransaction();
Set<DataScope> dataScopes = new HashSet<DataScope>();
dataScopes.add(DataScope.SNAPSHOTS); // collect data for BT snapshot.
currentTransaction.collectData("key", "value", dataScopes);
currentTransaction.end();
}
}
Use the IReflector Interface
All iSDK classes have access to a toolbox interface called IReflector
. This interface provides functionality that enables the use of Java reflection. Within any class that extends one of the template classes, the user can call getNewReflectionBuilder()
to get an instance of IReflectionBuilder
.
getNewReflectionBuilder()
must be called in the constructor of your plug-in class. You may hit a NullPointerException
if it is called elsewhere.
This interface supports the following methods:
/**
* load a class. classLoader specified in the IReflector.execute method
**/
IReflectionBuilder loadClass(String className);
/**
* Create a new object
* @param className class to create
* @param argTypes argument types
* @return this
*/
IReflectionBuilder createObject(String className, String... argTypes);
/**
* Invoke a static method
* @param methodName method to invoke
* @param searchSuperClass search super classes for the method. Else only declared methods are called
* @param argTypes for overloaded methods
* @return this
*/
IReflectionBuilder invokeStaticMethod(String methodName, boolean searchSuperClass, String... argTypes);
/**
* Similar to invoke static method, invoke a instance method
* @param methodName method to invoke
* @param searchSuperClass search super classes for the method. Else only declared methods are called
* @param argTypes for overloaded methods
* @return this
*/
IReflectionBuilder invokeInstanceMethod(String methodName, boolean searchSuperClass, String... argTypes);
/**
* Access a field
* @param fieldName field name to access
* @param searchSuperClass should search super class for the field name
* @return this
*/
IReflectionBuilder accessFieldValue(String fieldName, boolean searchSuperClass);
These can be called in any order, essentially forming a getter chain. After crafting your chain of calls, the build() method must be called, returning an IReflector.
The IReflector is used by invoking its execute() method:
/**
* Execute the reflector chain
* @param classLoader required - classloader referenced to load classes
* @param target The steps will start executing on this object. null if first step is loadClass or staticMethod
* @param params params to be passed to the steps
* @return returns with correct cast
* @throws ReflectorException
*/
<E> E execute(ClassLoader classLoader, Object target, Object[]... params) throws ReflectorException;
/**
* Execute the reflector chain
* @param classLoader required - classloader referenced to load classes
* @param target The steps will start executing on this object. null if first step is loadClass or staticMethod
* @param params params to be passed to the steps
* @return returns with correct cast
* @throws ReflectorException
*/
<E> E execute(ClassLoader classLoader, Object target, OperationParams params)
throws ReflectorException;
The use of these reflectors is often critical for getting the desired functionality (extracting values from payloads, decorating objects with correlation headers, and so on).
Java Agent API Documentation
See the AppDynamics APM Agent API for the Java Agent.