Browse Source

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.
Juan Linietsky 3 years ago
parent
commit
a3936adb29
6 changed files with 124 additions and 5 deletions
  1. 39 0
      core/config/engine.cpp
  2. 10 0
      core/config/engine.h
  3. 0 5
      editor/editor_file_system.cpp
  4. 20 0
      editor/editor_node.cpp
  5. 5 0
      editor/editor_node.h
  6. 50 0
      main/main.cpp

+ 39 - 0
core/config/engine.cpp

@@ -33,7 +33,9 @@
 #include "core/authors.gen.h"
 #include "core/authors.gen.h"
 #include "core/config/project_settings.h"
 #include "core/config/project_settings.h"
 #include "core/donors.gen.h"
 #include "core/donors.gen.h"
+#include "core/io/json.h"
 #include "core/license.gen.h"
 #include "core/license.gen.h"
+#include "core/os/os.h"
 #include "core/version.h"
 #include "core/version.h"
 
 
 void Engine::set_physics_ticks_per_second(int p_ips) {
 void Engine::set_physics_ticks_per_second(int p_ips) {
@@ -307,6 +309,43 @@ Engine::Engine() {
 	singleton = this;
 	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) :
 Engine::Singleton::Singleton(const StringName &p_name, Object *p_ptr, const StringName &p_class_name) :
 		name(p_name),
 		name(p_name),
 		ptr(p_ptr),
 		ptr(p_ptr),

+ 10 - 0
core/config/engine.h

@@ -79,6 +79,11 @@ private:
 	String write_movie_path;
 	String write_movie_path;
 	String shader_cache_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:
 public:
 	static Engine *get_singleton();
 	static Engine *get_singleton();
 
 
@@ -151,6 +156,11 @@ public:
 	bool is_validation_layers_enabled() const;
 	bool is_validation_layers_enabled() const;
 	int32_t get_gpu_index() 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();
 	Engine();
 	virtual ~Engine() {}
 	virtual ~Engine() {}
 };
 };

+ 0 - 5
editor/editor_file_system.cpp

@@ -1192,11 +1192,6 @@ void EditorFileSystem::scan_changes() {
 
 
 void EditorFileSystem::_notification(int p_what) {
 void EditorFileSystem::_notification(int p_what) {
 	switch (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: {
 		case NOTIFICATION_EXIT_TREE: {
 			Thread &active_thread = thread.is_started() ? thread : thread_sources;
 			Thread &active_thread = thread.is_started() ? thread : thread_sources;
 			if (use_threads && active_thread.is_started()) {
 			if (use_threads && active_thread.is_started()) {

+ 20 - 0
editor/editor_node.cpp

@@ -661,6 +661,7 @@ void EditorNode::_notification(int p_what) {
 
 
 			command_palette->register_shortcuts_as_command();
 			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 */
 			/* DO NOT LOAD SCENES HERE, WAIT FOR FILE SCANNING AND REIMPORT TO COMPLETE */
 		} break;
 		} break;
 
 
@@ -1043,6 +1044,8 @@ void EditorNode::_sources_changed(bool p_exist) {
 	if (waiting_for_first_scan) {
 	if (waiting_for_first_scan) {
 		waiting_for_first_scan = false;
 		waiting_for_first_scan = false;
 
 
+		Engine::get_singleton()->startup_benchmark_end_measure(); // editor_scan_and_reimport
+
 		// Reload the global shader variables, but this time
 		// Reload the global shader variables, but this time
 		// loading textures, as they are now properly imported.
 		// loading textures, as they are now properly imported.
 		RenderingServer::get_singleton()->global_shader_uniforms_load_settings(true);
 		RenderingServer::get_singleton()->global_shader_uniforms_load_settings(true);
@@ -1055,8 +1058,16 @@ void EditorNode::_sources_changed(bool p_exist) {
 		_load_docks();
 		_load_docks();
 
 
 		if (!defer_load_scene.is_empty()) {
 		if (!defer_load_scene.is_empty()) {
+			Engine::get_singleton()->startup_benchmark_begin_measure("editor_load_scene");
 			load_scene(defer_load_scene);
 			load_scene(defer_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;
 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) {
 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.preset = p_preset;
 	export_defer.path = p_path;
 	export_defer.path = p_path;

+ 5 - 0
editor/editor_node.h

@@ -682,6 +682,10 @@ private:
 	void _bottom_panel_switch(bool p_enable, int p_idx);
 	void _bottom_panel_switch(bool p_enable, int p_idx);
 	void _bottom_panel_raise_toggled(bool);
 	void _bottom_panel_raise_toggled(bool);
 
 
+	void _begin_first_scan();
+	bool use_startup_benchmark = false;
+	String startup_benchmark_file;
+
 protected:
 protected:
 	friend class FileSystemDock;
 	friend class FileSystemDock;
 
 
@@ -816,6 +820,7 @@ public:
 
 
 	void _copy_warning(const String &p_str);
 	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);
 	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; }
 	Control *get_gui_base() { return gui_base; }

+ 50 - 0
main/main.cpp

@@ -154,6 +154,8 @@ static OS::ProcessID editor_pid = 0;
 static bool auto_build_solutions = false;
 static bool auto_build_solutions = false;
 static String debug_server_uri;
 static String debug_server_uri;
 #endif
 #endif
+bool use_startup_benchmark = false;
+String startup_benchmark_file;
 
 
 // Display
 // 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("  --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("  --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("  --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
 #ifdef TESTS_ENABLED
 	OS::get_singleton()->print("  --test [--help]                              Run unit tests. Use --test --help for more information.\n");
 	OS::get_singleton()->print("  --test [--help]                              Run unit tests. Use --test --help for more information.\n");
 #endif
 #endif
@@ -594,6 +598,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
 	engine = memnew(Engine);
 	engine = memnew(Engine);
 
 
 	MAIN_PRINT("Main: Initialize CORE");
 	MAIN_PRINT("Main: Initialize CORE");
+	engine->startup_begin();
+	engine->startup_benchmark_begin_measure("core");
 
 
 	register_core_types();
 	register_core_types();
 	register_core_driver_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");
 				OS::get_singleton()->print("Missing --xr-mode argument, aborting.\n");
 				goto error;
 				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() == "--") {
 		} else if (I->get() == "--") {
 			adding_user_args = true;
 			adding_user_args = true;
 		} else {
 		} else {
@@ -1624,6 +1643,8 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
 
 
 	message_queue = memnew(MessageQueue);
 	message_queue = memnew(MessageQueue);
 
 
+	engine->startup_benchmark_end_measure(); // core
+
 	if (p_second_phase) {
 	if (p_second_phase) {
 		return setup2();
 		return setup2();
 	}
 	}
@@ -1690,6 +1711,8 @@ error:
 }
 }
 
 
 Error Main::setup2(Thread::ID p_main_tid_override) {
 Error Main::setup2(Thread::ID p_main_tid_override) {
+	engine->startup_benchmark_begin_measure("servers");
+
 	tsman = memnew(TextServerManager);
 	tsman = memnew(TextServerManager);
 
 
 	if (tsman) {
 	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.");
 		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");
 	MAIN_PRINT("Main: Load Scene Types");
 
 
+	engine->startup_benchmark_begin_measure("scene");
+
 	register_scene_types();
 	register_scene_types();
 	register_driver_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)));
 	print_verbose("EDITOR API HASH: " + uitos(ClassDB::get_api_hash(ClassDB::API_EDITOR)));
 	MAIN_PRINT("Main: Done");
 	MAIN_PRINT("Main: Done");
 
 
+	engine->startup_benchmark_end_measure(); // scene
+
 	return OK;
 	return OK;
 }
 }
 
 
@@ -2462,6 +2491,7 @@ bool Main::start() {
 		if (!project_manager && !editor) { // game
 		if (!project_manager && !editor) { // game
 			if (!game_path.is_empty() || !script.is_empty()) {
 			if (!game_path.is_empty() || !script.is_empty()) {
 				//autoload
 				//autoload
+				Engine::get_singleton()->startup_benchmark_begin_measure("load_autoloads");
 				HashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
 				HashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list();
 
 
 				//first pass, add the constants so they exist before any script is loaded
 				//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) {
 				for (Node *E : to_add) {
 					sml->get_root()->add_child(E);
 					sml->get_root()->add_child(E);
 				}
 				}
+				Engine::get_singleton()->startup_benchmark_end_measure(); // load autoloads
 			}
 			}
 		}
 		}
 
 
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED
 		EditorNode *editor_node = nullptr;
 		EditorNode *editor_node = nullptr;
 		if (editor) {
 		if (editor) {
+			Engine::get_singleton()->startup_benchmark_begin_measure("editor");
 			editor_node = memnew(EditorNode);
 			editor_node = memnew(EditorNode);
 			sml->get_root()->add_child(editor_node);
 			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);
 				editor_node->export_preset(_export_preset, positional_arg, export_debug, export_pack_only);
 				game_path = ""; // Do not load anything.
 				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
 #endif
 
 
@@ -2693,6 +2732,8 @@ bool Main::start() {
 
 
 		if (!project_manager && !editor) { // game
 		if (!project_manager && !editor) { // game
 
 
+			Engine::get_singleton()->startup_benchmark_begin_measure("game_load");
+
 			// Load SSL Certificates from Project Settings (or builtin).
 			// Load SSL Certificates from Project Settings (or builtin).
 			Crypto::load_default_certificates(GLOBAL_DEF("network/ssl/certificate_bundle_override", ""));
 			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
 #ifdef TOOLS_ENABLED
 		if (project_manager) {
 		if (project_manager) {
+			Engine::get_singleton()->startup_benchmark_begin_measure("project_manager");
 			Engine::get_singleton()->set_editor_hint(true);
 			Engine::get_singleton()->set_editor_hint(true);
 			ProjectManager *pmanager = memnew(ProjectManager);
 			ProjectManager *pmanager = memnew(ProjectManager);
 			ProgressDialog *progress_dialog = memnew(ProgressDialog);
 			ProgressDialog *progress_dialog = memnew(ProgressDialog);
 			pmanager->add_child(progress_dialog);
 			pmanager->add_child(progress_dialog);
 			sml->get_root()->add_child(pmanager);
 			sml->get_root()->add_child(pmanager);
 			DisplayServer::get_singleton()->set_context(DisplayServer::CONTEXT_PROJECTMAN);
 			DisplayServer::get_singleton()->set_context(DisplayServer::CONTEXT_PROJECTMAN);
+			Engine::get_singleton()->startup_benchmark_end_measure();
 		}
 		}
 
 
 		if (project_manager || editor) {
 		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;
 	return true;
 }
 }