Skip to content

Commit 1befb3a

Browse files
mccullsdeejgregoramarziali
authored
🪞 10269 - Feature: CICS tracing (#10301)
* 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 compilation 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. --------- Co-authored-by: DJ Gregor <dj@gregor.com> Co-authored-by: Andrea Marziali <andrea.marziali@datadoghq.com>
1 parent b89fa0a commit 1befb3a

8 files changed

Lines changed: 697 additions & 0 deletions

File tree

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
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+
}
149+
150+
tasks.named('forbiddenApisMain').configure {
151+
failOnMissingClasses = false
152+
}
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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+
11+
public class CicsDecorator extends ClientDecorator {
12+
public static final CharSequence CICS_CLIENT = UTF8BytesString.create("cics-client");
13+
public static final CharSequence ECI_EXECUTE_OPERATION = UTF8BytesString.create("cics.execute");
14+
public static final CharSequence GATEWAY_FLOW_OPERATION = UTF8BytesString.create("gateway.flow");
15+
16+
public static final CicsDecorator DECORATE = new CicsDecorator();
17+
18+
@Override
19+
protected String[] instrumentationNames() {
20+
return new String[] {"cics"};
21+
}
22+
23+
@Override
24+
protected String service() {
25+
return null; // Use default service name
26+
}
27+
28+
@Override
29+
protected CharSequence component() {
30+
return CICS_CLIENT;
31+
}
32+
33+
@Override
34+
protected CharSequence spanType() {
35+
return InternalSpanTypes.RPC;
36+
}
37+
38+
@Override
39+
public AgentSpan afterStart(AgentSpan span) {
40+
assert span != null;
41+
span.setTag("rpc.system", "cics");
42+
return super.afterStart(span);
43+
}
44+
45+
/**
46+
* Adds connection details to a span from JavaGatewayInterface fields.
47+
*
48+
* @param span the span to decorate
49+
* @param strAddress the hostname/address string
50+
* @param port the port number
51+
* @param ipGateway the resolved InetAddress (can be null)
52+
*/
53+
public AgentSpan onConnection(
54+
final AgentSpan span, final String strAddress, final int port, final InetAddress ipGateway) {
55+
if (strAddress != null) {
56+
span.setTag(Tags.PEER_HOSTNAME, strAddress);
57+
}
58+
59+
if (ipGateway != null) {
60+
onPeerConnection(span, ipGateway, false);
61+
}
62+
63+
if (port > 0) {
64+
setPeerPort(span, port);
65+
}
66+
67+
return span;
68+
}
69+
70+
/**
71+
* Converts ECI interaction verb code to string representation.
72+
*
73+
* @param verb the interaction verb code
74+
* @return string representation of the verb
75+
* @see <a
76+
* href="https://docs.oracle.com/javaee/6/api/constant-values.html#javax.resource.cci.InteractionSpec.SYNC_SEND">InteractionSpec
77+
* constants</a>
78+
*/
79+
private String getInteractionVerbString(final int verb) {
80+
switch (verb) {
81+
case 0:
82+
return "SYNC_SEND";
83+
case 1:
84+
return "SYNC_SEND_RECEIVE";
85+
case 2:
86+
return "SYNC_RECEIVE";
87+
default:
88+
return "UNKNOWN_" + verb;
89+
}
90+
}
91+
92+
public AgentSpan onECIInteraction(final AgentSpan span, final ECIInteractionSpec spec) {
93+
final String interactionVerb = getInteractionVerbString(spec.getInteractionVerb());
94+
final String functionName = spec.getFunctionName();
95+
final String tranName = spec.getTranName();
96+
final String tpnName = spec.getTPNName();
97+
98+
span.setResourceName(interactionVerb + " " + functionName);
99+
span.setTag("cics.interaction", interactionVerb);
100+
101+
if (functionName != null) {
102+
span.setTag("rpc.method", functionName);
103+
}
104+
if (tranName != null) {
105+
span.setTag("cics.tran", tranName);
106+
}
107+
if (tpnName != null) {
108+
span.setTag("cics.tpn", tpnName);
109+
}
110+
111+
return span;
112+
}
113+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package datadog.trace.instrumentation.cics;
2+
3+
import static java.util.Arrays.asList;
4+
5+
import com.google.auto.service.AutoService;
6+
import datadog.trace.agent.tooling.Instrumenter;
7+
import datadog.trace.agent.tooling.InstrumenterModule;
8+
import java.util.List;
9+
10+
@AutoService(InstrumenterModule.class)
11+
public class CicsModule extends InstrumenterModule.Tracing {
12+
public CicsModule() {
13+
super("cics");
14+
}
15+
16+
@Override
17+
public String[] helperClassNames() {
18+
return new String[] {packageName + ".CicsDecorator"};
19+
}
20+
21+
@Override
22+
public List<Instrumenter> typeInstrumentations() {
23+
return asList(new ECIInteractionInstrumentation(), new JavaGatewayInterfaceInstrumentation());
24+
}
25+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
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.ibm.connector2.cics.ECIInteraction;
10+
import com.ibm.connector2.cics.ECIInteractionSpec;
11+
import datadog.trace.agent.tooling.Instrumenter;
12+
import datadog.trace.bootstrap.CallDepthThreadLocalMap;
13+
import datadog.trace.bootstrap.instrumentation.api.AgentScope;
14+
import datadog.trace.bootstrap.instrumentation.api.AgentSpan;
15+
import net.bytebuddy.asm.Advice;
16+
17+
public final class ECIInteractionInstrumentation
18+
implements Instrumenter.ForSingleType, Instrumenter.HasMethodAdvice {
19+
@Override
20+
public String instrumentedType() {
21+
return "com.ibm.connector2.cics.ECIInteraction";
22+
}
23+
24+
@Override
25+
public void methodAdvice(MethodTransformer transformer) {
26+
transformer.applyAdvice(named("execute"), getClass().getName() + "$ExecuteAdvice");
27+
}
28+
29+
public static class ExecuteAdvice {
30+
@Advice.OnMethodEnter(suppress = Throwable.class)
31+
public static AgentScope enter(@Advice.Argument(0) final Object spec) {
32+
// Coordinating with JavaGatewayInterfaceInstrumentation
33+
CallDepthThreadLocalMap.incrementCallDepth(ECIInteraction.class);
34+
35+
if (!(spec instanceof ECIInteractionSpec)) {
36+
return null;
37+
}
38+
39+
AgentSpan span = startSpan(ECI_EXECUTE_OPERATION);
40+
DECORATE.afterStart(span);
41+
DECORATE.onECIInteraction(span, (ECIInteractionSpec) spec);
42+
43+
return activateSpan(span);
44+
}
45+
46+
@Advice.OnMethodExit(onThrowable = Throwable.class, suppress = Throwable.class)
47+
public static void exit(
48+
@Advice.Enter final AgentScope scope, @Advice.Thrown final Throwable throwable) {
49+
CallDepthThreadLocalMap.decrementCallDepth(ECIInteraction.class);
50+
51+
if (null != scope) {
52+
DECORATE.onError(scope.span(), throwable);
53+
DECORATE.beforeFinish(scope.span());
54+
scope.span().finish();
55+
scope.close();
56+
}
57+
}
58+
}
59+
}

0 commit comments

Comments
 (0)