Răsfoiți Sursa

Add network profiler

jfons 6 ani în urmă
părinte
comite
8244f535cd

+ 146 - 0
core/io/multiplayer_api.cpp

@@ -33,6 +33,10 @@
 #include "core/io/marshalls.h"
 #include "scene/main/node.h"
 
+#ifdef DEBUG_ENABLED
+#include "core/os/os.h"
+#endif
+
 _FORCE_INLINE_ bool _should_call_local(MultiplayerAPI::RPCMode mode, bool is_master, bool &r_skip_rpc) {
 
 	switch (mode) {
@@ -166,6 +170,14 @@ void MultiplayerAPI::_process_packet(int p_from, const uint8_t *p_packet, int p_
 	ERR_FAIL_COND_MSG(root_node == NULL, "Multiplayer root node was not initialized. If you are using custom multiplayer, remember to set the root node via MultiplayerAPI.set_root_node before using it.");
 	ERR_FAIL_COND_MSG(p_packet_len < 1, "Invalid packet received. Size too small.");
 
+#ifdef DEBUG_ENABLED
+	if (profiling) {
+		bandwidth_incoming_data.write[bandwidth_incoming_pointer].timestamp = OS::get_singleton()->get_ticks_msec();
+		bandwidth_incoming_data.write[bandwidth_incoming_pointer].packet_size = p_packet_len;
+		bandwidth_incoming_pointer = (bandwidth_incoming_pointer + 1) % bandwidth_incoming_data.size();
+	}
+#endif
+
 	uint8_t packet_type = p_packet[0];
 
 	switch (packet_type) {
@@ -284,6 +296,14 @@ void MultiplayerAPI::_process_rpc(Node *p_node, const StringName &p_name, int p_
 
 	p_offset++;
 
+#ifdef DEBUG_ENABLED
+	if (profiling) {
+		ObjectID id = p_node->get_instance_id();
+		_init_node_profile(id);
+		profiler_frame_data[id].incoming_rpc += 1;
+	}
+#endif
+
 	for (int i = 0; i < argc; i++) {
 
 		ERR_FAIL_COND_MSG(p_offset >= p_packet_len, "Invalid packet received. Size too small.");
@@ -322,6 +342,14 @@ void MultiplayerAPI::_process_rset(Node *p_node, const StringName &p_name, int p
 	bool can_call = _can_call_mode(p_node, rset_mode, p_from);
 	ERR_FAIL_COND_MSG(!can_call, "RSET '" + String(p_name) + "' is not allowed on node " + p_node->get_path() + " from: " + itos(p_from) + ". Mode is " + itos((int)rset_mode) + ", master is " + itos(p_node->get_network_master()) + ".");
 
+#ifdef DEBUG_ENABLED
+	if (profiling) {
+		ObjectID id = p_node->get_instance_id();
+		_init_node_profile(id);
+		profiler_frame_data[id].incoming_rset += 1;
+	}
+#endif
+
 	Variant value;
 	Error err = decode_variant(value, &p_packet[p_offset], p_packet_len - p_offset, NULL, allow_object_decoding || network_peer->is_object_decoding_allowed());
 
@@ -512,6 +540,14 @@ void MultiplayerAPI::_send_rpc(Node *p_from, int p_to, bool p_unreliable, bool p
 		}
 	}
 
+#ifdef DEBUG_ENABLED
+	if (profiling) {
+		bandwidth_outgoing_data.write[bandwidth_outgoing_pointer].timestamp = OS::get_singleton()->get_ticks_msec();
+		bandwidth_outgoing_data.write[bandwidth_outgoing_pointer].packet_size = ofs;
+		bandwidth_outgoing_pointer = (bandwidth_outgoing_pointer + 1) % bandwidth_outgoing_data.size();
+	}
+#endif
+
 	// See if all peers have cached path (is so, call can be fast).
 	bool has_all_peers = _send_confirm_path(from_path, psc, p_to);
 
@@ -615,6 +651,15 @@ void MultiplayerAPI::rpcp(Node *p_node, int p_peer_id, bool p_unreliable, const
 	}
 
 	if (!skip_rpc) {
+
+#ifdef DEBUG_ENABLED
+		if (profiling) {
+			ObjectID id = p_node->get_instance_id();
+			_init_node_profile(id);
+			profiler_frame_data[id].outgoing_rpc += 1;
+		}
+#endif
+
 		_send_rpc(p_node, p_peer_id, p_unreliable, false, p_method, p_arg, p_argcount);
 	}
 
@@ -709,6 +754,14 @@ void MultiplayerAPI::rsetp(Node *p_node, int p_peer_id, bool p_unreliable, const
 		return;
 	}
 
+#ifdef DEBUG_ENABLED
+	if (profiling) {
+		ObjectID id = p_node->get_instance_id();
+		_init_node_profile(id);
+		profiler_frame_data[id].outgoing_rset += 1;
+	}
+#endif
+
 	const Variant *vptr = &p_value;
 
 	_send_rpc(p_node, p_peer_id, p_unreliable, true, p_property, &vptr, 1);
@@ -792,6 +845,96 @@ bool MultiplayerAPI::is_object_decoding_allowed() const {
 	return allow_object_decoding;
 }
 
+void MultiplayerAPI::profiling_start() {
+#ifdef DEBUG_ENABLED
+	profiling = true;
+	profiler_frame_data.clear();
+
+	bandwidth_incoming_pointer = 0;
+	bandwidth_incoming_data.resize(16384); // ~128kB
+	for (int i = 0; i < bandwidth_incoming_data.size(); ++i) {
+		bandwidth_incoming_data.write[i].packet_size = -1;
+	}
+
+	bandwidth_outgoing_pointer = 0;
+	bandwidth_outgoing_data.resize(16384); // ~128kB
+	for (int i = 0; i < bandwidth_outgoing_data.size(); ++i) {
+		bandwidth_outgoing_data.write[i].packet_size = -1;
+	}
+#endif
+}
+
+void MultiplayerAPI::profiling_end() {
+#ifdef DEBUG_ENABLED
+	profiling = false;
+	bandwidth_incoming_data.clear();
+	bandwidth_outgoing_data.clear();
+#endif
+}
+
+int MultiplayerAPI::get_profiling_frame(ProfilingInfo *r_info) {
+	int i = 0;
+#ifdef DEBUG_ENABLED
+	for (Map<ObjectID, ProfilingInfo>::Element *E = profiler_frame_data.front(); E; E = E->next()) {
+		r_info[i] = E->get();
+		++i;
+	}
+	profiler_frame_data.clear();
+#endif
+	return i;
+}
+
+int MultiplayerAPI::get_incoming_bandwidth_usage() {
+#ifdef DEBUG_ENABLED
+	return _get_bandwidth_usage(bandwidth_incoming_data, bandwidth_incoming_pointer);
+#else
+	return 0;
+#endif
+}
+
+int MultiplayerAPI::get_outgoing_bandwidth_usage() {
+#ifdef DEBUG_ENABLED
+	return _get_bandwidth_usage(bandwidth_outgoing_data, bandwidth_outgoing_pointer);
+#else
+	return 0;
+#endif
+}
+
+#ifdef DEBUG_ENABLED
+int MultiplayerAPI::_get_bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer) {
+	int total_bandwidth = 0;
+
+	uint32_t timestamp = OS::get_singleton()->get_ticks_msec();
+	uint32_t final_timestamp = timestamp - 1000;
+
+	int i = (p_pointer + p_buffer.size() - 1) % p_buffer.size();
+
+	while (i != p_pointer && p_buffer[i].packet_size > 0) {
+		if (p_buffer[i].timestamp < final_timestamp) {
+			return total_bandwidth;
+		}
+		total_bandwidth += p_buffer[i].packet_size;
+		i = (i + p_buffer.size() - 1) % p_buffer.size();
+	}
+
+	ERR_EXPLAIN("Reached the end of the bandwidth profiler buffer, values might be inaccurate.");
+	ERR_FAIL_COND_V(i == p_pointer, total_bandwidth);
+	return total_bandwidth;
+}
+
+void MultiplayerAPI::_init_node_profile(ObjectID p_node) {
+	if (profiler_frame_data.has(p_node))
+		return;
+	profiler_frame_data.insert(p_node, ProfilingInfo());
+	profiler_frame_data[p_node].node = p_node;
+	profiler_frame_data[p_node].node_path = Object::cast_to<Node>(ObjectDB::get_instance(p_node))->get_path();
+	profiler_frame_data[p_node].incoming_rpc = 0;
+	profiler_frame_data[p_node].incoming_rset = 0;
+	profiler_frame_data[p_node].outgoing_rpc = 0;
+	profiler_frame_data[p_node].outgoing_rset = 0;
+}
+#endif
+
 void MultiplayerAPI::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("set_root_node", "node"), &MultiplayerAPI::set_root_node);
 	ClassDB::bind_method(D_METHOD("send_bytes", "bytes", "id", "mode"), &MultiplayerAPI::send_bytes, DEFVAL(NetworkedMultiplayerPeer::TARGET_PEER_BROADCAST), DEFVAL(NetworkedMultiplayerPeer::TRANSFER_MODE_RELIABLE));
@@ -842,6 +985,9 @@ MultiplayerAPI::MultiplayerAPI() :
 		allow_object_decoding(false) {
 	rpc_sender_id = 0;
 	root_node = NULL;
+#ifdef DEBUG_ENABLED
+	profiling = false;
+#endif
 	clear();
 }
 

+ 34 - 0
core/io/multiplayer_api.h

@@ -38,6 +38,16 @@ class MultiplayerAPI : public Reference {
 
 	GDCLASS(MultiplayerAPI, Reference);
 
+public:
+	struct ProfilingInfo {
+		ObjectID node;
+		String node_path;
+		int incoming_rpc;
+		int incoming_rset;
+		int outgoing_rpc;
+		int outgoing_rset;
+	};
+
 private:
 	//path sent caches
 	struct PathSentCache {
@@ -55,6 +65,23 @@ private:
 		Map<int, NodeInfo> nodes;
 	};
 
+#ifdef DEBUG_ENABLED
+	struct BandwidthFrame {
+		uint32_t timestamp;
+		int packet_size;
+	};
+
+	int bandwidth_incoming_pointer;
+	Vector<BandwidthFrame> bandwidth_incoming_data;
+	int bandwidth_outgoing_pointer;
+	Vector<BandwidthFrame> bandwidth_outgoing_data;
+	Map<ObjectID, ProfilingInfo> profiler_frame_data;
+	bool profiling;
+
+	void _init_node_profile(ObjectID p_node);
+	int _get_bandwidth_usage(const Vector<BandwidthFrame> &p_buffer, int p_pointer);
+#endif
+
 	Ref<NetworkedMultiplayerPeer> network_peer;
 	int rpc_sender_id;
 	Set<int> connected_peers;
@@ -130,6 +157,13 @@ public:
 	void set_allow_object_decoding(bool p_enable);
 	bool is_object_decoding_allowed() const;
 
+	void profiling_start();
+	void profiling_end();
+
+	int get_profiling_frame(ProfilingInfo *r_info);
+	int get_incoming_bandwidth_usage();
+	int get_outgoing_bandwidth_usage();
+
 	MultiplayerAPI();
 	~MultiplayerAPI();
 };

+ 57 - 0
core/script_debugger_remote.cpp

@@ -764,6 +764,14 @@ void ScriptDebuggerRemote::_poll_events() {
 			profiling = false;
 			_send_profiling_data(false);
 			print_line("PROFILING END!");
+		} else if (command == "start_network_profiling") {
+
+			multiplayer->profiling_start();
+			profiling_network = true;
+		} else if (command == "stop_network_profiling") {
+
+			multiplayer->profiling_end();
+			profiling_network = false;
 		} else if (command == "reload_scripts") {
 			reload_all_scripts = true;
 		} else if (command == "breakpoint") {
@@ -911,6 +919,18 @@ void ScriptDebuggerRemote::idle_poll() {
 		}
 	}
 
+	if (profiling_network) {
+		uint64_t pt = OS::get_singleton()->get_ticks_msec();
+		if (pt - last_net_bandwidth_time > 200) {
+			last_net_bandwidth_time = pt;
+			_send_network_bandwidth_usage();
+		}
+		if (pt - last_net_prof_time > 100) {
+			last_net_prof_time = pt;
+			_send_network_profiling_data();
+		}
+	}
+
 	if (reload_all_scripts) {
 
 		for (int i = 0; i < ScriptServer::get_language_count(); i++) {
@@ -922,6 +942,35 @@ void ScriptDebuggerRemote::idle_poll() {
 	_poll_events();
 }
 
+void ScriptDebuggerRemote::_send_network_profiling_data() {
+	ERR_FAIL_COND(multiplayer.is_null());
+
+	int n_nodes = multiplayer->get_profiling_frame(&network_profile_info.write[0]);
+
+	packet_peer_stream->put_var("network_profile");
+	packet_peer_stream->put_var(n_nodes * 6);
+	for (int i = 0; i < n_nodes; ++i) {
+		packet_peer_stream->put_var(network_profile_info[i].node);
+		packet_peer_stream->put_var(network_profile_info[i].node_path);
+		packet_peer_stream->put_var(network_profile_info[i].incoming_rpc);
+		packet_peer_stream->put_var(network_profile_info[i].incoming_rset);
+		packet_peer_stream->put_var(network_profile_info[i].outgoing_rpc);
+		packet_peer_stream->put_var(network_profile_info[i].outgoing_rset);
+	}
+}
+
+void ScriptDebuggerRemote::_send_network_bandwidth_usage() {
+	ERR_FAIL_COND(multiplayer.is_null());
+
+	int incoming_bandwidth = multiplayer->get_incoming_bandwidth_usage();
+	int outgoing_bandwidth = multiplayer->get_outgoing_bandwidth_usage();
+
+	packet_peer_stream->put_var("network_bandwidth");
+	packet_peer_stream->put_var(2);
+	packet_peer_stream->put_var(incoming_bandwidth);
+	packet_peer_stream->put_var(outgoing_bandwidth);
+}
+
 void ScriptDebuggerRemote::send_message(const String &p_message, const Array &p_args) {
 
 	mutex->lock();
@@ -1061,6 +1110,10 @@ void ScriptDebuggerRemote::set_live_edit_funcs(LiveEditFuncs *p_funcs) {
 	live_edit_funcs = p_funcs;
 }
 
+void ScriptDebuggerRemote::set_multiplayer(Ref<MultiplayerAPI> p_multiplayer) {
+	multiplayer = p_multiplayer;
+}
+
 bool ScriptDebuggerRemote::is_profiling() const {
 
 	return profiling;
@@ -1106,12 +1159,15 @@ ScriptDebuggerRemote::ResourceUsageFunc ScriptDebuggerRemote::resource_usage_fun
 
 ScriptDebuggerRemote::ScriptDebuggerRemote() :
 		profiling(false),
+		profiling_network(false),
 		max_frame_functions(16),
 		skip_profile_frame(false),
 		reload_all_scripts(false),
 		tcp_client(Ref<StreamPeerTCP>(memnew(StreamPeerTCP))),
 		packet_peer_stream(Ref<PacketPeerStream>(memnew(PacketPeerStream))),
 		last_perf_time(0),
+		last_net_prof_time(0),
+		last_net_bandwidth_time(0),
 		performance(Engine::get_singleton()->get_singleton_object("Performance")),
 		requested_quit(false),
 		mutex(Mutex::create()),
@@ -1143,6 +1199,7 @@ ScriptDebuggerRemote::ScriptDebuggerRemote() :
 	add_error_handler(&eh);
 
 	profile_info.resize(GLOBAL_GET("debug/settings/profiler/max_functions"));
+	network_profile_info.resize(GLOBAL_GET("debug/settings/profiler/max_functions"));
 	profile_info_ptrs.resize(profile_info.size());
 }
 

+ 9 - 0
core/script_debugger_remote.h

@@ -54,11 +54,13 @@ class ScriptDebuggerRemote : public ScriptDebugger {
 
 	Vector<ScriptLanguage::ProfilingInfo> profile_info;
 	Vector<ScriptLanguage::ProfilingInfo *> profile_info_ptrs;
+	Vector<MultiplayerAPI::ProfilingInfo> network_profile_info;
 
 	Map<StringName, int> profiler_function_signature_map;
 	float frame_time, idle_time, physics_time, physics_frame_time;
 
 	bool profiling;
+	bool profiling_network;
 	int max_frame_functions;
 	bool skip_profile_frame;
 	bool reload_all_scripts;
@@ -67,6 +69,8 @@ class ScriptDebuggerRemote : public ScriptDebugger {
 	Ref<PacketPeerStream> packet_peer_stream;
 
 	uint64_t last_perf_time;
+	uint64_t last_net_prof_time;
+	uint64_t last_net_bandwidth_time;
 	Object *performance;
 	bool requested_quit;
 	Mutex *mutex;
@@ -123,10 +127,14 @@ class ScriptDebuggerRemote : public ScriptDebugger {
 	void _send_video_memory();
 	LiveEditFuncs *live_edit_funcs;
 
+	Ref<MultiplayerAPI> multiplayer;
+
 	ErrorHandlerList eh;
 	static void _err_handler(void *, const char *, const char *, int p_line, const char *, const char *, ErrorHandlerType p_type);
 
 	void _send_profiling_data(bool p_for_frame);
+	void _send_network_profiling_data();
+	void _send_network_bandwidth_usage();
 
 	struct FrameData {
 
@@ -168,6 +176,7 @@ public:
 
 	virtual void set_request_scene_tree_message_func(RequestSceneTreeMessageFunc p_func, void *p_udata);
 	virtual void set_live_edit_funcs(LiveEditFuncs *p_funcs);
+	virtual void set_multiplayer(Ref<MultiplayerAPI> p_multiplayer);
 
 	virtual bool is_profiling() const;
 	virtual void add_profiling_frame_data(const StringName &p_name, const Array &p_data);

+ 1 - 0
core/script_language.h

@@ -476,6 +476,7 @@ public:
 
 	virtual void set_request_scene_tree_message_func(RequestSceneTreeMessageFunc p_func, void *p_udata) {}
 	virtual void set_live_edit_funcs(LiveEditFuncs *p_funcs) {}
+	virtual void set_multiplayer(Ref<MultiplayerAPI> p_multiplayer) {}
 
 	virtual bool is_profiling() const = 0;
 	virtual void add_profiling_frame_data(const StringName &p_name, const Array &p_data) = 0;

+ 208 - 0
editor/editor_network_profiler.cpp

@@ -0,0 +1,208 @@
+/*************************************************************************/
+/*  editor_network_profiler.cpp                                          */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md)    */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "editor_network_profiler.h"
+
+#include "core/os/os.h"
+#include "editor_scale.h"
+#include "editor_settings.h"
+
+void EditorNetworkProfiler::_bind_methods() {
+	ClassDB::bind_method(D_METHOD("_update_frame"), &EditorNetworkProfiler::_update_frame);
+	ClassDB::bind_method(D_METHOD("_activate_pressed"), &EditorNetworkProfiler::_activate_pressed);
+	ClassDB::bind_method(D_METHOD("_clear_pressed"), &EditorNetworkProfiler::_clear_pressed);
+	ADD_SIGNAL(MethodInfo("enable_profiling", PropertyInfo(Variant::BOOL, "enable")));
+}
+
+void EditorNetworkProfiler::_notification(int p_what) {
+
+	if (p_what == NOTIFICATION_ENTER_TREE) {
+		activate->set_icon(get_icon("Play", "EditorIcons"));
+		clear_button->set_icon(get_icon("Clear", "EditorIcons"));
+	}
+}
+
+void EditorNetworkProfiler::_update_frame() {
+
+	counters_display->clear();
+
+	TreeItem *root = counters_display->create_item();
+
+	for (Map<ObjectID, MultiplayerAPI::ProfilingInfo>::Element *E = nodes_data.front(); E; E = E->next()) {
+
+		TreeItem *node = counters_display->create_item(root);
+
+		for (int j = 0; j < counters_display->get_columns(); ++j) {
+			node->set_text_align(j, j > 0 ? TreeItem::ALIGN_RIGHT : TreeItem::ALIGN_LEFT);
+		}
+
+		node->set_text(0, E->get().node_path);
+		node->set_text(1, E->get().incoming_rpc == 0 ? "-" : itos(E->get().incoming_rpc));
+		node->set_text(2, E->get().incoming_rset == 0 ? "-" : itos(E->get().incoming_rset));
+		node->set_text(3, E->get().outgoing_rpc == 0 ? "-" : itos(E->get().outgoing_rpc));
+		node->set_text(4, E->get().outgoing_rset == 0 ? "-" : itos(E->get().outgoing_rset));
+	}
+}
+
+void EditorNetworkProfiler::_activate_pressed() {
+
+	if (activate->is_pressed()) {
+		activate->set_icon(get_icon("Stop", "EditorIcons"));
+		activate->set_text(TTR("Stop"));
+	} else {
+		activate->set_icon(get_icon("Play", "EditorIcons"));
+		activate->set_text(TTR("Start"));
+	}
+	emit_signal("enable_profiling", activate->is_pressed());
+}
+
+void EditorNetworkProfiler::_clear_pressed() {
+	nodes_data.clear();
+	set_bandwidth(0, 0);
+	if (frame_delay->is_stopped()) {
+		frame_delay->set_wait_time(0.1);
+		frame_delay->start();
+	}
+}
+
+String EditorNetworkProfiler::_format_bandwidth(int p_value) {
+	String unit = "B";
+	float v = p_value;
+	if (v > 1073741824.0) {
+		unit = "GiB";
+		v /= 1073741824.0;
+	} else if (v > 1048576.0) {
+		unit = "MiB";
+		v /= 1048576.0;
+	} else if (v > 1024.0) {
+		unit = "KiB";
+		v /= 1024.0;
+	}
+	return vformat("%.1f %s/s", v, unit);
+}
+
+void EditorNetworkProfiler::add_node_frame_data(const MultiplayerAPI::ProfilingInfo p_frame) {
+
+	if (!nodes_data.has(p_frame.node)) {
+		nodes_data.insert(p_frame.node, p_frame);
+	} else {
+		nodes_data[p_frame.node].incoming_rpc += p_frame.incoming_rpc;
+		nodes_data[p_frame.node].incoming_rset += p_frame.incoming_rset;
+		nodes_data[p_frame.node].outgoing_rpc += p_frame.outgoing_rpc;
+		nodes_data[p_frame.node].outgoing_rset += p_frame.outgoing_rset;
+	}
+
+	if (frame_delay->is_stopped()) {
+		frame_delay->set_wait_time(0.1);
+		frame_delay->start();
+	}
+}
+
+void EditorNetworkProfiler::set_bandwidth(int p_incoming, int p_outgoing) {
+
+	incoming_bandwidth_text->set_text(_format_bandwidth(p_incoming));
+	outgoing_bandwidth_text->set_text(_format_bandwidth(p_outgoing));
+}
+
+bool EditorNetworkProfiler::is_profiling() {
+	return activate->is_pressed();
+}
+
+EditorNetworkProfiler::EditorNetworkProfiler() {
+
+	HBoxContainer *hb = memnew(HBoxContainer);
+	hb->add_constant_override("separation", 8 * EDSCALE);
+	add_child(hb);
+
+	activate = memnew(Button);
+	activate->set_toggle_mode(true);
+	activate->set_text(TTR("Start"));
+	activate->connect("pressed", this, "_activate_pressed");
+	hb->add_child(activate);
+
+	clear_button = memnew(Button);
+	clear_button->set_text(TTR("Clear"));
+	clear_button->connect("pressed", this, "_clear_pressed");
+	hb->add_child(clear_button);
+
+	hb->add_spacer();
+
+	Label *lb = memnew(Label);
+	lb->set_text("Down ");
+	hb->add_child(lb);
+
+	incoming_bandwidth_text = memnew(LineEdit);
+	incoming_bandwidth_text->set_editable(false);
+	incoming_bandwidth_text->set_custom_minimum_size(Size2(100, 0));
+	incoming_bandwidth_text->set_align(LineEdit::Align::ALIGN_RIGHT);
+	incoming_bandwidth_text->set_text("0.0 B/s");
+	hb->add_child(incoming_bandwidth_text);
+
+	lb = memnew(Label);
+	lb->set_text("Up ");
+	hb->add_child(lb);
+
+	outgoing_bandwidth_text = memnew(LineEdit);
+	outgoing_bandwidth_text->set_editable(false);
+	outgoing_bandwidth_text->set_custom_minimum_size(Size2(100, 0));
+	outgoing_bandwidth_text->set_align(LineEdit::Align::ALIGN_RIGHT);
+	outgoing_bandwidth_text->set_text("0.0 B/s");
+	hb->add_child(outgoing_bandwidth_text);
+
+	counters_display = memnew(Tree);
+	counters_display->set_custom_minimum_size(Size2(300, 0) * EDSCALE);
+	counters_display->set_v_size_flags(SIZE_EXPAND_FILL);
+	counters_display->set_hide_folding(true);
+	counters_display->set_hide_root(true);
+	counters_display->set_columns(5);
+	counters_display->set_column_titles_visible(true);
+	counters_display->set_column_title(0, TTR("Node"));
+	counters_display->set_column_expand(0, true);
+	counters_display->set_column_min_width(0, 60);
+	counters_display->set_column_title(1, TTR("Incoming RPC"));
+	counters_display->set_column_expand(1, false);
+	counters_display->set_column_min_width(1, 120 * EDSCALE);
+	counters_display->set_column_title(2, TTR("Incoming RSET"));
+	counters_display->set_column_expand(2, false);
+	counters_display->set_column_min_width(2, 120 * EDSCALE);
+	counters_display->set_column_title(3, TTR("Outgoing RPC"));
+	counters_display->set_column_expand(3, false);
+	counters_display->set_column_min_width(3, 120 * EDSCALE);
+	counters_display->set_column_title(4, TTR("Outgoing RSET"));
+	counters_display->set_column_expand(4, false);
+	counters_display->set_column_min_width(4, 120 * EDSCALE);
+	add_child(counters_display);
+
+	frame_delay = memnew(Timer);
+	frame_delay->set_wait_time(0.1);
+	frame_delay->set_one_shot(true);
+	add_child(frame_delay);
+	frame_delay->connect("timeout", this, "_update_frame");
+}

+ 73 - 0
editor/editor_network_profiler.h

@@ -0,0 +1,73 @@
+/*************************************************************************/
+/*  editor_network_profiler.h                                            */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2019 Godot Engine contributors (cf. AUTHORS.md)    */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef EDITORNETWORKPROFILER_H
+#define EDITORNETWORKPROFILER_H
+
+#include "scene/gui/box_container.h"
+#include "scene/gui/button.h"
+#include "scene/gui/label.h"
+#include "scene/gui/split_container.h"
+#include "scene/gui/tree.h"
+
+class EditorNetworkProfiler : public VBoxContainer {
+
+	GDCLASS(EditorNetworkProfiler, VBoxContainer)
+
+private:
+	Button *activate;
+	Button *clear_button;
+	Tree *counters_display;
+	LineEdit *incoming_bandwidth_text;
+	LineEdit *outgoing_bandwidth_text;
+
+	Timer *frame_delay;
+
+	Map<ObjectID, MultiplayerAPI::ProfilingInfo> nodes_data;
+
+	void _update_frame();
+
+	void _activate_pressed();
+	void _clear_pressed();
+	String _format_bandwidth(int p_value);
+
+protected:
+	void _notification(int p_what);
+	static void _bind_methods();
+
+public:
+	void add_node_frame_data(const MultiplayerAPI::ProfilingInfo p_frame);
+	void set_bandwidth(int p_incoming, int p_outgoing);
+	bool is_profiling();
+
+	EditorNetworkProfiler();
+};
+
+#endif //EDITORNETWORKPROFILER_H

+ 46 - 1
editor/script_editor_debugger.cpp

@@ -33,6 +33,7 @@
 #include "core/io/marshalls.h"
 #include "core/project_settings.h"
 #include "core/ustring.h"
+#include "editor_network_profiler.h"
 #include "editor_node.h"
 #include "editor_profiler.h"
 #include "editor_settings.h"
@@ -977,7 +978,20 @@ void ScriptEditorDebugger::_parse_message(const String &p_msg, const Array &p_da
 			profiler->add_frame_metric(metric, false);
 		else
 			profiler->add_frame_metric(metric, true);
-
+	} else if (p_msg == "network_profile") {
+		int frame_size = 6;
+		for (int i = 0; i < p_data.size(); i += frame_size) {
+			MultiplayerAPI::ProfilingInfo pi;
+			pi.node = p_data[i + 0];
+			pi.node_path = p_data[i + 1];
+			pi.incoming_rpc = p_data[i + 2];
+			pi.incoming_rset = p_data[i + 3];
+			pi.outgoing_rpc = p_data[i + 4];
+			pi.outgoing_rset = p_data[i + 5];
+			network_profiler->add_node_frame_data(pi);
+		}
+	} else if (p_msg == "network_bandwidth") {
+		network_profiler->set_bandwidth(p_data[0], p_data[1]);
 	} else if (p_msg == "kill_me") {
 
 		editor->call_deferred("stop_child_process");
@@ -1193,6 +1207,10 @@ void ScriptEditorDebugger::_notification(int p_what) {
 					if (profiler->is_profiling()) {
 						_profiler_activate(true);
 					}
+
+					if (network_profiler->is_profiling()) {
+						_network_profiler_activate(true);
+					}
 				}
 			}
 
@@ -1412,6 +1430,25 @@ void ScriptEditorDebugger::_profiler_activate(bool p_enable) {
 	}
 }
 
+void ScriptEditorDebugger::_network_profiler_activate(bool p_enable) {
+
+	if (!connection.is_valid())
+		return;
+
+	if (p_enable) {
+		Array msg;
+		msg.push_back("start_network_profiling");
+		ppeer->put_var(msg);
+		print_verbose("Starting network profiling.");
+
+	} else {
+		Array msg;
+		msg.push_back("stop_network_profiling");
+		ppeer->put_var(msg);
+		print_verbose("Ending network profiling.");
+	}
+}
+
 void ScriptEditorDebugger::_profiler_seeked() {
 
 	if (!connection.is_valid() || !connection->is_connected_to_host())
@@ -2000,6 +2037,7 @@ void ScriptEditorDebugger::_bind_methods() {
 	ClassDB::bind_method(D_METHOD("_expand_errors_list"), &ScriptEditorDebugger::_expand_errors_list);
 	ClassDB::bind_method(D_METHOD("_collapse_errors_list"), &ScriptEditorDebugger::_collapse_errors_list);
 	ClassDB::bind_method(D_METHOD("_profiler_activate"), &ScriptEditorDebugger::_profiler_activate);
+	ClassDB::bind_method(D_METHOD("_network_profiler_activate"), &ScriptEditorDebugger::_network_profiler_activate);
 	ClassDB::bind_method(D_METHOD("_profiler_seeked"), &ScriptEditorDebugger::_profiler_seeked);
 	ClassDB::bind_method(D_METHOD("_clear_errors_list"), &ScriptEditorDebugger::_clear_errors_list);
 
@@ -2218,6 +2256,13 @@ ScriptEditorDebugger::ScriptEditorDebugger(EditorNode *p_editor) {
 		profiler->connect("break_request", this, "_profiler_seeked");
 	}
 
+	{ //network profiler
+		network_profiler = memnew(EditorNetworkProfiler);
+		network_profiler->set_name(TTR("Network Profiler"));
+		tabs->add_child(network_profiler);
+		network_profiler->connect("enable_profiling", this, "_network_profiler_activate");
+	}
+
 	{ //monitors
 
 		HSplitContainer *hsp = memnew(HSplitContainer);

+ 4 - 0
editor/script_editor_debugger.h

@@ -50,6 +50,7 @@ class TreeItem;
 class HSplitContainer;
 class ItemList;
 class EditorProfiler;
+class EditorNetworkProfiler;
 
 class ScriptEditorDebuggerInspectedObject;
 
@@ -152,6 +153,7 @@ class ScriptEditorDebugger : public Control {
 	Map<String, int> res_path_cache;
 
 	EditorProfiler *profiler;
+	EditorNetworkProfiler *network_profiler;
 
 	EditorNode *editor;
 
@@ -196,6 +198,8 @@ class ScriptEditorDebugger : public Control {
 	void _profiler_activate(bool p_enable);
 	void _profiler_seeked();
 
+	void _network_profiler_activate(bool p_enable);
+
 	void _paused();
 
 	void _set_remote_object(ObjectID p_id, ScriptEditorDebuggerInspectedObject *p_obj);

+ 1 - 0
scene/main/scene_tree.cpp

@@ -2063,6 +2063,7 @@ SceneTree::SceneTree() {
 
 	if (ScriptDebugger::get_singleton()) {
 		ScriptDebugger::get_singleton()->set_request_scene_tree_message_func(_debugger_request_tree, this);
+		ScriptDebugger::get_singleton()->set_multiplayer(multiplayer);
 	}
 
 	root->set_physics_object_picking(GLOBAL_DEF("physics/common/enable_object_picking", true));