Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ddtrace/reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ class AgentReporter(object):

SERVICES_FLUSH_INTERVAL = 120

def __init__(self):
self.transport = ThreadedHTTPTransport()
def __init__(self, hostname, port):
self.transport = ThreadedHTTPTransport(hostname, port)
self.last_services_flush = 0

def report(self, spans, services):
Expand Down
4 changes: 2 additions & 2 deletions ddtrace/sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@
log = logging.getLogger(__name__)


class DefaultSampler(object):
"""Default sampler, sampling all the traces"""
class AllSampler(object):
"""Sampler sampling all the traces"""

def sample(self, span):
span.sampled = True
Expand Down
67 changes: 41 additions & 26 deletions ddtrace/tracer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import threading

from .buffer import ThreadLocalSpanBuffer
from .sampler import DefaultSampler
from .sampler import AllSampler
from .span import Span
from .writer import AgentWriter

Expand All @@ -12,9 +12,7 @@


class Tracer(object):
"""
Tracer is used to create, sample and submit spans that measure the
execution time of sections of code.
"""Tracer is used to create, sample and submit spans that measure the execution time of sections of code.

If you're running an application that will serve a single trace per thread,
you can use the global traced instance:
Expand All @@ -23,37 +21,56 @@ class Tracer(object):
>>> tracer.trace("foo").finish()
"""

def __init__(self, enabled=True, writer=None, span_buffer=None, sampler=None):
"""
Create a new tracer.
DEFAULT_HOSTNAME = 'localhost'
DEFAULT_PORT = 7777

:param bool enabled: If true, finished traces will be submitted to the API. Otherwise they'll be dropped.
"""
self.enabled = enabled
def __init__(self):
"""Create a new tracer."""

self._writer = writer or AgentWriter()
self._span_buffer = span_buffer or ThreadLocalSpanBuffer()
self.sampler = sampler or DefaultSampler()
# Apply the default configuration
self.configure(enabled=True, hostname=self.DEFAULT_HOSTNAME, port=self.DEFAULT_PORT, sampler=AllSampler())

# a list of buffered spans.
self._spans_lock = threading.Lock()
self._spans = []

# track the active span
self.span_buffer = ThreadLocalSpanBuffer()

# a collection of registered services by name.
self._services = {}

# A hook for local debugging. shouldn't be needed or used
# in production.
self.debug_logging = False

def trace(self, name, service=None, resource=None, span_type=None):
def configure(self, enabled=None, hostname=None, port=None, sampler=None):
"""Configure an existing Tracer the easy way.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not sure about overwriting all of the things. if you don't pass in a sampler, wouldn't you want to use the sampler you already have? can we do these individually? i feel like it's simpler.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, this method works well only once (it doesn't "reconfigure" properly).
But it is easy to fix, doing it.

Copy link
Contributor Author

@LotharSee LotharSee Jul 27, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in this commit bc551f1


Allow to configure or reconfigure a Tracer instance.

:param bool enabled: If True, finished traces will be submitted to the API. Otherwise they'll be dropped.
:param string hostname: Hostname running the Trace Agent
:param int port: Port of the Trace Agent
:param object sampler: A custom Sampler instance
"""
Return a span that will trace an operation called `name`.
if enabled is not None:
self.enabled = enabled

if hostname is not None or port is not None:
self.writer = AgentWriter(hostname or self.DEFAULT_HOSTNAME, port or self.DEFAULT_PORT)

if sampler is not None:
self.sampler = sampler

def trace(self, name, service=None, resource=None, span_type=None):
"""Return a span that will trace an operation called `name`.

:param str name: the name of the operation being traced
:param str service: the name of the service being traced. If not set,
it will inherit the service from it's parent.
:param str resource: an optional name of the resource being tracked.
:param str span_type: an optional operation type.

You must call `finish` on all spans, either directly or with a context
manager.
Expand All @@ -78,7 +95,7 @@ def trace(self, name, service=None, resource=None, span_type=None):
>>> parent2.finish()
"""
span = None
parent = self._span_buffer.get()
parent = self.span_buffer.get()

if parent:
# if we have a current span link the parent + child nodes.
Expand All @@ -104,26 +121,26 @@ def trace(self, name, service=None, resource=None, span_type=None):
self.sampler.sample(span)

# Note the current trace.
self._span_buffer.set(span)
self.span_buffer.set(span)

return span

def current_span(self):
""" Return the current active span or None. """
return self._span_buffer.get()
"""Return the current active span or None."""
return self.span_buffer.get()

def record(self, span):
""" Record the given finished span. """
"""Record the given finished span."""
spans = []
with self._spans_lock:
self._spans.append(span)
parent = span._parent
self._span_buffer.set(parent)
self.span_buffer.set(parent)
if not parent:
spans = self._spans
self._spans = []

if self._writer and span.sampled:
if self.writer and span.sampled:
self.write(spans)

def write(self, spans):
Expand All @@ -137,11 +154,10 @@ def write(self, spans):

if self.enabled:
# only submit the spans if we're actually enabled.
self._writer.write(spans, self._services)
self.writer.write(spans, self._services)

def set_service_info(self, service, app, app_type):
"""
Set the information about the given service.
"""Set the information about the given service.

:param str service: the internal name of the service (e.g. acme_search, datadog_web)
:param str app: the off the shelf name of the application (e.g. rails, postgres, custom-app)
Expand All @@ -155,4 +171,3 @@ def set_service_info(self, service, app, app_type):
if self.debug_logging:
log.debug("set_service_info: service:%s app:%s type:%s",
service, app, app_type)

6 changes: 5 additions & 1 deletion ddtrace/transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ class ThreadedHTTPTransport(object):
# Async worker, to be defined at first run
_worker = None

def __init__(self, hostname, port):
self.hostname = hostname
self.port = port

def send(self, method, endpoint, data, headers):
return self.async_send(
method, endpoint, data, headers,
Expand All @@ -33,7 +37,7 @@ def async_send(self, method, endpoint, data, headers, success_cb, failure_cb):

def send_sync(self, method, endpoint, data, headers, success_cb, failure_cb):
try:
conn = httplib.HTTPConnection('localhost', 7777)
conn = httplib.HTTPConnection(self.hostname, self.port)
conn.request(method, endpoint, data, headers)
except Exception as e:
failure_cb(e)
Expand Down
4 changes: 2 additions & 2 deletions ddtrace/writer.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@

class AgentWriter(object):

def __init__(self):
self._reporter = AgentReporter()
def __init__(self, hostname='localhost', port=7777):
self._reporter = AgentReporter(hostname, port)

def write(self, spans, services=None):
self._reporter.report(spans, services)
Expand Down
6 changes: 4 additions & 2 deletions tests/contrib/cassandra/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ def _assert_result_correct(self, result):

def _traced_cluster(self):
writer = DummyWriter()
tracer = Tracer(writer=writer)
tracer = Tracer()
tracer.writer = writer
TracedCluster = get_traced_cassandra(tracer)
return TracedCluster, writer

Expand Down Expand Up @@ -79,7 +80,8 @@ def test_trace_with_service(self):
Tests tracing with a custom service
"""
writer = DummyWriter()
tracer = Tracer(writer=writer)
tracer = Tracer()
tracer.writer = writer
TracedCluster = get_traced_cassandra(tracer, service="custom")
session = TracedCluster(port=9042).connect(self.TEST_KEYSPACE)

Expand Down
8 changes: 4 additions & 4 deletions tests/contrib/django/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@

def test_template():
# trace and ensure it works
writer = DummyWriter()
tracer = Tracer(writer=writer)
assert not writer.pop()
tracer = Tracer()
tracer.writer = DummyWriter()
assert not tracer.writer.pop()
patch_template(tracer)

# setup a test template
Expand All @@ -36,7 +36,7 @@ def test_template():
eq_(t.render(c), 'hello matt')
end = time.time()

spans = writer.pop()
spans = tracer.writer.pop()
assert spans, spans
eq_(len(spans), 1)

Expand Down
3 changes: 2 additions & 1 deletion tests/contrib/elasticsearch/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ def test_elasticsearch(self):
All in this for now. Will split it later.
"""
writer = DummyWriter()
tracer = Tracer(writer=writer)
tracer = Tracer()
tracer.writer = writer
transport_class = get_traced_transport(datadog_tracer=tracer, datadog_service=self.TEST_SERVICE)

es = elasticsearch.Elasticsearch(transport_class=transport_class)
Expand Down
3 changes: 2 additions & 1 deletion tests/contrib/flask/test_flask.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@

# global writer tracer for the tests.
writer = DummyWriter()
tracer = Tracer(writer=writer)
tracer = Tracer()
tracer.writer =writer


class TestError(Exception): pass
Expand Down
3 changes: 2 additions & 1 deletion tests/contrib/psycopg/test_psycopg.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@

def test_wrap():
writer = DummyWriter()
tracer = Tracer(writer=writer)
tracer = Tracer()
tracer.writer = writer

params = {
'host': 'localhost',
Expand Down
3 changes: 2 additions & 1 deletion tests/contrib/pylons/test_pylons.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@ def start_response(self, status, headers):

def test_pylons():
writer = DummyWriter()
tracer = Tracer(writer=writer)
tracer = Tracer()
tracer.writer = writer
app = FakeWSGIApp()
traced = PylonsTraceMiddleware(app, tracer, service="p")

Expand Down
12 changes: 8 additions & 4 deletions tests/contrib/redis/test.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ def tearDown(self):

def test_basic_class(self):
writer = DummyWriter()
tracer = Tracer(writer=writer)
tracer = Tracer()
tracer.writer = writer

TracedRedisCache = get_traced_redis(tracer, service=self.SERVICE)
r = TracedRedisCache()
Expand All @@ -57,7 +58,8 @@ def test_basic_class(self):

def test_meta_override(self):
writer = DummyWriter()
tracer = Tracer(writer=writer)
tracer = Tracer()
tracer.writer = writer

TracedRedisCache = get_traced_redis(tracer, service=self.SERVICE, meta={'cheese': 'camembert'})
r = TracedRedisCache()
Expand All @@ -71,7 +73,8 @@ def test_meta_override(self):

def test_basic_class_pipeline(self):
writer = DummyWriter()
tracer = Tracer(writer=writer)
tracer = Tracer()
tracer.writer = writer

TracedRedisCache = get_traced_redis(tracer, service=self.SERVICE)
r = TracedRedisCache()
Expand Down Expand Up @@ -109,7 +112,8 @@ def execute_command(self, *args, **kwargs):


writer = DummyWriter()
tracer = Tracer(writer=writer)
tracer = Tracer()
tracer.writer = writer

TracedRedisCache = get_traced_redis_from(tracer, MyCustomRedis, service=self.SERVICE)
r = TracedRedisCache()
Expand Down
3 changes: 2 additions & 1 deletion tests/contrib/sqlite3/test_sqlite3.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@

def test_foo():
writer = DummyWriter()
tracer = Tracer(writer=writer)
tracer = Tracer()
tracer.writer = writer

# ensure we can trace multiple services without stomping

Expand Down
14 changes: 9 additions & 5 deletions tests/test_sampler.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ class RateSamplerTest(unittest.TestCase):

def test_random_sequence(self):
writer = DummyWriter()
sampler = RateSampler(0.5)
tracer = Tracer(writer=writer, sampler=sampler)
tracer = Tracer()
tracer.writer = writer
tracer.sampler = RateSampler(0.5)

# Set the seed so that the choice of sampled traces is deterministic, then write tests accordingly
random.seed(4012)
Expand Down Expand Up @@ -52,7 +53,8 @@ class ThroughputSamplerTest(unittest.TestCase):

def test_simple_limit(self):
writer = DummyWriter()
tracer = Tracer(writer=writer)
tracer = Tracer()
tracer.writer = writer

with patch_time() as fake_time:
tps = 5
Expand Down Expand Up @@ -85,7 +87,8 @@ def test_simple_limit(self):

def test_long_run(self):
writer = DummyWriter()
tracer = Tracer(writer=writer)
tracer = Tracer()
tracer.writer = writer

# Test a big matrix of combinaisons
# Ensure to have total_time >> BUFFER_DURATION to reduce edge effects
Expand Down Expand Up @@ -118,7 +121,8 @@ def test_long_run(self):
def test_concurrency(self):
# Test that the sampler works well when used in different threads
writer = DummyWriter()
tracer = Tracer(writer=writer)
tracer = Tracer()
tracer.writer = writer

total_time = 10
concurrency = 100
Expand Down
Loading