def test_s3_put(self): params = dict(Key="foo", Bucket="mybucket", Body=b"bar") with aiobotocore_client("s3", self.tracer) as s3: yield from s3.create_bucket(Bucket="mybucket") yield from s3.put_object(**params) spans = [trace[0] for trace in self.pop_traces()] assert spans self.assertEqual(len(spans), 2) self.assertEqual(spans[0].get_tag("aws.operation"), "CreateBucket") assert_is_measured(spans[0]) assert_span_http_status_code(spans[0], 200) self.assertEqual(spans[0].service, "aws.s3") self.assertEqual(spans[0].resource, "s3.createbucket") assert_is_measured(spans[1]) self.assertEqual(spans[1].get_tag("aws.operation"), "PutObject") self.assertEqual(spans[1].resource, "s3.putobject") self.assertEqual(spans[1].get_tag("params.Key"), stringify(params["Key"])) self.assertEqual(spans[1].get_tag("params.Bucket"), stringify(params["Bucket"])) self.assertIsNone(spans[1].get_tag("params.Body"))
def test_s3_put(self): s3 = self.session.create_client("s3", region_name="us-west-2") Pin(service=self.TEST_SERVICE, tracer=self.tracer).onto(s3) params = { "Bucket": "mybucket", "CreateBucketConfiguration": { "LocationConstraint": "us-west-2", }, } s3.create_bucket(**params) params = dict(Key="foo", Bucket="mybucket", Body=b"bar") s3.put_object(**params) spans = self.get_spans() assert spans span = spans[0] self.assertEqual(len(spans), 2) self.assertEqual(span.get_tag("aws.operation"), "CreateBucket") assert_is_measured(span) assert_span_http_status_code(span, 200) self.assertEqual(span.service, "test-botocore-tracing.s3") self.assertEqual(span.resource, "s3.createbucket") self.assertEqual(spans[1].get_tag("aws.operation"), "PutObject") self.assertEqual(spans[1].resource, "s3.putobject") self.assertEqual(spans[1].get_tag("params.Key"), stringify(params["Key"])) self.assertEqual(spans[1].get_tag("params.Bucket"), stringify(params["Bucket"])) # confirm blacklisted self.assertIsNone(spans[1].get_tag("params.Body"))
def test_s3_put(self): s3 = self.session.create_client('s3', region_name='us-west-2') Pin(service=self.TEST_SERVICE, tracer=self.tracer).onto(s3) params = { "Bucket": "mybucket", "CreateBucketConfiguration": { "LocationConstraint": "us-west-2", } } s3.create_bucket(**params) params = dict(Key='foo', Bucket='mybucket', Body=b'bar') s3.put_object(**params) spans = self.get_spans() assert spans span = spans[0] self.assertEqual(len(spans), 2) self.assertEqual(span.get_tag('aws.operation'), 'CreateBucket') assert_is_measured(span) assert_span_http_status_code(span, 200) self.assertEqual(span.service, 'test-botocore-tracing.s3') self.assertEqual(span.resource, 's3.createbucket') self.assertEqual(spans[1].get_tag('aws.operation'), 'PutObject') self.assertEqual(spans[1].resource, 's3.putobject') self.assertEqual(spans[1].get_tag('params.Key'), stringify(params['Key'])) self.assertEqual(spans[1].get_tag('params.Bucket'), stringify(params['Bucket'])) # confirm blacklisted self.assertIsNone(spans[1].get_tag('params.Body'))
def test_s3_put(self): params = dict(Key='foo', Bucket='mybucket', Body=b'bar') s3 = self.session.create_client('s3', region_name='us-west-2') tracer = get_dummy_tracer() writer = tracer.writer Pin(service=self.TEST_SERVICE, tracer=tracer).onto(s3) s3.create_bucket(Bucket='mybucket') s3.put_object(**params) spans = writer.pop() assert spans span = spans[0] self.assertEqual(len(spans), 2) self.assertEqual(span.get_tag('aws.operation'), 'CreateBucket') self.assertEqual(span.get_tag(http.STATUS_CODE), '200') self.assertEqual(span.service, 'test-botocore-tracing.s3') self.assertEqual(span.resource, 's3.createbucket') self.assertEqual(spans[1].get_tag('aws.operation'), 'PutObject') self.assertEqual(spans[1].resource, 's3.putobject') self.assertEqual(spans[1].get_tag('params.Key'), stringify(params['Key'])) self.assertEqual(spans[1].get_tag('params.Bucket'), stringify(params['Bucket'])) # confirm blacklisted self.assertIsNone(spans[1].get_tag('params.Body'))
def test_s3_put(self): params = dict(Key='foo', Bucket='mybucket', Body=b'bar') with aiobotocore_client('s3', self.tracer) as s3: yield from s3.create_bucket(Bucket='mybucket') yield from s3.put_object(**params) spans = [trace[0] for trace in self.tracer.writer.pop_traces()] assert spans self.assertEqual(len(spans), 2) self.assertEqual(spans[0].get_tag('aws.operation'), 'CreateBucket') assert_is_measured(spans[0]) assert_span_http_status_code(spans[0], 200) self.assertEqual(spans[0].service, 'aws.s3') self.assertEqual(spans[0].resource, 's3.createbucket') assert_is_measured(spans[1]) self.assertEqual(spans[1].get_tag('aws.operation'), 'PutObject') self.assertEqual(spans[1].resource, 's3.putobject') self.assertEqual(spans[1].get_tag('params.Key'), stringify(params['Key'])) self.assertEqual(spans[1].get_tag('params.Bucket'), stringify(params['Bucket'])) self.assertIsNone(spans[1].get_tag('params.Body'))
def _handle_error(span, response_error, status_code): # response_error should be a grpc.Future and so we expect to have cancelled(), # exception() and traceback() methods if a computation has resulted in an # exception being raised if (not callable(getattr(response_error, "cancelled", None)) and not callable(getattr(response_error, "exception", None)) and not callable(getattr(response_error, "traceback", None))): return if response_error.cancelled(): # handle cancelled futures separately to avoid raising grpc.FutureCancelledError span.error = 1 exc_val = to_unicode(response_error.details()) span._set_str_tag(errors.ERROR_MSG, exc_val) span._set_str_tag(errors.ERROR_TYPE, status_code) return exception = response_error.exception() traceback = response_error.traceback() if exception is not None and traceback is not None: span.error = 1 if isinstance(exception, grpc.RpcError): # handle internal gRPC exceptions separately to get status code and # details as tags properly exc_val = to_unicode(response_error.details()) span._set_str_tag(errors.ERROR_MSG, exc_val) span._set_str_tag(errors.ERROR_TYPE, status_code) span._set_str_tag(errors.ERROR_STACK, stringify(traceback)) else: exc_type = type(exception) span.set_exc_info(exc_type, exception, traceback) status_code = to_unicode(response_error.code())
def test_set_http_meta(span, int_config, method, url, status_code, status_msg, query, request_headers): int_config.http.trace_headers(["my-header"]) int_config.trace_query_string = True trace_utils.set_http_meta( span, int_config, method=method, url=url, status_code=status_code, status_msg=status_msg, query=query, request_headers=request_headers, ) if method is not None: assert span.meta[http.METHOD] == method else: assert http.METHOD not in span.meta if url is not None: assert span.meta[http.URL] == stringify(url) else: assert http.URL not in span.meta if status_code is not None: assert span.meta[http.STATUS_CODE] == str(status_code) if 500 <= int(status_code) < 600: assert span.error == 1 else: assert span.error == 0 else: assert http.STATUS_CODE not in span.meta if status_msg is not None: assert span.meta[http.STATUS_MSG] == stringify(status_msg) if query is not None and int_config.trace_query_string: assert span.meta[http.QUERY_STRING] == query if request_headers is not None: for header, value in request_headers.items(): tag = "http.request.headers." + header assert span.get_tag(tag) == value
def _sanitize_query(span, query): # TODO (aaditya): fix this hacky type check. we need it to avoid circular imports t = type(query).__name__ resource = None if t in ('SimpleStatement', 'PreparedStatement'): # reset query if a string is available resource = getattr(query, "query_string", query) elif t == 'BatchStatement': resource = 'BatchStatement' q = "; ".join(q[1] for q in query._statements_and_parameters[:2]) span.set_tag("cassandra.query", q) span.set_metric("cassandra.batch_size", len(query._statements_and_parameters)) elif t == 'BoundStatement': ps = getattr(query, 'prepared_statement', None) if ps: resource = getattr(ps, 'query_string', None) elif t == 'str': resource = query else: resource = 'unknown-query-type' # FIXME[matt] what else do to here? span.resource = stringify(resource)[:RESOURCE_MAX_LENGTH]
def test_set_http_meta(span, config, method, url, status_code): trace_utils.set_http_meta(span, config, method=method, url=url, status_code=status_code) if method is not None: assert span.meta[http.METHOD] == method else: assert http.METHOD not in span.meta if url is not None: assert span.meta[http.URL] == stringify(url) else: assert http.URL not in span.meta if status_code is not None: assert span.meta[http.STATUS_CODE] == str(status_code) if 500 <= int(status_code) < 600: assert span.error == 1 else: assert span.error == 0 else: assert http.STATUS_CODE not in span.meta
class PyMySQLCore(object): """PyMySQL test case reuses the connection across tests""" conn = None TEST_SERVICE = 'test-pymysql' DB_INFO = { 'out.host': MYSQL_CONFIG.get('host'), 'out.port': str(MYSQL_CONFIG.get('port')), } if PY2: DB_INFO.update({ 'db.user': MYSQL_CONFIG.get('user'), 'db.name': MYSQL_CONFIG.get('database') }) else: DB_INFO.update({ 'db.user': stringify(bytes(MYSQL_CONFIG.get('user'), encoding='utf-8')), 'db.name': stringify(bytes(MYSQL_CONFIG.get('database'), encoding='utf-8')) }) def setUp(self): patch() def tearDown(self): if self.conn and not self.conn._closed: self.conn.close() unpatch() def _get_conn_tracer(self): # implement me pass def test_simple_query(self): conn, tracer = self._get_conn_tracer() writer = tracer.writer cursor = conn.cursor() cursor.execute('SELECT 1') rows = cursor.fetchall() eq_(len(rows), 1) spans = writer.pop() eq_(len(spans), 2) span = spans[0] eq_(span.service, self.TEST_SERVICE) eq_(span.name, 'pymysql.query') eq_(span.span_type, 'sql') eq_(span.error, 0) meta = {} meta.update(self.DB_INFO) assert_dict_issuperset(span.meta, meta) fetch_span = spans[1] eq_(fetch_span.name, 'pymysql.query.fetchall') def test_query_with_several_rows(self): conn, tracer = self._get_conn_tracer() writer = tracer.writer cursor = conn.cursor() query = 'SELECT n FROM (SELECT 42 n UNION SELECT 421 UNION SELECT 4210) m' cursor.execute(query) rows = cursor.fetchall() eq_(len(rows), 3) spans = writer.pop() eq_(len(spans), 2) fetch_span = spans[1] eq_(fetch_span.name, 'pymysql.query.fetchall') def test_query_many(self): # tests that the executemany method is correctly wrapped. conn, tracer = self._get_conn_tracer() writer = tracer.writer tracer.enabled = False cursor = conn.cursor() cursor.execute(""" create table if not exists dummy ( dummy_key VARCHAR(32) PRIMARY KEY, dummy_value TEXT NOT NULL)""") tracer.enabled = True stmt = "INSERT INTO dummy (dummy_key, dummy_value) VALUES (%s, %s)" data = [("foo", "this is foo"), ("bar", "this is bar")] cursor.executemany(stmt, data) query = "SELECT dummy_key, dummy_value FROM dummy ORDER BY dummy_key" cursor.execute(query) rows = cursor.fetchall() eq_(len(rows), 2) eq_(rows[0][0], "bar") eq_(rows[0][1], "this is bar") eq_(rows[1][0], "foo") eq_(rows[1][1], "this is foo") spans = writer.pop() eq_(len(spans), 3) cursor.execute("drop table if exists dummy") fetch_span = spans[2] eq_(fetch_span.name, 'pymysql.query.fetchall') def test_query_proc(self): conn, tracer = self._get_conn_tracer() writer = tracer.writer # create a procedure tracer.enabled = False cursor = conn.cursor() cursor.execute("DROP PROCEDURE IF EXISTS sp_sum") cursor.execute(""" CREATE PROCEDURE sp_sum (IN p1 INTEGER, IN p2 INTEGER, OUT p3 INTEGER) BEGIN SET p3 := p1 + p2; END;""") tracer.enabled = True proc = "sp_sum" data = (40, 2, None) # spans[len(spans) - 2] cursor.callproc(proc, data) # spans[len(spans) - 1] cursor.execute(""" SELECT @_sp_sum_0, @_sp_sum_1, @_sp_sum_2 """) output = cursor.fetchone() eq_(len(output), 3) eq_(output[2], 42) spans = writer.pop() assert spans, spans # number of spans depends on PyMySQL implementation details, # typically, internal calls to execute, but at least we # can expect the last closed span to be our proc. span = spans[len(spans) - 2] eq_(span.service, self.TEST_SERVICE) eq_(span.name, 'pymysql.query') eq_(span.span_type, 'sql') eq_(span.error, 0) meta = {} meta.update(self.DB_INFO) assert_dict_issuperset(span.meta, meta) def test_simple_query_ot(self): """OpenTracing version of test_simple_query.""" conn, tracer = self._get_conn_tracer() writer = tracer.writer ot_tracer = init_tracer('mysql_svc', tracer) with ot_tracer.start_active_span('mysql_op'): cursor = conn.cursor() cursor.execute("SELECT 1") rows = cursor.fetchall() eq_(len(rows), 1) spans = writer.pop() eq_(len(spans), 3) ot_span, dd_span, fetch_span = spans # confirm parenting eq_(ot_span.parent_id, None) eq_(dd_span.parent_id, ot_span.span_id) eq_(ot_span.service, 'mysql_svc') eq_(ot_span.name, 'mysql_op') eq_(dd_span.service, self.TEST_SERVICE) eq_(dd_span.name, 'pymysql.query') eq_(dd_span.span_type, 'sql') eq_(dd_span.error, 0) meta = {} meta.update(self.DB_INFO) assert_dict_issuperset(dd_span.meta, meta) eq_(fetch_span.name, 'pymysql.query.fetchall') def test_commit(self): conn, tracer = self._get_conn_tracer() writer = tracer.writer conn.commit() spans = writer.pop() eq_(len(spans), 1) span = spans[0] eq_(span.service, self.TEST_SERVICE) eq_(span.name, 'pymysql.connection.commit') def test_rollback(self): conn, tracer = self._get_conn_tracer() writer = tracer.writer conn.rollback() spans = writer.pop() eq_(len(spans), 1) span = spans[0] eq_(span.service, self.TEST_SERVICE) eq_(span.name, 'pymysql.connection.rollback')
class PyMySQLCore(object): """PyMySQL test case reuses the connection across tests""" conn = None DB_INFO = { "out.host": MYSQL_CONFIG.get("host"), } if PY2: DB_INFO.update({ "db.user": MYSQL_CONFIG.get("user"), "db.name": MYSQL_CONFIG.get("database") }) else: DB_INFO.update({ "db.user": stringify(bytes(MYSQL_CONFIG.get("user"), encoding="utf-8")), "db.name": stringify(bytes(MYSQL_CONFIG.get("database"), encoding="utf-8")), }) def setUp(self): super(PyMySQLCore, self).setUp() patch() def tearDown(self): super(PyMySQLCore, self).tearDown() if self.conn and not self.conn._closed: self.conn.close() unpatch() def _get_conn_tracer(self): # implement me pass def test_simple_query(self): conn, tracer = self._get_conn_tracer() writer = tracer.writer cursor = conn.cursor() # PyMySQL returns back the rowcount instead of a cursor rowcount = cursor.execute("SELECT 1") assert rowcount == 1 rows = cursor.fetchall() assert len(rows) == 1 spans = writer.pop() assert len(spans) == 1 span = spans[0] assert_is_measured(span) assert span.service == "pymysql" assert span.name == "pymysql.query" assert span.span_type == "sql" assert span.error == 0 assert span.get_metric("out.port") == MYSQL_CONFIG.get("port") meta = {} meta.update(self.DB_INFO) assert_dict_issuperset(span.meta, meta) def test_simple_query_fetchall(self): with self.override_config("dbapi2", dict(trace_fetch_methods=True)): conn, tracer = self._get_conn_tracer() writer = tracer.writer cursor = conn.cursor() cursor.execute("SELECT 1") rows = cursor.fetchall() assert len(rows) == 1 spans = writer.pop() assert len(spans) == 2 span = spans[0] assert_is_measured(span) assert span.service == "pymysql" assert span.name == "pymysql.query" assert span.span_type == "sql" assert span.error == 0 assert span.get_metric("out.port") == MYSQL_CONFIG.get("port") meta = {} meta.update(self.DB_INFO) assert_dict_issuperset(span.meta, meta) fetch_span = spans[1] assert fetch_span.name == "pymysql.query.fetchall" def test_query_with_several_rows(self): conn, tracer = self._get_conn_tracer() writer = tracer.writer cursor = conn.cursor() query = "SELECT n FROM (SELECT 42 n UNION SELECT 421 UNION SELECT 4210) m" cursor.execute(query) rows = cursor.fetchall() assert len(rows) == 3 spans = writer.pop() assert len(spans) == 1 self.assertEqual(spans[0].name, "pymysql.query") def test_query_with_several_rows_fetchall(self): with self.override_config("dbapi2", dict(trace_fetch_methods=True)): conn, tracer = self._get_conn_tracer() writer = tracer.writer cursor = conn.cursor() query = "SELECT n FROM (SELECT 42 n UNION SELECT 421 UNION SELECT 4210) m" cursor.execute(query) rows = cursor.fetchall() assert len(rows) == 3 spans = writer.pop() assert len(spans) == 2 fetch_span = spans[1] assert fetch_span.name == "pymysql.query.fetchall" def test_query_many(self): # tests that the executemany method is correctly wrapped. conn, tracer = self._get_conn_tracer() writer = tracer.writer tracer.enabled = False cursor = conn.cursor() cursor.execute(""" create table if not exists dummy ( dummy_key VARCHAR(32) PRIMARY KEY, dummy_value TEXT NOT NULL)""") tracer.enabled = True stmt = "INSERT INTO dummy (dummy_key, dummy_value) VALUES (%s, %s)" data = [("foo", "this is foo"), ("bar", "this is bar")] # PyMySQL `executemany()` returns the rowcount rowcount = cursor.executemany(stmt, data) assert rowcount == 2 query = "SELECT dummy_key, dummy_value FROM dummy ORDER BY dummy_key" cursor.execute(query) rows = cursor.fetchall() assert len(rows) == 2 assert rows[0][0] == "bar" assert rows[0][1] == "this is bar" assert rows[1][0] == "foo" assert rows[1][1] == "this is foo" spans = writer.pop() assert len(spans) == 2 cursor.execute("drop table if exists dummy") def test_query_many_fetchall(self): with self.override_config("dbapi2", dict(trace_fetch_methods=True)): # tests that the executemany method is correctly wrapped. conn, tracer = self._get_conn_tracer() writer = tracer.writer tracer.enabled = False cursor = conn.cursor() cursor.execute(""" create table if not exists dummy ( dummy_key VARCHAR(32) PRIMARY KEY, dummy_value TEXT NOT NULL)""") tracer.enabled = True stmt = "INSERT INTO dummy (dummy_key, dummy_value) VALUES (%s, %s)" data = [("foo", "this is foo"), ("bar", "this is bar")] cursor.executemany(stmt, data) query = "SELECT dummy_key, dummy_value FROM dummy ORDER BY dummy_key" cursor.execute(query) rows = cursor.fetchall() assert len(rows) == 2 assert rows[0][0] == "bar" assert rows[0][1] == "this is bar" assert rows[1][0] == "foo" assert rows[1][1] == "this is foo" spans = writer.pop() assert len(spans) == 3 cursor.execute("drop table if exists dummy") fetch_span = spans[2] assert fetch_span.name == "pymysql.query.fetchall" def test_query_proc(self): conn, tracer = self._get_conn_tracer() writer = tracer.writer # create a procedure tracer.enabled = False cursor = conn.cursor() cursor.execute("DROP PROCEDURE IF EXISTS sp_sum") cursor.execute(""" CREATE PROCEDURE sp_sum (IN p1 INTEGER, IN p2 INTEGER, OUT p3 INTEGER) BEGIN SET p3 := p1 + p2; END;""") tracer.enabled = True proc = "sp_sum" data = (40, 2, None) # spans[len(spans) - 2] cursor.callproc(proc, data) # spans[len(spans) - 1] cursor.execute(""" SELECT @_sp_sum_0, @_sp_sum_1, @_sp_sum_2 """) output = cursor.fetchone() assert len(output) == 3 assert output[2] == 42 spans = writer.pop() assert spans, spans # number of spans depends on PyMySQL implementation details, # typically, internal calls to execute, but at least we # can expect the last closed span to be our proc. span = spans[len(spans) - 2] assert_is_measured(span) assert span.service == "pymysql" assert span.name == "pymysql.query" assert span.span_type == "sql" assert span.error == 0 assert span.get_metric("out.port") == MYSQL_CONFIG.get("port") meta = {} meta.update(self.DB_INFO) assert_dict_issuperset(span.meta, meta) def test_simple_query_ot(self): """OpenTracing version of test_simple_query.""" conn, tracer = self._get_conn_tracer() writer = tracer.writer ot_tracer = init_tracer("mysql_svc", tracer) with ot_tracer.start_active_span("mysql_op"): cursor = conn.cursor() cursor.execute("SELECT 1") rows = cursor.fetchall() assert len(rows) == 1 spans = writer.pop() assert len(spans) == 2 ot_span, dd_span = spans # confirm parenting assert ot_span.parent_id is None assert dd_span.parent_id == ot_span.span_id assert ot_span.service == "mysql_svc" assert ot_span.name == "mysql_op" assert_is_measured(dd_span) assert dd_span.service == "pymysql" assert dd_span.name == "pymysql.query" assert dd_span.span_type == "sql" assert dd_span.error == 0 assert dd_span.get_metric("out.port") == MYSQL_CONFIG.get("port") meta = {} meta.update(self.DB_INFO) assert_dict_issuperset(dd_span.meta, meta) def test_simple_query_ot_fetchall(self): """OpenTracing version of test_simple_query.""" with self.override_config("dbapi2", dict(trace_fetch_methods=True)): conn, tracer = self._get_conn_tracer() writer = tracer.writer ot_tracer = init_tracer("mysql_svc", tracer) with ot_tracer.start_active_span("mysql_op"): cursor = conn.cursor() cursor.execute("SELECT 1") rows = cursor.fetchall() assert len(rows) == 1 spans = writer.pop() assert len(spans) == 3 ot_span, dd_span, fetch_span = spans # confirm parenting assert ot_span.parent_id is None assert dd_span.parent_id == ot_span.span_id assert ot_span.service == "mysql_svc" assert ot_span.name == "mysql_op" assert_is_measured(dd_span) assert dd_span.service == "pymysql" assert dd_span.name == "pymysql.query" assert dd_span.span_type == "sql" assert dd_span.error == 0 assert dd_span.get_metric("out.port") == MYSQL_CONFIG.get("port") meta = {} meta.update(self.DB_INFO) assert_dict_issuperset(dd_span.meta, meta) assert fetch_span.name == "pymysql.query.fetchall" def test_commit(self): conn, tracer = self._get_conn_tracer() writer = tracer.writer conn.commit() spans = writer.pop() assert len(spans) == 1 span = spans[0] assert span.service == "pymysql" assert span.name == "pymysql.connection.commit" def test_rollback(self): conn, tracer = self._get_conn_tracer() writer = tracer.writer conn.rollback() spans = writer.pop() assert len(spans) == 1 span = spans[0] assert span.service == "pymysql" assert span.name == "pymysql.connection.rollback" def test_analytics_default(self): conn, tracer = self._get_conn_tracer() writer = tracer.writer cursor = conn.cursor() cursor.execute("SELECT 1") rows = cursor.fetchall() assert len(rows) == 1 spans = writer.pop() self.assertEqual(len(spans), 1) span = spans[0] self.assertIsNone(span.get_metric(ANALYTICS_SAMPLE_RATE_KEY)) def test_analytics_with_rate(self): with self.override_config( "dbapi2", dict(analytics_enabled=True, analytics_sample_rate=0.5)): conn, tracer = self._get_conn_tracer() writer = tracer.writer cursor = conn.cursor() cursor.execute("SELECT 1") rows = cursor.fetchall() assert len(rows) == 1 spans = writer.pop() self.assertEqual(len(spans), 1) span = spans[0] self.assertEqual(span.get_metric(ANALYTICS_SAMPLE_RATE_KEY), 0.5) def test_analytics_without_rate(self): with self.override_config("dbapi2", dict(analytics_enabled=True)): conn, tracer = self._get_conn_tracer() writer = tracer.writer cursor = conn.cursor() cursor.execute("SELECT 1") rows = cursor.fetchall() assert len(rows) == 1 spans = writer.pop() self.assertEqual(len(spans), 1) span = spans[0] self.assertEqual(span.get_metric(ANALYTICS_SAMPLE_RATE_KEY), 1.0)