Skip to content

Commit

Permalink
Add GraphQL Framework Info to Metrics (#367)
Browse files Browse the repository at this point in the history
* Add graphene and ariadne product info.

* Add strawberry product info.

* Add framework info to resolver metrics.

* Correct Ariadne tests.

* Update Starlette tests with Graphene details.

* Add protobuf version dependency to agent_streaming tests.

* Specify python version for protobuf dependency.

* Remove graphql node product property.
  • Loading branch information
umaannamalai authored and TimPansino committed Sep 16, 2021
1 parent acf43cb commit a1bf688
Show file tree
Hide file tree
Showing 14 changed files with 141 additions and 93 deletions.
24 changes: 24 additions & 0 deletions newrelic/api/graphql_trace.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ def __init__(self, **kwargs):
self.graphql = None
self.graphql_format = None
self.statement = None
self.product = "GraphQL"

def __repr__(self):
return "<%s object at 0x%x %s>" % (
Expand Down Expand Up @@ -89,6 +90,7 @@ def create_node(self):
operation_type=self.operation_type,
deepest_path=self.deepest_path,
graphql=self.graphql,
product=self.product,
)

def set_transaction_name(self, priority=None):
Expand Down Expand Up @@ -135,10 +137,31 @@ class GraphQLResolverTrace(TimeTrace):
def __init__(self, field_name=None, **kwargs):
super(GraphQLResolverTrace, self).__init__(**kwargs)
self.field_name = field_name
self._product = None

def __repr__(self):
return "<%s object at 0x%x %s>" % (self.__class__.__name__, id(self), dict(field_name=self.field_name))

def __enter__(self):
super(GraphQLResolverTrace, self).__enter__()
_ = self.product # Cache product value
return self

@property
def product(self):
if not self._product:
# Find GraphQLOperationTrace to obtain stored product info
parent = self # init to self for loop start
while parent is not None and not isinstance(parent, GraphQLOperationTrace):
parent = getattr(parent, "parent", None)

if parent is not None:
self._product = getattr(parent, "product", "GraphQL")
else:
self._product = "GraphQL"

return self._product

def finalize_data(self, *args, **kwargs):
self._add_agent_attribute("graphql.field.name", self.field_name)

Expand All @@ -155,6 +178,7 @@ def create_node(self):
guid=self.guid,
agent_attributes=self.agent_attributes,
user_attributes=self.user_attributes,
product=self.product,
)


Expand Down
8 changes: 2 additions & 6 deletions newrelic/core/graphql_node.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,13 @@
_GraphQLOperationNode = namedtuple('_GraphQLNode',
['operation_type', 'operation_name', 'deepest_path', 'graphql',
'children', 'start_time', 'end_time', 'duration', 'exclusive', 'guid',
'agent_attributes', 'user_attributes'])
'agent_attributes', 'user_attributes', 'product'])

_GraphQLResolverNode = namedtuple('_GraphQLNode',
['field_name', 'children', 'start_time', 'end_time', 'duration',
'exclusive', 'guid', 'agent_attributes', 'user_attributes'])
'exclusive', 'guid', 'agent_attributes', 'user_attributes', 'product'])

class GraphQLNodeMixin(GenericNodeMixin):
@property
def product(self):
return "GraphQL"

def trace_node(self, stats, root, connections):
name = root.string_table.cache(self.name)

Expand Down
2 changes: 2 additions & 0 deletions newrelic/hooks/framework_ariadne.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ def wrap_graphql_sync(wrapped, instance, args, kwargs):
transaction.set_transaction_name(callable_name(wrapped), "GraphQL", priority=10)

with GraphQLOperationTrace() as trace:
trace.product = "Ariadne"
trace.statement = graphql_statement(query)
with ErrorTrace(ignore=ignore_graphql_duplicate_exception):
return wrapped(*args, **kwargs)
Expand Down Expand Up @@ -93,6 +94,7 @@ async def wrap_graphql(wrapped, instance, args, kwargs):
transaction.set_transaction_name(callable_name(wrapped), "GraphQL", priority=10)

with GraphQLOperationTrace() as trace:
trace.product = "Ariadne"
trace.statement = graphql_statement(query)
with ErrorTrace(ignore=ignore_graphql_duplicate_exception):
result = wrapped(*args, **kwargs)
Expand Down
24 changes: 20 additions & 4 deletions newrelic/hooks/framework_graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -369,6 +369,12 @@ def bind_resolve_field_v2(exe_context, parent_type, source, field_asts, parent_i
return parent_type, field_asts, field_path


def graphene_framework_details():
import graphene

return ("Graphene", getattr(graphene, "__version__", None))


def wrap_resolve_field(wrapped, instance, args, kwargs):
transaction = current_transaction()
if transaction is None:
Expand Down Expand Up @@ -402,7 +408,7 @@ def wrap_resolve_field(wrapped, instance, args, kwargs):


def bind_graphql_impl_query(schema, source, *args, **kwargs):
return source
return schema, source


def bind_execute_graphql_query(
Expand All @@ -416,8 +422,7 @@ def bind_execute_graphql_query(
backend=None,
**execute_options
):

return request_string
return schema, request_string


def wrap_graphql_impl(wrapped, instance, args, kwargs):
Expand All @@ -433,7 +438,7 @@ def wrap_graphql_impl(wrapped, instance, args, kwargs):
bind_query = bind_graphql_impl_query

try:
query = bind_query(*args, **kwargs)
schema, query = bind_query(*args, **kwargs)
except TypeError:
return wrapped(*args, **kwargs)

Expand All @@ -444,6 +449,17 @@ def wrap_graphql_impl(wrapped, instance, args, kwargs):

with GraphQLOperationTrace() as trace:
trace.statement = graphql_statement(query)

# Handle Graphene Schemas
try:
from graphene.types.schema import Schema as GrapheneSchema
if isinstance(schema, GrapheneSchema):
trace.product = "Graphene"
framework = graphene_framework_details()
transaction.add_framework_info(name=framework[0], version=framework[1])
except ImportError:
pass

with ErrorTrace(ignore=ignore_graphql_duplicate_exception):
result = wrapped(*args, **kwargs)
return result
Expand Down
2 changes: 2 additions & 0 deletions newrelic/hooks/framework_strawberry.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def wrap_execute_sync(wrapped, instance, args, kwargs):
transaction.set_transaction_name(callable_name(wrapped), "GraphQL", priority=10)

with GraphQLOperationTrace() as trace:
trace.product = "Strawberry"
trace.statement = graphql_statement(query)
with ErrorTrace(ignore=ignore_graphql_duplicate_exception):
return wrapped(*args, **kwargs)
Expand All @@ -83,6 +84,7 @@ async def wrap_execute(wrapped, instance, args, kwargs):
transaction.set_transaction_name(callable_name(wrapped), "GraphQL", priority=10)

with GraphQLOperationTrace() as trace:
trace.product = "Strawberry"
trace.statement = graphql_statement(query)
with ErrorTrace(ignore=ignore_graphql_duplicate_exception):
return await wrapped(*args, **kwargs)
Expand Down
34 changes: 17 additions & 17 deletions tests/framework_ariadne/test_application.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ def error_middleware(next, root, info, **args): # pylint: disable=W0622
("OtherTransaction/all", 1),
("GraphQL/all", 1),
("GraphQL/allOther", 1),
("GraphQL/GraphQL/all", 1),
("GraphQL/GraphQL/allOther", 1),
("GraphQL/Ariadne/all", 1),
("GraphQL/Ariadne/allOther", 1),
]


Expand Down Expand Up @@ -118,17 +118,17 @@ def test_query_and_mutation(app, graphql_run):
("Python/Framework/GraphQL/%s" % version, 1),
]
_test_mutation_scoped_metrics = [
("GraphQL/resolve/GraphQL/storage", 1),
("GraphQL/resolve/GraphQL/storage_add", 1),
("GraphQL/operation/GraphQL/query/<anonymous>/storage", 1),
("GraphQL/operation/GraphQL/mutation/<anonymous>/storage_add.string", 1),
("GraphQL/resolve/Ariadne/storage", 1),
("GraphQL/resolve/Ariadne/storage_add", 1),
("GraphQL/operation/Ariadne/query/<anonymous>/storage", 1),
("GraphQL/operation/Ariadne/mutation/<anonymous>/storage_add.string", 1),
]
_test_mutation_unscoped_metrics = [
("OtherTransaction/all", 1),
("GraphQL/all", 2),
("GraphQL/GraphQL/all", 2),
("GraphQL/Ariadne/all", 2),
("GraphQL/allOther", 2),
("GraphQL/GraphQL/allOther", 2),
("GraphQL/Ariadne/allOther", 2),
] + _test_mutation_scoped_metrics

_expected_mutation_operation_attributes = {
Expand Down Expand Up @@ -180,8 +180,8 @@ def _test():
@dt_enabled
def test_middleware(app, graphql_run, is_graphql_2):
_test_middleware_metrics = [
("GraphQL/operation/GraphQL/query/<anonymous>/hello", 1),
("GraphQL/resolve/GraphQL/hello", 1),
("GraphQL/operation/Ariadne/query/<anonymous>/hello", 1),
("GraphQL/resolve/Ariadne/hello", 1),
("Function/test_application:example_middleware", 1),
]

Expand Down Expand Up @@ -212,8 +212,8 @@ def test_exception_in_middleware(app, graphql_run):

# Metrics
_test_exception_scoped_metrics = [
("GraphQL/operation/GraphQL/query/MyQuery/%s" % field, 1),
("GraphQL/resolve/GraphQL/%s" % field, 1),
("GraphQL/operation/Ariadne/query/MyQuery/%s" % field, 1),
("GraphQL/resolve/Ariadne/%s" % field, 1),
]
_test_exception_rollup_metrics = [
("Errors/all", 1),
Expand Down Expand Up @@ -262,8 +262,8 @@ def test_exception_in_resolver(app, graphql_run, field):

# Metrics
_test_exception_scoped_metrics = [
("GraphQL/operation/GraphQL/query/MyQuery/%s" % field, 1),
("GraphQL/resolve/GraphQL/%s" % field, 1),
("GraphQL/operation/Ariadne/query/MyQuery/%s" % field, 1),
("GraphQL/resolve/Ariadne/%s" % field, 1),
]
_test_exception_rollup_metrics = [
("Errors/all", 1),
Expand Down Expand Up @@ -326,7 +326,7 @@ def test_exception_in_validation(app, graphql_run, is_graphql_2, query, exc_clas
exc_class = callable_name(GraphQLError)

_test_exception_scoped_metrics = [
# ('GraphQL/operation/GraphQL/<unknown>/<anonymous>/<unknown>', 1),
('GraphQL/operation/Ariadne/<unknown>/<anonymous>/<unknown>', 1),
]
_test_exception_rollup_metrics = [
("Errors/all", 1),
Expand Down Expand Up @@ -360,7 +360,7 @@ def _test():

@dt_enabled
def test_operation_metrics_and_attrs(app, graphql_run):
operation_metrics = [("GraphQL/operation/GraphQL/query/MyQuery/library", 1)]
operation_metrics = [("GraphQL/operation/Ariadne/query/MyQuery/library", 1)]
operation_attrs = {
"graphql.operation.type": "query",
"graphql.operation.name": "MyQuery",
Expand Down Expand Up @@ -388,7 +388,7 @@ def _test():

@dt_enabled
def test_field_resolver_metrics_and_attrs(app, graphql_run):
field_resolver_metrics = [("GraphQL/resolve/GraphQL/hello", 1)]
field_resolver_metrics = [("GraphQL/resolve/Ariadne/hello", 1)]
graphql_attrs = {
"graphql.field.name": "hello",
"graphql.field.parentType": "Query",
Expand Down
12 changes: 6 additions & 6 deletions tests/framework_ariadne/test_application_async.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,17 @@ def test_query_and_mutation_async(app, graphql_run_async):
("Python/Framework/GraphQL/%s" % version, 1),
]
_test_mutation_scoped_metrics = [
("GraphQL/resolve/GraphQL/storage", 1),
("GraphQL/resolve/GraphQL/storage_add", 1),
("GraphQL/operation/GraphQL/query/<anonymous>/storage", 1),
("GraphQL/operation/GraphQL/mutation/<anonymous>/storage_add.string", 1),
("GraphQL/resolve/Ariadne/storage", 1),
("GraphQL/resolve/Ariadne/storage_add", 1),
("GraphQL/operation/Ariadne/query/<anonymous>/storage", 1),
("GraphQL/operation/Ariadne/mutation/<anonymous>/storage_add.string", 1),
]
_test_mutation_unscoped_metrics = [
("OtherTransaction/all", 1),
("GraphQL/all", 2),
("GraphQL/GraphQL/all", 2),
("GraphQL/Ariadne/all", 2),
("GraphQL/allOther", 2),
("GraphQL/GraphQL/allOther", 2),
("GraphQL/Ariadne/allOther", 2),
] + _test_mutation_scoped_metrics

_expected_mutation_operation_attributes = {
Expand Down
12 changes: 6 additions & 6 deletions tests/framework_ariadne/test_asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,19 +30,19 @@ def test_query_and_mutation_asgi(graphql_asgi_run):
("Python/Framework/GraphQL/%s" % version, 1),
]
_test_mutation_scoped_metrics = [
("GraphQL/resolve/GraphQL/storage_add", 1),
("GraphQL/operation/GraphQL/mutation/<anonymous>/storage_add.string", 1),
("GraphQL/resolve/Ariadne/storage_add", 1),
("GraphQL/operation/Ariadne/mutation/<anonymous>/storage_add.string", 1),
]
_test_query_scoped_metrics = [
("GraphQL/resolve/GraphQL/storage", 1),
("GraphQL/operation/GraphQL/query/<anonymous>/storage", 1),
("GraphQL/resolve/Ariadne/storage", 1),
("GraphQL/operation/Ariadne/query/<anonymous>/storage", 1),
]
_test_unscoped_metrics = [
("WebTransaction", 1),
("GraphQL/all", 1),
("GraphQL/GraphQL/all", 1),
("GraphQL/Ariadne/all", 1),
("GraphQL/allWeb", 1),
("GraphQL/GraphQL/allWeb", 1),
("GraphQL/Ariadne/allWeb", 1),
]
_test_mutation_unscoped_metrics = _test_unscoped_metrics + _test_mutation_scoped_metrics
_test_query_unscoped_metrics = _test_unscoped_metrics + _test_query_scoped_metrics
Expand Down
12 changes: 6 additions & 6 deletions tests/framework_ariadne/test_wsgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,20 @@ def test_query_and_mutation_wsgi(graphql_wsgi_run):
("Python/Framework/GraphQL/%s" % version, 1),
]
_test_mutation_scoped_metrics = [
("GraphQL/resolve/GraphQL/storage_add", 1),
("GraphQL/operation/GraphQL/mutation/<anonymous>/storage_add.string", 1),
("GraphQL/resolve/Ariadne/storage_add", 1),
("GraphQL/operation/Ariadne/mutation/<anonymous>/storage_add.string", 1),
]
_test_query_scoped_metrics = [
("GraphQL/resolve/GraphQL/storage", 1),
("GraphQL/operation/GraphQL/query/<anonymous>/storage", 1),
("GraphQL/resolve/Ariadne/storage", 1),
("GraphQL/operation/Ariadne/query/<anonymous>/storage", 1),
]
_test_unscoped_metrics = [
("WebTransaction", 1),
("Python/WSGI/Response", 1),
("GraphQL/all", 1),
("GraphQL/GraphQL/all", 1),
("GraphQL/Ariadne/all", 1),
("GraphQL/allWeb", 1),
("GraphQL/GraphQL/allWeb", 1),
("GraphQL/Ariadne/allWeb", 1),
]
_test_mutation_unscoped_metrics = _test_unscoped_metrics + _test_mutation_scoped_metrics
_test_query_unscoped_metrics = _test_unscoped_metrics + _test_query_scoped_metrics
Expand Down
Loading

0 comments on commit a1bf688

Please sign in to comment.