Skip to content

Commit 1e43d4f

Browse files
GraphQL Operation Metrics and Attrs (#276)
* Working on graphql operation testing * Working on graphql paths Co-authored-by: Uma Annamalai <[email protected]> * Ignore duplicate exceptions * Finalize split of resolver and operation traces * Clean up failing tests * Clean up attribute addition code * Fix bug in deepest path naming * Fix graphql 2 tests * Fix python2 copy bug Co-authored-by: Uma Annamalai <[email protected]>
1 parent 5f245de commit 1e43d4f

File tree

5 files changed

+295
-110
lines changed

5 files changed

+295
-110
lines changed

newrelic/api/graphql_trace.py

Lines changed: 61 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,32 +13,38 @@
1313
# limitations under the License.
1414

1515
import functools
16-
import logging
1716

1817
from newrelic.common.async_wrapper import async_wrapper
1918
from newrelic.api.time_trace import TimeTrace, current_trace
2019
from newrelic.common.object_wrapper import FunctionWrapper, wrap_object
21-
from newrelic.core.graphql_node import GraphQLNode
22-
from newrelic.core.stack_trace import current_stack
20+
from newrelic.core.graphql_node import GraphQLOperationNode, GraphQLResolverNode
2321

24-
_logger = logging.getLogger(__name__)
25-
26-
27-
class GraphQLTrace(TimeTrace):
22+
class GraphQLOperationTrace(TimeTrace):
2823
def __init__(self, **kwargs):
2924
parent = None
3025
if kwargs:
3126
if len(kwargs) > 1:
3227
raise TypeError("Invalid keyword arguments:", kwargs)
3328
parent = kwargs['parent']
34-
super(GraphQLTrace, self).__init__(parent)
29+
super(GraphQLOperationTrace, self).__init__(parent)
30+
31+
self.operation_name = None
32+
self.operation_type = None
33+
self.deepest_path = None
3534

3635
def __repr__(self):
3736
return '<%s %s>' % (self.__class__.__name__, dict())
3837

38+
def finalize_data(self, *args, **kwargs):
39+
self._add_agent_attribute("graphql.operation.type", self.operation_type)
40+
self._add_agent_attribute("graphql.operation.name", self.operation_name)
41+
self._add_agent_attribute("graphql.operation.deepestPath", self.deepest_path)
42+
43+
return super(GraphQLOperationTrace, self).finalize_data(*args, **kwargs)
44+
3945

4046
def create_node(self):
41-
return GraphQLNode(
47+
return GraphQLOperationNode(
4248
children=self.children,
4349
start_time=self.start_time,
4450
end_time=self.end_time,
@@ -47,23 +53,54 @@ def create_node(self):
4753
guid=self.guid,
4854
agent_attributes=self.agent_attributes,
4955
user_attributes=self.user_attributes,
56+
operation_name=self.operation_name,
57+
operation_type=self.operation_type,
58+
deepest_path=self.deepest_path,
5059
)
5160

5261

53-
class GraphQLResolverTrace(TimeTrace):
54-
def __init__(self, field_name=None, **kwargs):
55-
parent = None
56-
if kwargs:
57-
if len(kwargs) > 1:
58-
raise TypeError("Invalid keyword arguments:", kwargs)
59-
parent = kwargs['parent']
60-
super(GraphQLResolverTrace, self).__init__(parent)
62+
def GraphQLOperationTraceWrapper(wrapped):
63+
def _nr_graphql_trace_wrapper_(wrapped, instance, args, kwargs):
64+
wrapper = async_wrapper(wrapped)
65+
if not wrapper:
66+
parent = current_trace()
67+
if not parent:
68+
return wrapped(*args, **kwargs)
69+
else:
70+
parent = None
71+
72+
trace = GraphQLOperationTrace(parent=parent)
73+
74+
if wrapper:
75+
return wrapper(wrapped, trace)(*args, **kwargs)
76+
77+
with trace:
78+
return wrapped(*args, **kwargs)
79+
80+
return FunctionWrapper(wrapped, _nr_graphql_trace_wrapper_)
81+
82+
83+
def graphql_operation_trace():
84+
return functools.partial(GraphQLOperationTraceWrapper)
85+
86+
def wrap_graphql_operation_trace(module, object_path):
87+
wrap_object(module, object_path, GraphQLOperationTraceWrapper)
88+
6189

90+
class GraphQLResolverTrace(GraphQLOperationTrace):
91+
def __init__(self, field_name=None, **kwargs):
92+
super(GraphQLResolverTrace, self).__init__(**kwargs)
6293
self.field_name = field_name
6394

6495

96+
def finalize_data(self, *args, **kwargs):
97+
self._add_agent_attribute("graphql.field.name", self.field_name)
98+
99+
return super(GraphQLResolverTrace, self).finalize_data(*args, **kwargs)
100+
101+
65102
def create_node(self):
66-
return GraphQLNode(
103+
return GraphQLResolverNode(
67104
field_name=self.field_name,
68105
children=self.children,
69106
start_time=self.start_time,
@@ -76,7 +113,7 @@ def create_node(self):
76113
)
77114

78115

79-
def GraphQLTraceWrapper(wrapped):
116+
def GraphQLResolverTraceWrapper(wrapped):
80117
def _nr_graphql_trace_wrapper_(wrapped, instance, args, kwargs):
81118
wrapper = async_wrapper(wrapped)
82119
if not wrapper:
@@ -86,7 +123,7 @@ def _nr_graphql_trace_wrapper_(wrapped, instance, args, kwargs):
86123
else:
87124
parent = None
88125

89-
trace = GraphQLTrace(parent=parent)
126+
trace = GraphQLResolverTrace(parent=parent)
90127

91128
if wrapper:
92129
return wrapper(wrapped, trace)(*args, **kwargs)
@@ -97,8 +134,8 @@ def _nr_graphql_trace_wrapper_(wrapped, instance, args, kwargs):
97134
return FunctionWrapper(wrapped, _nr_graphql_trace_wrapper_)
98135

99136

100-
def graphql_trace():
101-
return functools.partial(GraphQLTraceWrapper)
137+
def graphql_resolver_trace():
138+
return functools.partial(GraphQLResolverTraceWrapper)
102139

103-
def wrap_graphql_trace(module, object_path):
104-
wrap_object(module, object_path, GraphQLTraceWrapper)
140+
def wrap_graphql_resolver_trace(module, object_path):
141+
wrap_object(module, object_path, GraphQLResolverTraceWrapper)

newrelic/core/attribute.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,11 @@
7474
'peer.address',
7575
'graphql.field.name',
7676
'graphql.field.parentType',
77-
'graphql.field.path'
77+
'graphql.field.path',
78+
'graphql.operation.name',
79+
'graphql.operation.type',
80+
'graphql.operation.deepestPath',
81+
'graphql.operation.query',
7882
))
7983

8084
MAX_NUM_USER_ATTRIBUTES = 128

newrelic/core/graphql_node.py

Lines changed: 85 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -14,52 +14,117 @@
1414

1515
from collections import namedtuple
1616

17-
import newrelic.core.attribute as attribute
1817
import newrelic.core.trace_node
1918

20-
from newrelic.common import system_info
2119
from newrelic.core.metric import TimeMetric
2220

2321
from newrelic.core.node_mixin import GenericNodeMixin
2422

2523

26-
_GraphQLNode = namedtuple('_GraphQLNode',
27-
['field_name', 'children', 'start_time', 'end_time',
28-
'duration', 'exclusive', 'guid', 'agent_attributes',
29-
'user_attributes'])
24+
_GraphQLOperationNode = namedtuple('_GraphQLNode',
25+
['operation_type', 'operation_name', 'deepest_path',
26+
'children', 'start_time', 'end_time', 'duration', 'exclusive',
27+
'guid', 'agent_attributes', 'user_attributes'])
3028

29+
_GraphQLResolverNode = namedtuple('_GraphQLNode',
30+
['field_name', 'children', 'start_time', 'end_time', 'duration',
31+
'exclusive', 'guid', 'agent_attributes', 'user_attributes'])
3132

32-
class GraphQLNode(_GraphQLNode, GenericNodeMixin):
33-
33+
class GraphQLNodeMixin(GenericNodeMixin):
3434
@property
3535
def product(self):
3636
return "GraphQL"
3737

38+
def trace_node(self, stats, root, connections):
39+
name = root.string_table.cache(self.name)
40+
41+
start_time = newrelic.core.trace_node.node_start_time(root, self)
42+
end_time = newrelic.core.trace_node.node_end_time(root, self)
43+
44+
root.trace_node_count += 1
45+
46+
children = []
47+
48+
# Now for the children
49+
50+
for child in self.children:
51+
if root.trace_node_count > root.trace_node_limit:
52+
break
53+
children.append(child.trace_node(stats, root, connections))
54+
55+
# Agent attributes
56+
params = self.get_trace_segment_params(root.settings)
57+
58+
return newrelic.core.trace_node.TraceNode(start_time=start_time,
59+
end_time=end_time, name=name, params=params, children=children,
60+
label=None)
61+
62+
class GraphQLResolverNode(_GraphQLResolverNode, GraphQLNodeMixin):
3863
@property
39-
def operation(self):
40-
return "select"
64+
def name(self):
65+
field_name = self.field_name or "<unknown>"
66+
product = self.product
4167

68+
name = 'GraphQL/resolve/%s/%s' % (product, field_name)
69+
70+
return name
4271

4372
def time_metrics(self, stats, root, parent):
4473
"""Return a generator yielding the timed metrics for this
4574
database node as well as all the child nodes.
4675
"""
4776

48-
field_name = self.field_name
77+
field_name = self.field_name or "<unknown>"
4978
product = self.product
50-
operation = self.operation or 'other'
5179

5280
# Determine the scoped metric
53-
operation_metric_name = 'GraphQL/operation/%s/%s' % (product,
54-
operation)
5581

5682
field_resolver_metric_name = 'GraphQL/resolve/%s/%s' % (product, field_name)
5783

58-
scoped_metric_name = operation_metric_name
59-
6084
yield TimeMetric(name=field_resolver_metric_name, scope=root.path, duration=self.duration,
6185
exclusive=self.exclusive)
6286

87+
yield TimeMetric(name=field_resolver_metric_name, scope='', duration=self.duration,
88+
exclusive=self.exclusive)
89+
90+
# Now for the children
91+
92+
for child in self.children:
93+
for metric in child.time_metrics(stats, root, self):
94+
yield metric
95+
96+
97+
class GraphQLOperationNode(_GraphQLOperationNode, GraphQLNodeMixin):
98+
@property
99+
def name(self):
100+
operation_type = self.operation_type or "<unknown>"
101+
operation_name = self.operation_name or "<anonymous>"
102+
deepest_path = self.deepest_path or "<unknown>"
103+
product = self.product
104+
105+
name = 'GraphQL/operation/%s/%s/%s/%s' % (product, operation_type,
106+
operation_name, deepest_path)
107+
108+
return name
109+
110+
def time_metrics(self, stats, root, parent):
111+
"""Return a generator yielding the timed metrics for this
112+
database node as well as all the child nodes.
113+
114+
"""
115+
116+
operation_type = self.operation_type or "<unknown>"
117+
operation_name = self.operation_name or "<anonymous>"
118+
deepest_path = self.deepest_path or "<unknown>"
119+
product = self.product
120+
121+
# Determine the scoped metric
122+
123+
operation_metric_name = 'GraphQL/operation/%s/%s/%s/%s' % (product,
124+
operation_type, operation_name, deepest_path)
125+
126+
scoped_metric_name = operation_metric_name
127+
63128
yield TimeMetric(name=scoped_metric_name, scope=root.path,
64129
duration=self.duration, exclusive=self.exclusive)
65130

@@ -89,37 +154,8 @@ def time_metrics(self, stats, root, parent):
89154
yield TimeMetric(name=operation_metric_name, scope='',
90155
duration=self.duration, exclusive=self.exclusive)
91156

92-
yield TimeMetric(name=field_resolver_metric_name, scope='', duration=self.duration,
93-
exclusive=self.exclusive)
94-
95-
96-
def trace_node(self, stats, root, connections):
97-
name = root.string_table.cache(self.name)
98-
99-
start_time = newrelic.core.trace_node.node_start_time(root, self)
100-
end_time = newrelic.core.trace_node.node_end_time(root, self)
101-
102-
root.trace_node_count += 1
103-
104-
children = []
105-
106-
# Agent attributes
107-
params = self.get_trace_segment_params(root.settings)
108-
109-
return newrelic.core.trace_node.TraceNode(start_time=start_time,
110-
end_time=end_time, name=name, params=params, children=children,
111-
label=None)
112-
113-
@property
114-
def name(self):
115-
product = self.product
116-
target = "test"
117-
operation = self.operation or 'other'
118-
119-
if target:
120-
name = 'GraphQL/statement/%s/%s/%s' % (product, target,
121-
operation)
122-
else:
123-
name = 'GraphQL/operation/%s/%s' % (product, operation)
157+
# Now for the children
124158

125-
return name
159+
for child in self.children:
160+
for metric in child.time_metrics(stats, root, self):
161+
yield metric

0 commit comments

Comments
 (0)