Skip to content

Commit 6a6228f

Browse files
authored
Fix database instance metric bug (#905)
* Remove enable_datastore_instance_feature This was added in 2016 when the database instance feature was first developed. It appears to be a method of gating this feature internally within the agent at the time that it was implemented. However, it is not needed now and database instrumentations that don't call this are actually broken in that the metric that is used to create the service map (namely `Datastore/instance/MySQL/<host>/<port>`) does not get created due to not calling this enable feature function. * Rename cross agent test * Add Database/instance metric check
1 parent f1a673e commit 6a6228f

File tree

27 files changed

+905
-695
lines changed

27 files changed

+905
-695
lines changed

newrelic/api/database_trace.py

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,6 @@ def register_database_client(
4444
dbapi2_module._nr_explain_query = explain_query
4545
dbapi2_module._nr_explain_stmts = explain_stmts
4646
dbapi2_module._nr_instance_info = instance_info
47-
dbapi2_module._nr_datastore_instance_feature_flag = False
48-
49-
50-
def enable_datastore_instance_feature(dbapi2_module):
51-
dbapi2_module._nr_datastore_instance_feature_flag = True
5247

5348

5449
class DatabaseTrace(TimeTrace):
@@ -153,12 +148,7 @@ def finalize_data(self, transaction, exc=None, value=None, tb=None):
153148

154149
if instance_enabled or db_name_enabled:
155150

156-
if (
157-
self.dbapi2_module
158-
and self.connect_params
159-
and self.dbapi2_module._nr_datastore_instance_feature_flag
160-
and self.dbapi2_module._nr_instance_info is not None
161-
):
151+
if self.dbapi2_module and self.connect_params and self.dbapi2_module._nr_instance_info is not None:
162152

163153
instance_info = self.dbapi2_module._nr_instance_info(*self.connect_params)
164154

newrelic/hooks/database_asyncpg.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,7 @@
1212
# See the License for the specific language governing permissions and
1313
# limitations under the License.
1414

15-
from newrelic.api.database_trace import (
16-
DatabaseTrace,
17-
enable_datastore_instance_feature,
18-
register_database_client,
19-
)
15+
from newrelic.api.database_trace import DatabaseTrace, register_database_client
2016
from newrelic.api.datastore_trace import DatastoreTrace
2117
from newrelic.common.object_wrapper import ObjectProxy, wrap_function_wrapper
2218

@@ -43,7 +39,6 @@ def instance_info(cls, args, kwargs):
4339
quoting_style="single+dollar",
4440
instance_info=PostgresApi.instance_info,
4541
)
46-
enable_datastore_instance_feature(PostgresApi)
4742

4843

4944
class ProtocolProxy(ObjectProxy):
@@ -94,9 +89,7 @@ async def query(self, query, *args, **kwargs):
9489

9590
async def prepare(self, stmt_name, query, *args, **kwargs):
9691
with DatabaseTrace(
97-
"PREPARE {stmt_name} FROM '{query}'".format(
98-
stmt_name=stmt_name, query=query
99-
),
92+
"PREPARE {stmt_name} FROM '{query}'".format(stmt_name=stmt_name, query=query),
10093
dbapi2_module=PostgresApi,
10194
connect_params=getattr(self, "_nr_connect_params", None),
10295
source=self.__wrapped__.prepare,
@@ -131,9 +124,7 @@ def proxy_protocol(wrapped, instance, args, kwargs):
131124
def wrap_connect(wrapped, instance, args, kwargs):
132125
host = port = database_name = None
133126
if "addr" in kwargs:
134-
host, port, database_name = PostgresApi._instance_info(
135-
kwargs["addr"], None, kwargs.get("params")
136-
)
127+
host, port, database_name = PostgresApi._instance_info(kwargs["addr"], None, kwargs.get("params"))
137128

138129
with DatastoreTrace(
139130
PostgresApi._nr_database_product,

newrelic/hooks/database_mysqldb.py

Lines changed: 50 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -14,99 +14,114 @@
1414

1515
import os
1616

17-
from newrelic.api.database_trace import (enable_datastore_instance_feature,
18-
DatabaseTrace, register_database_client)
17+
from newrelic.api.database_trace import DatabaseTrace, register_database_client
1918
from newrelic.api.function_trace import FunctionTrace
2019
from newrelic.api.transaction import current_transaction
2120
from newrelic.common.object_names import callable_name
2221
from newrelic.common.object_wrapper import wrap_object
22+
from newrelic.hooks.database_dbapi2 import ConnectionFactory as DBAPI2ConnectionFactory
23+
from newrelic.hooks.database_dbapi2 import ConnectionWrapper as DBAPI2ConnectionWrapper
2324

24-
from newrelic.hooks.database_dbapi2 import (ConnectionWrapper as
25-
DBAPI2ConnectionWrapper, ConnectionFactory as DBAPI2ConnectionFactory)
2625

2726
class ConnectionWrapper(DBAPI2ConnectionWrapper):
28-
2927
def __enter__(self):
3028
transaction = current_transaction()
3129
name = callable_name(self.__wrapped__.__enter__)
3230
with FunctionTrace(name, source=self.__wrapped__.__enter__):
33-
cursor = self.__wrapped__.__enter__()
31+
cursor = self.__wrapped__.__enter__()
3432

3533
# The __enter__() method of original connection object returns
3634
# a new cursor instance for use with 'as' assignment. We need
3735
# to wrap that in a cursor wrapper otherwise we will not track
3836
# any queries done via it.
3937

40-
return self.__cursor_wrapper__(cursor, self._nr_dbapi2_module,
41-
self._nr_connect_params, None)
38+
return self.__cursor_wrapper__(cursor, self._nr_dbapi2_module, self._nr_connect_params, None)
4239

4340
def __exit__(self, exc, value, tb):
4441
transaction = current_transaction()
4542
name = callable_name(self.__wrapped__.__exit__)
4643
with FunctionTrace(name, source=self.__wrapped__.__exit__):
4744
if exc is None:
48-
with DatabaseTrace('COMMIT', self._nr_dbapi2_module, self._nr_connect_params, source=self.__wrapped__.__exit__):
45+
with DatabaseTrace(
46+
"COMMIT", self._nr_dbapi2_module, self._nr_connect_params, source=self.__wrapped__.__exit__
47+
):
4948
return self.__wrapped__.__exit__(exc, value, tb)
5049
else:
51-
with DatabaseTrace('ROLLBACK', self._nr_dbapi2_module, self._nr_connect_params, source=self.__wrapped__.__exit__):
50+
with DatabaseTrace(
51+
"ROLLBACK", self._nr_dbapi2_module, self._nr_connect_params, source=self.__wrapped__.__exit__
52+
):
5253
return self.__wrapped__.__exit__(exc, value, tb)
5354

55+
5456
class ConnectionFactory(DBAPI2ConnectionFactory):
5557

5658
__connection_wrapper__ = ConnectionWrapper
5759

60+
5861
def instance_info(args, kwargs):
59-
def _bind_params(host=None, user=None, passwd=None, db=None, port=None,
60-
unix_socket=None, conv=None, connect_timeout=None, compress=None,
61-
named_pipe=None, init_command=None, read_default_file=None,
62-
read_default_group=None, *args, **kwargs):
63-
return (host, port, db, unix_socket,
64-
read_default_file, read_default_group)
62+
def _bind_params(
63+
host=None,
64+
user=None,
65+
passwd=None,
66+
db=None,
67+
port=None,
68+
unix_socket=None,
69+
conv=None,
70+
connect_timeout=None,
71+
compress=None,
72+
named_pipe=None,
73+
init_command=None,
74+
read_default_file=None,
75+
read_default_group=None,
76+
*args,
77+
**kwargs
78+
):
79+
return (host, port, db, unix_socket, read_default_file, read_default_group)
6580

6681
params = _bind_params(*args, **kwargs)
6782
host, port, db, unix_socket, read_default_file, read_default_group = params
6883
explicit_host = host
6984

7085
port_path_or_id = None
7186
if read_default_file or read_default_group:
72-
host = host or 'default'
73-
port_path_or_id = 'unknown'
87+
host = host or "default"
88+
port_path_or_id = "unknown"
7489
elif not host:
75-
host = 'localhost'
90+
host = "localhost"
7691

77-
if host == 'localhost':
92+
if host == "localhost":
7893
# precedence: explicit -> cnf (if used) -> env -> 'default'
79-
port_path_or_id = (unix_socket or
80-
port_path_or_id or
81-
os.getenv('MYSQL_UNIX_PORT', 'default'))
94+
port_path_or_id = unix_socket or port_path_or_id or os.getenv("MYSQL_UNIX_PORT", "default")
8295
elif explicit_host:
8396
# only reach here if host is explicitly passed in
8497
port = port and str(port)
8598
# precedence: explicit -> cnf (if used) -> env -> '3306'
86-
port_path_or_id = (port or
87-
port_path_or_id or
88-
os.getenv('MYSQL_TCP_PORT', '3306'))
99+
port_path_or_id = port or port_path_or_id or os.getenv("MYSQL_TCP_PORT", "3306")
89100

90101
# There is no default database if omitted from the connect params
91102
# In this case, we should report unknown
92-
db = db or 'unknown'
103+
db = db or "unknown"
93104

94105
return (host, port_path_or_id, db)
95106

96-
def instrument_mysqldb(module):
97-
register_database_client(module, database_product='MySQL',
98-
quoting_style='single+double', explain_query='explain',
99-
explain_stmts=('select',), instance_info=instance_info)
100107

101-
enable_datastore_instance_feature(module)
108+
def instrument_mysqldb(module):
109+
register_database_client(
110+
module,
111+
database_product="MySQL",
112+
quoting_style="single+double",
113+
explain_query="explain",
114+
explain_stmts=("select",),
115+
instance_info=instance_info,
116+
)
102117

103-
wrap_object(module, 'connect', ConnectionFactory, (module,))
118+
wrap_object(module, "connect", ConnectionFactory, (module,))
104119

105120
# The connect() function is actually aliased with Connect() and
106121
# Connection, the later actually being the Connection type object.
107122
# Instrument Connect(), but don't instrument Connection in case that
108123
# interferes with direct type usage. If people are using the
109124
# Connection object directly, they should really be using connect().
110125

111-
if hasattr(module, 'Connect'):
112-
wrap_object(module, 'Connect', ConnectionFactory, (module,))
126+
if hasattr(module, "Connect"):
127+
wrap_object(module, "Connect", ConnectionFactory, (module,))

0 commit comments

Comments
 (0)