コード例 #1
0
    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"))
コード例 #2
0
    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"))
コード例 #3
0
ファイル: test.py プロジェクト: mbmblbelt/dd-trace-py
    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'))
コード例 #4
0
ファイル: test.py プロジェクト: yuhonghong7035/dd-trace-py
    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'))
コード例 #5
0
    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'))
コード例 #6
0
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())
コード例 #7
0
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
コード例 #8
0
ファイル: session.py プロジェクト: zapier/dd-trace-py
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]
コード例 #9
0
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
コード例 #10
0
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')
コード例 #11
0
ファイル: test_pymysql.py プロジェクト: mbmblbelt/dd-trace-py
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)