open-telemetry / opentelemetry-python

OpenTelemetry Python API and SDK
https://opentelemetry.io
Apache License 2.0
1.67k stars 570 forks source link

Port of jaeger remote sampling #3865

Closed sconover closed 2 days ago

sconover commented 2 months ago

Description

I'm attempting to port the jaeger remote sampling support from the jaeger-client-python project. For background about why the PR is structured the way it is, please see this thread.

I'm opening this in draft mode to seek initial feedback+guidance. There's a series of TODO's that raise questions about approach.

Type of change

How Has This Been Tested?

Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration

Does This PR Require a Contrib Repo Change?

It doesn't require one, but there will be a companion PR that depends on this change.

Checklist:

sconover commented 2 months ago

@aabmass @srikanthccv Updated as requested, PTAL

sconover commented 2 months ago

In case it's helpful, here are some general instructions for flexing this e2e:

# A jaeger agent may be started using a command like:
#
# docker run \
# -v /dev/scratch/config:/config \
# -e SAMPLING_CONFIG_TYPE=file \
# -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 \
# -p 5775:5775/udp \
# -p 6831:6831/udp \ 
# -p 6832:6832/udp \ 
# -p 5778:5778 \
# -p 16686:16686 \   
# -p 14268:14268 \ 
# -p 14250:14250 \ 
# -p 9411:9411 \
# jaegertracing/all-in-one:1.22 \
# --sampling.strategies-file=/config/strategies.json \
# --sampling.strategies-reload-interval=5s
#
# Where the sampling strategies config file is available locally at
# 
# /dev/scratch/config/strategies.json 
#
#
# Run this python script and make a request using:
#   curl localhost:5000/
# If the sampling decision is 'sample' then the ConsoleSpanExporter
# will print the emitted span to the console.
#
# If the agent's strategies.json files is changed to something like
#
# 
# {
#   "service_strategies": [
#     {
#       "service": "foo",
#       "type": "probabilistic",
#       "param": 0.333
#     }
#   ],
#   "default_strategy": {
#     "type": "probabilistic",
#     "param": 1
#   }
# }
#
# ...once these settings sync over via RemoteControlledSampler,
# it will take several curl invocations to see an emitted span,
# reflecting the 1-in-3 chance that a trace is created.

from opentelemetry.sdk.resources import SERVICE_NAME, Resource

from opentelemetry import trace
from opentelemetry.sdk.trace.export import ConsoleSpanExporter
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import SimpleSpanProcessor

from opentelemetry.jaeger_remote_sampling import RemoteControlledSampler
from opentelemetry.jaeger_remote_sampling import LocalAgentSender

import threading
import tornado

main_loop = tornado.ioloop.IOLoop().current()

import logging
logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)

sampler = RemoteControlledSampler(
    channel=LocalAgentSender('localhost', 5778, 5778, io_loop=main_loop),
    service_name='foo',
    sampling_refresh_interval = 5,
    logger = logger,
)

resource = Resource(attributes={
    SERVICE_NAME: "foo"
})
traceProvider = TracerProvider(resource=resource, sampler=sampler)
traceProvider.add_span_processor(SimpleSpanProcessor(ConsoleSpanExporter("foo")))
trace.set_tracer_provider(traceProvider)

from flask import (
    Flask, 
    jsonify
)

tracer = trace.get_tracer("foo")

def create_app():
    app = Flask(__name__)

    @app.route('/')
    def hello_world(): 
        with tracer.start_as_current_span("foo") as span:
                span.set_attribute("hello.value", "world")
                return jsonify({
                    "status": "success",
                    "message": "Hello World!"
                })

    return app

app = create_app()

if __name__ == '__main__':
    threading.Thread(target=lambda: app.run(debug=True, use_reloader=False)).start()
    print("start main loop")
    main_loop.start()