Skip to content

Commit

Permalink
Adds rolling average usage tracking to SS overview (tgstation#85264)
Browse files Browse the repository at this point in the history
## Why its good for the game

Makes tracking subsystems which are using more than their fair share of
cpu more obvious by having the ability to identify spikes of usage and
overall usuage over a set timeframe.

Also cleans up existing UI code that improperly formatted existing
figures (assumed say cost was a percentage that would sum to 100% and
thus lowered cost values by a factor of 100)

## Changelog
:cl:
admin: Subsystem Overview now has the ability to track a rolling average
of tick by tick subsystem cpu usage.
/:cl:
  • Loading branch information
LemonInTheDark authored Aug 17, 2024
1 parent a7640ac commit 848b546
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 21 deletions.
40 changes: 40 additions & 0 deletions code/controllers/master.dm
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ GLOBAL_REAL(Master, /datum/controller/master)

/// 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)
Expand Down Expand Up @@ -151,12 +155,32 @@ ADMIN_VERB(cmd_controller_view_ui, R_SERVER|R_DEBUG, "Controller Overview", "Vie
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),
Expand All @@ -167,6 +191,7 @@ ADMIN_VERB(cmd_controller_view_ui, R_SERVER|R_DEBUG, "Controller Overview", "Vie
"doesnt_fire" = !!(subsystem.flags & SS_NO_FIRE),
"cost_ms" = subsystem.cost,
"tick_usage" = subsystem.tick_usage,
"usage_per_tick" = average,
"tick_overrun" = subsystem.tick_overrun,
"initialized" = subsystem.initialized,
"initialization_failure_message" = subsystem.initialization_failure_message,
Expand All @@ -175,6 +200,7 @@ ADMIN_VERB(cmd_controller_view_ui, R_SERVER|R_DEBUG, "Controller Overview", "Vie
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

Expand All @@ -187,6 +213,13 @@ ADMIN_VERB(cmd_controller_view_ui, R_SERVER|R_DEBUG, "Controller Overview", "Vie
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")
var/datum/controller/subsystem/subsystem = locate(params["ref"]) in subsystems
if(isnull(subsystem))
Expand Down Expand Up @@ -764,6 +797,12 @@ ADMIN_VERB(cmd_controller_view_ui, R_SERVER|R_DEBUG, "Controller Overview", "Vie
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(queue_node.profiler_focused)
world.Profile(PROFILE_STOP)

Expand Down Expand Up @@ -903,3 +942,4 @@ ADMIN_VERB(cmd_controller_view_ui, R_SERVER|R_DEBUG, "Controller Overview", "Vie
return FALSE
last_profiled = REALTIMEOFDAY
SSprofiler.DumpFile(allow_yield = FALSE)

12 changes: 12 additions & 0 deletions code/controllers/subsystem.dm
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@
/// Running average of the amount of tick usage (in percents of a game tick) the subsystem has spent past its allocated time without pausing
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()

/// How much of a tick (in percents of a tick) were we allocated last fire.
var/tick_allocation_last = 0

Expand Down Expand Up @@ -299,6 +302,15 @@
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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@ import { ControllerData } from './types';

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

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

Expand All @@ -18,16 +24,28 @@ export function OverviewSection(props) {
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
tooltip="Fast Update"
icon={fast_update ? 'check-square-o' : 'square-o'}
color={fast_update && 'average'}
onClick={() => {
act('toggle_fast_update');
}}
>
Fast
</Button>
<Button.Input
currentValue={(rolling_length / 10).toString()}
onCommit={(e, value) => {
act('set_rolling_length', {
rolling_length: value,
});
}}
>
Average: {rolling_length / 10} Second(s)
</Button.Input>
</>
}
>
<Stack fill>
Expand All @@ -43,11 +61,11 @@ export function OverviewSection(props) {
</Stack.Item>
<Stack.Item grow>
<LabeledList>
<LabeledList.Item label="Overall Usage">
{(overallUsage * 0.01).toFixed(2)}%
<LabeledList.Item label="Overall Avg Usage">
{avgUsage.toFixed(2)}%
</LabeledList.Item>
<LabeledList.Item label="Overall Overrun">
{(overallOverrun * 0.01).toFixed(2)}%
{overallOverrun.toFixed(2)}%
</LabeledList.Item>
</LabeledList>
</Stack.Item>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export function SubsystemDialog(props: Props) {
next_fire,
tick_overrun,
tick_usage,
usage_per_tick,
} = subsystem;

return (
Expand All @@ -42,12 +43,17 @@ export function SubsystemDialog(props: Props) {
<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}ms</LabeledList.Item>
<LabeledList.Item label="Cost">
{cost_ms.toFixed(2)}ms
</LabeledList.Item>
<LabeledList.Item label="Tick Usage">
{(tick_usage * 0.01).toFixed(2)}%
{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">
{(tick_overrun * 0.01).toFixed(2)}%
{tick_overrun.toFixed(2)}%
</LabeledList.Item>
{initialization_failure_message && (
<LabeledList.Item color="bad">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ export function SubsystemRow(props: Props) {
let rangeDisplay = {};
if (showBars) {
if (sortType === SortType.Cost) {
valueDisplay = value.toFixed(0) + 'ms';
valueDisplay = value.toFixed(2) + 'ms';
rangeDisplay = {
average: [75, 124.99],
bad: [125, Infinity],
};
} else {
valueDisplay = (value * 0.01).toFixed(2) + '%';
valueDisplay = value.toFixed(2) + '%';
rangeDisplay = {
average: [10, 24.99],
bad: [25, Infinity],
Expand Down
5 changes: 5 additions & 0 deletions tgui/packages/tgui/interfaces/ControllerOverview/contants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@ export const SORTING_TYPES: readonly SortType[] = [
propName: 'tick_usage',
inDeciseconds: true,
},
{
label: 'Avg Usage Per Tick',
propName: 'usage_per_tick',
inDeciseconds: true,
},
{
label: 'Tick Overrun',
propName: 'tick_overrun',
Expand Down
2 changes: 2 additions & 0 deletions tgui/packages/tgui/interfaces/ControllerOverview/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@ export type SubsystemData = {
ref: string;
tick_overrun: number;
tick_usage: number;
usage_per_tick: number;
};

export type ControllerData = {
world_time: number;
fast_update: BooleanLike;
rolling_length: number;
map_cpu: number;
subsystems: SubsystemData[];
};
Expand Down

0 comments on commit 848b546

Please sign in to comment.