Skip to content
13 changes: 13 additions & 0 deletions dd-java-agent/instrumentation/axway-api/axway-api.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
apply from: "$rootDir/gradle/java.gradle"

dependencies {
// It's difficult to split jars from axway apigateway monolith:
// Jars are mostly frontend to native code and this native code doesn't run separately without licence.
// Moreover, there is no component which you can run `java -jar ...` nor add it to classpath.
// Attempt to load any Axway class it results in segmentation fault.

//compileOnly group: 'com.axway.apigw', name: 'com.axway.apigw', version: '7.5'
//latestDepTestCompile group: 'com.axway.apigw', name: 'com.axway.apigw', version: '+'

//testCompile group: 'com.axway.ats.framework', name: 'ats-core', version: '4.0.6'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
package datadog.trace.instrumentation.axway;

import static java.lang.invoke.MethodType.methodType;

import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.DefaultURIDataAdapter;
import datadog.trace.bootstrap.instrumentation.api.Tags;
import datadog.trace.bootstrap.instrumentation.api.URIDataAdapter;
import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString;
import datadog.trace.bootstrap.instrumentation.decorator.HttpServerDecorator;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.URI;
import lombok.extern.slf4j.Slf4j;

// request = is com.vordel.circuit.net.State, connection = com.vordel.dwe.http.ServerTransaction
@Slf4j
public class AxwayHTTPPluginDecorator extends HttpServerDecorator<Object, Object, Object> {
public static final CharSequence AXWAY_REQUEST = UTF8BytesString.create("axway.request");
public static final CharSequence AXWAY_TRY_TRANSACTION =
UTF8BytesString.create("axway.trytransaction");

public static final AxwayHTTPPluginDecorator DECORATE = new AxwayHTTPPluginDecorator();

private static final MethodHandles.Lookup lookup = MethodHandles.lookup();

private static final String SERVERTRANSACTION_CLASSNAME = "com.vordel.dwe.http.ServerTransaction";
private static final Class<?> classServerTransaction;
private static final MethodHandle getRemoteAddr_mh;
private static final MethodHandle getMethod_mh;
private static final MethodHandle getURI_mh;

private static final String STATE_CLASSNAME = "com.vordel.circuit.net.State";
private static final Class<?> classState;
private static final MethodHandle hostField_mh;
private static final MethodHandle portField_mh;
private static final MethodHandle methodField_mh;
private static final MethodHandle uriField_mh;

static final String SERVER_TRANSACTION_CLASSNAME = "com.vordel.dwe.http.ServerTransaction";
static final Class<Object> SERVER_TRANSACTION_CLASS = getServerTransactionClass();

private static Class<Object> getServerTransactionClass() {
try {
return (Class<Object>) Class.forName(SERVER_TRANSACTION_CLASSNAME);
} catch (ClassNotFoundException e) {
log.debug("Can't get ServerTransaction class name", e);
}
return null;
}

static {
classServerTransaction = initClass(SERVERTRANSACTION_CLASSNAME);
getRemoteAddr_mh =
initNoArgServerTransactionMethodHandle("getRemoteAddr", InetSocketAddress.class);
getMethod_mh = initNoArgServerTransactionMethodHandle("getMethod", String.class);
getURI_mh = initGetURI();

classState = initClass(STATE_CLASSNAME);
hostField_mh = initStateFieldGetter("host");
portField_mh = initStateFieldGetter("port");
methodField_mh = initStateFieldGetter("verb");
uriField_mh = initStateFieldGetter("uri");
}

private static Class<?> initClass(final String name) {
try {
return Class.forName(name, false, AxwayHTTPPluginDecorator.class.getClassLoader());
} catch (ClassNotFoundException e) {
log.debug(
"Can't find class '{}': Axaway integration failed. ", SERVERTRANSACTION_CLASSNAME, e);
}
return null;
}

private static MethodHandle initNoArgServerTransactionMethodHandle(String name, Class<?> rtype) {
try {
return lookup.findVirtual(classServerTransaction, name, methodType(rtype));
} catch (NoSuchMethodException | IllegalAccessException e) {
log.debug("Can't find method handler '{}' ", name, e);
}
return null;
}

private static MethodHandle initGetURI() {
Method m = null;
try {
m = classServerTransaction.getDeclaredMethod("getURI"); // private method
m.setAccessible(true);
return lookup.unreflect(m);
} catch (Throwable e) {
log.debug("Can't unreflect method '{}': ", m, e);
}
return null;
}

private static MethodHandle initStateFieldGetter(String fieldName) {
Field field = null;
MethodHandle mh = null;
try {
field = classState.getDeclaredField(fieldName);
field.setAccessible(true);
mh = lookup.unreflectGetter(field);
log.debug(
"Initialized field '{}' of class '{}' unreflected to {}", fieldName, classState, mh);
} catch (NoSuchFieldException | IllegalAccessException e) {
log.debug(
"Can't find and unreflect declared field '{}' with name '{}' for class '{}' to mh: '{}'",
field,
fieldName,
classState,
mh,
e);
}
return mh;
}

@Override
protected String[] instrumentationNames() {
return new String[] {"axway-http"};
}

@Override
protected String component() {
return "axway-http";
}

@Override
protected String method(final Object serverTransaction) {
try {
return (String) getMethod_mh.invoke(serverTransaction);
} catch (Throwable throwable) {
log.debug(
"Can't invoke invoke '{}' on instance '{}' of class '{}'",
getMethod_mh,
serverTransaction,
serverTransaction.getClass(),
throwable);
}
return "UNKNOWN";
}

@Override
protected URIDataAdapter url(final Object serverTransaction) {
try {
return new DefaultURIDataAdapter((URI) getURI_mh.invoke(serverTransaction));
} catch (Throwable e) {
log.debug("Can't find invoke '{}}' on '{}': ", getURI_mh, serverTransaction, e);
Copy link
Copy Markdown
Contributor

@dougqh dougqh Mar 15, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this happens, should we stop trying to the URL extraction?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this exception should never happen. At the point we instrumenting ServerTransaction it should have some valid URI behind it: otherwise it wouldn't be able to reach the server where we instrumenting it.

}
return null;
}

@Override
protected String peerHostIP(Object serverTransaction) {
return getRemoteAddr(serverTransaction).getHostString();
}

@Override
protected int peerPort(Object serverTransaction) {
return getRemoteAddr(serverTransaction).getPort();
}

/** @param serverTransaction instance of {@value #SERVERTRANSACTION_CLASSNAME} */
@Override
protected int status(final Object serverTransaction) {
// TODO will be done manually
return 0;
}

/** @param stateInstance type com.vordel.circuit.net.State */
public AgentSpan onTransaction(AgentSpan span, Object stateInstance) {
if (span != null) {
setStringTagFromStateField(span, Tags.PEER_HOSTNAME, stateInstance, hostField_mh);
setStringTagFromStateField(span, Tags.PEER_PORT, stateInstance, portField_mh);
setStringTagFromStateField(span, Tags.HTTP_METHOD, stateInstance, methodField_mh);
setURLTagFromUriStateField(span, stateInstance);
}
return span;
}

/**
* class hierarchy in package com.vordel.dwe.http :
*
* <pre><code>
* public class Transaction implements IMetricsTransaction {
* public native InetSocketAddress getLocalAddr();
* public native InetSocketAddress getRemoteAddr();
* }
* public abstract class HTTPTransaction extends Transaction {
*
* }
* public class ServerTransaction extends HTTPTransaction {
*
* }
* </code></pre>
*
* @param obj instance of {@value #SERVERTRANSACTION_CLASSNAME}
* @return result of {@value #SERVERTRANSACTION_CLASSNAME}::getRemoteAddr()
*/
private static InetSocketAddress getRemoteAddr(Object obj) {
try {
return (InetSocketAddress) getRemoteAddr_mh.invoke(obj);
} catch (Throwable throwable) {
log.debug("Can't invoke '{}' on instance '{}': ", getRemoteAddr_mh, obj, throwable);
}
return new InetSocketAddress(0);
}

private static void setStringTagFromStateField(
AgentSpan span, String tag, Object stateInstance, MethodHandle mh) {
String v = "";
try {
v = (String) mh.invoke(stateInstance);
} catch (Throwable e) {
log.debug(
"Can't invoke '{}' on instance '{}'; ; Tag '{}' not set.", mh, stateInstance, tag, e);
}
span.setTag(tag, v);
}

private static void setURLTagFromUriStateField(AgentSpan span, Object stateInstance) {
try {
span.setTag(Tags.HTTP_URL, uriField_mh.invoke(stateInstance).toString());
} catch (Throwable e) {
log.debug(
"Can't invoke '{}' on instance '{}'; Tag '{}' not set.",
uriField_mh,
stateInstance,
Tags.HTTP_URL,
e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package datadog.trace.instrumentation.axway;

import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.namedOneOf;
import static java.util.Collections.singletonMap;
import static net.bytebuddy.matcher.ElementMatchers.isMethod;
import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;

import com.google.auto.service.AutoService;
import datadog.trace.agent.tooling.Instrumenter;
import java.util.HashMap;
import java.util.Map;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;

@AutoService(Instrumenter.class)
public final class AxwayHTTPPluginInstrumentation extends Instrumenter.Tracing {

public AxwayHTTPPluginInstrumentation() {
super("axway-api");
}

@Override
public Map<String, String> contextStore() {
return singletonMap("com.vordel.dwe.http.ServerTransaction", int.class.getName());
}

@Override
public ElementMatcher<? super TypeDescription> typeMatcher() {
return namedOneOf(
"com.vordel.dwe.http.HTTPPlugin",
"com.vordel.dwe.http.ServerTransaction",
"com.vordel.circuit.net.State");
}

@Override
public String[] helperClassNames() {
return new String[] {
packageName + ".StateAdvice",
packageName + ".AxwayHTTPPluginDecorator",
packageName + ".HTTPPluginAdvice",
packageName + ".ServerTransactionAdvice",
};
}

@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
final Map<ElementMatcher<? super MethodDescription>, String> transformers = new HashMap<>();
transformers.put(
isMethod().and(isPublic()).and(named("invokeDispose")), packageName + ".HTTPPluginAdvice");
transformers.put(
isMethod().and(isPublic()).and(named("tryTransaction")), packageName + ".StateAdvice");
transformers.put(
isMethod().and(isPublic()).and(named("sendResponse")),
packageName + ".ServerTransactionAdvice");
return transformers;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package datadog.trace.instrumentation.axway;

import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan;
import static datadog.trace.instrumentation.axway.AxwayHTTPPluginDecorator.AXWAY_REQUEST;
import static datadog.trace.instrumentation.axway.AxwayHTTPPluginDecorator.DECORATE;
import static datadog.trace.instrumentation.axway.AxwayHTTPPluginDecorator.SERVER_TRANSACTION_CLASS;

import datadog.trace.bootstrap.InstrumentationContext;
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.Tags;
import net.bytebuddy.asm.Advice;

public class HTTPPluginAdvice {

@Advice.OnMethodEnter(suppress = Throwable.class)
public static AgentScope onEnter(@Advice.Argument(value = 2) final Object serverTransaction) {
final AgentSpan span = startSpan(AXWAY_REQUEST);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem to be reading incoming headers to propagate the trace? Is this an acceptable tradeoff?

Copy link
Copy Markdown
Contributor Author

@lpriima lpriima Feb 4, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes for now. I might need to change it in next PR

final AgentScope scope = activateSpan(span);
span.setMeasured(true);
DECORATE.afterStart(span);
// serverTransaction is like request + connection in one object:
DECORATE.onRequest(span, serverTransaction, serverTransaction, null);
return scope;
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void onExit(
@Advice.Enter final AgentScope scope,
@Advice.Argument(value = 2) final Object serverTransaction,
@Advice.Thrown final Throwable throwable) {
if (scope == null) {
return;
}
final AgentSpan span = scope.span();
try {
if (null != serverTransaction) {
// manual DECORATE.onResponse(span, serverTransaction):
// TODO: It doesn't work. Rewriting of InstrumentationContext.get fails here, because both
// arguments should be
// class-literals (not runtime Class object) to make FieldBackedContextRequestRewriter
// work.
int respCode =
InstrumentationContext.get(SERVER_TRANSACTION_CLASS, int.class).get(serverTransaction);
span.setTag(Tags.HTTP_STATUS, respCode);
}
if (throwable != null) {
DECORATE.onError(span, throwable);
}
DECORATE.beforeFinish(span);
} finally {
scope.close();
span.finish();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package datadog.trace.instrumentation.axway;

import static datadog.trace.instrumentation.axway.AxwayHTTPPluginDecorator.SERVER_TRANSACTION_CLASS;

import datadog.trace.bootstrap.InstrumentationContext;
import net.bytebuddy.asm.Advice;

public class ServerTransactionAdvice {

@Advice.OnMethodExit(suppress = Throwable.class)
public static void onEnter(
@Advice.This Object thiz, @Advice.Argument(value = 0) final int responseCode) {
if (null != SERVER_TRANSACTION_CLASS) {
InstrumentationContext.get(SERVER_TRANSACTION_CLASS, int.class).put(thiz, responseCode);
}
}
}
Loading