Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
112 changes: 112 additions & 0 deletions code/controllers/master.dm
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,13 @@ GLOBAL_REAL(Master, /datum/controller/master)

var/static/initialized_all = FALSE

/// Whether the Overview UI will update as fast as possible for viewers.
var/overview_fast_update = FALSE
/// Enables rolling usage averaging
var/use_rolling_usage = FALSE
/// How long to run our rolling usage averaging
var/rolling_usage_length = 5 SECONDS

/datum/controller/master/New()
if(!config)
config = new
Expand Down Expand Up @@ -107,6 +114,105 @@ GLOBAL_REAL(Master, /datum/controller/master)
// Tell qdel() to Del() this object.
return QDEL_HINT_HARDDEL_NOW

/client/proc/cmd_controller_view_ui()
set name = "Controller Overview"
set desc = "View the current states of the Subsystem Controllers."
set category = "Debug"

if(!check_rights(R_SERVER|R_DEBUG))
return

Master.ui_interact(usr)

/datum/controller/master/ui_status(mob/user, datum/ui_state/state)
if(!user.client?.holder?.check_for_rights(R_SERVER|R_DEBUG))
return UI_CLOSE
return UI_INTERACTIVE

/datum/controller/master/ui_interact(mob/user, datum/tgui/ui)
ui = SStgui.try_update_ui(user, src, ui)
if(isnull(ui))
ui = new /datum/tgui(user, src, "ControllerOverview")
ui.open()
use_rolling_usage = TRUE

/datum/controller/master/ui_close(mob/user)
var/valid_found = FALSE
for(var/datum/tgui/open_ui as anything in open_uis)
if(open_ui.user == user)
continue
valid_found = TRUE
if(!valid_found)
use_rolling_usage = FALSE
return ..()

/datum/controller/master/ui_data(mob/user)
var/list/data = list()

var/list/subsystem_data = list()
for(var/datum/controller/subsystem/subsystem as anything in subsystems)
var/list/rolling_usage = subsystem.rolling_usage
subsystem.prune_rolling_usage()

// Then we sum
var/sum = 0
for(var/i in 2 to length(rolling_usage) step 2)
sum += rolling_usage[i]
var/average = sum / DS2TICKS(rolling_usage_length)

subsystem_data += list(list(
"name" = subsystem.name,
"ref" = REF(subsystem),
"init_order" = subsystem.init_order,
"last_fire" = subsystem.last_fire,
"next_fire" = subsystem.next_fire,
"can_fire" = subsystem.can_fire,
"doesnt_fire" = !!(subsystem.flags & SS_NO_FIRE),
"cost_ms" = subsystem.cost,
"tick_usage" = subsystem.tick_usage,
"usage_per_tick" = average,
"overtime" = subsystem.tick_overrun,
"initialized" = subsystem.initialized,
))
data["subsystems"] = subsystem_data
data["world_time"] = world.time
data["map_cpu"] = world.map_cpu
data["fast_update"] = overview_fast_update
data["rolling_length"] = rolling_usage_length

return data

/datum/controller/master/ui_act(action, list/params, datum/tgui/ui, datum/ui_state/state)
if(..())
return TRUE

switch(action)
if("toggle_fast_update")
overview_fast_update = !overview_fast_update
return TRUE

if("set_rolling_length")
var/length = text2num(params["rolling_length"])
if(!length || length < 0)
return
rolling_usage_length = length SECONDS
return TRUE

if("view_variables")
if(!check_rights_for(ui.user.client, R_DEBUG))
message_admins(
"[key_name(ui.user)] tried to view master controller variables while having improper rights, \
this is potentially a malicious exploit and worth noting."
)

var/datum/controller/subsystem/subsystem = locate(params["ref"]) in subsystems
if(isnull(subsystem))
to_chat(ui.user, span_warning("Failed to locate subsystem."))
return

ui.user.client.debug_variables(subsystem)
return TRUE

/datum/controller/master/Shutdown()
processing = FALSE
sortTim(subsystems, GLOBAL_PROC_REF(cmp_subsystem_init))
Expand Down Expand Up @@ -555,6 +661,12 @@ GLOBAL_REAL(Master, /datum/controller/master)
var/state = queue_node.ignite(queue_node_paused)
tick_usage = TICK_USAGE - tick_usage

if(use_rolling_usage)
queue_node.prune_rolling_usage()
// Rolling usage is an unrolled list that we know the order off
// OPTIMIZATION POSTING
queue_node.rolling_usage += list(DS2TICKS(world.time), tick_usage)

if (state == SS_RUNNING)
state = SS_IDLE
current_tick_budget -= queue_node_priority
Expand Down
12 changes: 12 additions & 0 deletions code/controllers/subsystem.dm
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
var/tick_usage = 0
// Average tick overrun
var/tick_overrun = 0
/// Flat list of usage and time, every odd index is a log time, every even index is a usage
var/list/rolling_usage = list()
/// Tracks the current state of the ss, running, paused, etc.
var/state = SS_IDLE
/// Ticks this ss is taking to run right now.
Expand Down Expand Up @@ -243,9 +245,19 @@
if (can_fire && cycles >= 1)
postponed_fires += cycles

/// Prunes out of date entries in our rolling usage list
/datum/controller/subsystem/proc/prune_rolling_usage()
var/list/rolling_usage = src.rolling_usage
var/cut_to = 0
while(cut_to + 2 <= length(rolling_usage) && rolling_usage[cut_to + 1] < DS2TICKS(world.time - Master.rolling_usage_length))
cut_to += 2
if(cut_to)
rolling_usage.Cut(1, cut_to + 1)

//usually called via datum/controller/subsystem/New() when replacing a subsystem (i.e. due to a recurring crash)
//should attempt to salvage what it can from the old instance of subsystem
/datum/controller/subsystem/Recover()
return

/datum/controller/subsystem/vv_edit_var(var_name, var_value)
switch (var_name)
Expand Down
3 changes: 2 additions & 1 deletion code/modules/admin/admin_verbs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ GLOBAL_PROTECT(admin_verbs_debug)
/client/proc/cmd_debug_mob_lists,
/client/proc/cmd_admin_delete,
/client/proc/cmd_debug_del_all,
/client/proc/restart_controller,
/client/proc/cmd_controller_view_ui,
/client/proc/enable_debug_verbs,
/client/proc/callproc,
/client/proc/callproc_datum,
Expand Down Expand Up @@ -257,6 +257,7 @@ GLOBAL_LIST_INIT(admin_verbs_hideable, list(
/client/proc/cmd_admin_direct_narrate,
/client/proc/cmd_admin_world_narrate,
/client/proc/cmd_admin_local_narrate,
/client/proc/cmd_controller_view_ui,
/client/proc/play_local_sound,
/client/proc/play_sound,
/client/proc/set_round_end_sound,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { Button, LabeledList, Section, Stack } from 'tgui-core/components';

import { useBackend } from '../../backend';
import type { ControllerData } from './types';

export function OverviewSection(props) {
const { act, data } = useBackend<ControllerData>();
const {
fast_update,
rolling_length,
map_cpu,
subsystems = [],
world_time,
} = data;

let avgUsage = 0;
let overallOverrun = 0;
for (let i = 0; i < subsystems.length; i++) {
avgUsage += subsystems[i].usage_per_tick;
overallOverrun += subsystems[i].overtime;
}

return (
<Section
fill
title="Master Overview"
buttons={
<>
<Button
tooltip="Fast Update"
icon={fast_update ? 'check-square-o' : 'square-o'}
color={fast_update && 'average'}
onClick={() => {
act('toggle_fast_update');
}}
>
Fast
</Button>
<Button.Input
buttonText={`Average: ${(rolling_length / 10).toFixed(2)} Second(s)`}
value={(rolling_length / 10).toString()}
onCommit={(value) => {
act('set_rolling_length', {
rolling_length: value,
});
}}
/>
</>
}
>
<Stack fill>
<Stack.Item grow>
<LabeledList>
<LabeledList.Item label="World Time">
{world_time.toFixed(1)}
</LabeledList.Item>
<LabeledList.Item label="Map CPU">
{map_cpu.toFixed(2)}%
</LabeledList.Item>
</LabeledList>
</Stack.Item>
<Stack.Item grow>
<LabeledList>
<LabeledList.Item label="Overall Avg Usage">
{avgUsage.toFixed(2)}%
</LabeledList.Item>
<LabeledList.Item label="Overall Overrun">
{overallOverrun.toFixed(2)}%
</LabeledList.Item>
</LabeledList>
</Stack.Item>
</Stack>
</Section>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {
Box,
Button,
Divider,
LabeledList,
Modal,
Stack,
} from 'tgui-core/components';

import type { SubsystemData } from './types';

type Props = {
subsystem: SubsystemData;
onClose: () => void;
};

export function SubsystemDialog(props: Props) {
const { subsystem, onClose } = props;
const {
cost_ms,
init_order,
initialization_failure_message,
last_fire,
name,
next_fire,
overtime,
tick_usage,
usage_per_tick,
} = subsystem;

return (
<Modal width="85%" ml={7}>
<Stack fill>
<Stack.Item grow fontSize="22px">
{name}
</Stack.Item>
<Stack.Item>
<Button color="bad" icon="times" onClick={onClose} />
</Stack.Item>
</Stack>
<Divider />
<Box p={1}>
<LabeledList>
<LabeledList.Item label="Init Order">{init_order}</LabeledList.Item>
<LabeledList.Item label="Last Fire">{last_fire}</LabeledList.Item>
<LabeledList.Item label="Next Fire">{next_fire}</LabeledList.Item>
<LabeledList.Item label="Cost">
{cost_ms.toFixed(2)}ms
</LabeledList.Item>
<LabeledList.Item label="Tick Usage">
{tick_usage.toFixed(2)}%
</LabeledList.Item>
<LabeledList.Item label="Avg Usage Per Tick">
{usage_per_tick.toFixed(2)}%
</LabeledList.Item>
<LabeledList.Item label="Tick Overrun">
{overtime.toFixed(2)}%
</LabeledList.Item>
{initialization_failure_message && (
<LabeledList.Item color="bad">
{initialization_failure_message}
</LabeledList.Item>
)}
</LabeledList>
</Box>
<Stack fill justify="space-between">
<Stack.Item />
<Stack.Item>
<Button color="good" onClick={onClose} px={3} py={1}>
Close
</Button>
</Stack.Item>
</Stack>
</Modal>
);
}
Loading
Loading