mirror of
https://github.com/open-telemetry/opentelemetry-python-contrib.git
synced 2025-08-01 09:13:23 +08:00
321 lines
9.7 KiB
Python
321 lines
9.7 KiB
Python
# stdlib
|
|
import time
|
|
from unittest.case import SkipTest
|
|
|
|
# 3p
|
|
import pylibmc
|
|
|
|
# project
|
|
from ddtrace import Pin
|
|
from ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY
|
|
from ddtrace.contrib.pylibmc import TracedClient
|
|
from ddtrace.contrib.pylibmc.patch import patch, unpatch
|
|
from ddtrace.ext import memcached
|
|
|
|
# testing
|
|
from ...opentracer.utils import init_tracer
|
|
from ...contrib.config import MEMCACHED_CONFIG as cfg
|
|
from ...base import BaseTracerTestCase
|
|
|
|
|
|
class PylibmcCore(object):
|
|
"""Core of the test suite for pylibmc
|
|
|
|
Shared tests between the patch and TracedClient interface.
|
|
Will be merge back to a single class once the TracedClient is deprecated.
|
|
"""
|
|
|
|
TEST_SERVICE = memcached.SERVICE
|
|
|
|
def get_client(self):
|
|
# Implement me
|
|
pass
|
|
|
|
def test_upgrade(self):
|
|
raise SkipTest('upgrade memcached')
|
|
# add tests for touch, cas, gets etc
|
|
|
|
def test_append_prepend(self):
|
|
client, tracer = self.get_client()
|
|
# test
|
|
start = time.time()
|
|
client.set('a', 'crow')
|
|
client.prepend('a', 'holy ')
|
|
client.append('a', '!')
|
|
|
|
# FIXME[matt] there is a bug in pylibmc & python 3 (perhaps with just
|
|
# some versions of the libmemcache?) where append/prepend are replaced
|
|
# with get. our traced versions do the right thing, so skipping this
|
|
# test.
|
|
try:
|
|
assert client.get('a') == 'holy crow!'
|
|
except AssertionError:
|
|
pass
|
|
|
|
end = time.time()
|
|
# verify spans
|
|
spans = tracer.writer.pop()
|
|
for s in spans:
|
|
self._verify_cache_span(s, start, end)
|
|
expected_resources = sorted(['append', 'prepend', 'get', 'set'])
|
|
resources = sorted(s.resource for s in spans)
|
|
assert expected_resources == resources
|
|
|
|
def test_incr_decr(self):
|
|
client, tracer = self.get_client()
|
|
# test
|
|
start = time.time()
|
|
client.set('a', 1)
|
|
client.incr('a', 2)
|
|
client.decr('a', 1)
|
|
v = client.get('a')
|
|
assert v == 2
|
|
end = time.time()
|
|
# verify spans
|
|
spans = tracer.writer.pop()
|
|
for s in spans:
|
|
self._verify_cache_span(s, start, end)
|
|
expected_resources = sorted(['get', 'set', 'incr', 'decr'])
|
|
resources = sorted(s.resource for s in spans)
|
|
assert expected_resources == resources
|
|
|
|
def test_incr_decr_ot(self):
|
|
"""OpenTracing version of test_incr_decr."""
|
|
client, tracer = self.get_client()
|
|
ot_tracer = init_tracer('memcached', tracer)
|
|
|
|
start = time.time()
|
|
with ot_tracer.start_active_span('mc_ops'):
|
|
client.set('a', 1)
|
|
client.incr('a', 2)
|
|
client.decr('a', 1)
|
|
v = client.get('a')
|
|
assert v == 2
|
|
end = time.time()
|
|
|
|
# verify spans
|
|
spans = tracer.writer.pop()
|
|
ot_span = spans[0]
|
|
|
|
assert ot_span.name == 'mc_ops'
|
|
|
|
for s in spans[1:]:
|
|
assert s.parent_id == ot_span.span_id
|
|
self._verify_cache_span(s, start, end)
|
|
expected_resources = sorted(['get', 'set', 'incr', 'decr'])
|
|
resources = sorted(s.resource for s in spans[1:])
|
|
assert expected_resources == resources
|
|
|
|
def test_clone(self):
|
|
# ensure cloned connections are traced as well.
|
|
client, tracer = self.get_client()
|
|
cloned = client.clone()
|
|
start = time.time()
|
|
cloned.get('a')
|
|
end = time.time()
|
|
spans = tracer.writer.pop()
|
|
for s in spans:
|
|
self._verify_cache_span(s, start, end)
|
|
expected_resources = ['get']
|
|
resources = sorted(s.resource for s in spans)
|
|
assert expected_resources == resources
|
|
|
|
def test_get_set_multi(self):
|
|
client, tracer = self.get_client()
|
|
# test
|
|
start = time.time()
|
|
client.set_multi({'a': 1, 'b': 2})
|
|
out = client.get_multi(['a', 'c'])
|
|
assert out == {'a': 1}
|
|
client.delete_multi(['a', 'c'])
|
|
end = time.time()
|
|
# verify
|
|
spans = tracer.writer.pop()
|
|
for s in spans:
|
|
self._verify_cache_span(s, start, end)
|
|
expected_resources = sorted(['get_multi', 'set_multi', 'delete_multi'])
|
|
resources = sorted(s.resource for s in spans)
|
|
assert expected_resources == resources
|
|
|
|
def test_get_set_multi_prefix(self):
|
|
client, tracer = self.get_client()
|
|
# test
|
|
start = time.time()
|
|
client.set_multi({'a': 1, 'b': 2}, key_prefix='foo')
|
|
out = client.get_multi(['a', 'c'], key_prefix='foo')
|
|
assert out == {'a': 1}
|
|
client.delete_multi(['a', 'c'], key_prefix='foo')
|
|
end = time.time()
|
|
# verify
|
|
spans = tracer.writer.pop()
|
|
for s in spans:
|
|
self._verify_cache_span(s, start, end)
|
|
assert s.get_tag('memcached.query') == '%s foo' % s.resource
|
|
expected_resources = sorted(['get_multi', 'set_multi', 'delete_multi'])
|
|
resources = sorted(s.resource for s in spans)
|
|
assert expected_resources == resources
|
|
|
|
def test_get_set_delete(self):
|
|
client, tracer = self.get_client()
|
|
# test
|
|
k = u'cafe'
|
|
v = 'val-foo'
|
|
start = time.time()
|
|
client.delete(k) # just in case
|
|
out = client.get(k)
|
|
assert out is None, out
|
|
client.set(k, v)
|
|
out = client.get(k)
|
|
assert out == v
|
|
end = time.time()
|
|
# verify
|
|
spans = tracer.writer.pop()
|
|
for s in spans:
|
|
self._verify_cache_span(s, start, end)
|
|
assert s.get_tag('memcached.query') == '%s %s' % (s.resource, k)
|
|
expected_resources = sorted(['get', 'get', 'delete', 'set'])
|
|
resources = sorted(s.resource for s in spans)
|
|
assert expected_resources == resources
|
|
|
|
def _verify_cache_span(self, s, start, end):
|
|
assert s.start > start
|
|
assert s.start + s.duration < end
|
|
assert s.service == self.TEST_SERVICE
|
|
assert s.span_type == 'cache'
|
|
assert s.name == 'memcached.cmd'
|
|
assert s.get_tag('out.host') == cfg['host']
|
|
assert s.get_metric('out.port') == cfg['port']
|
|
|
|
def test_analytics_default(self):
|
|
client, tracer = self.get_client()
|
|
client.set('a', 'crow')
|
|
|
|
spans = self.get_spans()
|
|
self.assertEqual(len(spans), 1)
|
|
self.assertIsNone(spans[0].get_metric(ANALYTICS_SAMPLE_RATE_KEY))
|
|
|
|
def test_analytics_with_rate(self):
|
|
with self.override_config(
|
|
'pylibmc',
|
|
dict(analytics_enabled=True, analytics_sample_rate=0.5)
|
|
):
|
|
client, tracer = self.get_client()
|
|
client.set('a', 'crow')
|
|
|
|
spans = self.get_spans()
|
|
self.assertEqual(len(spans), 1)
|
|
self.assertEqual(spans[0].get_metric(ANALYTICS_SAMPLE_RATE_KEY), 0.5)
|
|
|
|
def test_analytics_without_rate(self):
|
|
with self.override_config(
|
|
'pylibmc',
|
|
dict(analytics_enabled=True)
|
|
):
|
|
client, tracer = self.get_client()
|
|
client.set('a', 'crow')
|
|
|
|
spans = self.get_spans()
|
|
self.assertEqual(len(spans), 1)
|
|
self.assertEqual(spans[0].get_metric(ANALYTICS_SAMPLE_RATE_KEY), 1.0)
|
|
|
|
def test_disabled(self):
|
|
"""
|
|
Ensure client works when the tracer is disabled
|
|
"""
|
|
client, tracer = self.get_client()
|
|
try:
|
|
tracer.enabled = False
|
|
|
|
client.set('a', 'crow')
|
|
|
|
spans = self.get_spans()
|
|
assert len(spans) == 0
|
|
finally:
|
|
tracer.enabled = True
|
|
|
|
|
|
class TestPylibmcLegacy(BaseTracerTestCase, PylibmcCore):
|
|
"""Test suite for the tracing of pylibmc with the legacy TracedClient interface"""
|
|
|
|
TEST_SERVICE = 'mc-legacy'
|
|
|
|
def get_client(self):
|
|
url = '%s:%s' % (cfg['host'], cfg['port'])
|
|
raw_client = pylibmc.Client([url])
|
|
raw_client.flush_all()
|
|
|
|
client = TracedClient(raw_client, tracer=self.tracer, service=self.TEST_SERVICE)
|
|
return client, self.tracer
|
|
|
|
|
|
class TestPylibmcPatchDefault(BaseTracerTestCase, PylibmcCore):
|
|
"""Test suite for the tracing of pylibmc with the default lib patching"""
|
|
|
|
def setUp(self):
|
|
super(TestPylibmcPatchDefault, self).setUp()
|
|
patch()
|
|
|
|
def tearDown(self):
|
|
unpatch()
|
|
super(TestPylibmcPatchDefault, self).tearDown()
|
|
|
|
def get_client(self):
|
|
url = '%s:%s' % (cfg['host'], cfg['port'])
|
|
client = pylibmc.Client([url])
|
|
client.flush_all()
|
|
|
|
Pin.get_from(client).clone(tracer=self.tracer).onto(client)
|
|
|
|
return client, self.tracer
|
|
|
|
|
|
class TestPylibmcPatch(TestPylibmcPatchDefault):
|
|
"""Test suite for the tracing of pylibmc with a configured lib patching"""
|
|
|
|
TEST_SERVICE = 'mc-custom-patch'
|
|
|
|
def get_client(self):
|
|
client, tracer = TestPylibmcPatchDefault.get_client(self)
|
|
|
|
Pin.get_from(client).clone(service=self.TEST_SERVICE).onto(client)
|
|
|
|
return client, tracer
|
|
|
|
def test_patch_unpatch(self):
|
|
url = '%s:%s' % (cfg['host'], cfg['port'])
|
|
|
|
# Test patch idempotence
|
|
patch()
|
|
patch()
|
|
|
|
client = pylibmc.Client([url])
|
|
Pin.get_from(client).clone(
|
|
service=self.TEST_SERVICE,
|
|
tracer=self.tracer).onto(client)
|
|
|
|
client.set('a', 1)
|
|
|
|
spans = self.tracer.writer.pop()
|
|
assert spans, spans
|
|
assert len(spans) == 1
|
|
|
|
# Test unpatch
|
|
unpatch()
|
|
|
|
client = pylibmc.Client([url])
|
|
client.set('a', 1)
|
|
|
|
spans = self.tracer.writer.pop()
|
|
assert not spans, spans
|
|
|
|
# Test patch again
|
|
patch()
|
|
|
|
client = pylibmc.Client([url])
|
|
Pin(service=self.TEST_SERVICE, tracer=self.tracer).onto(client)
|
|
client.set('a', 1)
|
|
|
|
spans = self.tracer.writer.pop()
|
|
assert spans, spans
|
|
assert len(spans) == 1
|