diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm index 55e778dc9d2f5..6d2bc588b21aa 100644 --- a/code/controllers/configuration/entries/general.dm +++ b/code/controllers/configuration/entries/general.dm @@ -163,6 +163,14 @@ min_val = 0 //oranges warned us integer = FALSE +/datum/config_entry/flag/mc_diagnostics + +/datum/config_entry/flag/mc_diagnostics/ValidateAndSet(str_val) + . = ..() + if (!.) + return FALSE + Master.diagnostic_mode = config_entry_value + /datum/config_entry/flag/admin_legacy_system //Defines whether the server uses the legacy admin system with admins.txt or the SQL system protection = CONFIG_ENTRY_LOCKED @@ -615,5 +623,4 @@ /datum/config_entry/flag/enable_mrat - /datum/config_entry/string/discord_ooc_tag diff --git a/code/controllers/master.dm b/code/controllers/master.dm index c9c2a109a69fd..eb69369e6d740 100644 --- a/code/controllers/master.dm +++ b/code/controllers/master.dm @@ -76,9 +76,21 @@ GLOBAL_REAL(Master, /datum/controller/master) = new ///used by CHECK_TICK as well so that the procs subsystems call can obey that SS's tick limits var/static/current_ticklimit = TICK_LIMIT_RUNNING + var/diagnostic_mode = FALSE + + var/list/queued_ticks = list() + /// A circular queue for diagnostic purposes + var/list/previous_ticks[10] + /// The current head of the circular queue + var/circular_queue_head = 1 + /datum/controller/master/New() if(!config) config = new + + for (var/i in 1 to length(previous_ticks)) + previous_ticks[i] = new /datum/mc_tick + // Highlander-style: there can only be one! Kill off the old and replace it with the new. if(!random_seed) @@ -502,6 +514,17 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/bg_calc //have we swtiched current_tick_budget to background mode yet? var/tick_usage + var/datum/mc_tick/diagnostic_tick + if (diagnostic_mode) + diagnostic_tick = new() + diagnostic_tick.tick_number = world.time + queued_ticks += diagnostic_tick + previous_ticks[circular_queue_head = (circular_queue_head % length(previous_ticks)) + 1] = diagnostic_tick + else + diagnostic_tick = previous_ticks[circular_queue_head = (circular_queue_head % length(previous_ticks)) + 1] + diagnostic_tick.fired_subsystems.len = 0 + diagnostic_tick.tick_number = world.time + //keep running while we have stuff to run and we haven't gone over a tick // this is so subsystems paused eariler can use tick time that later subsystems never used while (ran && queue_head && TICK_USAGE < TICK_LIMIT_MC) @@ -546,6 +569,11 @@ GLOBAL_REAL(Master, /datum/controller/master) = new var/state = queue_node.ignite(queue_node_paused) tick_usage = TICK_USAGE - tick_usage + if (diagnostic_tick.fired_subsystems[queue_node]) + diagnostic_tick.fired_subsystems[queue_node] = diagnostic_tick.fired_subsystems[queue_node] + tick_usage + else + diagnostic_tick.fired_subsystems[queue_node] = tick_usage + if (state == SS_RUNNING) state = SS_IDLE current_tick_budget -= queue_node_priority @@ -679,3 +707,23 @@ GLOBAL_REAL(Master, /datum/controller/master) = new for (var/thing in subsystems) var/datum/controller/subsystem/SS = thing SS.OnConfigLoad() + +/** + * Diagnostic queue information + */ + +/datum/mc_tick + var/tick_number = 0 + /// Assoc list containing a list of the subsystems that were fired + /// along with how much time thye used this tick + var/list/fired_subsystems = list() + +/datum/mc_tick/proc/get_stat_text() + var/list/output = list() + var/list/tickers = list() + for (var/datum/controller/subsystem/ss as() in fired_subsystems) + if (ss.flags & SS_TICKER) + tickers += "([ss.name]: [TICK_DELTA_TO_MS(fired_subsystems[ss])]ms)" + else + output += "([ss.name]: [TICK_DELTA_TO_MS(fired_subsystems[ss])]ms)" + return "Systems: [jointext(output, " | ")] ######## Tickers: [jointext(tickers, " | ")]" diff --git a/code/controllers/subsystem/metrics.dm b/code/controllers/subsystem/metrics.dm index a72ae73e6ae8a..de2ad88e28ce2 100644 --- a/code/controllers/subsystem/metrics.dm +++ b/code/controllers/subsystem/metrics.dm @@ -46,6 +46,23 @@ SUBSYSTEM_DEF(metrics) out["harddel_count"] = length(GLOB.world_qdel_log) out["round_id"] = text2num(GLOB.round_id) // This is so we can filter the metrics by a single round ID + if (Master.diagnostic_mode) + var/list/diagnostic_report = list() + for (var/datum/mc_tick/diag_tick in Master.queued_ticks) + var/list/current_tick_info = list() + for (var/datum/controller/subsystem/ss in diag_tick.fired_subsystems) + current_tick_info["[ss.ss_id]"] = diag_tick.fired_subsystems[ss] + diagnostic_report["[diag_tick.tick_number]"] = current_tick_info + out["master_controller"] = list( + "diagnostic_mode" = 1, + "diagnostic_report" = diagnostic_report + ) + Master.queued_ticks.Cut() + else + out["master_controller"] = list( + "diagnostic_mode" = 0, + ) + var/server_name = CONFIG_GET(string/serversqlname) if(server_name) out["server_name"] = server_name @@ -56,6 +73,7 @@ SUBSYSTEM_DEF(metrics) ss_data[SS.ss_id] = SS.get_metrics() out["subsystems"] = ss_data + // And send it all return json_encode(out) diff --git a/code/modules/mob/mob_stat.dm b/code/modules/mob/mob_stat.dm index b1d12dbb86399..c355c9dcf7ff7 100644 --- a/code/modules/mob/mob_stat.dm +++ b/code/modules/mob/mob_stat.dm @@ -266,6 +266,20 @@ tab_data["divider_2"] = GENERATE_STAT_DIVIDER for(var/datum/controller/subsystem/SS in Master.subsystems) tab_data += SS.stat_entry() + tab_data["divider_3"] = GENERATE_STAT_DIVIDER + var/datum/controller/subsystem/queue_node = Master.last_type_processed + if (queue_node) + tab_data["Last Processed:"] = GENERATE_STAT_TEXT("[queue_node.name] \[FI: [queue_node.next_fire - world.time]ds\] [(queue_node.flags & SS_TICKER) ? " (Ticker)" : ""][(queue_node.flags & SS_BACKGROUND) ? " (Background)" : ""][(queue_node.flags & SS_KEEP_TIMING) ? " (Keep Timing)" : ""]") + queue_node = Master.queue_head + var/i = 0 + while (queue_node) + tab_data["Queue [i++]:"] = GENERATE_STAT_TEXT("[queue_node.name] \[FI: [queue_node.next_fire - world.time]ds\] [(queue_node.flags & SS_TICKER) ? " (Ticker)" : ""][(queue_node.flags & SS_BACKGROUND) ? " (Background)" : ""][(queue_node.flags & SS_KEEP_TIMING) ? " (Keep Timing)" : ""]") + queue_node = queue_node.queue_next + tab_data["divider_4"] = GENERATE_STAT_DIVIDER + for (var/j in 1 to length(Master.previous_ticks)) + var/datum/mc_tick/tick = Master.previous_ticks[(Master.circular_queue_head - j + length(Master.previous_ticks)) % length(Master.previous_ticks) + 1] + tab_data["Tick [tick.tick_number]"] = GENERATE_STAT_TEXT(tick.get_stat_text()) + tab_data["divider_5"] = GENERATE_STAT_DIVIDER tab_data += GLOB.cameranet.stat_entry() return tab_data diff --git a/config/config.txt b/config/config.txt index b0f7c33d17470..20d176e33d118 100644 --- a/config/config.txt +++ b/config/config.txt @@ -335,6 +335,10 @@ ALLOW_HOLIDAYS ##This is currently a testing optimized setting. A good value for production would be 98. TICK_LIMIT_MC_INIT 500 +## Uncomment to enable MC diagnostics, the MC will record all information about prior ticks and then report them +## to SSmetrics. +# MC_DIAGNOSTICS + ##Defines the ticklag for the world. Ticklag is the amount of time between game ticks (aka byond ticks) (in 1/10ths of a second). ## This also controls the client network update rate, as well as the default client fps TICKLAG 0.5