Skip to content

Commit 9ade058

Browse files
committed
Add CICS instrumentation
- ECIInteraction.execute -- instruments the entry point for CICS calls via IBM's javax.resource.cci.Interaction implementation, creating "cics.execute" span and recording a few tags. - JavaGatewayInstrumentation.flow -- records the peer.* tags on the "cics.execute" span created above, or if it doesn't exist, creates a new "gateway.flow" span. The tests don't fully exercise the CICS client-side code, however they exercise enough to ensure the instrumentation creates spans and adds tags as expected. This requires a few JAR files from IBM's CICS SDK for compliation and testing that are not available in Maven Central. A tar.gz artifact is downloaded from IBM's public CICS support archive and the necessary JARs are extracted, following the same pattern used for the JBoss Wildfly smoke tests.
1 parent ab049bd commit 9ade058

File tree

7 files changed

+721
-0
lines changed

7 files changed

+721
-0
lines changed
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
apply from: "$rootDir/gradle/java.gradle"
2+
3+
// Configuration for downloading CICS SDK from IBM
4+
ext {
5+
cicsVersion = '9.1'
6+
cicsSdkName = 'CICS_TG_SDK_91_Unix'
7+
}
8+
9+
repositories {
10+
ivy {
11+
url = 'https://public.dhe.ibm.com/software/htp/cics/support/supportpacs/individual/'
12+
patternLayout {
13+
artifact '[module].[ext]'
14+
}
15+
metadataSources {
16+
it.artifact()
17+
}
18+
}
19+
}
20+
21+
configurations {
22+
register('cicsSdk') {
23+
canBeResolved = true
24+
canBeConsumed = false
25+
}
26+
register('cicsJars') {
27+
canBeResolved = true
28+
canBeConsumed = false
29+
}
30+
}
31+
32+
// Task to extract the CICS SDK and get the required JARs
33+
abstract class ExtractCicsJars extends DefaultTask {
34+
@InputFiles
35+
final ConfigurableFileCollection sdkArchive = project.objects.fileCollection()
36+
37+
@OutputDirectory
38+
final DirectoryProperty outputDir = project.objects.directoryProperty()
39+
40+
ExtractCicsJars() {
41+
outputDir.convention(project.layout.buildDirectory.dir('cics-jars'))
42+
}
43+
44+
@TaskAction
45+
def extract() {
46+
def sdkFile = sdkArchive.singleFile
47+
def buildDir = outputDir.get().asFile
48+
buildDir.mkdirs()
49+
50+
// Extract outer tar.gz to get the inner tar.gz
51+
def tempDir = new File(buildDir, 'temp')
52+
tempDir.mkdirs()
53+
54+
project.copy {
55+
from project.tarTree(sdkFile)
56+
into tempDir
57+
}
58+
59+
// Find and extract the multiplatforms SDK
60+
def multiplatformsSdk = new File(tempDir, 'CICS_TG_SDK_91_Multiplatforms.tar.gz')
61+
if (!multiplatformsSdk.exists()) {
62+
throw new GradleException("Could not find CICS_TG_SDK_91_Multiplatforms.tar.gz in extracted archive")
63+
}
64+
65+
def sdkDir = new File(tempDir, 'sdk')
66+
sdkDir.mkdirs()
67+
68+
project.copy {
69+
from project.tarTree(multiplatformsSdk)
70+
into sdkDir
71+
}
72+
73+
// Extract cicseci.rar to get cicseci.jar, ctgclient.jar, and ctgserver.jar
74+
def cicsEciRar = new File(sdkDir, 'cicstgsdk/api/jee/runtime/managed/cicseci.rar')
75+
if (!cicsEciRar.exists()) {
76+
throw new GradleException("Could not find cicseci.rar at expected location")
77+
}
78+
79+
project.copy {
80+
from project.zipTree(cicsEciRar)
81+
into buildDir
82+
include 'cicseci.jar'
83+
include 'ctgclient.jar'
84+
include 'ctgserver.jar'
85+
}
86+
87+
// Copy cicsjee.jar
88+
def cicsJeeJar = new File(sdkDir, 'cicstgsdk/api/jee/runtime/nonmanaged/cicsjee.jar')
89+
if (!cicsJeeJar.exists()) {
90+
throw new GradleException("Could not find cicsjee.jar at expected location")
91+
}
92+
93+
project.copy {
94+
from cicsJeeJar
95+
into buildDir
96+
}
97+
98+
// Clean up temp directory
99+
tempDir.deleteDir()
100+
101+
logger.lifecycle("Extracted CICS JARs to: ${buildDir.absolutePath}")
102+
}
103+
}
104+
105+
tasks.register('extractCicsJars', ExtractCicsJars) {
106+
sdkArchive.from(configurations.named('cicsSdk'))
107+
108+
// Only extract if the output directory doesn't exist or SDK configuration changed
109+
outputs.upToDateWhen {
110+
def outputDir = it.outputDir.get().asFile
111+
outputDir.exists() &&
112+
new File(outputDir, 'cicseci.jar').exists() &&
113+
new File(outputDir, 'ctgclient.jar').exists() &&
114+
new File(outputDir, 'ctgserver.jar').exists() &&
115+
new File(outputDir, 'cicsjee.jar').exists()
116+
}
117+
}
118+
119+
dependencies {
120+
// Download the CICS SDK from IBM
121+
cicsSdk "${cicsSdkName}:${cicsSdkName}:@tar.gz"
122+
123+
// Compile-time dependencies (eliminates reflection)
124+
compileOnly group: 'javax.resource', name: 'javax.resource-api', version: '1.7.1'
125+
compileOnly files(tasks.named('extractCicsJars').map { task ->
126+
project.fileTree(task.outputDir) {
127+
include 'cicseci.jar'
128+
}
129+
})
130+
131+
// Test dependencies
132+
testImplementation group: 'javax.resource', name: 'javax.resource-api', version: '1.7.1'
133+
testImplementation libs.bundles.mockito
134+
testImplementation files(tasks.named('extractCicsJars').map { task ->
135+
project.fileTree(task.outputDir) {
136+
include '*.jar'
137+
}
138+
})
139+
}
140+
141+
// Ensure extraction happens before compilation
142+
tasks.named('compileJava') {
143+
dependsOn 'extractCicsJars'
144+
}
145+
146+
tasks.named('compileTestGroovy') {
147+
dependsOn 'extractCicsJars'
148+
}
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
package datadog.trace.instrumentation.cics;
2+
3+
import com.ibm.connector2.cics.ECIInteractionSpec;
4+
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
5+
import datadog.trace.bootstrap.instrumentation.api.InternalSpanTypes;
6+
import datadog.trace.bootstrap.instrumentation.api.Tags;
7+
import datadog.trace.bootstrap.instrumentation.api.UTF8BytesString;
8+
import datadog.trace.bootstrap.instrumentation.decorator.ClientDecorator;
9+
import java.net.InetAddress;
10+
import java.net.InetSocketAddress;
11+
12+
public class CicsDecorator extends ClientDecorator {
13+
public static final CharSequence CICS_CLIENT = UTF8BytesString.create("cics-client");
14+
public static final CharSequence ECI_EXECUTE_OPERATION = UTF8BytesString.create("cics.execute");
15+
public static final CharSequence GATEWAY_FLOW_OPERATION = UTF8BytesString.create("gateway.flow");
16+
17+
public static final CicsDecorator DECORATE = new CicsDecorator();
18+
19+
@Override
20+
protected String[] instrumentationNames() {
21+
return new String[] {"cics"};
22+
}
23+
24+
@Override
25+
protected String service() {
26+
return null; // Use default service name
27+
}
28+
29+
@Override
30+
protected CharSequence component() {
31+
return CICS_CLIENT;
32+
}
33+
34+
@Override
35+
protected CharSequence spanType() {
36+
return InternalSpanTypes.RPC;
37+
}
38+
39+
@Override
40+
public AgentSpan afterStart(AgentSpan span) {
41+
assert span != null;
42+
span.setTag("rpc.system", "cics");
43+
return super.afterStart(span);
44+
}
45+
46+
/**
47+
* Adds connection details to a span from JavaGatewayInterface fields.
48+
*
49+
* @param span the span to decorate
50+
* @param strAddress the hostname/address string
51+
* @param port the port number
52+
* @param ipGateway the resolved InetAddress (can be null)
53+
*/
54+
public AgentSpan onConnection(
55+
final AgentSpan span, final String strAddress, final int port, final InetAddress ipGateway) {
56+
if (strAddress != null) {
57+
span.setTag(Tags.PEER_HOSTNAME, strAddress);
58+
}
59+
60+
if (ipGateway != null) {
61+
onPeerConnection(span, ipGateway, false);
62+
}
63+
64+
if (port > 0) {
65+
setPeerPort(span, port);
66+
}
67+
68+
return span;
69+
}
70+
71+
/**
72+
* Adds local connection details to a span from a socket address.
73+
*
74+
* @param span the span to decorate
75+
* @param localAddr the socket (can be null)
76+
*/
77+
public AgentSpan onLocalConnection(final AgentSpan span, final InetSocketAddress localAddr) {
78+
if (localAddr != null && localAddr.getAddress() != null) {
79+
span.setTag("network.local.address", localAddr.getAddress().getHostAddress());
80+
span.setTag("network.local.port", localAddr.getPort());
81+
}
82+
return span;
83+
}
84+
85+
/**
86+
* Converts ECI interaction verb code to string representation.
87+
*
88+
* @param verb the interaction verb code
89+
* @return string representation of the verb
90+
* @see <a
91+
* href="https://docs.oracle.com/javaee/6/api/constant-values.html#javax.resource.cci.InteractionSpec.SYNC_SEND">InteractionSpec
92+
* constants</a>
93+
*/
94+
private String getInteractionVerbString(final int verb) {
95+
switch (verb) {
96+
case 0:
97+
return "SYNC_SEND";
98+
case 1:
99+
return "SYNC_SEND_RECEIVE";
100+
case 2:
101+
return "SYNC_RECEIVE";
102+
default:
103+
return "UNKNOWN_" + verb;
104+
}
105+
}
106+
107+
public AgentSpan onECIInteraction(final AgentSpan span, final ECIInteractionSpec spec) {
108+
final String interactionVerb = getInteractionVerbString(spec.getInteractionVerb());
109+
final String functionName = spec.getFunctionName();
110+
final String tranName = spec.getTranName();
111+
final String tpnName = spec.getTPNName();
112+
113+
span.setResourceName(interactionVerb + " " + functionName);
114+
span.setTag("cics.interaction", interactionVerb);
115+
116+
if (functionName != null) {
117+
span.setTag("rpc.method", functionName);
118+
}
119+
if (tranName != null) {
120+
span.setTag("cics.tran", tranName);
121+
}
122+
if (tpnName != null) {
123+
span.setTag("cics.tpn", tpnName);
124+
}
125+
126+
return span;
127+
}
128+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package datadog.trace.instrumentation.cics;
2+
3+
import static datadog.trace.agent.tooling.bytebuddy.matcher.NameMatchers.named;
4+
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.activateSpan;
5+
import static datadog.trace.bootstrap.instrumentation.api.AgentTracer.startSpan;
6+
import static datadog.trace.instrumentation.cics.CicsDecorator.DECORATE;
7+
import static datadog.trace.instrumentation.cics.CicsDecorator.ECI_EXECUTE_OPERATION;
8+
9+
import com.google.auto.service.AutoService;
10+
import com.ibm.connector2.cics.ECIInteraction;
11+
import com.ibm.connector2.cics.ECIInteractionSpec;
12+
import datadog.trace.agent.tooling.Instrumenter;
13+
import datadog.trace.agent.tooling.InstrumenterModule;
14+
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
15+
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
16+
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
17+
import net.bytebuddy.asm.Advice;
18+
19+
@AutoService(InstrumenterModule.class)
20+
public final class ECIInteractionInstrumentation extends InstrumenterModule.Tracing
21+
implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice {
22+
23+
public ECIInteractionInstrumentation() {
24+
super("cics");
25+
}
26+
27+
@Override
28+
public String[] helperClassNames() {
29+
return new String[] {packageName + ".CicsDecorator"};
30+
}
31+
32+
@Override
33+
public String instrumentedType() {
34+
return "com.ibm.connector2.cics.ECIInteraction";
35+
}
36+
37+
@Override
38+
public void methodAdvice(MethodTransformer transformer) {
39+
transformer.applyAdvice(named("execute"), getClass().getName() + "$ExecuteAdvice");
40+
}
41+
42+
public static class ExecuteAdvice {
43+
@Advice.OnMethodEnter(suppress = Throwable.class)
44+
public static AgentScope enter(@Advice.Argument(0) final Object spec) {
45+
// Coordinating with JavaGatewayInterfaceInstrumentation
46+
CallDepthThreadLocalMap.incrementCallDepth(ECIInteraction.class);
47+
48+
if (!(spec instanceof ECIInteractionSpec)) {
49+
return null;
50+
}
51+
52+
AgentSpan span = startSpan(ECI_EXECUTE_OPERATION);
53+
DECORATE.afterStart(span);
54+
DECORATE.onECIInteraction(span, (ECIInteractionSpec) spec);
55+
56+
return activateSpan(span);
57+
}
58+
59+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
60+
public static void exit(
61+
@Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) {
62+
CallDepthThreadLocalMap.decrementCallDepth(ECIInteraction.class);
63+
64+
if (null != scope) {
65+
DECORATE.onError(scope.span(), throwable);
66+
DECORATE.beforeFinish(scope.span());
67+
scope.span().finish();
68+
scope.close();
69+
}
70+
}
71+
}
72+
}

0 commit comments

Comments
 (0)