| 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 | |