def test_configure_dogstatsd_url_host_port(self): tracer = Tracer() tracer.configure(dogstatsd_url="foo:1234") assert tracer.writer.dogstatsd.host == "foo" assert tracer.writer.dogstatsd.port == 1234 tracer = Tracer() writer = AgentWriter("http://localhost:8126") tracer.configure(writer=writer, dogstatsd_url="foo:1234") assert tracer.writer.dogstatsd.host == "foo" assert tracer.writer.dogstatsd.port == 1234
def test_configure_dogstatsd_url_socket(self): tracer = Tracer() tracer.configure(dogstatsd_url="unix:///foo.sock") assert tracer.writer.dogstatsd.host is None assert tracer.writer.dogstatsd.port is None assert tracer.writer.dogstatsd.socket_path == "/foo.sock" tracer = Tracer() writer = AgentWriter("http://localhost:8126") tracer.configure(writer=writer, dogstatsd_url="unix:///foo.sock") assert tracer.writer.dogstatsd.host is None assert tracer.writer.dogstatsd.port is None assert tracer.writer.dogstatsd.socket_path == "/foo.sock"
def test_tracer_trace_across_fork(): """ When a trace is started in a parent process and a child process is spawned The trace should be continued in the child process """ tracer = Tracer() tracer.writer = DummyWriter() def task(tracer, q): tracer.writer = DummyWriter() with tracer.trace("child"): pass spans = tracer.writer.pop() q.put( [dict(trace_id=s.trace_id, parent_id=s.parent_id) for s in spans]) # Assert tracer in a new process correctly recreates the writer q = multiprocessing.Queue() with tracer.trace("parent") as parent: p = multiprocessing.Process(target=task, args=(tracer, q)) p.start() p.join() children = q.get() assert len(children) == 1 (child, ) = children assert parent.trace_id == child["trace_id"] assert child["parent_id"] == parent.span_id
def test_tracer_wrap_class(): writer = DummyWriter() tracer = Tracer() tracer.writer = writer class Foo(object): @staticmethod @tracer.wrap() def s(): return 1 @classmethod @tracer.wrap() def c(cls): return 2 @tracer.wrap() def i(cls): return 3 f = Foo() eq_(f.s(), 1) eq_(f.c(), 2) eq_(f.i(), 3) spans = writer.pop() eq_(len(spans), 3) names = [s.name for s in spans] # FIXME[matt] include the class name here. eq_(sorted(names), sorted(["tests.test_tracer.%s" % n for n in ["s", "c", "i"]]))
def test_memcached_cache_tracing_with_a_wrong_connection(self): # initialize the dummy writer writer = DummyWriter() tracer = Tracer() tracer.writer = writer # create the TracedCache instance for a Flask app Cache = get_traced_cache(tracer, service=self.SERVICE) app = Flask(__name__) config = { 'CACHE_TYPE': 'memcached', 'CACHE_MEMCACHED_SERVERS': ['localhost:2230'], } cache = Cache(app, config=config) # use a wrong memcached connection try: cache.get(u'á_complex_operation') except Exception: pass # ensure that the error is not caused by our tracer spans = writer.pop() assert len(spans) == 1 span = spans[0] assert span.service == self.SERVICE assert span.resource == 'get' assert span.name == 'flask_cache.cmd' assert span.span_type == 'cache' assert span.meta[CACHE_BACKEND] == 'memcached' assert span.meta[net.TARGET_HOST] == 'localhost' assert span.metrics[net.TARGET_PORT] == 2230
def test_cache_add_without_arguments(self): # initialize the dummy writer writer = DummyWriter() tracer = Tracer() tracer.writer = writer # create the TracedCache instance for a Flask app Cache = get_traced_cache(tracer, service=self.SERVICE) app = Flask(__name__) cache = Cache(app, config={'CACHE_TYPE': 'simple'}) # make a wrong call with pytest.raises(TypeError) as ex: cache.add() # ensure that the error is not caused by our tracer assert 'add()' in ex.value.args[0] assert 'argument' in ex.value.args[0] spans = writer.pop() # an error trace must be sent assert len(spans) == 1 span = spans[0] assert span.service == self.SERVICE assert span.resource == 'add' assert span.name == 'flask_cache.cmd' assert span.span_type == 'cache' assert span.error == 1
def test_simple_cache_delete_many(self): # initialize the dummy writer writer = DummyWriter() tracer = Tracer() tracer.writer = writer # create the TracedCache instance for a Flask app Cache = get_traced_cache(tracer, service=self.SERVICE) app = Flask(__name__) cache = Cache(app, config={"CACHE_TYPE": "simple"}) cache.delete_many("complex_operation", "another_complex_op") spans = writer.pop() eq_(len(spans), 1) span = spans[0] eq_(span.service, self.SERVICE) eq_(span.resource, "delete_many") eq_(span.name, "flask_cache.cmd") eq_(span.span_type, "cache") eq_(span.error, 0) expected_meta = { "flask_cache.key": "['complex_operation', 'another_complex_op']", "flask_cache.backend": "simple", } eq_(span.meta, expected_meta)
def test_redis_cache_tracing_with_a_wrong_connection(self): # initialize the dummy writer writer = DummyWriter() tracer = Tracer() tracer.writer = writer # create the TracedCache instance for a Flask app Cache = get_traced_cache(tracer, service=self.SERVICE) app = Flask(__name__) config = { 'CACHE_TYPE': 'redis', 'CACHE_REDIS_PORT': 2230, 'CACHE_REDIS_HOST': '127.0.0.1' } cache = Cache(app, config=config) # use a wrong redis connection with pytest.raises(ConnectionError) as ex: cache.get(u'á_complex_operation') # ensure that the error is not caused by our tracer assert '127.0.0.1:2230. Connection refused.' in ex.value.args[0] spans = writer.pop() # an error trace must be sent assert len(spans) == 1 span = spans[0] assert span.service == self.SERVICE assert span.resource == 'get' assert span.name == 'flask_cache.cmd' assert span.span_type == 'cache' assert span.meta[CACHE_BACKEND] == 'redis' assert span.meta[net.TARGET_HOST] == '127.0.0.1' assert span.metrics[net.TARGET_PORT] == 2230 assert span.error == 1
def test_memcached_cache_tracing_with_a_wrong_connection(self): # initialize the dummy writer writer = DummyWriter() tracer = Tracer() tracer.writer = writer # create the TracedCache instance for a Flask app Cache = get_traced_cache(tracer, service=self.SERVICE) app = Flask(__name__) config = { "CACHE_TYPE": "memcached", "CACHE_MEMCACHED_SERVERS": ['localhost:22230'], } cache = Cache(app, config=config) # use a wrong memcached connection try: cache.get(u"á_complex_operation") except Exception: pass # ensure that the error is not caused by our tracer spans = writer.pop() eq_(len(spans), 1) span = spans[0] eq_(span.service, self.SERVICE) eq_(span.resource, "get") eq_(span.name, "flask_cache.cmd") eq_(span.span_type, "cache") eq_(span.meta[CACHE_BACKEND], "memcached") eq_(span.meta[net.TARGET_HOST], 'localhost') eq_(span.meta[net.TARGET_PORT], '22230')
def test_simple_cache_add(self): # initialize the dummy writer writer = DummyWriter() tracer = Tracer() tracer.writer = writer # create the TracedCache instance for a Flask app Cache = get_traced_cache(tracer, service=self.SERVICE) app = Flask(__name__) cache = Cache(app, config={"CACHE_TYPE": "simple"}) cache.add(u"á_complex_number", 50) spans = writer.pop() eq_(len(spans), 1) span = spans[0] eq_(span.service, self.SERVICE) eq_(span.resource, "add") eq_(span.name, "flask_cache.cmd") eq_(span.span_type, "cache") eq_(span.error, 0) expected_meta = { "flask_cache.key": u"á_complex_number", "flask_cache.backend": "simple", } eq_(span.meta, expected_meta)
def test_simple_cache_set_many(self): # initialize the dummy writer writer = DummyWriter() tracer = Tracer() tracer.writer = writer # create the TracedCache instance for a Flask app Cache = get_traced_cache(tracer, service=self.SERVICE) app = Flask(__name__) cache = Cache(app, config={"CACHE_TYPE": "simple"}) cache.set_many({ 'first_complex_op': 10, 'second_complex_op': 20, }) spans = writer.pop() eq_(len(spans), 1) span = spans[0] eq_(span.service, self.SERVICE) eq_(span.resource, "set_many") eq_(span.name, "flask_cache.cmd") eq_(span.span_type, "cache") eq_(span.error, 0) expected_meta = { "flask_cache.key": "['first_complex_op', 'second_complex_op']", "flask_cache.backend": "simple", } eq_(span.meta["flask_cache.backend"], "simple") ok_("first_complex_op" in span.meta["flask_cache.key"]) ok_("second_complex_op" in span.meta["flask_cache.key"])
def test_long_run(self): writer = DummyWriter() tracer = Tracer() tracer.writer = writer # Test a big matrix of combinaisons # Ensure to have total_time >> BUFFER_DURATION to reduce edge effects for tps in [10, 23, 15, 31]: for (traces_per_s, total_time) in [(80, 23), (75, 66), (1000, 77)]: with patch_time() as fake_time: # We do tons of operations in this test, do not let the time slowly shift fake_time.set_delta(0) tracer.sampler = ThroughputSampler(tps) for _ in range(total_time): for _ in range(traces_per_s): s = tracer.trace("whatever") s.finish() fake_time.sleep(1) traces = writer.pop() # The current sampler implementation can introduce an error of up to # `tps * BUFFER_DURATION` traces at initialization (since the sampler starts empty) got = len(traces) expected = tps * total_time error_delta = tps * tracer.sampler.BUFFER_DURATION assert abs(got - expected) <= error_delta, \ "Wrong number of traces sampled, %s instead of %s (error_delta > %s)" % (got, expected, error_delta)
def test_simple_limit(self): writer = DummyWriter() tracer = Tracer() tracer.writer = writer with patch_time() as fake_time: tps = 5 tracer.sampler = ThroughputSampler(tps) for _ in range(10): s = tracer.trace("whatever") s.finish() traces = writer.pop() got = len(traces) expected = 10 assert got == expected, \ "Wrong number of traces sampled, %s instead of %s" % (got, expected) # Wait enough to reset fake_time.sleep(tracer.sampler.BUFFER_DURATION + 1) for _ in range(100): s = tracer.trace("whatever") s.finish() traces = writer.pop() got = len(traces) expected = tps * tracer.sampler.BUFFER_DURATION assert got == expected, \ "Wrong number of traces sampled, %s instead of %s" % (got, expected)
def test_sample_rate_deviation(self): writer = DummyWriter() for sample_rate in [0.1, 0.25, 0.5, 1]: tracer = Tracer() tracer.writer = writer sample_rate = 0.5 tracer.sampler = RateSampler(sample_rate) random.seed(1234) iterations = int(2e4) for i in range(iterations): span = tracer.trace(i) span.finish() samples = writer.pop() # We must have at least 1 sample, check that it has its sample rate properly assigned assert samples[0].get_metric(SAMPLE_RATE_METRIC_KEY) == 0.5 # Less than 1% deviation when "enough" iterations (arbitrary, just check if it converges) deviation = abs(len(samples) - (iterations * sample_rate)) / (iterations * sample_rate) assert deviation < 0.01, "Deviation too high %f with sample_rate %f" % ( deviation, sample_rate)
def gen_trace(nspans=1000, ntags=50, key_size=15, value_size=20, nmetrics=10): t = Tracer() root = None trace = [] for i in range(0, nspans): parent_id = root.span_id if root else None with Span( t, "span_name", resource="/fsdlajfdlaj/afdasd%s" % i, service="myservice", parent_id=parent_id, ) as span: span._parent = root span.set_tags({rands(key_size): rands(value_size) for _ in range(0, ntags)}) # only apply a span type to the root span if not root: span.span_type = "web" for _ in range(0, nmetrics): span.set_tag(rands(key_size), random.randint(0, 2 ** 16)) trace.append(span) if not root: root = span return trace
def test_detect_agentless_env_with_lambda(self): assert _in_aws_lambda() assert not _has_aws_lambda_agent_extension() tracer = Tracer() assert isinstance(tracer.writer, LogWriter) tracer.configure(enabled=True) assert isinstance(tracer.writer, LogWriter)
def test_redis_cache_tracing_with_a_wrong_connection(self): # initialize the dummy writer writer = DummyWriter() tracer = Tracer() tracer.writer = writer # create the TracedCache instance for a Flask app Cache = get_traced_cache(tracer, service=self.SERVICE) app = Flask(__name__) config = { "CACHE_TYPE": "redis", "CACHE_REDIS_PORT": 22230, } cache = Cache(app, config=config) # use a wrong redis connection with assert_raises(ConnectionError) as ex: cache.get(u"á_complex_operation") # ensure that the error is not caused by our tracer ok_("localhost:22230. Connection refused." in ex.exception.args[0]) spans = writer.pop() # an error trace must be sent eq_(len(spans), 1) span = spans[0] eq_(span.service, self.SERVICE) eq_(span.resource, "get") eq_(span.name, "flask_cache.cmd") eq_(span.span_type, "cache") eq_(span.meta[CACHE_BACKEND], "redis") eq_(span.meta[net.TARGET_HOST], 'localhost') eq_(span.meta[net.TARGET_PORT], '22230') eq_(span.error, 1)
def test_simple_cache_set(self): # initialize the dummy writer writer = DummyWriter() tracer = Tracer() tracer.writer = writer # create the TracedCache instance for a Flask app Cache = get_traced_cache(tracer, service=self.SERVICE) app = Flask(__name__) cache = Cache(app, config={"CACHE_TYPE": "simple"}) cache.set(u"á_complex_operation", u"with_á_value\nin two lines") spans = writer.pop() eq_(len(spans), 1) span = spans[0] eq_(span.service, self.SERVICE) eq_(span.resource, "set") eq_(span.name, "flask_cache.cmd") eq_(span.span_type, "cache") eq_(span.error, 0) expected_meta = { "flask_cache.key": u"á_complex_operation", "flask_cache.backend": "simple", } assert_dict_issuperset(span.meta, expected_meta)
def test_cache_add_without_arguments(self): # initialize the dummy writer writer = DummyWriter() tracer = Tracer() tracer.writer = writer # create the TracedCache instance for a Flask app Cache = get_traced_cache(tracer, service=self.SERVICE) app = Flask(__name__) cache = Cache(app, config={"CACHE_TYPE": "simple"}) # make a wrong call with assert_raises(TypeError) as ex: cache.add() # ensure that the error is not caused by our tracer ok_("add()" in ex.exception.args[0]) ok_("argument" in ex.exception.args[0]) spans = writer.pop() # an error trace must be sent eq_(len(spans), 1) span = spans[0] eq_(span.service, self.SERVICE) eq_(span.resource, "add") eq_(span.name, "flask_cache.cmd") eq_(span.span_type, "cache") eq_(span.error, 1)
def test_tracer_wrap_factory(): # it should use a wrap_factory if defined writer = DummyWriter() tracer = Tracer() tracer.writer = writer def wrap_executor(tracer, fn, args, kwargs, span_name=None, service=None, resource=None, span_type=None): with tracer.trace('wrap.overwrite') as span: span.set_tag('args', args) span.set_tag('kwargs', kwargs) return fn(*args, **kwargs) @tracer.wrap() def wrapped_function(param, kw_param=None): eq_(42, param) eq_(42, kw_param) # set the custom wrap factory after the wrapper has been called tracer.configure(wrap_executor=wrap_executor) # call the function expecting that the custom tracing wrapper is used wrapped_function(42, kw_param=42) eq_(writer.spans[0].name, 'wrap.overwrite') eq_(writer.spans[0].get_tag('args'), '(42,)') eq_(writer.spans[0].get_tag('kwargs'), '{\'kw_param\': 42}')
def test_tracer_pid(): writer = DummyWriter() tracer = Tracer() tracer.writer = writer with tracer.trace("root") as root_span: with tracer.trace("child") as child_span: time.sleep(0.05) eq_(root_span.get_tag(system.PID), str(getpid())) # Root span should contain the pid of the current process eq_(child_span.get_tag(system.PID), None) # Child span should not contain a pid tag
def test_configure_keeps_api_hostname_and_port(self): tracer = Tracer() # use real tracer with real api assert 'localhost' == tracer.writer.api.hostname assert 8126 == tracer.writer.api.port tracer.configure(hostname='127.0.0.1', port=8127) assert '127.0.0.1' == tracer.writer.api.hostname assert 8127 == tracer.writer.api.port tracer.configure(priority_sampling=True) assert '127.0.0.1' == tracer.writer.api.hostname assert 8127 == tracer.writer.api.port
def setUp(self): """ Create a tracer with running workers, while spying the ``_put()`` method to keep trace of triggered API calls. """ # create a new tracer self.tracer = Tracer() # spy the send() method self.api = self.tracer.writer.api self.api._put = mock.Mock(self.api._put, wraps=self.api._put)
def test_configure_keeps_api_hostname_and_port(self): tracer = Tracer() # use real tracer with real api eq_('localhost', tracer.writer.api.hostname) eq_(8126, tracer.writer.api.port) tracer.configure(hostname='127.0.0.1', port=8127) eq_('127.0.0.1', tracer.writer.api.hostname) eq_(8127, tracer.writer.api.port) tracer.configure(priority_sampling=True) eq_('127.0.0.1', tracer.writer.api.hostname) eq_(8127, tracer.writer.api.port)
def test_resource_from_cache_without_prefix(self): # create the TracedCache instance for a Flask app tracer = Tracer() Cache = get_traced_cache(tracer, service=self.SERVICE) app = Flask(__name__) traced_cache = Cache(app, config={"CACHE_TYPE": "redis"}) # expect only the resource name expected_resource = "get" resource = _resource_from_cache_prefix("GET", traced_cache.config) assert resource == expected_resource
def test_tracer_wrap_default_name(): writer = DummyWriter() tracer = Tracer() tracer.writer = writer @tracer.wrap() def f(): pass f() eq_(writer.spans[0].name, 'tests.test_tracer.f')
def test_manual_keep_then_drop(): tracer = Tracer() tracer.writer = DummyWriter() # Test changing the value before finish. with tracer.trace("asdf") as root: with tracer.trace("child") as child: child.set_tag(MANUAL_KEEP_KEY) root.set_tag(MANUAL_DROP_KEY) spans = tracer.writer.pop() assert spans[0].metrics[SAMPLING_PRIORITY_KEY] is priority.USER_REJECT
def test_bad_agent_url(monkeypatch): with pytest.raises(ValueError): Tracer(url="bad://localhost:8126") monkeypatch.setenv("DD_TRACE_AGENT_URL", "bad://localhost:1234") with pytest.raises(ValueError) as e: Tracer() assert ( str(e.value) == "Unsupported protocol 'bad' in Agent URL 'bad://localhost:1234'. Must be one of: http, https, unix" ) monkeypatch.setenv("DD_TRACE_AGENT_URL", "unix://") with pytest.raises(ValueError) as e: Tracer() assert str(e.value) == "Invalid file path in Agent URL 'unix://'" monkeypatch.setenv("DD_TRACE_AGENT_URL", "http://") with pytest.raises(ValueError) as e: Tracer() assert str(e.value) == "Invalid hostname in Agent URL 'http://'"
def test_default_span_tags(self): # create the TracedCache instance for a Flask app tracer = Tracer() Cache = get_traced_cache(tracer, service=self.SERVICE) app = Flask(__name__) cache = Cache(app, config={"CACHE_TYPE": "simple"}) # test tags and attributes with cache._TracedCache__trace("flask_cache.cmd") as span: eq_(span.service, cache._datadog_service) eq_(span.span_type, TYPE) eq_(span.meta[CACHE_BACKEND], "simple") ok_(net.TARGET_HOST not in span.meta) ok_(net.TARGET_PORT not in span.meta)
def test_tracer_wrap_exception(): writer = DummyWriter() tracer = Tracer() tracer.writer = writer @tracer.wrap() def f(): raise Exception('bim') assert_raises(Exception, f) eq_(len(writer.spans), 1) eq_(writer.spans[0].error, 1)