-
Notifications
You must be signed in to change notification settings - Fork 152
/
Copy path_godot_profiling.pxi
196 lines (161 loc) Β· 6.5 KB
/
_godot_profiling.pxi
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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
# cython: c_string_type=unicode, c_string_encoding=utf8
from godot._hazmat.gdnative_api_struct cimport (
godot_pluginscript_language_data,
godot_pluginscript_profiling_data,
)
from godot._hazmat.gdapi cimport pythonscript_gdapi10 as gdapi10
import sys
from collections import defaultdict
from time import perf_counter
# TODO: should be greatly improved by using cdef struct and godot_string_name everywhere
class MethProfile:
__slots__ = (
"call_count",
"self_time",
"total_time",
"cur_frame_call_count",
"cur_frame_self_time",
"cur_frame_total_time",
"last_frame_call_count",
"last_frame_self_time",
"last_frame_total_time",
)
def __init__(self):
self.call_count = 0
self.self_time = 0
self.total_time = 0
self.cur_frame_call_count = 0
self.cur_frame_self_time = 0
self.cur_frame_total_time = 0
self.last_frame_call_count = 0
self.last_frame_self_time = 0
self.last_frame_total_time = 0
class FuncCallProfile:
__slots__ = ("signature", "start", "end", "out_of_func_time")
def __init__(self, signature):
self.signature = signature
self.start = perf_counter()
self.end = None
# Time spend calling another function
self.out_of_func_time = 0
def add_out_of_func(self, time):
self.out_of_func_time += time
def get_self_time(self):
return self.get_total_time() - self.out_of_func_time
def done(self):
self.end = perf_counter()
def get_total_time(self):
return self.end - self.start
class Profiler:
def __init__(self):
self.per_meth_profiling = defaultdict(MethProfile)
self._profile_stack = []
@property
def _per_thread_profile_stack(self):
return self._profile_stack
# TODO: Make this thread safe
# Not sure if multithreading is supported by sys.setprofile anyway...
# loc = threading.local()
# key = 'profile_stack_%s' % id(self)
# stack = getattr(loc, key, None)
# if not stack:
# stack = []
# setattr(loc, key, stack)
# return stack
def next_frame(self):
for meth_profile in self.per_meth_profiling.values():
meth_profile.call_count = meth_profile.cur_frame_call_count
meth_profile.self_time = meth_profile.cur_frame_self_time
meth_profile.total_time = meth_profile.cur_frame_total_time
meth_profile.last_frame_call_count = meth_profile.cur_frame_call_count
meth_profile.last_frame_self_time = meth_profile.cur_frame_self_time
meth_profile.last_frame_total_time = meth_profile.cur_frame_total_time
meth_profile.cur_frame_call_count = 0
meth_profile.cur_frame_self_time = 0
meth_profile.cur_frame_total_time = 0
def get_profilefunc(self):
def profilefunc(frame, event, arg):
# TODO: improve this hack to avoid profiling builtins functions
if frame.f_code.co_filename.startswith("<"):
return
if event in ("call", "c_call"):
# TODO generate signature ahead of time and store it into the object
signature = "{path}::{line}::{name}".format(
path=frame.f_code.co_filename,
line=frame.f_lineno,
name=frame.f_code.co_name,
)
self.per_meth_profiling[signature].cur_frame_call_count += 1
self._per_thread_profile_stack.append(FuncCallProfile(signature))
else:
try:
callprof = self._per_thread_profile_stack.pop()
except IndexError:
# `pybind_profiling_start` has been called before the
# profiler was enable, so _per_thread_profile_stack lacks
# it representation
return
callprof.done()
signature = callprof.signature
prof = self.per_meth_profiling[signature]
prof.cur_frame_total_time += callprof.get_total_time()
prof.cur_frame_self_time += callprof.get_self_time()
if self._per_thread_profile_stack:
self._per_thread_profile_stack[-1].add_out_of_func(
callprof.get_total_time()
)
return profilefunc
cdef object profiler = None
cdef api void pythonscript_profiling_start(
godot_pluginscript_language_data *p_data
) with gil:
global profiler
profiler = Profiler()
sys.setprofile(profiler.get_profilefunc())
cdef api void pythonscript_profiling_stop(
godot_pluginscript_language_data *p_data
) with gil:
global profiler
profiler = None
sys.setprofile(None)
cdef api int pythonscript_profiling_get_accumulated_data(
godot_pluginscript_language_data *p_data,
godot_pluginscript_profiling_data *r_info,
int p_info_max
) with gil:
# Sort function to make sure we can display the most consuming ones
sorted_and_limited = sorted(
profiler.per_meth_profiling.items(), key=lambda x: -x[1].self_time
)[:p_info_max]
cdef int i
cdef object signature
cdef object profile
for i, (signature, profile) in enumerate(sorted_and_limited):
pyobj_to_godot_string_name(signature, &r_info[i].signature)
r_info[i].call_count = profile.call_count
r_info[i].total_time = int(profile.total_time * 1e6)
r_info[i].self_time = int(profile.self_time * 1e6)
return len(sorted_and_limited)
cdef api int pythonscript_profiling_get_frame_data(
godot_pluginscript_language_data *p_data,
godot_pluginscript_profiling_data *r_info,
int p_info_max
) with gil:
# Sort function to make sure we can display the most consuming ones
sorted_and_limited = sorted(
profiler.per_meth_profiling.items(), key=lambda x: -x[1].last_frame_self_time
)[:p_info_max]
cdef int i
cdef object signature
cdef object profile
for i, (signature, profile) in enumerate(sorted_and_limited):
pyobj_to_godot_string_name(signature, &r_info[i].signature)
r_info[i].call_count = profile.last_frame_call_count
r_info[i].total_time = int(profile.last_frame_total_time * 1e6)
r_info[i].self_time = int(profile.last_frame_self_time * 1e6)
return len(sorted_and_limited)
cdef api void pythonscript_profiling_frame(
godot_pluginscript_language_data *p_data
) with gil:
if profiler is not None:
profiler.next_frame()