Files
deb-python-eventlet/eventlet/zipkin/api.py
Yuichi Bando 654a271b82 New feature: Add zipkin tracing to eventlet
Zipkin is a trend distributed tracing framewrok developed at Twitter.
Such tracing is useful for both developers and operatos to
understand the behavior of complex distributed systems
and find performance bottlenecks.

This patch provides a WSGI application using eventlet
with tracing facility that complies with Zipkin.

Signed-off-by: Yuichi Bando <bando.yuichi@lab.ntt.co.jp>

Original commit modified for PEP-8 fixes.
https://github.com/eventlet/eventlet/pull/218
2017-03-17 22:42:27 +03:00

187 lines
5.3 KiB
Python

import os
import sys
import time
import struct
import socket
import random
from eventlet.green import threading
from eventlet.zipkin._thrift.zipkinCore import ttypes
from eventlet.zipkin._thrift.zipkinCore.constants import SERVER_SEND
client = None
_tls = threading.local() # thread local storage
def put_annotation(msg, endpoint=None):
""" This is annotation API.
You can add your own annotation from in your code.
Annotation is recorded with timestamp automatically.
e.g.) put_annotation('cache hit for %s' % request)
:param msg: String message
:param endpoint: host info
"""
if is_sample():
a = ZipkinDataBuilder.build_annotation(msg, endpoint)
trace_data = get_trace_data()
trace_data.add_annotation(a)
def put_key_value(key, value, endpoint=None):
""" This is binary annotation API.
You can add your own key-value extra information from in your code.
Key-value doesn't have a time component.
e.g.) put_key_value('http.uri', '/hoge/index.html')
:param key: String
:param value: String
:param endpoint: host info
"""
if is_sample():
b = ZipkinDataBuilder.build_binary_annotation(key, value, endpoint)
trace_data = get_trace_data()
trace_data.add_binary_annotation(b)
def is_tracing():
""" Return whether the current thread is tracking or not """
return hasattr(_tls, 'trace_data')
def is_sample():
""" Return whether it should record trace information
for the request or not
"""
return is_tracing() and _tls.trace_data.sampled
def get_trace_data():
if is_tracing():
return _tls.trace_data
def set_trace_data(trace_data):
_tls.trace_data = trace_data
def init_trace_data():
if is_tracing():
del _tls.trace_data
def _uniq_id():
"""
Create a random 64-bit signed integer appropriate
for use as trace and span IDs.
XXX: By experimentation zipkin has trouble recording traces with ids
larger than (2 ** 56) - 1
"""
return random.randint(0, (2 ** 56) - 1)
def generate_trace_id():
return _uniq_id()
def generate_span_id():
return _uniq_id()
class TraceData(object):
END_ANNOTATION = SERVER_SEND
def __init__(self, name, trace_id, span_id, parent_id, sampled, endpoint):
"""
:param name: RPC name (String)
:param trace_id: int
:param span_id: int
:param parent_id: int or None
:param sampled: lets the downstream servers know
if I should record trace data for the request (bool)
:param endpoint: zipkin._thrift.zipkinCore.ttypes.EndPoint
"""
self.name = name
self.trace_id = trace_id
self.span_id = span_id
self.parent_id = parent_id
self.sampled = sampled
self.endpoint = endpoint
self.annotations = []
self.bannotations = []
self._done = False
def add_annotation(self, annotation):
if annotation.host is None:
annotation.host = self.endpoint
if not self._done:
self.annotations.append(annotation)
if annotation.value == self.END_ANNOTATION:
self.flush()
def add_binary_annotation(self, bannotation):
if bannotation.host is None:
bannotation.host = self.endpoint
if not self._done:
self.bannotations.append(bannotation)
def flush(self):
span = ZipkinDataBuilder.build_span(name=self.name,
trace_id=self.trace_id,
span_id=self.span_id,
parent_id=self.parent_id,
annotations=self.annotations,
bannotations=self.bannotations)
client.send_to_collector(span)
self.annotations = []
self.bannotations = []
self._done = True
class ZipkinDataBuilder:
@staticmethod
def build_span(name, trace_id, span_id, parent_id,
annotations, bannotations):
return ttypes.Span(
name=name,
trace_id=trace_id,
id=span_id,
parent_id=parent_id,
annotations=annotations,
binary_annotations=bannotations
)
@staticmethod
def build_annotation(value, endpoint=None):
if isinstance(value, unicode):
value = value.encode('utf-8')
return ttypes.Annotation(time.time() * 1000 * 1000,
str(value), endpoint)
@staticmethod
def build_binary_annotation(key, value, endpoint=None):
annotation_type = ttypes.AnnotationType.STRING
return ttypes.BinaryAnnotation(key, value, annotation_type, endpoint)
@staticmethod
def build_endpoint(ipv4=None, port=None, service_name=None):
if ipv4 is not None:
ipv4 = ZipkinDataBuilder._ipv4_to_int(ipv4)
if service_name is None:
service_name = ZipkinDataBuilder._get_script_name()
return ttypes.Endpoint(
ipv4=ipv4,
port=port,
service_name=service_name
)
@staticmethod
def _ipv4_to_int(ipv4):
return struct.unpack('!i', socket.inet_aton(ipv4))[0]
@staticmethod
def _get_script_name():
return os.path.basename(sys.argv[0])