Skip to content

Commit 7e3d817

Browse files
authored
New mysqlclient Instrumentation, support and tests (#169)
* mysqlclient Instrumentation, support and tests * Switch tests to use MariaDB
1 parent b440749 commit 7e3d817

File tree

6 files changed

+251
-6
lines changed

6 files changed

+251
-6
lines changed

.circleci/config.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
# CircleCI maintains a library of pre-built images
1313
# documented at https://circleci.com/docs/2.0/circleci-images/
1414
- image: circleci/postgres:9.6.5-alpine-ram
15-
- image: circleci/mysql:5.5.62-ram
15+
- image: circleci/mariadb:10-ram
1616
- image: circleci/redis:5.0.4
1717
- image: rabbitmq:3.5.4
1818

@@ -63,7 +63,7 @@ jobs:
6363
# CircleCI maintains a library of pre-built images
6464
# documented at https://circleci.com/docs/2.0/circleci-images/
6565
- image: circleci/postgres:9.6.5-alpine-ram
66-
- image: circleci/mysql:8.0.16
66+
- image: circleci/mariadb:10-ram
6767
- image: circleci/redis:5.0.4
6868
- image: rabbitmq:3.5.4
6969

@@ -112,7 +112,7 @@ jobs:
112112
# CircleCI maintains a library of pre-built images
113113
# documented at https://circleci.com/docs/2.0/circleci-images/
114114
- image: circleci/postgres:9.6.5-alpine-ram
115-
- image: circleci/mysql:8.0.16
115+
- image: circleci/mariadb:10-ram
116116
- image: circleci/redis:5.0.4
117117
- image: rabbitmq:3.5.4
118118

instana/__init__.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,11 +60,18 @@ def boot_agent():
6060
from .instrumentation.aiohttp import client
6161
from .instrumentation.aiohttp import server
6262
from .instrumentation import asynqp
63+
64+
if sys.version_info[0] < 3:
65+
# MySQL-python
66+
from .instrumentation import mysqlpython
67+
else:
68+
# mysqlclient
69+
from .instrumentation import mysqlclient
70+
6371
from .instrumentation import flask
6472
from .instrumentation.tornado import client
6573
from .instrumentation.tornado import server
6674
from .instrumentation import logging
67-
from .instrumentation import mysqlpython
6875
from .instrumentation import pymysql
6976
from .instrumentation import redis
7077
from .instrumentation import sqlalchemy
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
from __future__ import absolute_import
2+
3+
from ..log import logger
4+
from .pep0249 import ConnectionFactory
5+
6+
try:
7+
import MySQLdb
8+
9+
cf = ConnectionFactory(connect_func=MySQLdb.connect, module_name='mysql')
10+
11+
setattr(MySQLdb, 'connect', cf)
12+
if hasattr(MySQLdb, 'Connect'):
13+
setattr(MySQLdb, 'Connect', cf)
14+
15+
logger.debug("Instrumenting mysqlclient")
16+
except ImportError:
17+
pass

instana/instrumentation/mysqlpython.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
from .pep0249 import ConnectionFactory
55

66
try:
7-
import MySQLdb # noqa
7+
import MySQLdb
88

99
cf = ConnectionFactory(connect_func=MySQLdb.connect, module_name='mysql')
1010

1111
setattr(MySQLdb, 'connect', cf)
12-
if hasattr(MySQLdb, 'Connect'):
12+
if hasattr(MySQLdb, 'Connect'):
1313
setattr(MySQLdb, 'Connect', cf)
1414

1515
logger.debug("Instrumenting mysql-python")

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ def check_setuptools():
7474
'flask>=0.12.2',
7575
'lxml>=3.4',
7676
'mock>=2.0.0',
77+
'mysqlclient>=1.3.14;python_version>="3.5"',
7778
'MySQL-python>=1.2.5;python_version<="2.7"',
7879
'psycopg2>=2.7.1',
7980
'PyMySQL[rsa]>=0.9.1',

tests/test_mysqlclient.py

Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
from __future__ import absolute_import
2+
3+
import logging
4+
import sys
5+
from unittest import SkipTest
6+
7+
from nose.tools import assert_equals
8+
9+
from instana.singletons import tracer
10+
11+
from .helpers import testenv
12+
13+
if sys.version_info[0] > 2:
14+
import MySQLdb
15+
else:
16+
raise SkipTest("mysqlclient supported on Python 3 only")
17+
18+
19+
logger = logging.getLogger(__name__)
20+
21+
create_table_query = 'CREATE TABLE IF NOT EXISTS users(id serial primary key, \
22+
name varchar(40) NOT NULL, email varchar(40) NOT NULL)'
23+
24+
create_proc_query = """
25+
CREATE PROCEDURE test_proc(IN t VARCHAR(255))
26+
BEGIN
27+
SELECT name FROM users WHERE name = t;
28+
END
29+
"""
30+
31+
db = MySQLdb.connect(host=testenv['mysql_host'], port=testenv['mysql_port'],
32+
user=testenv['mysql_user'], passwd=testenv['mysql_pw'],
33+
db=testenv['mysql_db'])
34+
35+
cursor = db.cursor()
36+
cursor.execute(create_table_query)
37+
38+
while cursor.nextset() is not None:
39+
pass
40+
41+
cursor.execute('DROP PROCEDURE IF EXISTS test_proc')
42+
43+
while cursor.nextset() is not None:
44+
pass
45+
46+
cursor.execute(create_proc_query)
47+
48+
while cursor.nextset() is not None:
49+
pass
50+
51+
cursor.close()
52+
db.close()
53+
54+
55+
class TestMySQLPython:
56+
def setUp(self):
57+
logger.warn("MySQL connecting: %s:<pass>@%s:3306/%s", testenv['mysql_user'], testenv['mysql_host'], testenv['mysql_db'])
58+
self.db = MySQLdb.connect(host=testenv['mysql_host'], port=testenv['mysql_port'],
59+
user=testenv['mysql_user'], passwd=testenv['mysql_pw'],
60+
db=testenv['mysql_db'])
61+
self.cursor = self.db.cursor()
62+
self.recorder = tracer.recorder
63+
self.recorder.clear_spans()
64+
tracer.cur_ctx = None
65+
66+
def tearDown(self):
67+
""" Do nothing for now """
68+
return None
69+
70+
def test_vanilla_query(self):
71+
self.cursor.execute("""SELECT * from users""")
72+
result = self.cursor.fetchone()
73+
assert_equals(3, len(result))
74+
75+
spans = self.recorder.queued_spans()
76+
assert_equals(0, len(spans))
77+
78+
def test_basic_query(self):
79+
result = None
80+
with tracer.start_active_span('test'):
81+
result = self.cursor.execute("""SELECT * from users""")
82+
self.cursor.fetchone()
83+
84+
assert(result >= 0)
85+
86+
spans = self.recorder.queued_spans()
87+
assert_equals(2, len(spans))
88+
89+
db_span = spans[0]
90+
test_span = spans[1]
91+
92+
assert_equals("test", test_span.data.sdk.name)
93+
assert_equals(test_span.t, db_span.t)
94+
assert_equals(db_span.p, test_span.s)
95+
96+
assert_equals(None, db_span.error)
97+
assert_equals(None, db_span.ec)
98+
99+
assert_equals(db_span.n, "mysql")
100+
assert_equals(db_span.data.mysql.db, testenv['mysql_db'])
101+
assert_equals(db_span.data.mysql.user, testenv['mysql_user'])
102+
assert_equals(db_span.data.mysql.stmt, 'SELECT * from users')
103+
assert_equals(db_span.data.mysql.host, "%s:3306" % testenv['mysql_host'])
104+
105+
def test_basic_insert(self):
106+
result = None
107+
with tracer.start_active_span('test'):
108+
result = self.cursor.execute(
109+
"""INSERT INTO users(name, email) VALUES(%s, %s)""",
110+
('beaker', '[email protected]'))
111+
112+
assert_equals(1, result)
113+
114+
spans = self.recorder.queued_spans()
115+
assert_equals(2, len(spans))
116+
117+
db_span = spans[0]
118+
test_span = spans[1]
119+
120+
assert_equals("test", test_span.data.sdk.name)
121+
assert_equals(test_span.t, db_span.t)
122+
assert_equals(db_span.p, test_span.s)
123+
124+
assert_equals(None, db_span.error)
125+
assert_equals(None, db_span.ec)
126+
127+
assert_equals(db_span.n, "mysql")
128+
assert_equals(db_span.data.mysql.db, testenv['mysql_db'])
129+
assert_equals(db_span.data.mysql.user, testenv['mysql_user'])
130+
assert_equals(db_span.data.mysql.stmt, 'INSERT INTO users(name, email) VALUES(%s, %s)')
131+
assert_equals(db_span.data.mysql.host, "%s:3306" % testenv['mysql_host'])
132+
133+
def test_executemany(self):
134+
result = None
135+
with tracer.start_active_span('test'):
136+
result = self.cursor.executemany("INSERT INTO users(name, email) VALUES(%s, %s)",
137+
[('beaker', '[email protected]'), ('beaker', '[email protected]')])
138+
self.db.commit()
139+
140+
assert_equals(2, result)
141+
142+
spans = self.recorder.queued_spans()
143+
assert_equals(2, len(spans))
144+
145+
db_span = spans[0]
146+
test_span = spans[1]
147+
148+
assert_equals("test", test_span.data.sdk.name)
149+
assert_equals(test_span.t, db_span.t)
150+
assert_equals(db_span.p, test_span.s)
151+
152+
assert_equals(None, db_span.error)
153+
assert_equals(None, db_span.ec)
154+
155+
assert_equals(db_span.n, "mysql")
156+
assert_equals(db_span.data.mysql.db, testenv['mysql_db'])
157+
assert_equals(db_span.data.mysql.user, testenv['mysql_user'])
158+
assert_equals(db_span.data.mysql.stmt, 'INSERT INTO users(name, email) VALUES(%s, %s)')
159+
assert_equals(db_span.data.mysql.host, "%s:3306" % testenv['mysql_host'])
160+
161+
def test_call_proc(self):
162+
result = None
163+
with tracer.start_active_span('test'):
164+
result = self.cursor.callproc('test_proc', ('beaker',))
165+
166+
assert(result)
167+
168+
spans = self.recorder.queued_spans()
169+
assert_equals(2, len(spans))
170+
171+
db_span = spans[0]
172+
test_span = spans[1]
173+
174+
assert_equals("test", test_span.data.sdk.name)
175+
assert_equals(test_span.t, db_span.t)
176+
assert_equals(db_span.p, test_span.s)
177+
178+
assert_equals(None, db_span.error)
179+
assert_equals(None, db_span.ec)
180+
181+
assert_equals(db_span.n, "mysql")
182+
assert_equals(db_span.data.mysql.db, testenv['mysql_db'])
183+
assert_equals(db_span.data.mysql.user, testenv['mysql_user'])
184+
assert_equals(db_span.data.mysql.stmt, 'test_proc')
185+
assert_equals(db_span.data.mysql.host, "%s:3306" % testenv['mysql_host'])
186+
187+
def test_error_capture(self):
188+
result = None
189+
span = None
190+
try:
191+
with tracer.start_active_span('test'):
192+
result = self.cursor.execute("""SELECT * from blah""")
193+
self.cursor.fetchone()
194+
except Exception:
195+
pass
196+
finally:
197+
if span:
198+
span.finish()
199+
200+
assert(result is None)
201+
202+
spans = self.recorder.queued_spans()
203+
assert_equals(2, len(spans))
204+
205+
db_span = spans[0]
206+
test_span = spans[1]
207+
208+
assert_equals("test", test_span.data.sdk.name)
209+
assert_equals(test_span.t, db_span.t)
210+
assert_equals(db_span.p, test_span.s)
211+
212+
assert_equals(True, db_span.error)
213+
assert_equals(1, db_span.ec)
214+
assert_equals(db_span.data.mysql.error, '(1146, "Table \'%s.blah\' doesn\'t exist")' % testenv['mysql_db'])
215+
216+
assert_equals(db_span.n, "mysql")
217+
assert_equals(db_span.data.mysql.db, testenv['mysql_db'])
218+
assert_equals(db_span.data.mysql.user, testenv['mysql_user'])
219+
assert_equals(db_span.data.mysql.stmt, 'SELECT * from blah')
220+
assert_equals(db_span.data.mysql.host, "%s:3306" % testenv['mysql_host'])

0 commit comments

Comments
 (0)