Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 148 additions & 0 deletions dd-java-agent/instrumentation/cics/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
apply from: "$rootDir/gradle/java.gradle"

// Configuration for downloading CICS SDK from IBM
ext {
cicsVersion = '9.1'
cicsSdkName = 'CICS_TG_SDK_91_Unix'
}

repositories {
ivy {
url = 'https://public.dhe.ibm.com/software/htp/cics/support/supportpacs/individual/'
patternLayout {
artifact '[module].[ext]'
}
metadataSources {
it.artifact()
}
}
}

configurations {
register('cicsSdk') {
canBeResolved = true
canBeConsumed = false
}
register('cicsJars') {
canBeResolved = true
canBeConsumed = false
}
}

// Task to extract the CICS SDK and get the required JARs
abstract class ExtractCicsJars extends DefaultTask {
@InputFiles
final ConfigurableFileCollection sdkArchive = project.objects.fileCollection()

@OutputDirectory
final DirectoryProperty outputDir = project.objects.directoryProperty()

ExtractCicsJars() {
outputDir.convention(project.layout.buildDirectory.dir('cics-jars'))
}

@TaskAction
def extract() {
def sdkFile = sdkArchive.singleFile
def buildDir = outputDir.get().asFile
buildDir.mkdirs()

// Extract outer tar.gz to get the inner tar.gz
def tempDir = new File(buildDir, 'temp')
tempDir.mkdirs()

project.copy {
from project.tarTree(sdkFile)
into tempDir
}

// Find and extract the multiplatforms SDK
def multiplatformsSdk = new File(tempDir, 'CICS_TG_SDK_91_Multiplatforms.tar.gz')
if (!multiplatformsSdk.exists()) {
throw new GradleException("Could not find CICS_TG_SDK_91_Multiplatforms.tar.gz in extracted archive")
}

def sdkDir = new File(tempDir, 'sdk')
sdkDir.mkdirs()

project.copy {
from project.tarTree(multiplatformsSdk)
into sdkDir
}

// Extract cicseci.rar to get cicseci.jar, ctgclient.jar, and ctgserver.jar
def cicsEciRar = new File(sdkDir, 'cicstgsdk/api/jee/runtime/managed/cicseci.rar')
if (!cicsEciRar.exists()) {
throw new GradleException("Could not find cicseci.rar at expected location")
}

project.copy {
from project.zipTree(cicsEciRar)
into buildDir
include 'cicseci.jar'
include 'ctgclient.jar'
include 'ctgserver.jar'
}

// Copy cicsjee.jar
def cicsJeeJar = new File(sdkDir, 'cicstgsdk/api/jee/runtime/nonmanaged/cicsjee.jar')
if (!cicsJeeJar.exists()) {
throw new GradleException("Could not find cicsjee.jar at expected location")
}

project.copy {
from cicsJeeJar
into buildDir
}

// Clean up temp directory
tempDir.deleteDir()

logger.lifecycle("Extracted CICS JARs to: ${buildDir.absolutePath}")
}
}

tasks.register('extractCicsJars', ExtractCicsJars) {
sdkArchive.from(configurations.named('cicsSdk'))

// Only extract if the output directory doesn't exist or SDK configuration changed
outputs.upToDateWhen {
def outputDir = it.outputDir.get().asFile
outputDir.exists() &&
new File(outputDir, 'cicseci.jar').exists() &&
new File(outputDir, 'ctgclient.jar').exists() &&
new File(outputDir, 'ctgserver.jar').exists() &&
new File(outputDir, 'cicsjee.jar').exists()
}
}

dependencies {
// Download the CICS SDK from IBM
cicsSdk "${cicsSdkName}:${cicsSdkName}:@tar.gz"

// Compile-time dependencies (eliminates reflection)
compileOnly group: 'javax.resource', name: 'javax.resource-api', version: '1.7.1'
compileOnly files(tasks.named('extractCicsJars').map { task ->
project.fileTree(task.outputDir) {
include 'cicseci.jar'
}
})

// Test dependencies
testImplementation group: 'javax.resource', name: 'javax.resource-api', version: '1.7.1'
testImplementation libs.bundles.mockito
testImplementation files(tasks.named('extractCicsJars').map { task ->
project.fileTree(task.outputDir) {
include '*.jar'
}
})
}

// Ensure extraction happens before compilation
tasks.named('compileJava') {
dependsOn 'extractCicsJars'
}

tasks.named('compileTestGroovy') {
dependsOn 'extractCicsJars'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package datadog.trace.instrumentation.cics;

import com.ibm.connector2.cics.ECIInteractionSpec;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes;
import datadog.trace.bootstrap.instrumentation.api.Tags;
import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString;
import datadog.trace.bootstrap.instrumentation.decorator.ClientDecorator;
import java.net.InetAddress;
import java.net.InetSocketAddress;

public class CicsDecorator extends ClientDecorator {
public static final CharSequence CICS_CLIENT = UTF8BytesString.create("cics-client");
public static final CharSequence ECI_EXECUTE_OPERATION = UTF8BytesString.create("cics.execute");
public static final CharSequence GATEWAY_FLOW_OPERATION = UTF8BytesString.create("gateway.flow");

public static final CicsDecorator DECORATE = new CicsDecorator();

@Override
protected String[] instrumentationNames() {
return new String[] {"cics"};
}

@Override
protected String service() {
return null; // Use default service name
}

@Override
protected CharSequence component() {
return CICS_CLIENT;
}

@Override
protected CharSequence spanType() {
return InternalSpanTypes.RPC;
}

@Override
public AgentSpan afterStart(AgentSpan span) {
assert span != null;
span.setTag("rpc.system", "cics");
return super.afterStart(span);
}

/**
* Adds connection details to a span from JavaGatewayInterface fields.
*
* @param span the span to decorate
* @param strAddress the hostname/address string
* @param port the port number
* @param ipGateway the resolved InetAddress (can be null)
*/
public AgentSpan onConnection(
final AgentSpan span, final String strAddress, final int port, final InetAddress ipGateway) {
if (strAddress != null) {
span.setTag(Tags.PEER_HOSTNAME, strAddress);
}

if (ipGateway != null) {
onPeerConnection(span, ipGateway, false);
}

if (port > 0) {
setPeerPort(span, port);
}

return span;
}

/**
* Adds local connection details to a span from a socket address.
*
* @param span the span to decorate
* @param localAddr the socket (can be null)
*/
public AgentSpan onLocalConnection(final AgentSpan span, final InetSocketAddress localAddr) {
if (localAddr != null && localAddr.getAddress() != null) {
span.setTag("network.local.address", localAddr.getAddress().getHostAddress());
span.setTag("network.local.port", localAddr.getPort());
}
return span;
}

/**
* Converts ECI interaction verb code to string representation.
*
* @param verb the interaction verb code
* @return string representation of the verb
* @see <a
* href="https://docs.oracle.com/javaee/6/api/constant-values.html#javax.resource.cci.InteractionSpec.SYNC_SEND">InteractionSpec
* constants</a>
*/
private String getInteractionVerbString(final int verb) {
switch (verb) {
case 0:
return "SYNC_SEND";
case 1:
return "SYNC_SEND_RECEIVE";
case 2:
return "SYNC_RECEIVE";
default:
return "UNKNOWN_" + verb;
}
}

public AgentSpan onECIInteraction(final AgentSpan span, final ECIInteractionSpec spec) {
final String interactionVerb = getInteractionVerbString(spec.getInteractionVerb());
final String functionName = spec.getFunctionName();
final String tranName = spec.getTranName();
final String tpnName = spec.getTPNName();

span.setResourceName(interactionVerb + " " + functionName);
span.setTag("cics.interaction", interactionVerb);

if (functionName != null) {
span.setTag("rpc.method", functionName);
}
if (tranName != null) {
span.setTag("cics.tran", tranName);
}
if (tpnName != null) {
span.setTag("cics.tpn", tpnName);
}

return span;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package datadog.trace.instrumentation.cics;

import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan;
import static datadog.trace.instrumentation.cics.CicsDecorator.DECORATE;
import static datadog.trace.instrumentation.cics.CicsDecorator.ECI_EXECUTE_OPERATION;

import com.google.auto.service.AutoService;
import com.ibm.connector2.cics.ECIInteraction;
import com.ibm.connector2.cics.ECIInteractionSpec;
import datadog.trace.agent.tooling.Instrumenter;
import datadog.trace.agent.tooling.InstrumenterModule;
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
import net.bytebuddy.asm.Advice;

@AutoService(InstrumenterModule.class)
public final class ECIInteractionInstrumentation extends InstrumenterModule.Tracing
implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice {

public ECIInteractionInstrumentation() {
super("cics");
}

@Override
public String[] helperClassNames() {
return new String[] {packageName + ".CicsDecorator"};
}

@Override
public String instrumentedType() {
return "com.ibm.connector2.cics.ECIInteraction";
}

@Override
public void methodAdvice(MethodTransformer transformer) {
transformer.applyAdvice(named("execute"), getClass().getName() + "$ExecuteAdvice");
}

public static class ExecuteAdvice {
@Advice.OnMethodEnter(suppress = Throwable.class)
public static AgentScope enter(@Advice.Argument(0) final Object spec) {
// Coordinating with JavaGatewayInterfaceInstrumentation
CallDepthThreadLocalMap.incrementCallDepth(ECIInteraction.class);

if (!(spec instanceof ECIInteractionSpec)) {
return null;
}

AgentSpan span = startSpan(ECI_EXECUTE_OPERATION);
DECORATE.afterStart(span);
DECORATE.onECIInteraction(span, (ECIInteractionSpec) spec);

return activateSpan(span);
}

@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
public static void exit(
@Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) {
CallDepthThreadLocalMap.decrementCallDepth(ECIInteraction.class);

if (null != scope) {
DECORATE.onError(scope.span(), throwable);
DECORATE.beforeFinish(scope.span());
scope.span().finish();
scope.close();
}
}
}
}
Loading