Skip to content

Commit 2cb4a44

Browse files
committed
Add asyncpg instrumentation. Closes: #15
1 parent 92e3701 commit 2cb4a44

File tree

5 files changed

+162
-0
lines changed

5 files changed

+162
-0
lines changed

newrelic/config.py

+7
Original file line numberDiff line numberDiff line change
@@ -2269,6 +2269,13 @@ def _process_module_builtin_defaults():
22692269
'newrelic.hooks.database_psycopg2cffi',
22702270
'instrument_psycopg2cffi_extensions')
22712271

2272+
_process_module_definition('asyncpg.connect_utils',
2273+
'newrelic.hooks.database_asyncpg',
2274+
'instrument_asyncpg_connect_utils')
2275+
_process_module_definition('asyncpg.protocol',
2276+
'newrelic.hooks.database_asyncpg',
2277+
'instrument_asyncpg_protocol')
2278+
22722279
_process_module_definition('postgresql.driver.dbapi20',
22732280
'newrelic.hooks.database_postgresql',
22742281
'instrument_postgresql_driver_dbapi20')

newrelic/core/attribute.py

+1
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@
6565
'aws.lambda.coldStart',
6666
'aws.lambda.eventSource.arn',
6767
'db.instance',
68+
'db.operation',
6869
'db.statement',
6970
'error.class',
7071
'error.message',

newrelic/core/database_utils.py

+3
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,9 @@ def _parse_alter(sql):
401401
'alter': None,
402402
'commit': None,
403403
'rollback': None,
404+
'begin': None,
405+
'prepare': None,
406+
'copy': None,
404407
}
405408

406409
_parse_operation_p = r'(\w+)'

newrelic/core/datastore_node.py

+5
Original file line numberDiff line numberDiff line change
@@ -135,3 +135,8 @@ def trace_node(self, stats, root, connections):
135135
return newrelic.core.trace_node.TraceNode(start_time=start_time,
136136
end_time=end_time, name=name, params=params, children=children,
137137
label=None)
138+
139+
def span_event(self, *args, **kwargs):
140+
if self.operation:
141+
self.agent_attributes["db.operation"] = self.operation
142+
return super(DatastoreNode, self).span_event(*args, **kwargs)

newrelic/hooks/database_asyncpg.py

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# Copyright 2010 New Relic, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from newrelic.api.database_trace import (
16+
DatabaseTrace,
17+
enable_datastore_instance_feature,
18+
register_database_client,
19+
)
20+
from newrelic.api.datastore_trace import DatastoreTrace
21+
from newrelic.common.object_wrapper import ObjectProxy, wrap_function_wrapper
22+
23+
24+
class PostgresApi(object):
25+
@staticmethod
26+
def _instance_info(addr, connected_fut, con_params, *args, **kwargs):
27+
if isinstance(addr, str):
28+
host = "localhost"
29+
port = addr
30+
else:
31+
host, port = addr
32+
33+
return (host, port, getattr(con_params, "database", None))
34+
35+
@classmethod
36+
def instance_info(cls, args, kwargs):
37+
return cls._instance_info(*args, **kwargs)
38+
39+
40+
register_database_client(
41+
PostgresApi,
42+
"Postgres",
43+
quoting_style="single+dollar",
44+
instance_info=PostgresApi.instance_info,
45+
)
46+
enable_datastore_instance_feature(PostgresApi)
47+
48+
49+
class ProtocolProxy(ObjectProxy):
50+
async def bind_execute(self, state, *args, **kwargs):
51+
with DatabaseTrace(
52+
state.query,
53+
dbapi2_module=PostgresApi,
54+
connect_params=getattr(self, "_nr_connect_params", None),
55+
):
56+
return await self.__wrapped__.bind_execute(state, *args, **kwargs)
57+
58+
async def bind_execute_many(self, state, *args, **kwargs):
59+
with DatabaseTrace(
60+
state.query,
61+
dbapi2_module=PostgresApi,
62+
connect_params=getattr(self, "_nr_connect_params", None),
63+
):
64+
return await self.__wrapped__.bind_execute_many(state, *args, **kwargs)
65+
66+
async def bind(self, state, *args, **kwargs):
67+
with DatabaseTrace(
68+
state.query,
69+
dbapi2_module=PostgresApi,
70+
connect_params=getattr(self, "_nr_connect_params", None),
71+
):
72+
return await self.__wrapped__.bind(state, *args, **kwargs)
73+
74+
async def execute(self, state, *args, **kwargs):
75+
with DatabaseTrace(
76+
state.query,
77+
dbapi2_module=PostgresApi,
78+
connect_params=getattr(self, "_nr_connect_params", None),
79+
):
80+
return await self.__wrapped__.execute(state, *args, **kwargs)
81+
82+
async def query(self, query, *args, **kwargs):
83+
with DatabaseTrace(
84+
query,
85+
dbapi2_module=PostgresApi,
86+
connect_params=getattr(self, "_nr_connect_params", None),
87+
):
88+
return await self.__wrapped__.query(query, *args, **kwargs)
89+
90+
async def prepare(self, stmt_name, query, *args, **kwargs):
91+
with DatabaseTrace(
92+
"PREPARE {stmt_name} FROM '{query}'".format(
93+
stmt_name=stmt_name, query=query
94+
),
95+
dbapi2_module=PostgresApi,
96+
connect_params=getattr(self, "_nr_connect_params", None),
97+
):
98+
return await self.__wrapped__.prepare(stmt_name, query, *args, **kwargs)
99+
100+
async def copy_in(self, copy_stmt, *args, **kwargs):
101+
with DatabaseTrace(
102+
copy_stmt,
103+
dbapi2_module=PostgresApi,
104+
connect_params=getattr(self, "_nr_connect_params", None),
105+
):
106+
return await self.__wrapped__.copy_in(copy_stmt, *args, **kwargs)
107+
108+
async def copy_out(self, copy_stmt, *args, **kwargs):
109+
with DatabaseTrace(
110+
copy_stmt,
111+
dbapi2_module=PostgresApi,
112+
connect_params=getattr(self, "_nr_connect_params", None),
113+
):
114+
return await self.__wrapped__.copy_out(copy_stmt, *args, **kwargs)
115+
116+
117+
def proxy_protocol(wrapped, instance, args, kwargs):
118+
proxy = ProtocolProxy(wrapped(*args, **kwargs))
119+
proxy._nr_connect_params = (args, kwargs)
120+
return proxy
121+
122+
123+
def wrap_connect(wrapped, instance, args, kwargs):
124+
host = port = database_name = None
125+
if "addr" in kwargs:
126+
host, port, database_name = PostgresApi._instance_info(
127+
kwargs["addr"], None, kwargs.get("params")
128+
)
129+
130+
with DatastoreTrace(
131+
PostgresApi._nr_database_product,
132+
None,
133+
"connect",
134+
host=host,
135+
port_path_or_id=port,
136+
database_name=database_name,
137+
):
138+
return wrapped(*args, **kwargs)
139+
140+
141+
def instrument_asyncpg_protocol(module):
142+
wrap_function_wrapper(module, "Protocol", proxy_protocol)
143+
144+
145+
def instrument_asyncpg_connect_utils(module):
146+
wrap_function_wrapper(module, "_connect_addr", wrap_connect)

0 commit comments

Comments
 (0)