Db2 for z/OS and its ecosystem

Db2 for z/OS and its ecosystem

Connect with Db2, Informix, Netezza, open source, and other data experts to gain value from your data, share insights, and solve problems.

 View Only

Practical guide to OpenTelemetry tracing for Java programs connecting to Db for z/OS – Part 1

By Jørn Thyssen posted 10 hours ago

  

Practical guide to OpenTelemetry tracing for Java programs connecting to Db for z/OS – Part 1

By Christoph Theisen and Jørn Thyssen

Introduction


You may have seen several blogs by IBM on the OpenTelemetry trace initiative across all of the z/OS subsystems including Db2 for z/OS: https://community.ibm.com/community/user/blogs/m-sueli-almeida/2025/09/29/opentelemetry-support-in-db2-for-zos 
In this blog we give you a practical guide to enabling OTEL tracing for a Java application connecting to Db2. We are not discussing the basic principles of OpenTelemetry in this article. See the official OpenTelemetry web site for more information: https://opentelemetry.io/

In the architecture diagram below you can see that there are multiple components in play:
•    Java program
•    Db2 for z/OS
•    SMF
•    z/OS Data Gatherer - OTEL emitter
•    Observability platform (e.g., IBM Instana, Jaeger, or Grafana)

The emphasis on this blog will be on the Java program, but we will briefly cover the other components first.


Db2 & OpenTelemetry

You need to apply Db2 APAR PH67971 and then start the Db2 OTEL emitter with: -START OTEL EMIT(YES). Note that you'll have to do this on all members in your data sharing group (there is no SCOPE(GROUP) support for this command).
You can use the command -DIS OTEL DETAIL to view the status (by member)
DSNW300I  !I9A2 DSNWVCM3 DISPLAY OTEL REPORT FOLLOWS:          
    STATUS = STARTED                                           
    EMIT   = YES                                               
    RECORDS EMITTED: SUCCESSFUL  =          822                
                     FAILED      =            0                
    TRACE PARENT:    SAMPLING ON =          903                
                     VALID       =          983                
                     INVALID     =            0                
    TRACE STATE:     VALID       =            0                
                     TRUNCATED   =            0                
                     INVALID     =            0                
    LAST STARTED 12:20:50 NOV  4, 2025                         
    DISPLAY OTEL REPORT COMPLETE.                              

As you instrument your java application this provides valuable information about the number of incoming UOWs with OTEL tracing enabled and whether SMF records were emitted: run the command before and after running your java program, and you can easily verify if the tracing is working or not. 
See https://www.ibm.com/docs/en/db2-for-zos/13.0.0?topic=messages-dsnw300i for more details on the output. 


SMF configuration for OpenTelemetry

Db2 writes SMF1161 records for OpenTelemetry tracing. 
Similarly, MQ writes SMF1158, CICS writes SMF1159, and IMS writes SMF1160.
SMF1157 is reserved for vendors that want to write OpenTelemetry tracing records. We're not aware of any exploiters of SMF1157 at the time this blog was published. 
See more info here https://www.ibm.com/docs/en/zos/3.2.0?topic=sr-record-types-1157-1158-1159-1160-1161-x485-x486-x487-x488-x489-zos-opentelemetry-emitter-input and https://www.ibm.com/docs/en/zos/3.2.0?topic=specifics-schema-version-1
The z/OS Data Gatherer - OTEL Emitter only supports in-memory SMF log streams, so we will focus on that option here. 

Here's an example of SMFPRMxx with separate log streams for each product
  [...]
  INMEM(IFASMF.OTEL.T1157,TYPE(1157)) /* WRITE VENDOR OTEL 1157 */
  INMEM(IFASMF.OTEL.T1158,TYPE(1158)) /* WRITE MQ OTEL 1158   */
  INMEM(IFASMF.OTEL.T1159,TYPE(1159)) /* WRITE CICS OTEL 1159 */
  INMEM(IFASMF.OTEL.T1160,TYPE(1160)) /* WRITE IMS OTEL 1160  */
  INMEM(IFASMF.OTEL.T1161,TYPE(1161)) /* WRITE DB2 OTEL 1161  */
  RECORDING(LOGSTREAM)                                              
  [...]

For simplicity you could also create a single log stream to hold records for all subsystems
  [...]
  INMEM(IFASMF.OTEL.ALL,TYPE(1157:1161)) /* OTEL  */
  RECORDING(LOGSTREAM)                                             
  [...]
   

Note that you also need RACF profiles to protect these resources.
Example 1: discrete profile
RDEFINE FACILITY IFA.IFASMF.OTEL.T1161 UACC(NONE)
Example 2: generic profile
RDEFINE FACILITY IFA.IFASMF.OTEL.* UACC(NONE)

According to the Db2 documentation the Db2 STC user needs CONTROL permission on this resource and any consumer, such as the z/OS Data Gatherer - OTEL emitter needs READ.
See more information in the SMF manual on how to configure SMF in-memory log streams:  https://www.ibm.com/docs/en/zos/3.2.0?topic=interface-defining-in-memory-resources . There is also information available in the Db2 manual https://www.ibm.com/docs/en/db2-for-zos/13.0.0?topic=db2-configuring-smf-opentelemetry-span-records
After configuring the in-memory log streams, you can view the status with operator command /D SMF:
IFA714I 13.56.29 SMF STATUS 471 
          LOGSTREAM NAME               BUFFERS        STATUS   
        A-IFASMF.SMFDFLT.RS01            251666       CONNECTED
        A-IFASMF.OTEL.T1158                   0       IN-MEMORY
        A-IFASMF.OTEL.T1159                   0       IN-MEMORY
        A-IFASMF.OTEL.T1160                   0       IN-MEMORY
        A-IFASMF.OTEL.T1161              650536       IN-MEMORY

In this example we have a regular SMF log stream for the all SMF records, but also four in-memory log streams for the OpenTelemetry trace SMF records. 
Also note that a non-zero number of buffers have been used for the IFASMF.OTEL.T1161 log stream, which suggests it contains SMF1161 records produced by Db2. As you instrument your Java application this provides valuable information about whether the SMF records emitted by Db2 have been written to SMF. 


z/OS Data Gatherer - OTEL emitter

The final component on the z/OS platform is the z/OS Data Gatherer - OTEL emitter. This is a new component delivered in the maintenance stream for z/OS 3.1 and 3.2 with APAR OA66345.
Note that only the manual for z/OS 3.2 has been updated even though the feature is also available for z/OS 3.1.
The PTF includes z/OS Unix files installed into the directory /usr/lpp/grb/opentelemetry_emitter/dt/ and a sample JCLPROC in SYS1.PROCLIB(GRBZOESP).
In a production setting you would run this as a STC on each LPAR in your sysplex, but for testing purposes you can also run this as a job. Note that SYS1.PROCLIB(GRBZOESP) and /usr/lpp/grb/opentelemetry_emitter/dt/GRBZOESS are identical except that the former has a PROC statement and the latter a JOB statement. 
The manual does not contain any information on the configuration of this component. Instead refer to /usr/lpp/grb/opentelemetry_emitter/dt/README . 
Note that not all parameters supported by the OTEL Emitter are externalized in the sample JCLPROC GRBZOESP / job GRBZOESS so you may have to add additional parameters to the job or JCLPROC. 
Tips for configuration:
The emitter can connect to multiple SMF in-memory logstreams.
Depending on your naming standard you might need wrapping in the JCLPROC:
•    must truncate in column 71
•    must continue in col 16
----+----1----+----2----+----3----+----4----+----5----+----6----+----7-
//   SET $INMRESL='IFASMF.OTEL.T1158,IFASMF.OTEL.T1159,IFASMF.OTEL.T116
//             0,IFASMF.OTEL.T1161'                                    

For debugging we recommend
1) setting $SMFRDFL to 0 which causes the emitter to read SMF records one-by-one without delay (the default is to collect 128 records before emitting them):
SET $SMFRDFL='0'    

2) setting $SMFDUMP to true, which gives a binary dump of the SMF records. While it is binary you can easily eyeball the traceid, spanid, the Db2 "tags", etc., see also: https://www.ibm.com/docs/en/zos/3.2.0?topic=specifics-schema-version-1
We have included a sample below. 
SET $SMFDUMP='true' 

3) Configure Jaeger endpoints:
By default Jaeger uses port 4317 for the grpc endpoint and port 4318 for the http protobuf endpoint. 
Jaeger GRPC endpoint:
SET $OTLENDP='http://<jaeger host>:4317' 
SET $OTLPRT='grpc'      
Jaeger HTTP protobuf endpoint:
SET $OTLENDP='http://<jaeger host>:4318' 
SET $OTLPRT='http'      
Sample output from OTEL emitter startup: 
service-launcher - 'Starting OtelLauncher'  
service-launcher - 'Module version '1.4.0''                  
service-launcher - 'Build from '2025-08-26T09:17:50Z''       
...
service-launcher - 'z/OS OTel Emitter Service successfully connected t
o inMemory resource IFASMF.OTEL.T1161'  

Sample output showing binary dump of SMF record - you can eyeball the traceid (3b198cdb95118205aeb83281eca39b2c), spanid (e1d935f38d55e082), and parent spanid (a916872c95467969) as well as the Db2 specific fields

pool-3-thread-2¨  - '   
00000000 03 18 00 00 7E 7E 00 46 DD 5E 01 25 33 0F D9 E2 ....==...;....RS
00000010 F0 F1 C9 F9 C1 F2 00 01 00 20 01 00 00 E1 D9 35 01I9A2........R.
00000020 F3 8D 6F AA 10 00 00 00 08 90 00 01 FF FF BC F1 3.?............1
00000030 DC C0 00 00 04 89 00 00 00 00 00 40 00 00 00 01 .{...i..... ....
00000040 00 01 02 D8 E2 D7 C1 D5 00 E1 D9 35 F3 8D 53 1C ...QSPAN..R.3...
00000050 62 00 00 00 00 00 00 00 00 E1 D9 35 F3 8D 6E 61 ..........R.3.>.
00000060 3A 00 00 00 08 90 00 01 F3 82 F1 F9 F8 83 84 82 ........3b198cdb
00000070 F9 F5 F1 F1 F8 F2 F0 F5 81 85 82 F8 F3 F2 F8 F1 95118205aeb83281
00000080 85 83 81 F3 F9 82 F2 83 85 F1 84 F9 F3 F5 86 F3 eca39b2ce1d935f3
00000090 F8 84 F5 F5 85 F0 F8 F2 81 F9 F1 F6 F8 F7 F2 83 8d55e082a916872c
000000A0 F9 F5 F4 F6 F7 F9 F6 F9 00 01 00 10 00 18 0C 01 95467969........
000000B0 A2 85 99 A5 89 83 85 4B 95 81 94 85 00 04 00 25 service.name....
000000C0 C9 F9 C1 F2 00 24 09 01 A2 97 81 95 4B 95 81 94 I9A2....span.nam
000000D0 85 00 00 00 00 10 00 25 C4 82 F2 40 A4 95 89 A3 e.......Db2 unit
000000E0 40 96 86 40 A6 96 99 92 00 20 0E 01 84 82 4B A2  of work....db.s
000000F0 A8 A2 A3 85 94 4B 95 81 94 85 00 00 00 07 00 25 ystem.name......
00000100 89 82 94 4B 84 82 F2 00 00 28 12 01 84 82 4B 89 ibm.db2.....db.i
00000110 82 94 4B 84 82 F2 4B A5 85 99 A2 89 96 95 00 00 bm.db2.version..
00000120 00 09 00 25 E5 F1 F3 D9 F1 D4 F5 F0 F8 00 00 00 ....V13R1M508...
00000130 00 24 15 01 84 82 4B 89 82 94 4B 84 82 F2 4B 87 ....db.ibm.db2.g
00000140 99 96 A4 97 4B 95 81 94 85 00 00 00 00 04 00 25 roup.name.......
00000150 C9 C4 E2 F2 00 24 16 01 84 82 4B 89 82 94 4B 84 IDS2....db.ibm.d
00000160 82 F2 4B 94 85 94 82 85 99 4B 95 81 94 85 00 00 b2.member.name..
00000170 00 04 00 25 C9 F9 C1 F2 00 28 19 01 84 82 4B 89 ....I9A2....db.i
... [truncated]


Observability platform: Jaeger

In this blog we've used Jaeger as the observability platform, but there are many other options available including IBM Instana and Grafana. 
The easiest way to get started is to simply run Jaeger in a container as documented here: https://www.jaegertracing.io/docs/2.12/getting-started/#all-in-one

Instrumenting a Java program

Once you have set up the overall infrastructure on the z/OS side and made available an observability platform (such as Jaeger, IBM Instana, Grafana, or any observability platform that supports the OTLP protocol), you can configure OpenTelemetry tracing on the Java application side.

OpenTelemetry supports various programming languages (such as Java, JavaScript, Python, .NET, Go, etc.) and provides SDKs to add instrumentation to an application. In addition, it has the concept of so-called “Zero-code” instrumentation. This allows enabling OpenTelemetry-based instrumentation in an application without any code changes or recompilation.

In this section, we will show how you can use Zero-code instrumentation as your first step to generate OpenTelemetry tracing for a Java program connecting to an OpenTelemetry enabled Db2 for z/OS server.

Important: Db2 for z/OS can only generate trace records when a Java application connects via JDBC Type 4 with a Db2 JDBC driver (JCC) of architecture level 4.36 and above. This is available with version 12.1 Mod 3 Fixpack 0 of the IBM Db2 Data Server Driver for JDBC and SQLJ. 

See here for download information: https://www.ibm.com/support/pages/node/7248540

OpenTelemetry has the concept of instrumenting applications with metrics, logs, and traces. The current OpenTelemetry implementation in Db2 for z/OS only supports OpenTelemetry traces. You can still instrument your application with metrics and logs in addition to traces, but that would be outside of the scope for Db2 for z/OS.

From a Java perspective, Zero-code instrumentation implies that you use a so-called “Java agent” in conjunction with your existing JDBC program. A Java agent is a special type of program that is loaded in the JVM before the execution of the actual application program starts. It adds classes at runtime and is primarily used for monitoring, profiling, and instrumentation.

So, you need to download the OpenTelemetry Java agent first. Follow the instructions on OTel’s web site to download “opentelemetry-javaagent.jar” from OTel’s Github repository (https://opentelemetry.io/docs/zero-code/java/agent/getting-started/)

In addition, you need to download a couple of OpenTelemetry jar files which must be added to the application’s CLASSPATH:
•    opentelemetry-api-<version number>.jar
•    opentelemetry-common-<version number>.jar
•    opentelemetry-context-<version number>.jar
There are several versions of these libraries available. We obtained them from Maven Repository and tested with version 1.55.0: https://mvnrepository.com/artifact/io.opentelemetry

Once you have downloaded the Db2 JDBC driver, the Java agent, and the additional jar files it’s time to set up a JDBC sample program. 
We anticipate that you already have a simple Java application that connects to a Db2 for z/OS server via JDBC data source and runs a series of simple SQL statements. It would also make sense that you run at least a couple of these SQLs without autocommit and code explicit COMMITs in your application. 
Let us further assume your application, named “JdbcOtelZeroCode”, is launched with a simple Windows bat-file or Shell script:
export CLASSPATH=$CLASSPATH:.:/u/user1/otel/JdbcOtelZeroCode.class:/u/user1/otel/db2jcc4.jar
java otel.JdbcOtelZeroCode javaproperties.txt
In this example, javaproperties.txt contains the Db2 location name, port number, host name as well user id and password to connect to Db2

As you can see, no instrumentation to the Java program is applied, so you will not get OpenTelemetry trace records from Db2, even if the Db2 OTEL tracing is started and the z/OS OTEL Emitter is active.

For Zero-code instrumentation, changes to your environment variables are required. Assuming the downloaded OpenTelemetry libraries are located in /u/user1/otel, set the following environment variables for your program execution:
JAVA_TOOL_OPTIONS="-javaagent:/u/user1/otel/opentelemetry-javaagent.jar"

This points to the location of the Java agent’s jar file.

OTEL_METRICS_EXPORTER=none

This tells the Java agent that you are not interested in exporting metrics from your JVM to an observability backend. In case you want to export JVM metrics as well, specify at least one of the other supported values here (e.g. otlp). 

OTEL_TRACES_EXPORTER=otlp

This defines the protocol which is used for the OTel trace records when they are sent to an observability backend. Specify “otlp” for a Jaeger backend.

OTEL_EXPORTER_OTLP_ENDPOINT=http://<ip address>:<port>

Replace ip address and port with the values for your observability backend. This should be the same backend which receives the trace records emitted by Db2.

OTEL_PROPAGATORS=tracecontext,baggage

This tells the Java agent which OTel propagators should be used.

OTEL_SERVICE_NAME=JDBC_OTEL_ZeroCode

Choose a meaningful name which helps to locate the service requests in your observability backend.

OTEL_INSTRUMENTATION_METHODS_INCLUDE=<package name>.<program name>[main, <method1>, <method2>, …]

This tells the Java agent to which methods of your Java program instrumentation should be added. Multiple classes and methods can be specified. Make sure that at least your main method is listed here. You can achieve more granularity if you add all methods that make JDBC calls to a Db2 server.
Example: the class “JdbcOtelZeroCode” in package “otel” contains several methods. Trace records should be written for the main method and another method “getDBInfo”. The environment variable would look like this:

OTEL_INSTRUMENTATION_METHODS_INCLUDE=otel.JdbcOtelZeroCode[main,getDBInfo]

Finally, add the following jar files to your class path:
•    opentelemetry-api-<version number>.jar
•    opentelemetry-common-<version number>.jar
•    opentelemetry-context-<version number>.jar

With these environment variables, you can run your JDBC program as you would do without instrumentation. Before that, see the output of the DISPLAY OTEL command in Db2 to check the current number of written OTel trace records:
DSNW300I  !I9A2 DSNWVCM3 DISPLAY OTEL REPORT FOLLOWS: 
     STATUS = STARTED                                  
     EMIT   = YES                                      
     RECORDS EMITTED: SUCCESSFUL  =          880       
                      FAILED      =            0       
     LAST STARTED 12:20:50 NOV  4, 2025                
     DISPLAY OTEL REPORT COMPLETE.                     

After starting the JDBC program, look for an information message written by the Java agent:
[otel.javaagent 2025-12-04 08:51:59:032 -0500] [main] INFO io.opentelemetry.javaagent.tooling.VersionLogger - opentelemetry-javaagent - version: 2.22.0
When the program is finished, the number of emitted OTEL trace records from Db2 should have increased:
DSNW300I  !I9A2 DSNWVCM3 DISPLAY OTEL REPORT FOLLOWS: 
     STATUS = STARTED                                  
     EMIT   = YES                                      
     RECORDS EMITTED: SUCCESSFUL  =          885       
                      FAILED      =            0       
     LAST STARTED 12:20:50 NOV  4, 2025                
     DISPLAY OTEL REPORT COMPLETE.                     

Now it’s time to check in your observability backend whether you can find the Db2 trace records and whether you can correlate them to your Java program. We used Jaeger for our testing. 

In the Jaeger search window, you should see the service name which you provided in the OTEL_SERVICE_NAME  environment variable under the “Service” drop down.


 
In the “Lookback” drop down choose a value that includes the time when you ran your test program, e.g. the last 15 minutes.


 
You should see at least one entry on the right-hand side of the window which represents the execution of your instrumented JDBC program:
 

As you can see in this example, a total of 24 span records has been captured by OTEL for this program execution. 19 of 24 spans have been written by the Java program itself and another 5 by Db2.

You can click on the field that contains the total number of spans (“24 Spans” in this example) to get a visual representation of the spans.


 
Without going too much into the details, you can get an impression of how OTEL instrumentation can help you to trace the flow of an application across multiple platforms. In this simple example, a JDBC application under z/OS USS executing Db2 for z/OS SQL statements via JDBC T4.

Every bar in this graph represents one span for which OTEL has captured execution times and other metrics. Most of the spans are generated in your JDBC application (by the Java agent), the rest of the spans (in blue color in this example) by Db2.

As you could see, no code change has been made to your Java program. Zero-code instrumentation should be your first step, when you want to start your OTEL journey for Db2 for z/OS, however you need to consider some important things:

Db2 creates a span for every unit of work. Every Db2 span contains some environmental information (such as correlation ID, plan name, LUWID) plus basic metrics: class 2 elapsed, CPU, and zIIP time. Since a Db2 unit of work can contain multiple SQL statements, these spans generated by Db2 do not necessarily provide metrics for a single SQL statement execution.

Zero-code instrumentation injects your Java code at certain places. For example, when your Java code executes a JDBC call it creates a new span for every single JDBC execute call of your Java application. However, the Db2 span is created as child span for the first JDBC call in the Db2 unit of work.

Example:
The JDBC application issues 4 SQL statements in one unit of work and a COMMIT at the end. 
•    SELECT … FROM SYSIBM.SYSDUMMY1
•    WITH X AS (SELECT ….)
•    SELECT … FROM SYSIBM.SYSTABLES
•    SELECT … FROM SYSIBM.SYSDUMMY1

In Jaeger, the sequence of spans looks like this:

 
The first span in this graph represents the main method of your Java program (covering the entire program execution). The second span represents the method “getDBInfo” in the program, containing some JDBC calls. As a child of this span, the third span, annotated with “SELECT SYSIBM.SYSDUMMY1” is generated when the “execute” method is called. The span for the Db2 unit work, generated by Db2 for z/OS OTel service, is shown as child of this previous JDBC span. However, from a Db2 perspective, it covers not just this SQL statement but also the following three statements until a COMMIT is reached. From the observability backend, you may think that this Db2 unit of work covers the first SQL statement only, which is not the case. As you can see, the span for the 4th SQL statement (SELECT SYSIBM.SYSDUMMY1) ends shortly after the Db2 unit of work finishes.

Zero-code instrumentation is not aware of the fact that the Db2 unit of work contains multiple SQL statements, so one span is created for every SQL statement. 
In case this level of granularity is not required, JDBC-related method invocation can be excluded from instrumentation. This requires an additional environment variable setting:
OTEL_INSTRUMENTATION_JDBC_ENABLED=false
With this setting, Db2 will still create a span for every unit of work, but no spans would be created for every single client JDBC call:

 
As you can also see in these examples, you can also instrument calls to Db2 native REST services. Every REST call is a Db2 unit of work and, with Zero-code instrumentation, the OTEL framework will create spans for the calling GET and POST methods as well.

Also, there are no specific considerations for Db2 units of work which contain stored procedure executions (with the exception of autonomous stored procedures). Their executions would be part of a Db2 span like other SQL executions. 

Conclusion and other considerations

As you can see it is possible to open your Db2 for z/OS JDBC application for OpenTelemetry with little effort and no programming changes. However, for more granular control, manual instrumentation could be a better choice. This requires changes to your Java application and additional OpenTelemetry libraries in the build path. We will cover this topic in a future blog.

From an operational perspective, additional topics should be considered. Since the OTEL instrumented application and Db2 emit trace spans independently of each other, it is very important that the clocks on all affected platforms are in synch.

Also, the observability backend should be able to handle the incoming span records without causing timeouts.

On the Db2 side, at least accounting trace classes 1 and 2 must be active to generate the metrics emitted to OTEL. Also, there is a minimal overhead in generating and emitting the spans on the Db2 side as well as on the SMF side.

OpenTelemetry is a relatively new topic in the IBM Z and Db2 for z/OS ecosystem. For Db2, it is not limited to JDBC T4 connections. As you can see in this blog 
other attachment facilities such as IMS and CICS are supported as well. This opens up OpenTelemetry tracing also for applications which run primarily on IBM Z.

0 comments
4 views

Permalink