/
code_level_metrics.py
135 lines (113 loc) · 4.16 KB
/
code_level_metrics.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# Copyright 2010 New Relic, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import functools
import inspect
from collections import namedtuple
from newrelic.common.object_names import object_context
_CodeLevelMetricsNode = namedtuple(
"CodeLevelMetricsNode",
[
"filepath",
"function",
"lineno",
"namespace",
],
)
class CodeLevelMetricsNode(_CodeLevelMetricsNode):
def add_attrs(self, add_attr_function):
# Add attributes
for k, v in self._asdict().items():
if v is not None:
add_attr_function("code.%s" % k, v)
def extract_code_from_callable(func):
"""Extract source code context from a callable and add appropriate attributes."""
original_func = func # Save original reference
if hasattr(func, "_nr_source_code"):
return func._nr_source_code
# Fully unwrap object
while (hasattr(func, "__wrapped__") and func.__wrapped__ is not None) or isinstance(func, functools.partial):
# Remove Partials
if isinstance(func, functools.partial):
func = func.func
# Unwrap wrapped objects
else:
if func.__wrapped__ == func:
# Infinite loop protection
break
func = func.__wrapped__
# Retrieve basic object details
module_name, func_path = object_context(func)
if inspect.isbuiltin(func):
# Set attributes for builtins
file_path = "<builtin>"
line_number = None
elif hasattr(func, "__code__"):
# Extract details from function __code__ attr
co = func.__code__
line_number = co.co_firstlineno
file_path = co.co_filename
else:
# Extract call method for callable objects
if inspect.isclass(func):
# For class types don't change anything
pass
elif hasattr(func, "__call__"):
# For callable object, use the __call__ attribute
func = func.__call__
module_name, func_path = object_context(func)
elif hasattr(func, "__class__"):
# Extract class from object instances
func = func.__class__
# Initialize here instead of in except to potentially get file_path but not line_number
file_path = None
line_number = None
try:
# Use inspect to get file and line number
file_path = inspect.getsourcefile(func)
line_number = inspect.getsourcelines(func)[1]
except Exception:
pass
# Split function path to extract class name
func_path = func_path.split(".")
func_name = func_path[-1] # function name is last in path
if len(func_path) > 1:
class_name = ".".join((func_path[:-1]))
namespace = ".".join((module_name, class_name))
else:
namespace = module_name
node = CodeLevelMetricsNode(
filepath=file_path,
function=func_name,
lineno=line_number,
namespace=namespace,
)
try:
if hasattr(original_func, "__func__"):
# Must store on underlying function not bound method
original_func = original_func.__func__
original_func._nr_source_code = node
except Exception: # Don't raise exceptions for any reason
pass
return node
def extract_code_from_traceback(tb):
# Walk traceback
while getattr(tb, "tb_next", None) is not None:
tb = tb.tb_next
frame = tb.tb_frame
code = frame.f_code
# Extract exception source code
lineno = tb.tb_lineno
filepath = code.co_filename
funcname = code.co_name
return CodeLevelMetricsNode(filepath=filepath, lineno=lineno, function=funcname, namespace=None)