1 | /**************************************************************************/ |
2 | /* servers_debugger.cpp */ |
3 | /**************************************************************************/ |
4 | /* This file is part of: */ |
5 | /* GODOT ENGINE */ |
6 | /* https://godotengine.org */ |
7 | /**************************************************************************/ |
8 | /* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ |
9 | /* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ |
10 | /* */ |
11 | /* Permission is hereby granted, free of charge, to any person obtaining */ |
12 | /* a copy of this software and associated documentation files (the */ |
13 | /* "Software"), to deal in the Software without restriction, including */ |
14 | /* without limitation the rights to use, copy, modify, merge, publish, */ |
15 | /* distribute, sublicense, and/or sell copies of the Software, and to */ |
16 | /* permit persons to whom the Software is furnished to do so, subject to */ |
17 | /* the following conditions: */ |
18 | /* */ |
19 | /* The above copyright notice and this permission notice shall be */ |
20 | /* included in all copies or substantial portions of the Software. */ |
21 | /* */ |
22 | /* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ |
23 | /* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ |
24 | /* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ |
25 | /* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ |
26 | /* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ |
27 | /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ |
28 | /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ |
29 | /**************************************************************************/ |
30 | |
31 | #include "servers_debugger.h" |
32 | |
33 | #include "core/config/project_settings.h" |
34 | #include "core/debugger/engine_debugger.h" |
35 | #include "core/debugger/engine_profiler.h" |
36 | #include "core/io/marshalls.h" |
37 | #include "servers/display_server.h" |
38 | |
39 | #define CHECK_SIZE(arr, expected, what) ERR_FAIL_COND_V_MSG((uint32_t)arr.size() < (uint32_t)(expected), false, String("Malformed ") + what + " message from script debugger, message too short. Expected size: " + itos(expected) + ", actual size: " + itos(arr.size())) |
40 | #define CHECK_END(arr, expected, what) ERR_FAIL_COND_V_MSG((uint32_t)arr.size() > (uint32_t)expected, false, String("Malformed ") + what + " message from script debugger, message too long. Expected size: " + itos(expected) + ", actual size: " + itos(arr.size())) |
41 | |
42 | Array ServersDebugger::ResourceUsage::serialize() { |
43 | infos.sort(); |
44 | |
45 | Array arr; |
46 | arr.push_back(infos.size() * 4); |
47 | for (const ResourceInfo &E : infos) { |
48 | arr.push_back(E.path); |
49 | arr.push_back(E.format); |
50 | arr.push_back(E.type); |
51 | arr.push_back(E.vram); |
52 | } |
53 | return arr; |
54 | } |
55 | |
56 | bool ServersDebugger::ResourceUsage::deserialize(const Array &p_arr) { |
57 | CHECK_SIZE(p_arr, 1, "ResourceUsage" ); |
58 | uint32_t size = p_arr[0]; |
59 | ERR_FAIL_COND_V(size % 4, false); |
60 | CHECK_SIZE(p_arr, 1 + size, "ResourceUsage" ); |
61 | uint32_t idx = 1; |
62 | while (idx < 1 + size) { |
63 | ResourceInfo info; |
64 | info.path = p_arr[idx]; |
65 | info.format = p_arr[idx + 1]; |
66 | info.type = p_arr[idx + 2]; |
67 | info.vram = p_arr[idx + 3]; |
68 | infos.push_back(info); |
69 | idx += 4; |
70 | } |
71 | CHECK_END(p_arr, idx, "ResourceUsage" ); |
72 | return true; |
73 | } |
74 | |
75 | Array ServersDebugger::ScriptFunctionSignature::serialize() { |
76 | Array arr; |
77 | arr.push_back(name); |
78 | arr.push_back(id); |
79 | return arr; |
80 | } |
81 | |
82 | bool ServersDebugger::ScriptFunctionSignature::deserialize(const Array &p_arr) { |
83 | CHECK_SIZE(p_arr, 2, "ScriptFunctionSignature" ); |
84 | name = p_arr[0]; |
85 | id = p_arr[1]; |
86 | CHECK_END(p_arr, 2, "ScriptFunctionSignature" ); |
87 | return true; |
88 | } |
89 | |
90 | Array ServersDebugger::ServersProfilerFrame::serialize() { |
91 | Array arr; |
92 | arr.push_back(frame_number); |
93 | arr.push_back(frame_time); |
94 | arr.push_back(process_time); |
95 | arr.push_back(physics_time); |
96 | arr.push_back(physics_frame_time); |
97 | arr.push_back(script_time); |
98 | |
99 | arr.push_back(servers.size()); |
100 | for (int i = 0; i < servers.size(); i++) { |
101 | ServerInfo &s = servers[i]; |
102 | arr.push_back(s.name); |
103 | arr.push_back(s.functions.size() * 2); |
104 | for (int j = 0; j < s.functions.size(); j++) { |
105 | ServerFunctionInfo &f = s.functions[j]; |
106 | arr.push_back(f.name); |
107 | arr.push_back(f.time); |
108 | } |
109 | } |
110 | |
111 | arr.push_back(script_functions.size() * 4); |
112 | for (int i = 0; i < script_functions.size(); i++) { |
113 | arr.push_back(script_functions[i].sig_id); |
114 | arr.push_back(script_functions[i].call_count); |
115 | arr.push_back(script_functions[i].self_time); |
116 | arr.push_back(script_functions[i].total_time); |
117 | } |
118 | return arr; |
119 | } |
120 | |
121 | bool ServersDebugger::ServersProfilerFrame::deserialize(const Array &p_arr) { |
122 | CHECK_SIZE(p_arr, 7, "ServersProfilerFrame" ); |
123 | frame_number = p_arr[0]; |
124 | frame_time = p_arr[1]; |
125 | process_time = p_arr[2]; |
126 | physics_time = p_arr[3]; |
127 | physics_frame_time = p_arr[4]; |
128 | script_time = p_arr[5]; |
129 | int servers_size = p_arr[6]; |
130 | int idx = 7; |
131 | while (servers_size) { |
132 | CHECK_SIZE(p_arr, idx + 2, "ServersProfilerFrame" ); |
133 | servers_size--; |
134 | ServerInfo si; |
135 | si.name = p_arr[idx]; |
136 | int sub_data_size = p_arr[idx + 1]; |
137 | idx += 2; |
138 | CHECK_SIZE(p_arr, idx + sub_data_size, "ServersProfilerFrame" ); |
139 | for (int j = 0; j < sub_data_size / 2; j++) { |
140 | ServerFunctionInfo sf; |
141 | sf.name = p_arr[idx]; |
142 | sf.time = p_arr[idx + 1]; |
143 | idx += 2; |
144 | si.functions.push_back(sf); |
145 | } |
146 | servers.push_back(si); |
147 | } |
148 | CHECK_SIZE(p_arr, idx + 1, "ServersProfilerFrame" ); |
149 | int func_size = p_arr[idx]; |
150 | idx += 1; |
151 | CHECK_SIZE(p_arr, idx + func_size, "ServersProfilerFrame" ); |
152 | for (int i = 0; i < func_size / 4; i++) { |
153 | ScriptFunctionInfo fi; |
154 | fi.sig_id = p_arr[idx]; |
155 | fi.call_count = p_arr[idx + 1]; |
156 | fi.self_time = p_arr[idx + 2]; |
157 | fi.total_time = p_arr[idx + 3]; |
158 | script_functions.push_back(fi); |
159 | idx += 4; |
160 | } |
161 | CHECK_END(p_arr, idx, "ServersProfilerFrame" ); |
162 | return true; |
163 | } |
164 | |
165 | Array ServersDebugger::VisualProfilerFrame::serialize() { |
166 | Array arr; |
167 | arr.push_back(frame_number); |
168 | arr.push_back(areas.size() * 3); |
169 | for (int i = 0; i < areas.size(); i++) { |
170 | arr.push_back(areas[i].name); |
171 | arr.push_back(areas[i].cpu_msec); |
172 | arr.push_back(areas[i].gpu_msec); |
173 | } |
174 | return arr; |
175 | } |
176 | |
177 | bool ServersDebugger::VisualProfilerFrame::deserialize(const Array &p_arr) { |
178 | CHECK_SIZE(p_arr, 2, "VisualProfilerFrame" ); |
179 | frame_number = p_arr[0]; |
180 | int size = p_arr[1]; |
181 | CHECK_SIZE(p_arr, size, "VisualProfilerFrame" ); |
182 | int idx = 2; |
183 | areas.resize(size / 3); |
184 | RS::FrameProfileArea *w = areas.ptrw(); |
185 | for (int i = 0; i < size / 3; i++) { |
186 | w[i].name = p_arr[idx]; |
187 | w[i].cpu_msec = p_arr[idx + 1]; |
188 | w[i].gpu_msec = p_arr[idx + 2]; |
189 | idx += 3; |
190 | } |
191 | CHECK_END(p_arr, idx, "VisualProfilerFrame" ); |
192 | return true; |
193 | } |
194 | class ServersDebugger::ScriptsProfiler : public EngineProfiler { |
195 | typedef ServersDebugger::ScriptFunctionSignature FunctionSignature; |
196 | typedef ServersDebugger::ScriptFunctionInfo FunctionInfo; |
197 | struct ProfileInfoSort { |
198 | bool operator()(ScriptLanguage::ProfilingInfo *A, ScriptLanguage::ProfilingInfo *B) const { |
199 | return A->total_time < B->total_time; |
200 | } |
201 | }; |
202 | Vector<ScriptLanguage::ProfilingInfo> info; |
203 | Vector<ScriptLanguage::ProfilingInfo *> ptrs; |
204 | HashMap<StringName, int> sig_map; |
205 | int max_frame_functions = 16; |
206 | |
207 | public: |
208 | void toggle(bool p_enable, const Array &p_opts) { |
209 | if (p_enable) { |
210 | sig_map.clear(); |
211 | for (int i = 0; i < ScriptServer::get_language_count(); i++) { |
212 | ScriptServer::get_language(i)->profiling_start(); |
213 | } |
214 | if (p_opts.size() == 1 && p_opts[0].get_type() == Variant::INT) { |
215 | max_frame_functions = MAX(0, int(p_opts[0])); |
216 | } |
217 | } else { |
218 | for (int i = 0; i < ScriptServer::get_language_count(); i++) { |
219 | ScriptServer::get_language(i)->profiling_stop(); |
220 | } |
221 | } |
222 | } |
223 | |
224 | void write_frame_data(Vector<FunctionInfo> &r_funcs, uint64_t &r_total, bool p_accumulated) { |
225 | int ofs = 0; |
226 | for (int i = 0; i < ScriptServer::get_language_count(); i++) { |
227 | if (p_accumulated) { |
228 | ofs += ScriptServer::get_language(i)->profiling_get_accumulated_data(&info.write[ofs], info.size() - ofs); |
229 | } else { |
230 | ofs += ScriptServer::get_language(i)->profiling_get_frame_data(&info.write[ofs], info.size() - ofs); |
231 | } |
232 | } |
233 | |
234 | for (int i = 0; i < ofs; i++) { |
235 | ptrs.write[i] = &info.write[i]; |
236 | } |
237 | |
238 | SortArray<ScriptLanguage::ProfilingInfo *, ProfileInfoSort> sa; |
239 | sa.sort(ptrs.ptrw(), ofs); |
240 | |
241 | int to_send = MIN(ofs, max_frame_functions); |
242 | |
243 | // Check signatures first, and compute total time. |
244 | r_total = 0; |
245 | for (int i = 0; i < to_send; i++) { |
246 | if (!sig_map.has(ptrs[i]->signature)) { |
247 | int idx = sig_map.size(); |
248 | FunctionSignature sig; |
249 | sig.name = ptrs[i]->signature; |
250 | sig.id = idx; |
251 | EngineDebugger::get_singleton()->send_message("servers:function_signature" , sig.serialize()); |
252 | sig_map[ptrs[i]->signature] = idx; |
253 | } |
254 | r_total += ptrs[i]->self_time; |
255 | } |
256 | |
257 | // Send frame, script time, functions information then |
258 | r_funcs.resize(to_send); |
259 | |
260 | FunctionInfo *w = r_funcs.ptrw(); |
261 | for (int i = 0; i < to_send; i++) { |
262 | if (sig_map.has(ptrs[i]->signature)) { |
263 | w[i].sig_id = sig_map[ptrs[i]->signature]; |
264 | } |
265 | w[i].call_count = ptrs[i]->call_count; |
266 | w[i].total_time = ptrs[i]->total_time / 1000000.0; |
267 | w[i].self_time = ptrs[i]->self_time / 1000000.0; |
268 | } |
269 | } |
270 | |
271 | ScriptsProfiler() { |
272 | info.resize(GLOBAL_GET("debug/settings/profiler/max_functions" )); |
273 | ptrs.resize(info.size()); |
274 | } |
275 | }; |
276 | |
277 | class ServersDebugger::ServersProfiler : public EngineProfiler { |
278 | bool skip_profile_frame = false; |
279 | typedef ServersDebugger::ServerInfo ServerInfo; |
280 | typedef ServersDebugger::ServerFunctionInfo ServerFunctionInfo; |
281 | |
282 | HashMap<StringName, ServerInfo> server_data; |
283 | ScriptsProfiler scripts_profiler; |
284 | |
285 | double frame_time = 0; |
286 | double process_time = 0; |
287 | double physics_time = 0; |
288 | double physics_frame_time = 0; |
289 | |
290 | void _send_frame_data(bool p_final) { |
291 | ServersDebugger::ServersProfilerFrame frame; |
292 | frame.frame_number = Engine::get_singleton()->get_process_frames(); |
293 | frame.frame_time = frame_time; |
294 | frame.process_time = process_time; |
295 | frame.physics_time = physics_time; |
296 | frame.physics_frame_time = physics_frame_time; |
297 | HashMap<StringName, ServerInfo>::Iterator E = server_data.begin(); |
298 | while (E) { |
299 | if (!p_final) { |
300 | frame.servers.push_back(E->value); |
301 | } |
302 | E->value.functions.clear(); |
303 | ++E; |
304 | } |
305 | uint64_t time = 0; |
306 | scripts_profiler.write_frame_data(frame.script_functions, time, p_final); |
307 | frame.script_time = USEC_TO_SEC(time); |
308 | if (skip_profile_frame) { |
309 | skip_profile_frame = false; |
310 | return; |
311 | } |
312 | if (p_final) { |
313 | EngineDebugger::get_singleton()->send_message("servers:profile_total" , frame.serialize()); |
314 | } else { |
315 | EngineDebugger::get_singleton()->send_message("servers:profile_frame" , frame.serialize()); |
316 | } |
317 | } |
318 | |
319 | public: |
320 | void toggle(bool p_enable, const Array &p_opts) { |
321 | skip_profile_frame = false; |
322 | if (p_enable) { |
323 | server_data.clear(); // Clear old profiling data. |
324 | } else { |
325 | _send_frame_data(true); // Send final frame. |
326 | } |
327 | scripts_profiler.toggle(p_enable, p_opts); |
328 | } |
329 | |
330 | void add(const Array &p_data) { |
331 | String name = p_data[0]; |
332 | if (!server_data.has(name)) { |
333 | ServerInfo info; |
334 | info.name = name; |
335 | server_data[name] = info; |
336 | } |
337 | ServerInfo &srv = server_data[name]; |
338 | |
339 | ServerFunctionInfo fi; |
340 | fi.name = p_data[1]; |
341 | fi.time = p_data[2]; |
342 | srv.functions.push_back(fi); |
343 | } |
344 | |
345 | void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) { |
346 | frame_time = p_frame_time; |
347 | process_time = p_process_time; |
348 | physics_time = p_physics_time; |
349 | physics_frame_time = p_physics_frame_time; |
350 | _send_frame_data(false); |
351 | } |
352 | |
353 | void skip_frame() { |
354 | skip_profile_frame = true; |
355 | } |
356 | }; |
357 | |
358 | class ServersDebugger::VisualProfiler : public EngineProfiler { |
359 | typedef ServersDebugger::ServerInfo ServerInfo; |
360 | typedef ServersDebugger::ServerFunctionInfo ServerFunctionInfo; |
361 | |
362 | HashMap<StringName, ServerInfo> server_data; |
363 | |
364 | public: |
365 | void toggle(bool p_enable, const Array &p_opts) { |
366 | RS::get_singleton()->set_frame_profiling_enabled(p_enable); |
367 | } |
368 | |
369 | void add(const Array &p_data) {} |
370 | |
371 | void tick(double p_frame_time, double p_process_time, double p_physics_time, double p_physics_frame_time) { |
372 | Vector<RS::FrameProfileArea> profile_areas = RS::get_singleton()->get_frame_profile(); |
373 | ServersDebugger::VisualProfilerFrame frame; |
374 | if (!profile_areas.size()) { |
375 | return; |
376 | } |
377 | |
378 | frame.frame_number = RS::get_singleton()->get_frame_profile_frame(); |
379 | frame.areas.append_array(profile_areas); |
380 | EngineDebugger::get_singleton()->send_message("visual:profile_frame" , frame.serialize()); |
381 | } |
382 | }; |
383 | |
384 | ServersDebugger *ServersDebugger::singleton = nullptr; |
385 | |
386 | void ServersDebugger::initialize() { |
387 | if (EngineDebugger::is_active()) { |
388 | memnew(ServersDebugger); |
389 | } |
390 | } |
391 | |
392 | void ServersDebugger::deinitialize() { |
393 | if (singleton) { |
394 | memdelete(singleton); |
395 | } |
396 | } |
397 | |
398 | Error ServersDebugger::_capture(void *p_user, const String &p_cmd, const Array &p_data, bool &r_captured) { |
399 | ERR_FAIL_COND_V(!singleton, ERR_BUG); |
400 | r_captured = true; |
401 | if (p_cmd == "memory" ) { |
402 | singleton->_send_resource_usage(); |
403 | } else if (p_cmd == "draw" ) { // Forced redraw. |
404 | // For camera override to stay live when the game is paused from the editor. |
405 | double delta = 0.0; |
406 | if (singleton->last_draw_time) { |
407 | delta = (OS::get_singleton()->get_ticks_usec() - singleton->last_draw_time) / 1000000.0; |
408 | } |
409 | singleton->last_draw_time = OS::get_singleton()->get_ticks_usec(); |
410 | RenderingServer::get_singleton()->sync(); |
411 | if (RenderingServer::get_singleton()->has_changed()) { |
412 | RenderingServer::get_singleton()->draw(true, delta); |
413 | } |
414 | EngineDebugger::get_singleton()->send_message("servers:drawn" , Array()); |
415 | } else if (p_cmd == "foreground" ) { |
416 | singleton->last_draw_time = 0.0; |
417 | DisplayServer::get_singleton()->window_move_to_foreground(); |
418 | singleton->servers_profiler->skip_frame(); |
419 | } else { |
420 | r_captured = false; |
421 | } |
422 | return OK; |
423 | } |
424 | |
425 | void ServersDebugger::_send_resource_usage() { |
426 | ServersDebugger::ResourceUsage usage; |
427 | |
428 | List<RS::TextureInfo> tinfo; |
429 | RS::get_singleton()->texture_debug_usage(&tinfo); |
430 | |
431 | for (const RS::TextureInfo &E : tinfo) { |
432 | ServersDebugger::ResourceInfo info; |
433 | info.path = E.path; |
434 | info.vram = E.bytes; |
435 | info.id = E.texture; |
436 | info.type = "Texture" ; |
437 | if (E.depth == 0) { |
438 | info.format = itos(E.width) + "x" + itos(E.height) + " " + Image::get_format_name(E.format); |
439 | } else { |
440 | info.format = itos(E.width) + "x" + itos(E.height) + "x" + itos(E.depth) + " " + Image::get_format_name(E.format); |
441 | } |
442 | usage.infos.push_back(info); |
443 | } |
444 | |
445 | EngineDebugger::get_singleton()->send_message("servers:memory_usage" , usage.serialize()); |
446 | } |
447 | |
448 | ServersDebugger::ServersDebugger() { |
449 | singleton = this; |
450 | |
451 | // Generic servers profiler (audio/physics/...) |
452 | servers_profiler.instantiate(); |
453 | servers_profiler->bind("servers" ); |
454 | |
455 | // Visual Profiler (cpu/gpu times) |
456 | visual_profiler.instantiate(); |
457 | visual_profiler->bind("visual" ); |
458 | |
459 | EngineDebugger::Capture servers_cap(nullptr, &_capture); |
460 | EngineDebugger::register_message_capture("servers" , servers_cap); |
461 | } |
462 | |
463 | ServersDebugger::~ServersDebugger() { |
464 | EngineDebugger::unregister_message_capture("servers" ); |
465 | singleton = nullptr; |
466 | } |
467 | |