From cf696239e472a87f22364f3ebc5ad319a792b5c6 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Wed, 3 Dec 2025 19:32:23 +0100 Subject: [PATCH] Profiler and profiler graph improvements --- src/client/clientenvironment.cpp | 1 - src/client/clientmap.cpp | 1 - src/client/clouds.cpp | 3 +- src/client/game.cpp | 27 ++++++------ src/client/gameui.cpp | 74 +++++++++++++++++++------------- src/client/gameui.h | 2 +- src/gui/profilergraph.cpp | 48 ++++++++++----------- src/gui/profilergraph.h | 3 +- src/network/mtp/impl.cpp | 6 +-- src/network/mtp/threads.cpp | 3 +- src/profiler.cpp | 43 +++++++++++++++++-- src/server.cpp | 1 - src/util/numeric.h | 36 ---------------- 13 files changed, 130 insertions(+), 118 deletions(-) diff --git a/src/client/clientenvironment.cpp b/src/client/clientenvironment.cpp index bcb3841ca..ab4f0d454 100644 --- a/src/client/clientenvironment.cpp +++ b/src/client/clientenvironment.cpp @@ -263,7 +263,6 @@ void ClientEnvironment::step(float dtime) /* Step and handle simple objects */ - g_profiler->avg("ClientEnv: CSO count [#]", m_simple_objects.size()); for (auto i = m_simple_objects.begin(); i != m_simple_objects.end();) { ClientSimpleObject *simple = *i; diff --git a/src/client/clientmap.cpp b/src/client/clientmap.cpp index b25b5cc5f..8f9ce086d 100644 --- a/src/client/clientmap.cpp +++ b/src/client/clientmap.cpp @@ -717,7 +717,6 @@ void ClientMap::updateDrawList() g_profiler->avg("MapBlock sides skipped [#]", sides_skipped); g_profiler->avg("MapBlocks examined [#]", blocks_visited); } - g_profiler->avg("MapBlocks shortlist [#]", shortlist.size()); // must populate either only to avoid duplicates assert(m_drawlist.empty() || shortlist.empty()); diff --git a/src/client/clouds.cpp b/src/client/clouds.cpp index c782b36dc..4baf9b0f5 100644 --- a/src/client/clouds.cpp +++ b/src/client/clouds.cpp @@ -108,7 +108,6 @@ void Clouds::updateMesh() return; } - ScopeProfiler sp(g_profiler, "Clouds::updateMesh()", SPT_AVG); m_mesh_origin = m_origin; m_last_noise_center = center_of_drawing_in_noise_i; m_mesh_valid = true; @@ -374,7 +373,7 @@ void Clouds::render() return; #if 0 - { + { // proof that m_box is correct video::SMaterial tmp; tmp.Thickness = 1.f; driver->setTransform(video::ETS_WORLD, core::IdentityMatrix); diff --git a/src/client/game.cpp b/src/client/game.cpp index f76c3101e..d91435f6c 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -1257,16 +1257,6 @@ void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times, profiler_print_interval = 3; } - if (profiler_interval.step(dtime, profiler_print_interval)) { - if (print_to_log) { - infostream << "Profiler:" << std::endl; - g_profiler->print(infostream); - } - - m_game_ui->updateProfiler(); - g_profiler->clear(); - } - // Update graphs g_profiler->graphAdd("Time non-rendering [us]", draw_times.busy_time - stats.drawtime); @@ -1281,6 +1271,16 @@ void Game::updateProfilers(const RunStats &stats, const FpsControl &draw_times, stats2.PrimitivesDrawn / float(stats2.Drawcalls)); g_profiler->avg("Irr: HW buffers uploaded", stats2.HWBuffersUploaded); g_profiler->avg("Irr: HW buffers active", stats2.HWBuffersActive); + + if (profiler_interval.step(dtime, profiler_print_interval)) { + if (print_to_log) { + infostream << "Profiler:" << std::endl; + g_profiler->print(infostream); + } + + m_game_ui->updateProfiler(); + g_profiler->clear(); + } } void Game::updateStats(RunStats *stats, const FpsControl &draw_times, @@ -3644,8 +3644,11 @@ void Game::drawScene(ProfilerGraph *graph, RunStats *stats) */ v2u32 screensize = this->driver->getScreenSize(); - if (this->m_game_ui->m_flags.show_profiler_graph) - graph->draw(10, screensize.Y - 10, driver, g_fontengine->getFont()); + if (this->m_game_ui->m_flags.show_profiler_graph) { + auto font = g_fontengine->getFont( + g_fontengine->getDefaultFontSize() * 0.9f, FM_Mono); + graph->draw(10, screensize.Y - 10, driver, font); + } /* Damage flash diff --git a/src/client/gameui.cpp b/src/client/gameui.cpp index 72807e620..74fb20bf8 100644 --- a/src/client/gameui.cpp +++ b/src/client/gameui.cpp @@ -44,15 +44,14 @@ void GameUI::init() { // First line of debug text m_guitext = gui::StaticText::add(guienv, utf8_to_wide(PROJECT_NAME_C).c_str(), - core::rect(0, 0, 0, 0), false, true, guiroot); + core::recti(), false, true, guiroot); // Second line of debug text - m_guitext2 = gui::StaticText::add(guienv, L"", core::rect(0, 0, 0, 0), false, + m_guitext2 = gui::StaticText::add(guienv, L"", core::recti(), false, true, guiroot); // Chat text - m_guitext_chat = gui::StaticText::add(guienv, L"", core::rect(0, 0, 0, 0), - //false, false); // Disable word wrap as of now + m_guitext_chat = gui::StaticText::add(guienv, L"", core::recti(), false, true, guiroot); u16 chat_font_size = g_settings->getU16("chat_font_size"); if (chat_font_size != 0) { @@ -74,12 +73,12 @@ void GameUI::init() // Status text (displays info when showing and hiding GUI stuff, etc.) m_guitext_status = gui::StaticText::add(guienv, L"", - core::rect(0, 0, 0, 0), false, false, guiroot); + core::recti(), false, false, guiroot); m_guitext_status->setVisible(false); // Profiler text (size is updated when text is updated) m_guitext_profiler = gui::StaticText::add(guienv, L"", - core::rect(0, 0, 0, 0), false, false, guiroot); + core::recti(), false, false, guiroot); m_guitext_profiler->setOverrideFont(g_fontengine->getFont( g_fontengine->getDefaultFontSize() * 0.9f, FM_Mono)); m_guitext_profiler->setVisible(false); @@ -97,7 +96,7 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ // Minimal debug text must only contain info that can't give a gameplay advantage if (m_flags.show_minimal_debug) { - const u16 fps = 1.0 / stats.dtime_jitter.avg; + const u16 fps = 1.0f / stats.dtime_jitter.avg; m_drawtime_avg *= 0.95f; m_drawtime_avg += 0.05f * (stats.drawtime / 1000); @@ -105,7 +104,7 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ os << std::fixed << PROJECT_NAME_C " " << g_version_hash << " | FPS: " << fps - << std::setprecision(fps >= 100 ? 1 : 0) + << std::setprecision(m_drawtime_avg < 10 ? 1 : 0) << " | drawtime: " << m_drawtime_avg << "ms" << std::setprecision(1) << " | dtime jitter: " @@ -118,7 +117,7 @@ void GameUI::update(const RunStats &stats, Client *client, MapDrawControl *draw_ m_guitext->setRelativePosition(core::rect(5, 5, screensize.X, screensize.Y)); - setStaticText(m_guitext, utf8_to_wide(os.str()).c_str()); + setStaticText(m_guitext, utf8_to_wide(os.str())); minimal_debug_height = m_guitext->getTextHeight(); } @@ -240,7 +239,7 @@ void GameUI::updateChatSize() if (m_flags.show_basic_debug) chat_y += m_guitext2->getTextHeight(); - const v2u32 &window_size = RenderingEngine::getWindowSize(); + const v2u32 window_size = RenderingEngine::getWindowSize(); core::rect chat_size(10, chat_y, window_size.X - 20, 0); chat_size.LowerRightCorner.Y = std::min((s32)window_size.Y, @@ -255,28 +254,41 @@ void GameUI::updateChatSize() void GameUI::updateProfiler() { - if (m_profiler_current_page != 0) { - std::ostringstream os(std::ios_base::binary); - os << " Profiler page " << (int)m_profiler_current_page << - ", elapsed: " << g_profiler->getElapsedMs() << " ms)" << std::endl; - - g_profiler->print(os, m_profiler_current_page, m_profiler_max_page); - - EnrichedString str(utf8_to_wide(os.str())); - str.setBackground(video::SColor(120, 0, 0, 0)); - setStaticText(m_guitext_profiler, str); - - core::dimension2d size = m_guitext_profiler->getOverrideFont()-> - getDimension(str.c_str()); - core::position2di upper_left(6, m_guitext->getTextHeight() * 2.5f); - core::position2di lower_right = upper_left; - lower_right.X += size.Width + 10; - lower_right.Y += size.Height; - - m_guitext_profiler->setRelativePosition(core::rect(upper_left, lower_right)); - } - m_guitext_profiler->setVisible(m_profiler_current_page != 0); + if (m_profiler_current_page == 0) + return; + + std::ostringstream oss(std::ios_base::binary); + oss << "Profiler page " << (int)m_profiler_current_page + << "/" << (int)m_profiler_max_page + << ", elapsed: " << g_profiler->getElapsedMs() << " ms" << std::endl; + g_profiler->print(oss, m_profiler_current_page, m_profiler_max_page); + + EnrichedString str(utf8_to_wide(oss.str())); + str.setBackground(video::SColor(120, 0, 0, 0)); + setStaticText(m_guitext_profiler, str); + + v2s32 upper_left(5, 10); + if (m_flags.show_minimal_debug) + upper_left.Y += m_guitext->getTextHeight(); + if (m_flags.show_basic_debug) + upper_left.Y += m_guitext2->getTextHeight(); + + v2s32 lower_right = upper_left; + lower_right.X += m_guitext_profiler->getTextWidth() + 5; + lower_right.Y += m_guitext_profiler->getTextHeight(); + + m_guitext_profiler->setRelativePosition(core::recti(upper_left, lower_right)); + + // Really dumb heuristic (we have a fixed number of pages, not a fixed page size) + const v2u32 window_size = RenderingEngine::getWindowSize(); + if (upper_left.Y + m_guitext_profiler->getTextHeight() + > window_size.Y * 0.7f) { + if (m_profiler_max_page < 5) { + m_profiler_max_page++; + updateProfiler(); // do it again + } + } } void GameUI::toggleChat(Client *client) diff --git a/src/client/gameui.h b/src/client/gameui.h index 57ea0861d..c113f54b7 100644 --- a/src/client/gameui.h +++ b/src/client/gameui.h @@ -102,5 +102,5 @@ private: gui::IGUIStaticText *m_guitext_profiler = nullptr; // Profiler text u8 m_profiler_current_page = 0; - const u8 m_profiler_max_page = 3; + u8 m_profiler_max_page = 1; }; diff --git a/src/gui/profilergraph.cpp b/src/gui/profilergraph.cpp index 4e633ea5d..4dbdfe08d 100644 --- a/src/gui/profilergraph.cpp +++ b/src/gui/profilergraph.cpp @@ -7,6 +7,7 @@ #include "profilergraph.h" #include "IVideoDriver.h" #include "util/string.h" +#include "util/basic_macros.h" void ProfilerGraph::put(const Profiler::GraphValues &values) { @@ -34,35 +35,33 @@ void ProfilerGraph::draw(s32 x_left, s32 y_bottom, video::IVideoDriver *driver, continue; } - if (value < j->second.min) - j->second.min = value; - - if (value > j->second.max) - j->second.max = value; + j->second.min = std::min(j->second.min, value); + j->second.max = std::max(j->second.max, value); } } + if (m_meta.empty()) + return; + // Assign colors - static const video::SColor usable_colors[] = {video::SColor(255, 255, 100, 100), - video::SColor(255, 90, 225, 90), - video::SColor(255, 100, 100, 255), - video::SColor(255, 255, 150, 50), - video::SColor(255, 220, 220, 100)}; - static const u32 usable_colors_count = - sizeof(usable_colors) / sizeof(*usable_colors); + static const video::SColor usable_colors[] = { + 0xffc5000b, 0xffff950e, 0xffaecf00, 0xffffd320, + 0xffff420e, 0xffff8080, 0xff729fcf, 0xffff99cc, + }; u32 next_color_i = 0; for (auto &i : m_meta) { Meta &meta = i.second; video::SColor color(255, 200, 200, 200); - if (next_color_i < usable_colors_count) + if (next_color_i < ARRLEN(usable_colors)) color = usable_colors[next_color_i++]; meta.color = color; } - s32 graphh = 50; + const s32 texth = font->getDimension(L"Ay").Height; + const s32 graphh = 52; s32 textx = x_left + m_log_max_size + 15; s32 textx2 = textx + 200 - 15; s32 meta_i = 0; @@ -71,23 +70,22 @@ void ProfilerGraph::draw(s32 x_left, s32 y_bottom, video::IVideoDriver *driver, const std::string &id = p.first; const Meta &meta = p.second; s32 x = x_left; - s32 y = y_bottom - meta_i * 50; + s32 y = y_bottom - meta_i * graphh; float show_min = meta.min; float show_max = meta.max; - if (show_min >= -0.0001 && show_max >= -0.0001) { - if (show_min <= show_max * 0.5) + if (show_min >= -0.0001f && show_max >= -0.0001f) { + if (show_min <= show_max * 0.5f || show_max <= graphh) show_min = 0; } - const s32 texth = 15; char buf[20]; if (floorf(show_max) == show_max) porting::mt_snprintf(buf, sizeof(buf), "%.5g", show_max); else porting::mt_snprintf(buf, sizeof(buf), "%.3g", show_max); font->draw(utf8_to_wide(buf).c_str(), - core::rect(textx, y - graphh, textx2, + core::recti(textx, y - graphh, textx2, y - graphh + texth), meta.color); @@ -96,23 +94,23 @@ void ProfilerGraph::draw(s32 x_left, s32 y_bottom, video::IVideoDriver *driver, else porting::mt_snprintf(buf, sizeof(buf), "%.3g", show_min); font->draw(utf8_to_wide(buf).c_str(), - core::rect(textx, y - texth, textx2, y), meta.color); + core::recti(textx, y - texth, textx2, y), meta.color); font->draw(utf8_to_wide(id).c_str(), - core::rect(textx, y - graphh / 2 - texth / 2, textx2, + core::recti(textx, y - graphh / 2 - texth / 2, textx2, y - graphh / 2 + texth / 2), meta.color); s32 graph1y = y; s32 graph1h = graphh; bool relativegraph = (show_min != 0 && show_min != show_max); - float lastscaledvalue = 0.0; + float lastscaledvalue = 0; bool lastscaledvalue_exists = false; for (const Piece &piece : m_log) { float value = 0; bool value_exists = false; - Profiler::GraphValues::const_iterator k = piece.values.find(id); + auto k = piece.values.find(id); if (k != piece.values.end()) { value = k->second; @@ -125,12 +123,12 @@ void ProfilerGraph::draw(s32 x_left, s32 y_bottom, video::IVideoDriver *driver, continue; } - float scaledvalue = 1.0; + float scaledvalue = 1.0f; if (show_max != show_min) scaledvalue = (value - show_min) / (show_max - show_min); - if (scaledvalue == 1.0 && value == 0) { + if (scaledvalue == 1.0f && value == 0) { x++; lastscaledvalue_exists = false; continue; diff --git a/src/gui/profilergraph.h b/src/gui/profilergraph.h index 9ca5b3d86..f425924d5 100644 --- a/src/gui/profilergraph.h +++ b/src/gui/profilergraph.h @@ -18,9 +18,10 @@ namespace video { class ProfilerGraph { private: + // FIXME: this data structure is awfully inefficient struct Piece { - Piece(Profiler::GraphValues v) : values(std::move(v)) {} + Piece(const Profiler::GraphValues &v) : values(v) {} Profiler::GraphValues values; }; struct Meta diff --git a/src/network/mtp/impl.cpp b/src/network/mtp/impl.cpp index 78b69254f..79a2aa1ec 100644 --- a/src/network/mtp/impl.cpp +++ b/src/network/mtp/impl.cpp @@ -897,7 +897,7 @@ void Peer::RTTStatistics(float rtt, const std::string &profiler_id, m_rtt.max_rtt = rtt; /* do average calculation */ - if (m_rtt.avg_rtt < 0.0) + if (m_rtt.avg_rtt < 0) m_rtt.avg_rtt = rtt; else m_rtt.avg_rtt = m_rtt.avg_rtt * (num_samples/(num_samples-1)) + @@ -919,7 +919,7 @@ void Peer::RTTStatistics(float rtt, const std::string &profiler_id, if (jitter >= m_rtt.jitter_max) m_rtt.jitter_max = jitter; - if (m_rtt.jitter_avg < 0.0) + if (m_rtt.jitter_avg < 0) m_rtt.jitter_avg = jitter; else m_rtt.jitter_avg = m_rtt.jitter_avg * (num_samples/(num_samples-1)) + @@ -1002,7 +1002,7 @@ void UDPPeer::reportRTT(float rtt) { if (rtt < 0) return; - RTTStatistics(rtt,"rudp",MAX_RELIABLE_WINDOW_SIZE*10); + RTTStatistics(rtt, "network", MAX_RELIABLE_WINDOW_SIZE*10); // use this value to decide the resend timeout const float rtt_stat = getStat(AVG_RTT); diff --git a/src/network/mtp/threads.cpp b/src/network/mtp/threads.cpp index 9162b8801..4cdb31674 100644 --- a/src/network/mtp/threads.cpp +++ b/src/network/mtp/threads.cpp @@ -220,7 +220,8 @@ void ConnectionSendThread::runTimeouts(float dtime, u32 peer_packet_quota) resend_timeout, peer_packet_quota); channel.UpdatePacketLossCounter(timed_outs.size()); - g_profiler->graphAdd("packets_lost", timed_outs.size()); + if (timed_outs.size() > 0) + g_profiler->graphAdd("packets_lost", timed_outs.size()); // Note that this only happens during connection setup, it would // break badly otherwise. diff --git a/src/profiler.cpp b/src/profiler.cpp index 2f061cb97..cd8863037 100644 --- a/src/profiler.cpp +++ b/src/profiler.cpp @@ -11,6 +11,42 @@ static Profiler main_profiler; Profiler *g_profiler = &main_profiler; +/* + Splits a list into "pages". For example, the list [1,2,3,4,5] split + into two pages would be [1,2,3],[4,5]. This function computes the + minimum and maximum indices of a single page. + + length: Length of the list that should be split + page: Page number, 1 <= page <= pagecount + pagecount: The number of pages, >= 1 + minindex: Receives the minimum index (inclusive). + maxindex: Receives the maximum index (exclusive). + + Ensures 0 <= minindex <= maxindex <= length. +*/ +static void paging(u32 length, u32 page, u32 pagecount, u32 &minindex, u32 &maxindex) +{ + if (length < 1 || pagecount < 1 || page < 1 || page > pagecount) { + // Special cases or invalid parameters + minindex = maxindex = 0; + } else if(pagecount <= length) { + // Less pages than entries in the list: + // Each page contains at least one entry + minindex = (length * (page-1) + (pagecount-1)) / pagecount; + maxindex = (length * page + (pagecount-1)) / pagecount; + } else { + // More pages than entries in the list: + // Make sure the empty pages are at the end + if (page < length) { + minindex = page-1; + maxindex = page; + } else { + minindex = 0; + maxindex = 0; + } + } +} + ScopeProfiler::ScopeProfiler(Profiler *profiler, const std::string &name, ScopeProfilerType type, TimePrecision prec) : m_profiler(profiler), @@ -137,14 +173,15 @@ int Profiler::print(std::ostream &o, u32 page, u32 pagecount) { // Padding - s32 space = std::max(0, 46 - (s32)i.first.size()); + s32 space = std::max(0, 48 - (s32)i.first.size()); memset(buffer, '_', space); buffer[space] = '\0'; o << buffer; } - porting::mt_snprintf(buffer, sizeof(buffer), "% 5ix % 7g", - getAvgCount(i.first), floor(i.second * 1000.0) / 1000.0); + float rounded = floorf(i.second * 1000.0f) / 1000.0f; + porting::mt_snprintf(buffer, sizeof(buffer), "% 5ix % 8g", + getAvgCount(i.first), rounded); o << buffer << std::endl; } return values.size(); diff --git a/src/server.cpp b/src/server.cpp index f585e7705..806ff8047 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -136,7 +136,6 @@ void *ServerThread::run() while (!stopRequested()) { framemarker.start(); - ScopeProfiler spm(g_profiler, "Server::RunStep() (max)", SPT_MAX); u64 t0 = porting::getTimeUs(); diff --git a/src/util/numeric.h b/src/util/numeric.h index 858cc9ec0..d731f830a 100644 --- a/src/util/numeric.h +++ b/src/util/numeric.h @@ -407,42 +407,6 @@ private: }; -/* - Splits a list into "pages". For example, the list [1,2,3,4,5] split - into two pages would be [1,2,3],[4,5]. This function computes the - minimum and maximum indices of a single page. - - length: Length of the list that should be split - page: Page number, 1 <= page <= pagecount - pagecount: The number of pages, >= 1 - minindex: Receives the minimum index (inclusive). - maxindex: Receives the maximum index (exclusive). - - Ensures 0 <= minindex <= maxindex <= length. -*/ -inline void paging(u32 length, u32 page, u32 pagecount, u32 &minindex, u32 &maxindex) -{ - if (length < 1 || pagecount < 1 || page < 1 || page > pagecount) { - // Special cases or invalid parameters - minindex = maxindex = 0; - } else if(pagecount <= length) { - // Less pages than entries in the list: - // Each page contains at least one entry - minindex = (length * (page-1) + (pagecount-1)) / pagecount; - maxindex = (length * page + (pagecount-1)) / pagecount; - } else { - // More pages than entries in the list: - // Make sure the empty pages are at the end - if (page < length) { - minindex = page-1; - maxindex = page; - } else { - minindex = 0; - maxindex = 0; - } - } -} - constexpr inline bool is_power_of_two(u32 n) { return n != 0 && (n & (n - 1)) == 0;