Processing Opentelemetry data – what’s missing (or is hard)

I’ve spent several weeks implementing Opentelemetry, and although it works, I feel there are bits missing, rather than “wow, what a perfect solution”. I tried to put myself in the position of supporting an enterprise customer who has many critical business applications, across many systems and platforms. The customer wants to be able to identify problems, before their end users notice, and before the end users flood social media with complaints.

Below are my thoughts on what I have learned. I may be totally wrong; maybe I didn’t know about some facilities which would solve the problems. If you know differently – please tell me. I love getting feedback – and I will update the documents.

I want to see the business view not the detailed plumbing view

Problem:

I’ve been to visit customers, and been allowed to visit their “mission control”. This is like the mission control for a moon landing. There are perhaps 200 screens showing all aspects of the business from the application throughput, through to the temperature of the water in the cooling systems. A screen can be selected and displayed “full screen” so every one can see it.

How I see the Opentelemetry data.

By default the information reported in Opentelemetry dashboards shows the plumbing; This database table, that CICS transaction, this MQ queue.

The business wants to know about “online banking”, or a function like “credit user’s account”, and not the name of a CICS transaction. Some services are common to all business applications, such as logon, and move_money. Some services are business application specific, such as “ATM statistics”.

One solution

An input stream of OTEL information can be fanned out to multiple streams. You might have one stream for “Enterprise monitoring”, and another stream for “CICS monitoring”, or “MQ monitoring.

The Opentelemetry collector can fan out the data to a CICS Grafana, an MQ grafana, and a z/OS grafana. Each of these grafanas has been configured to provide the information that the CICS, MQ, and z/OS people need to see.

The Opentelemetry collect can transform the input data, from “MQGET COLIN” to the name of a business application.

- set(span.name,"Payroll MQPA app gets the reply") where span.name == "MQGET COLIN"
- set(span.name,"Creditcheck application GET") where span.name == "MQGET CSERVER"

A different approach

When displaying data in Prometheus or Grafana, you can select which data is included in the displays.

For example if you have specified tracestate when you created your OTEL data, you could have one window for when w3.tracestate=”APPL=MYAPPL”, and another window with w3.tracestate=”APPL=ONLINE BANKING”.

Half the information is not available

Problem.

The aim of the Opentelementry is to identify where work is being delayed, and why. However it only reports on how long a piece of work took.

For any work request there are two components

  • The transaction is doing something, reading a file, sending a request etc
  • Waiting for something. This could be the response to a request. The work could be waiting, for example, because of insufficient resources (the CPU is too busy), or there is a long network round trip time.

The Opentelementry dashboard only reports on when something is being done (a database update has been done). It does not report on the waiting. Often the waiting is the longest part of a transaction. As a result the dashboard is not reporting all the facts.

If you consider the simplest application where an application queues some work, and the work executes at a later date. For example a CICS transaction schedules another CICS transaction in another CICS region on a different LPAR in the sysplex. The started transaction may be delayed because too many other requests are queued up.

The business transaction is:

  • Run a CICS transaction ABCD – which issue a START of transaction WXYZ. The whole transaction takes 1 millisecond elapsed time.
  • There is some delay due to getting the request to the remote system, and a delay until the transaction can run
  • After 10 milliseconds, transaction WXYX runs, which takes 1 millisecond.

The dashboards will report

  • Transaction ABCD 1ms
  • Transaction WXYZ 1m

So shows the business transaction taking 2 ms.

It does not always show the 10 ms delay before the work was scheduled. (it depends on what data is generated)

  • If the time before transaction WXYZ increases to 20ms – the dashboards do not show it.
  • If the transaction ABCD takes longer (perhaps it had a longer database request), then it would show up.

Answer:

I wrote some Python code which creates a new Opentelemetry record covering the gap between two records, so from the start of transaction WXYZ back to the end of its parent. This gave me three records in my dashboard

  • Transaction ABCD 1ms
  • Delay before transaction WXYZ starts 10 ms
  • Transaction WXYZ 1m

This could be done properly by writing a processing stage in GO in Opentelemetry.

There is too much information

Problem: Too many layers in the cake

In simplest typical MQ transaction, I have 8 items displayed on the dashboard

  • Client application puts a message to a queue
  • The mover gets the message and sends it to the remote system
  • The mover on the remote system puts the message to the queue
  • The server application gets the message
  • The server puts the reply

On the dashboard it shows the average time for each of these, but it is hard to tell which colour is for which action.

Answer:

You can say display the top n values. ( I used 4). If something takes longer than usual, it will appear in the list – and so the list will have a new colour – and you can see something is different.

Problem: you are using too many values

Each data record has a value (such as duration) and “dimension” (or attributes) of the record, such as Originating system, Opentelemetry instance, Span-name(such as transaction ABCD).

By default if you display the data, there will be a “layer” in the cake for every unique combination of dimensions.

If you have 50 different CICS transactions, you will have 50 slices. You can select which attributes to select by, and can group them by regular expression.

The problem is if the span is called “CHECK USERID xxxxxxx ” or MQPUT CSQX……” where there is a span for each userid checked, and for every MQ dynamic queue. The number of these depends on activity. It is hard to display the data so you can get useful data out of it.

Answer:

In the Opentelemetry collector you can use a transformation to set(or add) a value depending on the contents of a field. The following checks the name of an MQ Queue. If the queue name starts CSQX. then consider it a dynamic queue and give these entries the generic name CSQX*

set(span.name,"MQPUT *CSQX") where Substring(attributes["span,name], 0, 11) = "MQPUT CSQX."

See Understanding the OpenTelemetry Transform Language

The statement would replace all span names starting with MQPUT CSQX. with the string MQPUT *CSQX, and so be obvious this is a substituted name.

Drilling down on outliers

When a problem occurs (the duration of the business transaction take much longer than usual), you want to be able to drill down, and find out why.

Problem: Drilling down to find the root cause is hard

Jaeger display

If you are using Jaeger display to display the business transaction response time, you may spot outliers, and be able to click on one, and see the profile of the data.

You cannot select a time range, only the last 5m, 15m, 1h etc. You may have millions of records in 1 hour, and I do not think Jaeger is up to it, because it suggests processing 20 records.

Grafana display

Grafana displays aggregated information, so does not have individual records. You can see the time interval when the long durations occurred.

Answer:

You can configure Grafana links. When you click on a data item, a pop up giving information about that point is displayed. You can configure links which can be selected. For example select the Jaeger display of this service, between the two time stamps selected.

This was not easy to set up – because the links did not display every time. When it works it works well.

You can configure links to pass a URL and parameters from the data, or take an action.

Start here for processing Opentelemetry data – visualisation in Grafana.

I’ve written a High level view of Opentelemetry, and visualising Opentelemetry data in Jaeger.

This blog post is about the Opentelemetry aggregated data, and displaying it in Grafana.

Prometheus accepts metric data from sources like Opentelemetry, but it does not take the raw Opentelemetry “log” data from application directly into Prometheus. There is a component System Performance Measuring(SPM) which takes the OTEL log data and creates metrics. It runs in Opentelemetry collector, or the Jaeger program. (I think running it in Opentelemetry collector is better, because running it in Jaeger did not immediatelygive me all of the records)

The SPM code takes each record

  • extracts hinformation from the OTEL logs. For example the service name, the span name, and others fields that have been configured in the configuration file

Conceptually the SPM code takes all the records received in a time interval – say 10 seconds, and creates two records

  • traces_span_metrics_duration_milliseconds_count
  • traces_span_metrics_duration_milliseconds_sum

and copies in the default dimensions (meta data) and which additional dimensions have been configured to be included. My simplistic view would have had one record with both the sum and the count, but this is not how it is done. There is a lot of additional data for each of the generated records, but I cannot find how to use it.

See here for information

The span metrics processor works by inspecting every received span and computing the total count and the duration of spans for every unique combination of dimensions. Dimensions can be the service name, the operation, the span kind, the status code and any attribute present in the span.

You can display the data in Prometheus, but you can do so much more with Grafana. I did Unit Testing with Grafana, and my System Testing using Grafana, because Grafana can save queries and dashboards.

It will help if you are familiar with the Prometheus data model.

First query

The values traces_span_metrics_duration_milliseconds_sum and traces_span_metrics_duration_milliseconds_count are ever increasing.

To find the average value over 1 minute intervals use (see here for an explanation)

increase(traces_span_metrics_duration_milliseconds_sum[1m])/ increase(traces_span_metrics_duration_milliseconds_count[1m])

If you move the mouse over a colour, it gives you information about that point (see the purple data below). The pop-up shows it is for an MQPUT to CSERVER for queue manager (=service name) CSQ9, with a duration of 1.52 ms.

Below the chart is all the data for the items.

There is data on the chart for every combination of dimensions/parameters/meta data. These items include information like

service.name: CSQ9
span.name: MQPUT CSERVER
span.kind: SPAN_KIND_PRODUCER
status.code: STATUS_CODE_UNSET
collector.instance.id: 2e1c51fc-7f54-4da5-aa9f-9c3cac190103
w3.tracestate: rojo=MODEL – this is what I passed on the trace data.

I had some data with service.name MQPA, and different span.names

This is for all resources

You can select which records are displayed, for example

rate(traces_span_metrics_duration_milliseconds_sum{w3_tracestate= "rojo=MODEL"}[$__rate_interval])

Selects those with the property w3.tracestate = “rojo=MODEL”. I specified this by setting the MQ message propery

tracestate=rojo=MODEL  

You can control which parameters are passed from the original records into the aggregated records. See the dimensions parameter in the span_metrics: definitions.

Set up data source

You can set up different data sources. I have one for Prometheus, and one or Jaeger. Once you have configured them you can use explore to look at the data.

Set up dashboards

You can set up dashboard to display the data in different ways.

You specify the data source, and then how you want the data formatted, typically I use a timeline.

You can use Builder |code to define your query. With builder you define the query through a GUI. With code you specify the query.

You can define multiple queries which display on the same windows. You can hide/unhide the data from each query, by selecting/unselecting the query.

You could define one query for w3_tracestate= “appl=OnlineBanking”, and another for w3_tracestate= “appl=OfflineBanking”.

Time ranges

From a pull down you can select “last 5 minutes”, “last 15 minutes”, or you can specify a date and time range. This means you can go back to a previous days data.

I have it refreshing the data every 5 seconds, and the graph data gradually creeps left, until the data falls out of the displayed time range.

Data links

On each page you can set up data links. If you hover your graphic cursor over a data item, it shows the data item’s attributes in a pop up window. You can also configure data links, which appear on in the pop up window, so you can link to another URL, passing data from the data items.

In the figure below, I graphed the top 4 span_names with the highest average value. It has a data link called colins jaeger2

By clicking on the “colins jaeger2” it invoked the URL, and passed the __from and __to times, in the URL.

Start here for processing Opentelemetry data – visualisation in Jaeger

Ive written

Jaeger

Jaeger is a component which does two things

  • Visualises the data – see the image at the top of the blog post
  • Can aggregates the data for Grafana to use

Visualise the data

By default it displays 20 entries

and shows information about the individual points

This shows there were two queue manager CSQ9 and MQPA, there were 8 spans (recorded data)the duration if each item, and the date. ( I think the date format is wrong .. you want hh:mm:ss – because you know what the day and date are!

If you click on the blue Trace Name – you get a detailed picture for that trace point of where the time was spent

There is a limited search capability. You can display data going back 5 minutes, last 15 minutes etc.

You can limit how many records are displayed.

Displaying 100 records over half an hour gave me

If you click on an outlier you get the details of that point

You can click on each Service & Operation and see all the attributes associated with the record. For example z/OS image name, message persistence, size of message etc.

The challenges with using Jaeger visualisation

What transaction is this for ?

I ran two different transactions at the same time. The data displayed was the same as above. I could not identify which trace record was for which transaction.

If you display the details, the “title” such as MQPA MQPUT RSERVER gives you a clue as to which transaction the data is for.

Whoops missed it

If you have a low transaction rate, then if you display the last 10 minutes, you may get all of the data.

If you are doing a high transaction rate, and need to go back half an hour, there may be so much data you cannot find the entries of interest.

Start here for processing Opentelemetry data

I’ve spend a few weeks trying to generate and use Opentelemetry data. My salesman’s vision does not match the practice. Below is what I have learned from hands on. It shows the typical usage of displaying Opentelemetry data using Jaeger and Grafana. What I say may be wrong, if so please tell me and I’ll correct it.

There are follow-on posts

What is opentelemetry?

As work moves through a system it reports where it is. A central collector takes this data and can display where the work item spent its time.

This shows an application putting a message to an MQ queue, on queue manager MQPA, flowing through to a server on queue manager on CSQ9, and a response flowing back.

The overall transaction time was 71 ms.

The architecture

On z/OS the OTEL data is written to SMF. There is a Java application which runs on z/OS, which reads from SMF, and sends it to an Opentelemetry collector (running on my Linux laptop).

The Opentelemetry collector generates data in the form for “standard” packages Prometheus and Grafana to process, and display dashboards of the information.

There are two data models for sending data between components

  1. The Opentelemetry collector has a push model. The recipient is a web server, and the OTEL collector sends JSON data over a POST request to the HTTP server.
  2. Grafana has a pull model. It periodically sends a request to its providers saying “send me data on ….”

Prometheus has a database, which has a web server for capturing the data from the Opentelemetry client. Prometheus saves the timestamp data efficiently. Prometheus provides another web server to respond to the “send me data …” from Grafana.

Prometheus accepts metric data from sources like Opentelemetry, but it does not take the raw Opentelemetry “log” data directly into Prometheus – I think of Prometheus as a database.

Grafana takes data from many sources (such as Prometheus) and displays dash boards, which typically are time sequences of data. See below.

There is another component Jaeger, which converts the data from OTEL into data suitable for Grafana.

I expect people will use Grafana for overall monitoring, and Jaeger to dig down into a time range.

Note 1: You cannot go directly from Opentelemetry to Grafana, because Opentelemetry only provides a push model.
Note 2: You can write your own components to process the data from Opentelemetry.

Grafana

A typical chart showing where time was spent

There are many layer – each one is for a unique span name and dimensions (meta data). There is a pop up with information about where the graphical cursor is. The information shows for queue manager MQPA, the MQPUT to RSERVER took 0.635 milliseconds.

Overall the work took between 7 and 4 milliseconds.

You can display multiple charts in a dashboard

This shows the average times for the different spans ( MQGET COLIN), and the count of “transactions”.

The MQPUT to CSERVER was highest, and about 1.2 milliseconds.

For more information on Grafa see Start here for processing Opentelemetry data – visualisation in Grafana

Jaeger

Jaeger is a component which does two things

  • Visualises the data – see the image at the top of the blog post.
  • Can aggregates the data for Grafana to use

Visualise the data in Jaeger

By default it displays 20 entries

and

This shows there were two queue manager CSQ9 and MQPA, there were 8 spans (recorded data) the duration if each item, and the date. ( I think the date format is wrong .. you want hh:mm:ss – because you know what the day and date are!

If you click on the blue Trace Name or a blue dot – you get a detailed picture of where the time was spent

For more information on displaying data in Jaeger see Start here for processing Opentelemetry data – visualisation in Jaeger

How does a C program wait for a short random time?

As part of getting a workload with a varying response time, I wanted the server to wait for a short amount of time between processing requests. This was pretty easy, but there were a couple of challenges

The code

#ifndef __timespec_struct
#define __timespec_struct 1
struct timespec
{
time_t tv_sec;
long tv_nsec;
};
#endif


struct timespec randomWait;

#undef RAND_MAX
#define RAND_MAX 100
j = RAND_MAX;
int myrand = rand();
printf("Random number %i %i\n",myrand,j);

randomWait.tv_sec = 0 ;
randomWait.tv_nsec = myrand * 1000000;

nanosleep(randomWait, NULL); // if specified

Define timespec structure

The documentation for nanosleep gave some example code. When I used it, I got a compile error

"struct timespec" is undefined.

I found it easier to define the timespec structure myself.

Set the upper limit for the random number.

You do not pass the upper limit to the rand() function. The value is taken from the #DEFINE RAN_MAX. (A strange way of doing it). The default upper limit is 32767.

To set your own limit, you need to #undef, then #define with the new value.

Setting the random time

The timespec structure has the value in seconds and nanoseconds. Because I wanted a random wait in milliseconds, I used random_value * 1000000 to convert to nano seconds.

Why can’t I change my Linux terminal environment from a script

I wanted to configure my terminal sessions, with a tab label to say what the window is, and colour them, so I get a visual clue.

For example I have a script

PS1='[\e]0;Opentel\a]\u@\$ '
docker run --rm --name ozf \
...
otel/opentelemetry-collector-contrib:latest --config opentransform.yaml

If I enter the PS1 command in the window – it changes tab’s label.

If I put it in a shell script, I execute the commands to change my tab and run the docker image, but it does not change the tab’s label. Why not?

  • The source command (or the . command) treats the commands in the file, as if I had entered them at the terminal.
  • With the bash or shell, the command creates a new shell, and then executes commands inside it. Once the command finishes, the shell is destroyed, but the original shell is unchanged.g

Another of the it is obvious when you know the reason.

Configuring opentelemetry, Jaeger, Prometheus, Grafana – problem determination

It took me a long time to get all of the parts of the solution working. There were many reasons; I was new to the environment, some things were not obvious, and some of the documentation feels like it is written by experts for experts.

I learned the debug techniques while writing the Configuring opentelemetry, Jaeger, Prometheus, Grafana 101 the basics,102 doing processing, and 103 http endpoints posts.

Topics

Opentelemetry configuration problems.

You can specify print-initial-config as in

otel/opentelemetry-collector-contrib:latest print-initial-config –config otel-config.yaml

This is useful if you have more than one –config file.

Reading test data from a file

You can have JSON data read from a file, so you do not need to have a test creating data for you. It also means you get consistent data while developing the scenario

receivers:
otlp_json_file:
include:
- /fooin.file
start_at: beginning
...
service:
pipelines:

traces/a:
receivers: [otlp_json_file] # oltp
exporters: [span_metrics] # otlp_http,debug

Monitoring traffic coming out of a pipe

The output end of the pipe is defined using an exporter. There can be more than one end point defined, you can add an exported which writes to the terminal, or writes to a file.

For example the debug writes data to the terminal log. You can use the file to write data to a file.

exporters:

file/b:
path: /b.file
debug:
verbosity: detailed # Options: basic, normal, detailed
# sampling_initial: 2
# sampling_thereafter: 10
service:
pipelines:
traces/a:
receivers: [otlp_json_file] # oltp
exporters: [span_metrics] # otlp_http,debug
metrics:
receivers: [span_metrics]
exporters: [file/b,debug]

You can tell debug to write every row, or the first n rows, then sample the remainder.

Using docker makes it easier and harder.

Using docker it was easy to spin up the parts and get them working. All pre-reqs were installed etc.
Docker uses it’s own internal network, and getting the component to talk to each other was harder.

Displaying network traffic between containers

You can use the docker command

docker network ls

this gives information like

NETWORK ID     NAME                  DRIVER    SCOPE
26d793ac45cf bridge bridge local
4e4dc158fa88 host host local
0cfcba5bb0ff none null local
03617d139834 otel-jaeger-network bridge local

Where the docker network I was using was called otel-jaeger-network.

On Linux the ip link command gave

4: br-03617d139834: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default 
link/ether 12:87:1d:dc:7d:9a brd ff:ff:ff:ff:ff:ff
5: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN mode DEFAULT group default
link/ether 9a:9f:53:76:9d:a6 brd ff:ff:ff:ff:ff:ff

with the network prefixed with br ( for bridge).

Starting wireshark gave me a list of networks including br-03617d139834. I used this to display the traffic.

You can filter the traffic using filters like

tcp.dstport==4317

which says display records where the destination port was 4317. You could also use tcp.port=4317

This will allow you to see the traffic between containers.

Is z/OS data gathered writing data?

Initially it was challenging to see if data was being sent from z/OS. I had the situation where the first data was sent from z/OS, but then no more. I had to restart the data gatherer on z/OS.

I used

IJO="$IJO -Djavax.net.debug=ssl:handshake " 

to display the TLS handshake, and the message flow.

Example output

"certificate" : {                                                      
"version" : "v3",
"serial number" : "02:ba",
"signature algorithm": "SHA256withECDSA",
"issuer" : "CN=SSCA256, OU=CA, O=SSS, C=GB",
"not before" : "2026-06-29 02:36:41.000 EDT",
"not after" : "2029-01-30 11:46:00.000 EST",
"subject" : "CN=tempcert, O=cpwebuse2, C=GB",
"subject public key" : "RSA",
....

javax.net.ssl|DEBUG|D2|OkHttp https://10.1.0.2:4317/...|
2026-07-10 03:05:31.784 EDT|SSLSocketOutputRecord.java:334|
WRITE: TLSv1.3 application_data, length = 4854
javax.net.ssl|DEBUG|F2|OkHttp 10.1.0.2
|2026-07-10 03:05:31.789 EDT|SSLSocketInputRecord.java:214
|READ: TLSv1.2 application_data, length = 47
...

You can also use Wireshark to monitor traffic. With TLS 1.2 you can see details in the TLS handshake. With TLS 1.3 the details of the handshake are encrypted, and you just see the flow, and the data length.

Configuring opentelemetry, Jaeger, Prometheus, Grafana – 103 http endpoints

The above packages take tracking data or metrics and can display them in dashboards. I found it a struggle to understand how they were configured, as the documentation assumes you are an expert, and I could not find any “starting from zero” documentation.

This is one of my blog posts on using the above packages with Docker. See Configuring opentelemetry, Jaeger, Prometheus, Grafana

Understanding endpoints

If you think of the processing as a pipe line. Data from one or more sources go into the pipe, and at the remote end of the pipe the data can be copied to one or more destinations (fanned out).

When you define the pipeline you define

  • receivers – these identify what goes into the pipe
  • exporters – these identify what goes out of a pipe

Pass on data

As well as writing data to a file, or displaying it using debug, the most common pattern is to pass data on to another package

For example with Opentelemetry data it is passed it on to the Jaeger package which displays transaction delay information in near real time.

The code snippet below takes oltp data and exports it to a driver which exports otlp data over http.

The otlphttp definition sends it to the url http://jaeger2:4318. Where jaeger2 is some Docker magic which routes it to the Docker image called jaeger2.

...
exporters:
otlphttp:
endpoint: "http://jaeger2:4318"
tls:
insecure: true
service:
pipelines:
traces:
receivers: [ otlp ]
processors:
exporters: [otlphttp]

The jaeger2 configuration has

receivers:
otlp:
protocols:
grpc:
endpoint: "${env:JAEGER_LISTEN_HOST:-localhost}:4317"
http:
endpoint: "${env:JAEGER_LISTEN_HOST:-localhost}:4318"

This defines a receiver of type otlp, for http data, read from port 4318.

Endpoints

You might see

${env:JAEGER_LISTEN_HOST:-localhost}           

This is a configuration syntax commonly used in Jaeger or OpenTelemetry to set default values for environment variables.

It evaluates as follows:

  • If JAEGER_LISTEN_HOST is set: It uses the IP address or host defined in the environment variable.
  • If JAEGER_LISTEN_HOST is empty or not set: It falls back to the default value of localhost.

A typical flow between containers is Opentelemetry, Jaeger, Prometheus, Grafana. You do not need to know what these are, just they are just containers which do processing.

Push and pull.

Push

For the connection between Opentelemetry and Jaeger, data is pushed to Jaeger.

  • The Jaeger acts like a server, opens a TCP/IP socket, and listens for connections to it. You typically specify a network address of 0.0.0.0, which means listen on all networks on this box for the specified port. You can specify an address such as 172.26.5.0 which says only clients using addresses 172.26.5.* can connect.
  • The Opentelemetry container acts like a client, and has the IP address and port of the Jaeger connection. It initiates a session to Jaeger.

Running with docker adds a layer of complexity to it. Docker uses its own (sub) network. I could run two docker environments on my laptop, and both could use port 999. Each environment has its own network, and each subnetwork can have its own ports. The client has to use the correct IP address,

To get my docker environment working consistently, I used a definiton like

endpoint: "http://jaeger2:4318"

where jaeger2 is the name of the docker image of the jaeger container. Docker then substitues the IP addess of that container.

Pull

The situation is more complex than I have written.

For the Jaeger to Prometheus connect, typically Prometheus “scrapes” the data from Jaeger. For a few hours my mental picture was Jaeger pushing data to Prometheus; so Jaeger needed the IP address of Prometheus. This is the wrong way round.

Prometheus contacts Jaeger and says “send me your data”. Prometheus is the client, and Jaeger is the server. For this connection, Prometheus has to have a definitions like

endpoint: "http://jaeger2:7654"

Receivers

With a receiver you just specify a port; to be more specific you specify an adapter and a port.

  • Localhost is 127.0.0.0 is the internal adaptor
  • 0.0.0.0 stands for all adaptors

You specify an endpoint like

receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317

The endpoint url of 0.0.0.0 seems to work. The documentation says use localhost, but this did not always work for me.

The 4317 port is an external port, I can send data to it from z/OS.

In the docker container, the internal value above is mapped to the external using

  --publish 4317:4317 

Exporters

You specify an endpoint like

exporters:

otlp_http:
endpoint: "http://jaeger2:4318"
tls:
insecure: true

This says use the “driver” to export otlp data over http. The jaeger2 directs the data to the docker image with name jaeger2. You do not need

  --publish 4318:4318 

in the docker file for this to work, because it is internal to the docker configuration.

Getting in and out of the docker environment.

Because docker runs its own sub-network, you need to configure external ports to docker.

To pass data from z/OS down to the Opentelemetry container I used port 4317 on my laptop.

The Opentelemetry configuration is

receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
tls:
cert_file: /server.pem
key_file: /server.key.pem
min_version: "1.3" # Enforces TLS 1.3 as the minimum requirement
max_version: "1.3" # Locks maximum version to TLS 1.3
cipher_suites: []
reload_interval: "1h"

The docker configuration has

 --publish 4317:4317 \

which is of the format external:internal.

Configuring opentelemetry, Jaeger, Prometheus, Grafana – 102 processing data

This is one of my blog posts on using the above packages with Docker.

See Configuring opentelemetry, Jaeger, Prometheus, Grafana

The above packages take tracking data or metrics and can display them in dashboards. I found it a struggle to understand how they were configured, as the documentation assumes you are an expert, and I could not find any “starting from zero” documentation.

This follows on from the 101 the basics.

Doing processing – counting records

With the configuration file

receivers:
otlp_json_file:
include:
- /fooin.file
start_at: beginning
exporters:
file/a:
path: /fooout.file
file/b:
path: /b.file

connectors:
count:

service:
pipelines:
traces:
receivers: [otlp_json_file]
exporters: [count]
metrics:
receivers: [count]
exporters: [file/b]

This reads the input file, and copies the data to “count”. Count is a pipeline stage which reads the input, counts the records and outputs a summary of the data to the receiver count, which maps to the file b.file.

This runs in Docker, so there is b.file maps to a real file.

 -v "$(pwd)/foob.json":"/b.file" \

The file needs to exist ( use touch foob.json) and the userid docker needs write to it. I used chmod 777 foob.josn

Another example using span_metrics

This counts the number of span records from opentelemetry collector. It is defined here.

The documentation says

Exporter Pipeline TypeReceiver Pipeline Type
tracesmetrics

This says it receives data with a type of metrics, and outputs data with a type of trace.

  • traces: … exporters: [span_metrics]
  • metrics: receivers: [span_metrics]
receivers:
otlp_json_file:
include:
- /fooin.file
start_at: beginning

exporters:
file/a:
path: /fooout.file

file/b:
path: /b.file

debug:
verbosity: detailed # Options: basic, normal, detailed
sampling_initial: 2
sampling_thereafter: 10


connectors:
count:
span_metrics:

processors:
batch:

service:
pipelines:

traces:
receivers: [otlp_json_file]
exporters: [span_metrics]
metrics:
receivers: [span_metrics]

exporters: [file/b,debug]

Configuring opentelemetry, Jaeger, Prometheus, Grafana – 101

The above packages take tracking data or metrics and can display them in dashboards. I found it a struggle to understand how they were configured, as the documentation assumes you are an expert, and I could not find any “starting from zero” documentation.

These packages often run under Docker, which introduces additional complexity.

My mission

My missions was to take the Opentelemetry data from MQ on z/OS and display a summary of the data in Grafana, so I could see “the average transaction time over the last hour was ..”.

I can capture the data and send it down to Opentelemetry running on Ubuntu. I can display it in Jaeger so I know the basics work.

The basics

Configuration is done using YAML. This is a good interface and easy to use. Sub parameters are indented.

There is documentation on Opentelemetry collector configuration.

The configuration breaks down to

  • input definitions ( receivers)
  • output definitions ( exporters)
  • processing

The simplest configuration file for Opentelemetry is

receivers:
otlp_json_file:
include:
- /fooin.file
start_at: beginning
exporters:
file:
path: /fooout.file

service:
pipelines:
traces:
receivers: [otlp_json_file]
exporters: [file]

Within the receivers and exporters you have “driver definitions” (my term). These drivers are like external functions. otlp_json_file is a driver for reading from a json file. Someone has written this (in go). It is not in the default opentelemetry package, so you have to use the package which includes these drivers.

For example the docker definition is

docker run --rm  --name otelcollector \
...
otel/opentelemetry-collector-contrib:latest ...

where the standard package is otel/opentelemetry-collector:latest, without the -contrib.

The parameters for this driver are

     include: /fooin.file
start_at: beginning
  • read the file fooin.file
  • and start at the beginning.

There are many parameters you can specify – for example which code page the data is in. See the code on github.

In a similar way there is a “file driver” which writes data to the file with path: /fooout.file.

You cannot use a random name as a driver – it has to be available in the configuration.

Docker

When running under Docker, there is a level of indirection. In my Docker configuration I have

  -v "$(pwd)/myfoo.file":"/foo.file" 

Where /foo.file mentioned in the configuration file, and this maps to myfoo.file in the current directory. If you are using an output file, create it (use the touch myfoo.file command), and use chmod 777 myfoo.file so the container ( running under the docker userid) can access it.

The processing

There is a section

service:
pipelines:
traces:
receivers: [otlp_json_file]
exporters: [file]

Which says create a pipe line between the input receiver(s) and the output exporters. For trace data read from the device driver otlp_json_file and write it to the device driver file.

This just reads from the input file and writes the output to the output file.

A more complex example

receivers:
otlp_json_file:
include:
- /fooin.file
start_at: beginning

exporters:
file/a:
path: /fooout.file
file/b:
path: /b.file


service:
pipelines:
traces:
receivers: [otlp_json_file]
exporters: [file/a,file/b]

This has two exporters file/a,file/b. In the exporters section, there is still the same device driver file, but there are now two of them file/a and file/b.

With only one definition you could use just file, or you could have a more descriptive definition file/mydata.

Can I just write it to the terminal?

Yes use debug

exporters:
debug:
verbosity: detailed # Options: basic, normal, detailed
...

exporters: [file/b,debug]

This can produce a lot of output, so only use it when there is only a little data.

You can use sampling_initial and sampling_thereafter to display the first few messages, then sample the rest, so you do not get flooded.

Running under docker

Within the docker definition I have code

  -v "$(pwd)/foo.file":"/fooin.file" \
-v "$(pwd)/fooout.json":"/fooout.file" \
-v "$(pwd)/b.json":"/b.file" \

which maps the name in the yaml, eg /fooin.file to the name outside of docker $(pwd)/foo.file, and so the file used by b.file is actually b.json