Skip to content

Commit 261410d

Browse files
committed
Span buffering; SDK Span type support:
* SDK span type definition and management * Use a Queue to periodically batch send spans * Add support for 'span.kind' tag and translation * Add tests to validate SDK span types * Add tests to validate proper span buffering * Add functions to manage span queue * Add Setup/Teardown functions for tests
1 parent a61125e commit 261410d

File tree

3 files changed

+206
-60
lines changed

3 files changed

+206
-60
lines changed

instana/recorder.py

Lines changed: 101 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,120 @@
11
from basictracer import Sampler, SpanRecorder
22
import instana.agent_const as a
3-
import instana.http as http
4-
import instana.custom as c
53
import threading as t
64
import opentracing.ext.tags as ext
75
import socket
8-
import instana.data as d
6+
import instana.span_data as sd
7+
import Queue
8+
import time
99

10-
class InstanaSpan(object):
11-
t = 0
12-
p = None
13-
s = 0
14-
ts = 0
15-
d = 0
16-
n = None
17-
f = None
18-
data = None
19-
20-
def __init__(self, **kwds):
21-
self.__dict__.update(kwds)
2210

2311
class InstanaRecorder(SpanRecorder):
2412
sensor = None
13+
registered_spans = ("memcache", "rpc-client", "rpc-server")
14+
queue = Queue.Queue()
2515

2616
def __init__(self, sensor):
2717
super(InstanaRecorder, self).__init__()
2818
self.sensor = sensor
19+
self.run()
20+
21+
def run(self):
22+
""" Span a background thread to periodically report queued spans """
23+
self.timer = t.Thread(target=self.report_spans)
24+
self.timer.daemon = True
25+
self.timer.name = "Instana Span Reporting"
26+
self.timer.start()
27+
28+
def report_spans(self):
29+
""" Periodically report the queued spans """
30+
while 1:
31+
if self.sensor.agent.can_send() and self.queue.qsize:
32+
url = self.sensor.agent.make_url(a.AGENT_TRACES_URL)
33+
self.sensor.agent.request(url, "POST", self.queued_spans())
34+
time.sleep(2)
35+
36+
def queue_size(self):
37+
""" Return the size of the queue; how may spans are queued, """
38+
return self.queue.qsize()
39+
40+
def queued_spans(self):
41+
""" Get all of the spans in the queue """
42+
spans = []
43+
while True:
44+
try:
45+
s = self.queue.get(False)
46+
except Queue.Empty:
47+
break
48+
else:
49+
spans.append(s)
50+
return spans
51+
52+
def clear_spans(self):
53+
""" Clear the queue of spans """
54+
self.queued_spans()
2955

3056
def record_span(self, span):
57+
"""
58+
Convert the passed BasicSpan into an InstanaSpan and
59+
add it to the span queue
60+
"""
3161
if self.sensor.agent.can_send():
32-
data = d.Data(service=self.get_service_name(span),
33-
http=http.HttpData(host=self.get_host_name(span),
34-
url=self.get_string_tag(span, ext.HTTP_URL),
35-
method=self.get_string_tag(span, ext.HTTP_METHOD),
36-
status=self.get_tag(span, ext.HTTP_STATUS_CODE)),
37-
baggage=span.context.baggage,
38-
custom=c.CustomData(tags=span.tags,
39-
logs=self.collect_logs(span)))
40-
41-
t.Thread(target=self.sensor.agent.request,
42-
args=(self.sensor.agent.make_url(a.AGENT_TRACES_URL), "POST",
43-
[InstanaSpan(t=span.context.trace_id,
44-
p=span.parent_id,
45-
s=span.context.span_id,
46-
ts=int(round(span.start_time * 1000)),
47-
d=int(round(span.duration * 1000)),
48-
n=self.get_http_type(span),
49-
f=self.sensor.agent.from_,
50-
data=data)])).start()
62+
instana_span = None
63+
64+
if span.operation_name in self.registered_spans:
65+
instana_span = self.build_registered_span(span)
66+
else:
67+
instana_span = self.build_sdk_span(span)
68+
69+
self.queue.put(instana_span)
70+
71+
def build_registered_span(self, span):
72+
""" Takes a BasicSpan and converts it into a registered InstanaSpan """
73+
data = sd.Data(service=self.get_service_name(span),
74+
http=sd.HttpData(host=self.get_host_name(span),
75+
url=self.get_string_tag(span, ext.HTTP_URL),
76+
method=self.get_string_tag(span, ext.HTTP_METHOD),
77+
status=self.get_tag(span, ext.HTTP_STATUS_CODE)),
78+
baggage=span.context.baggage,
79+
custom=sd.CustomData(tags=span.tags,
80+
logs=self.collect_logs(span)))
81+
return sd.InstanaSpan(
82+
t=span.context.trace_id,
83+
p=span.parent_id,
84+
s=span.context.span_id,
85+
ts=int(round(span.start_time * 1000)),
86+
d=int(round(span.duration * 1000)),
87+
n=self.get_http_type(span),
88+
f=self.sensor.agent.from_,
89+
data=data)
90+
91+
def build_sdk_span(self, span):
92+
""" Takes a BasicSpan and converts into an SDK type InstanaSpan """
93+
94+
custom_data = sd.CustomData(
95+
tags=span.tags,
96+
logs=self.collect_logs(span))
97+
98+
sdk_data = sd.SDKData(
99+
Name=span.operation_name,
100+
Custom=custom_data
101+
)
102+
103+
if "span.kind" in span.tags:
104+
sdk_data.Type = span.tags["span.kind"]
105+
106+
data = sd.Data(service=self.get_service_name(span),
107+
sdk=sdk_data)
108+
109+
return sd.InstanaSpan(
110+
t=span.context.trace_id,
111+
p=span.parent_id,
112+
s=span.context.span_id,
113+
ts=int(round(span.start_time * 1000)),
114+
d=int(round(span.duration * 1000)),
115+
n="sdk",
116+
f=self.sensor.agent.from_,
117+
data=data)
51118

52119
def get_tag(self, span, tag):
53120
if tag in span.tags:

instana/span_data.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,21 @@
11
HTTP_CLIENT = "g.hc"
22
HTTP_SERVER = "g.http"
33

4+
class InstanaSpan(object):
5+
t = 0
6+
p = None
7+
s = 0
8+
ts = 0
9+
ta = "py"
10+
d = 0
11+
n = None
12+
f = None
13+
ec = 0
14+
data = None
15+
16+
def __init__(self, **kwds):
17+
self.__dict__.update(kwds)
18+
419
class Data(object):
520
service = None
621
http = None
@@ -26,3 +41,13 @@ class CustomData(object):
2641

2742
def __init__(self, **kwds):
2843
self.__dict__.update(kwds)
44+
45+
class SDKData(object):
46+
Name = None
47+
Type = None
48+
Arguments = None
49+
Return = None
50+
Custom = None
51+
52+
def __init__(self, **kwds):
53+
self.__dict__.update(kwds)

tests/test_ot_span.py

Lines changed: 80 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,90 @@
11
import opentracing
22
from nose.tools import assert_equals
3+
import time
34

5+
class OTSpanTest:
6+
def setUp():
7+
""" Clear all spans before a test run """
8+
recorder = opentracing.global_tracer.recorder
9+
recorder.clear_spans()
410

5-
def test_span_interface():
6-
span = opentracing.global_tracer.start_span("blah")
7-
assert hasattr(span, "finish")
8-
assert hasattr(span, "set_tag")
9-
assert hasattr(span, "tags")
10-
assert hasattr(span, "operation_name")
11-
assert hasattr(span, "set_baggage_item")
12-
assert hasattr(span, "get_baggage_item")
13-
assert hasattr(span, "context")
14-
assert hasattr(span, "log")
11+
def tearDown():
12+
""" Do nothing for now """
13+
return None
1514

1615

17-
def test_span_ids():
18-
count = 0
19-
while count <= 1000:
20-
count += 1
21-
span = opentracing.global_tracer.start_span("test_span_ids")
22-
context = span.context
23-
assert -9223372036854775808 <= context.span_id <= 9223372036854775807
24-
assert -9223372036854775808 <= context.trace_id <= 9223372036854775807
16+
def test_span_interface():
17+
span = opentracing.global_tracer.start_span("blah")
18+
assert hasattr(span, "finish")
19+
assert hasattr(span, "set_tag")
20+
assert hasattr(span, "tags")
21+
assert hasattr(span, "operation_name")
22+
assert hasattr(span, "set_baggage_item")
23+
assert hasattr(span, "get_baggage_item")
24+
assert hasattr(span, "context")
25+
assert hasattr(span, "log")
2526

2627

27-
def test_span_fields():
28-
span = opentracing.global_tracer.start_span("mycustom")
29-
assert_equals("mycustom", span.operation_name)
30-
assert span.context
28+
def test_span_ids():
29+
count = 0
30+
while count <= 1000:
31+
count += 1
32+
span = opentracing.global_tracer.start_span("test_span_ids")
33+
context = span.context
34+
assert -9223372036854775808 <= context.span_id <= 9223372036854775807
35+
assert -9223372036854775808 <= context.trace_id <= 9223372036854775807
3136

32-
span.set_tag("tagone", "string")
33-
span.set_tag("tagtwo", 150)
3437

35-
assert_equals("string", span.tags['tagone'])
36-
assert_equals(150, span.tags['tagtwo'])
38+
def test_span_fields():
39+
span = opentracing.global_tracer.start_span("mycustom")
40+
assert_equals("mycustom", span.operation_name)
41+
assert span.context
42+
43+
span.set_tag("tagone", "string")
44+
span.set_tag("tagtwo", 150)
45+
46+
assert_equals("string", span.tags['tagone'])
47+
assert_equals(150, span.tags['tagtwo'])
48+
49+
def test_span_queueing():
50+
recorder = opentracing.global_tracer.recorder
51+
52+
count = 1
53+
while count <= 20:
54+
count += 1
55+
span = opentracing.global_tracer.start_span("queuethisplz")
56+
span.set_tag("tagone", "string")
57+
span.set_tag("tagtwo", 150)
58+
span.finish()
59+
60+
assert_equals(20, recorder.queue_size())
61+
62+
63+
def test_sdk_spans():
64+
recorder = opentracing.global_tracer.recorder
65+
66+
span = opentracing.global_tracer.start_span("custom_sdk_span")
67+
span.set_tag("tagone", "string")
68+
span.set_tag("tagtwo", 150)
69+
span.set_tag('span.kind', "entry")
70+
time.sleep(0.5)
71+
span.finish()
72+
73+
spans = recorder.queued_spans()
74+
assert 1, len(spans)
75+
76+
sdk_span = spans[0]
77+
assert_equals('sdk', sdk_span.n)
78+
assert_equals(None, sdk_span.p)
79+
assert_equals(sdk_span.s, sdk_span.t)
80+
assert sdk_span.ts
81+
assert sdk_span.ts > 0
82+
assert sdk_span.d
83+
assert sdk_span.d > 0
84+
assert_equals("py", sdk_span.ta)
85+
86+
assert sdk_span.data
87+
assert sdk_span.data.sdk
88+
assert_equals('entry', sdk_span.data.sdk.Type)
89+
assert sdk_span.data.sdk.Custom
90+
assert sdk_span.data.sdk.Custom.tags

0 commit comments

Comments
 (0)