Capturing and replaying Opentelemetry data

I wanted to create some files so that people could get Opentelemetry, and displaying data working, but also to provide sample data. There are several sites offering configuration files, but not the data to display.

This has an additional challenge. With tools like Jaeger and Grafana, you typically display data “in the last 5 minutes”, or “in the last hour”. If I provide data, then it may be viewed a year later. However with a small bit of Python code I fixed this.

The Opentelemetry collector to capture data

This takes the data from the various sources in the network, and collects the information.

The receiver

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

The exporter

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

otlp_http:
endpoint: "http://jaeger2:4318" # was 4318
tls:
insecure: true
file/in:
path: /file.in

The magic

service:
pipelines:
traces:
receivers: [otlp]
# processors: [transform/timestamps] # I did some transformation of the data
exporters: [debug,file/in,otlp_http]

The flow

  • The data came from z/O into the oltp receiver (grpc: endpoint: 0.0.0.0:4317)
  • The service: traces: receivers…, exporters, says pipe the data from otlp and put it to
    • debug – output to the terminal
    • file/in – this maps to a file definition file.in
    • otlp_http – for Jaeger to use

The docker command

I ran the opentelemetry collector in Docker, and this needs some statements to map from internal names (such as file.in), to the real file.


touch file.in.json
truncate -s 0 file.in.json
chmod 777 file.in.json
docker run --rm --name ozf \
--volume "$(pwd)/o2.yaml":/otel-config.yaml \
-v "$(pwd)/file.in.json":"/file.in" \
--env COLLECTOR_OTLP_ENABLED=true \
--publish 4317:4317 \
--publish 4318:4318 \
--network otel-jaeger-network \
otel/opentelemetry-collector-contrib:latest --config otel-config.yaml

The command executed is

otel/opentelemetry-collector-contrib:latest --config otel-config.yaml 

Docker will download the code if needed. It will take the latest level of code.

The truncate file -s o resets the file to empty.

The configuration is in otel-config.yaml. The option –volume “$(pwd)/o2.yaml”:/otel-config.yaml maps otel-config.yaml to the file “$(pwd)/o2.yaml”

The yaml configuration file refers to /file.in. This maps to $(pwd)/file.in.json on my file system.

You can then issue commands like

cat file.in.json | jq |less

to look at the data.

The Opentelemetry collector to replay data

As well as receiving data over http, there are other sources, one of which is oltp_json_file which reads opentelemetry data from a file

receivers:
otlp_json_file:
include:
- "/input.json"
start_at: beginning

The docker command

docker run --rm  --name ozf \
--volume "$(pwd)/o1.yaml":"/otel-config.yaml" \
-v "$(pwd)/otel.in.json":"/input.json" \
--env COLLECTOR_OTLP_ENABLED=true \
--publish 4317:4317 \
--publish 4318:4318 \
--network otel-jaeger-network \
otel/opentelemetry-collector-contrib:latest --config otel-config.yaml

The data is read from file file otel.in.json in the current directory.

Transforming the date times

The JSON data has start and end times. The display tools display “recent data”, such as the “last 5 minutes”. I wrote some python code to find the earliest time stamp, calculate the data from the time stamp to now, and adjust all the timestamps.

'''
Python script to adjust the datetimes in an opentelemety json file, so it
looks as it the data has just been created.

This has been tested on Ubuntu

Format

python3 fixtime filename1 <filename2>

Where
filename1 is the name of the input file
filename2 is the name of the output file. The default is otelts.json

'''
import csv, json
import sys
import json
from datetime import datetime
import json
import time
earliestTime = 0
if len(sys.argv) < 1:
print("fixtime adjusts the otel datetime stampts in otel data to look like they've just been created")
print("The format is fixtime <inputfile> <outputfile>")
print("Where the defaults are inputfile is file.in.json and the outputfile is otel.in.json ")
sys.exit(0)
if len(sys.argv) < 2:
inputFile = "file.in.json"
else:
inputFile = sys.argv[1]
if len(sys.argv) < 3:
outputFile = "otel.in.json"
else:
outputFile = sys.argv[2]
timenow = time.time_ns()
lineno = 0
with open(inputFile, 'r') as file, open(outputFile, 'w') as fout:

for line in file:
data = json.loads(line)
# get the first time - so we can offset all of the time values
if earliestTime == 0: # get the first time found in a span
for d,dvalue in data.items(): # a dict with one item, resourceSpans
for resourceSpan in dvalue : # it is a list - do each one
scopeSpans= resourceSpan["scopeSpans"]
for scopeSpan in scopeSpans: #"scopeSpans": [ is a list of scopes
spans = scopeSpan["spans"] # a dict
# print(spans)
for s in spans: # each span in the list Only element
# s["scopeSpan"] = scopeSpan
earliestTime = s["startTimeUnixNano"]
delta = timenow- int(earliestTime)
break
break
break

# now process all of the data and add the delta to the time stampb
for d,dvalue in data.items(): # a dict with one item resourceSpans
for resourceSpan in dvalue : # it is a list - do each one
scopeSpans= resourceSpan["scopeSpans"]
for scopeSpan in scopeSpans: #"scopeSpans": [ is a list of scopes
spans = scopeSpan["spans"] # a dict
# print(spans)
for s in spans: # each span in the list Only element
s["startTimeUnixNano"] = int(s["startTimeUnixNano"]) + delta
s["endTimeUnixNano"] = int(s["endTimeUnixNano"])
# and now write out the updated data
jsonstr = json.dumps(data)
fout.write(jsonstr+"\n")

sys.exit(0)

How does it all fit together?

  • I ran the otel collector in one terminal, and Jaeger in another.
  • I stopped both of them
  • Use python3 fixtime json.file.in otel.in.json This created file otel.in.json.
  • Started otel
  • Start jaeger
  • Look at the data in a web browser pointing to the Jaeger address and port.