Skip to content

Commit 44598cc

Browse files
committed
Add query and aggregation query instrumentation
1 parent b9eaa5b commit 44598cc

File tree

6 files changed

+201
-7
lines changed

6 files changed

+201
-7
lines changed

newrelic/config.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2289,6 +2289,16 @@ def _process_module_builtin_defaults():
22892289
"newrelic.hooks.datastore_firestore",
22902290
"instrument_google_cloud_firestore_v1_collection",
22912291
)
2292+
_process_module_definition(
2293+
"google.cloud.firestore_v1.query",
2294+
"newrelic.hooks.datastore_firestore",
2295+
"instrument_google_cloud_firestore_v1_query",
2296+
)
2297+
_process_module_definition(
2298+
"google.cloud.firestore_v1.aggregation",
2299+
"newrelic.hooks.datastore_firestore",
2300+
"instrument_google_cloud_firestore_v1_aggregation",
2301+
)
22922302

22932303
_process_module_definition(
22942304
"ariadne.asgi",

newrelic/hooks/datastore_firestore.py

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,27 @@
1919
from newrelic.api.datastore_trace import DatastoreTrace
2020

2121

22-
_get_object_id = lambda obj, *args, **kwargs: obj.id
22+
def _get_object_id(obj, *args, **kwargs):
23+
try:
24+
return obj.id
25+
except Exception:
26+
return None
2327

2428

25-
def wrap_generator_method(module, class_name, method_name, target=_get_object_id):
29+
def _get_parent_id(obj, *args, **kwargs):
30+
try:
31+
return obj._parent.id
32+
except Exception:
33+
return None
34+
35+
def _get_nested_query_parent_id(obj, *args, **kwargs):
36+
try:
37+
return obj._nested_query._parent.id
38+
except Exception:
39+
return None
40+
41+
42+
def wrap_generator_method(module, class_name, method_name, target):
2643
def _wrapper(wrapped, instance, args, kwargs):
2744
target_ = target(instance) if callable(target) else target
2845
trace = DatastoreTrace(product="Firestore", target=target_, operation=method_name)
@@ -61,7 +78,7 @@ def instrument_google_cloud_firestore_v1_collection(module):
6178

6279
for method in ("stream", "list_documents"):
6380
if hasattr(class_, method):
64-
wrap_generator_method(module, "CollectionReference", method)
81+
wrap_generator_method(module, "CollectionReference", method, target=_get_object_id)
6582

6683

6784
def instrument_google_cloud_firestore_v1_document(module):
@@ -75,4 +92,32 @@ def instrument_google_cloud_firestore_v1_document(module):
7592

7693
for method in ("collections",):
7794
if hasattr(class_, method):
78-
wrap_generator_method(module, "DocumentReference", method)
95+
wrap_generator_method(module, "DocumentReference", method, target=_get_object_id)
96+
97+
98+
def instrument_google_cloud_firestore_v1_query(module):
99+
if hasattr(module, "Query"):
100+
class_ = module.Query
101+
for method in ("get",):
102+
if hasattr(class_, method):
103+
wrap_datastore_trace(
104+
module, "Query.%s" % method, product="Firestore", target=_get_parent_id, operation=method
105+
)
106+
107+
for method in ("stream",):
108+
if hasattr(class_, method):
109+
wrap_generator_method(module, "Query", method, target=_get_parent_id)
110+
111+
112+
def instrument_google_cloud_firestore_v1_aggregation(module):
113+
if hasattr(module, "AggregationQuery"):
114+
class_ = module.AggregationQuery
115+
for method in ("get",):
116+
if hasattr(class_, method):
117+
wrap_datastore_trace(
118+
module, "AggregationQuery.%s" % method, product="Firestore", target=_get_nested_query_parent_id, operation=method
119+
)
120+
121+
for method in ("stream",):
122+
if hasattr(class_, method):
123+
wrap_generator_method(module, "AggregationQuery", method, target=_get_nested_query_parent_id)
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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+
import pytest
16+
17+
from testing_support.validators.validate_transaction_metrics import validate_transaction_metrics
18+
from newrelic.api.background_task import background_task
19+
from testing_support.validators.validate_database_duration import (
20+
validate_database_duration,
21+
)
22+
23+
24+
@pytest.fixture(autouse=True)
25+
def sample_data(collection, reset_firestore):
26+
# reset_firestore must be run before, not after this fixture
27+
for x in range(1, 6):
28+
collection.add({"x": x})
29+
30+
31+
def _exercise_firestore(collection):
32+
aggregation_query = collection.select("x").where("x", "<=", 3).count()
33+
assert aggregation_query.get()[0][0].value == 3
34+
assert list(aggregation_query.stream())[0][0].value == 3
35+
36+
37+
def test_firestore_aggregation_query(collection):
38+
_test_scoped_metrics = [
39+
("Datastore/statement/Firestore/%s/stream" % collection.id, 1),
40+
("Datastore/statement/Firestore/%s/get" % collection.id, 1),
41+
]
42+
43+
_test_rollup_metrics = [
44+
("Datastore/operation/Firestore/get", 1),
45+
("Datastore/operation/Firestore/stream", 1),
46+
("Datastore/all", 2),
47+
("Datastore/allOther", 2),
48+
]
49+
@validate_transaction_metrics(
50+
"test_firestore_aggregation_query",
51+
scoped_metrics=_test_scoped_metrics,
52+
rollup_metrics=_test_rollup_metrics,
53+
background_task=True,
54+
)
55+
@background_task(name="test_firestore_aggregation_query")
56+
def _test():
57+
_exercise_firestore(collection)
58+
59+
_test()
60+
61+
62+
@background_task()
63+
def test_firestore_aggregation_query_generators(collection, assert_trace_for_generator):
64+
aggregation_query = collection.select("x").where("x", "<=", 3).count()
65+
assert_trace_for_generator(aggregation_query.stream)
66+
67+
68+
@validate_database_duration()
69+
@background_task()
70+
def test_firestore_aggregation_query_db_duration(collection):
71+
_exercise_firestore(collection)

tests/datastore_firestore/test_collections.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ def _test():
6565

6666
@background_task()
6767
def test_firestore_collections_generators(collection, assert_trace_for_generator):
68-
txn = current_trace()
6968
collection.add({})
7069
collection.add({})
7170
assert len(list(collection.list_documents())) == 2

tests/datastore_firestore/test_documents.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,8 +74,6 @@ def _test():
7474

7575
@background_task()
7676
def test_firestore_documents_generators(collection, assert_trace_for_generator):
77-
txn = current_trace()
78-
7977
subcollection_doc = collection.document("SubCollections")
8078
subcollection_doc.set({})
8179
subcollection_doc.collection("collection1").add({})
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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+
import pytest
16+
17+
from testing_support.validators.validate_transaction_metrics import validate_transaction_metrics
18+
from newrelic.api.background_task import background_task
19+
from testing_support.validators.validate_database_duration import (
20+
validate_database_duration,
21+
)
22+
23+
24+
@pytest.fixture(autouse=True)
25+
def sample_data(collection, reset_firestore):
26+
# reset_firestore must be run before, not after this fixture
27+
for x in range(1, 6):
28+
collection.add({"x": x})
29+
30+
31+
def _exercise_firestore(collection):
32+
query = collection.select("x").limit(10).order_by("x").where("x", "<=", 3)
33+
assert len(query.get()) == 3
34+
assert len(list(query.stream())) == 3
35+
36+
37+
def test_firestore_query(collection):
38+
_test_scoped_metrics = [
39+
("Datastore/statement/Firestore/%s/stream" % collection.id, 1),
40+
("Datastore/statement/Firestore/%s/get" % collection.id, 1),
41+
]
42+
43+
_test_rollup_metrics = [
44+
("Datastore/operation/Firestore/get", 1),
45+
("Datastore/operation/Firestore/stream", 1),
46+
("Datastore/all", 2),
47+
("Datastore/allOther", 2),
48+
]
49+
@validate_transaction_metrics(
50+
"test_firestore_query",
51+
scoped_metrics=_test_scoped_metrics,
52+
rollup_metrics=_test_rollup_metrics,
53+
background_task=True,
54+
)
55+
@background_task(name="test_firestore_query")
56+
def _test():
57+
_exercise_firestore(collection)
58+
59+
_test()
60+
61+
62+
@background_task()
63+
def test_firestore_query_generators(collection, assert_trace_for_generator):
64+
query = collection.select("x").where("x", "<=", 3)
65+
assert_trace_for_generator(query.stream)
66+
67+
68+
@validate_database_duration()
69+
@background_task()
70+
def test_firestore_query_db_duration(collection):
71+
_exercise_firestore(collection)

0 commit comments

Comments
 (0)