Add Startup benchmarking support

This adds support for benchmarking engine startup (and editor startup if used).
The goal is to use this in the benchmarking server to track improvements and changes to engine, editor, importer and scene loading startup times.
This commit is contained in:
Juan Linietsky 2022-08-19 14:21:43 +02:00
parent 35cfaafda8
commit a3936adb29
6 changed files with 124 additions and 5 deletions

View file

@ -33,7 +33,9 @@
#include "core/authors.gen.h"
#include "core/config/project_settings.h"
#include "core/donors.gen.h"
#include "core/io/json.h"
#include "core/license.gen.h"
#include "core/os/os.h"
#include "core/version.h"
void Engine::set_physics_ticks_per_second(int p_ips) {
@ -307,6 +309,43 @@ Engine::Engine() {
singleton = this;
}
void Engine::startup_begin() {
startup_benchmark_total_from = OS::get_singleton()->get_ticks_usec();
}
void Engine::startup_benchmark_begin_measure(const String &p_what) {
startup_benchmark_section = p_what;
startup_benchmark_from = OS::get_singleton()->get_ticks_usec();
}
void Engine::startup_benchmark_end_measure() {
uint64_t total = OS::get_singleton()->get_ticks_usec() - startup_benchmark_from;
double total_f = double(total) / double(1000000);
startup_benchmark_json[startup_benchmark_section] = total_f;
}
void Engine::startup_dump(const String &p_to_file) {
uint64_t total = OS::get_singleton()->get_ticks_usec() - startup_benchmark_total_from;
double total_f = double(total) / double(1000000);
startup_benchmark_json["total_time"] = total_f;
if (!p_to_file.is_empty()) {
Ref<FileAccess> f = FileAccess::open(p_to_file, FileAccess::WRITE);
if (f.is_valid()) {
Ref<JSON> json;
json.instantiate();
f->store_string(json->stringify(startup_benchmark_json, "\t", false, true));
}
} else {
List<Variant> keys;
startup_benchmark_json.get_key_list(&keys);
print_line("STARTUP BENCHMARK:");
for (const Variant &K : keys) {
print_line("\t-", K, ": ", startup_benchmark_json[K], +" sec.");
}
}
}
Engine::Singleton::Singleton(const StringName &p_name, Object *p_ptr, const StringName &p_class_name) :
name(p_name),
ptr(p_ptr),

View file

@ -79,6 +79,11 @@ private:
String write_movie_path;
String shader_cache_path;
Dictionary startup_benchmark_json;
String startup_benchmark_section;
uint64_t startup_benchmark_from = 0;
uint64_t startup_benchmark_total_from = 0;
public:
static Engine *get_singleton();
@ -151,6 +156,11 @@ public:
bool is_validation_layers_enabled() const;
int32_t get_gpu_index() const;
void startup_begin();
void startup_benchmark_begin_measure(const String &p_what);
void startup_benchmark_end_measure();
void startup_dump(const String &p_to_file);
Engine();
virtual ~Engine() {}
};

View file

@ -1192,11 +1192,6 @@ void EditorFileSystem::scan_changes() {
void EditorFileSystem::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
call_deferred(SNAME("scan")); //this should happen after every editor node entered the tree
} break;
case NOTIFICATION_EXIT_TREE: {
Thread &active_thread = thread.is_started() ? thread : thread_sources;
if (use_threads && active_thread.is_started()) {

View file

@ -661,6 +661,7 @@ void EditorNode::_notification(int p_what) {
command_palette->register_shortcuts_as_command();
MessageQueue::get_singleton()->push_callable(callable_mp(this, &EditorNode::_begin_first_scan));
/* DO NOT LOAD SCENES HERE, WAIT FOR FILE SCANNING AND REIMPORT TO COMPLETE */
} break;
@ -1043,6 +1044,8 @@ void EditorNode::_sources_changed(bool p_exist) {
if (waiting_for_first_scan) {
waiting_for_first_scan = false;
Engine::get_singleton()->startup_benchmark_end_measure(); // editor_scan_and_reimport
// Reload the global shader variables, but this time
// loading textures, as they are now properly imported.
RenderingServer::get_singleton()->global_shader_uniforms_load_settings(true);
@ -1055,8 +1058,16 @@ void EditorNode::_sources_changed(bool p_exist) {
_load_docks();
if (!defer_load_scene.is_empty()) {
Engine::get_singleton()->startup_benchmark_begin_measure("editor_load_scene");
load_scene(defer_load_scene);
defer_load_scene = "";
Engine::get_singleton()->startup_benchmark_end_measure();
if (use_startup_benchmark) {
Engine::get_singleton()->startup_dump(startup_benchmark_file);
startup_benchmark_file = String();
use_startup_benchmark = false;
}
}
}
}
@ -4318,6 +4329,15 @@ void EditorNode::_editor_file_dialog_unregister(EditorFileDialog *p_dialog) {
Vector<EditorNodeInitCallback> EditorNode::_init_callbacks;
void EditorNode::_begin_first_scan() {
Engine::get_singleton()->startup_benchmark_begin_measure("editor_scan_and_import");
EditorFileSystem::get_singleton()->scan();
}
void EditorNode::set_use_startup_benchmark(bool p_use_startup_benchmark, const String &p_startup_benchmark_file) {
use_startup_benchmark = p_use_startup_benchmark;
startup_benchmark_file = p_startup_benchmark_file;
}
Error EditorNode::export_preset(const String &p_preset, const String &p_path, bool p_debug, bool p_pack_only) {
export_defer.preset = p_preset;
export_defer.path = p_path;

View file

@ -682,6 +682,10 @@ private:
void _bottom_panel_switch(bool p_enable, int p_idx);
void _bottom_panel_raise_toggled(bool);
void _begin_first_scan();
bool use_startup_benchmark = false;
String startup_benchmark_file;
protected:
friend class FileSystemDock;
@ -816,6 +820,7 @@ public:
void _copy_warning(const String &p_str);
void set_use_startup_benchmark(bool p_use_startup_benchmark, const String &p_startup_benchmark_file);
Error export_preset(const String &p_preset, const String &p_path, bool p_debug, bool p_pack_only);
Control *get_gui_base() { return gui_base; }

View file

@ -154,6 +154,8 @@ static OS::ProcessID editor_pid = 0;
static bool auto_build_solutions = false;
static String debug_server_uri;
#endif
bool use_startup_benchmark = false;
String startup_benchmark_file;
// Display
@ -386,6 +388,8 @@ void Main::print_help(const char *p_binary) {
OS::get_singleton()->print(" --no-docbase Disallow dumping the base types (used with --doctool).\n");
OS::get_singleton()->print(" --build-solutions Build the scripting solutions (e.g. for C# projects). Implies --editor and requires a valid project to edit.\n");
OS::get_singleton()->print(" --dump-extension-api Generate JSON dump of the Godot API for GDExtension bindings named 'extension_api.json' in the current folder.\n");
OS::get_singleton()->print(" --startup-benchmark Benchmark the startup time and print it to console.\n");
OS::get_singleton()->print(" --startup-benchmark-file <path> Benchmark the startup time and save it to a given file in JSON format.\n");
#ifdef TESTS_ENABLED
OS::get_singleton()->print(" --test [--help] Run unit tests. Use --test --help for more information.\n");
#endif
@ -594,6 +598,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
engine = memnew(Engine);
MAIN_PRINT("Main: Initialize CORE");
engine->startup_begin();
engine->startup_benchmark_begin_measure("core");
register_core_types();
register_core_driver_types();
@ -1208,6 +1214,19 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
OS::get_singleton()->print("Missing --xr-mode argument, aborting.\n");
goto error;
}
} else if (I->get() == "--startup-benchmark") {
use_startup_benchmark = true;
} else if (I->get() == "--startup-benchmark-file") {
if (I->next()) {
use_startup_benchmark = true;
startup_benchmark_file = I->next()->get();
N = I->next()->next();
} else {
OS::get_singleton()->print("Missing <path> argument for --startup-benchmark-file <path>.\n");
goto error;
}
} else if (I->get() == "--") {
adding_user_args = true;
} else {
@ -1624,6 +1643,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
message_queue = memnew(MessageQueue);
engine->startup_benchmark_end_measure(); // core
if (p_second_phase) {
return setup2();
}
@ -1690,6 +1711,8 @@ error:
}
Error Main::setup2(Thread::ID p_main_tid_override) {
engine->startup_benchmark_begin_measure("servers");
tsman = memnew(TextServerManager);
if (tsman) {
@ -2047,8 +2070,12 @@ Error Main::setup2(Thread::ID p_main_tid_override) {
ERR_FAIL_V_MSG(ERR_CANT_CREATE, "TextServer: Unable to create TextServer interface.");
}
engine->startup_benchmark_end_measure(); // servers
MAIN_PRINT("Main: Load Scene Types");
engine->startup_benchmark_begin_measure("scene");
register_scene_types();
register_driver_types();
@ -2124,6 +2151,8 @@ Error Main::setup2(Thread::ID p_main_tid_override) {
print_verbose("EDITOR API HASH: " + uitos(ClassDB::get_api_hash(ClassDB::API_EDITOR)));
MAIN_PRINT("Main: Done");
engine->startup_benchmark_end_measure(); // scene
return OK;
}
@ -2462,6 +2491,7 @@ bool Main::start() {
if (!project_manager && !editor) { // game
if (!game_path.is_empty() || !script.is_empty()) {
//autoload
Engine::get_singleton()->startup_benchmark_begin_measure("load_autoloads");
HashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
//first pass, add the constants so they exist before any script is loaded
@ -2516,12 +2546,14 @@ bool Main::start() {
for (Node *E : to_add) {
sml->get_root()->add_child(E);
}
Engine::get_singleton()->startup_benchmark_end_measure(); // load autoloads
}
}
#ifdef TOOLS_ENABLED
EditorNode *editor_node = nullptr;
if (editor) {
Engine::get_singleton()->startup_benchmark_begin_measure("editor");
editor_node = memnew(EditorNode);
sml->get_root()->add_child(editor_node);
@ -2529,6 +2561,13 @@ bool Main::start() {
editor_node->export_preset(_export_preset, positional_arg, export_debug, export_pack_only);
game_path = ""; // Do not load anything.
}
Engine::get_singleton()->startup_benchmark_end_measure();
editor_node->set_use_startup_benchmark(use_startup_benchmark, startup_benchmark_file);
// Editor takes over
use_startup_benchmark = false;
startup_benchmark_file = String();
}
#endif
@ -2693,6 +2732,8 @@ bool Main::start() {
if (!project_manager && !editor) { // game
Engine::get_singleton()->startup_benchmark_begin_measure("game_load");
// Load SSL Certificates from Project Settings (or builtin).
Crypto::load_default_certificates(GLOBAL_DEF("network/ssl/certificate_bundle_override", ""));
@ -2732,16 +2773,20 @@ bool Main::start() {
}
}
}
Engine::get_singleton()->startup_benchmark_end_measure(); // game_load
}
#ifdef TOOLS_ENABLED
if (project_manager) {
Engine::get_singleton()->startup_benchmark_begin_measure("project_manager");
Engine::get_singleton()->set_editor_hint(true);
ProjectManager *pmanager = memnew(ProjectManager);
ProgressDialog *progress_dialog = memnew(ProgressDialog);
pmanager->add_child(progress_dialog);
sml->get_root()->add_child(pmanager);
DisplayServer::get_singleton()->set_context(DisplayServer::CONTEXT_PROJECTMAN);
Engine::get_singleton()->startup_benchmark_end_measure();
}
if (project_manager || editor) {
@ -2771,6 +2816,11 @@ bool Main::start() {
}
}
if (use_startup_benchmark) {
Engine::get_singleton()->startup_dump(startup_benchmark_file);
startup_benchmark_file = String();
}
return true;
}