From 3af785315ce947cf454627fae9e40e0fda485315 Mon Sep 17 00:00:00 2001 From: silicons <2003111+silicons@users.noreply.github.com> Date: Sun, 10 Nov 2024 20:28:50 -0800 Subject: [PATCH] Metrics API (hollow) (#6825) Adds an unimplemented metrics API to replace blackbox. --------- Co-authored-by: LordME <58342752+TheLordME@users.noreply.github.com> --- citadel.dme | 11 ++- code/global.dm | 2 +- .../{metric => legacy_metric}/activity.dm | 12 +-- .../{metric => legacy_metric}/count.dm | 14 ++-- .../{metric => legacy_metric}/department.dm | 18 ++--- .../{metric => legacy_metric}/metric.dm | 2 +- code/modules/metrics/README.md | 3 + code/modules/metrics/api.dm | 78 +++++++++++++++++++ code/modules/metrics/metric.dm | 24 ++++++ code/modules/metrics/metric_base.dm | 28 +++++++ 10 files changed, 164 insertions(+), 28 deletions(-) rename code/modules/{metric => legacy_metric}/activity.dm (87%) rename code/modules/{metric => legacy_metric}/count.dm (77%) rename code/modules/{metric => legacy_metric}/department.dm (84%) rename code/modules/{metric => legacy_metric}/metric.dm (94%) create mode 100644 code/modules/metrics/README.md create mode 100644 code/modules/metrics/api.dm create mode 100644 code/modules/metrics/metric.dm create mode 100644 code/modules/metrics/metric_base.dm diff --git a/citadel.dme b/citadel.dme index 6bf3846e787a..a6da8994250c 100644 --- a/citadel.dme +++ b/citadel.dme @@ -3196,6 +3196,10 @@ #include "code\modules\language\languages\species\vulpkanin.dm" #include "code\modules\language\languages\species\zaddat.dm" #include "code\modules\language\languages\species\zorren.dm" +#include "code\modules\legacy_metric\activity.dm" +#include "code\modules\legacy_metric\count.dm" +#include "code\modules\legacy_metric\department.dm" +#include "code\modules\legacy_metric\metric.dm" #include "code\modules\library\book.dm" #include "code\modules\library\lib_items.dm" #include "code\modules\library\lib_machines.dm" @@ -3490,10 +3494,9 @@ #include "code\modules\media\media_tracks.dm" #include "code\modules\media\mediamanager.dm" #include "code\modules\media\walkpod.dm" -#include "code\modules\metric\activity.dm" -#include "code\modules\metric\count.dm" -#include "code\modules\metric\department.dm" -#include "code\modules\metric\metric.dm" +#include "code\modules\metrics\api.dm" +#include "code\modules\metrics\metric.dm" +#include "code\modules\metrics\metric_base.dm" #include "code\modules\mining\mine_turfs.dm" #include "code\modules\mining\drilling\drill.dm" #include "code\modules\mining\drilling\scanner.dm" diff --git a/code/global.dm b/code/global.dm index b6283b5c4a75..0302a3e13af4 100644 --- a/code/global.dm +++ b/code/global.dm @@ -65,7 +65,7 @@ var/gravity_is_on = 1 var/join_motd = null -var/datum/metric/metric = new() // Metric datum, used to keep track of the round. +var/datum/legacy_metric/metric = new() // Metric datum, used to keep track of the round. var/list/awaydestinations = list() // Away missions. A list of landmarks that the warpgate can take you to. diff --git a/code/modules/metric/activity.dm b/code/modules/legacy_metric/activity.dm similarity index 87% rename from code/modules/metric/activity.dm rename to code/modules/legacy_metric/activity.dm index 22834ac19bd9..c60e8a21f222 100644 --- a/code/modules/metric/activity.dm +++ b/code/modules/legacy_metric/activity.dm @@ -1,6 +1,6 @@ // This checks an individual player's activity level. People who have been afk for a few minutes aren't punished as much as those // who were afk for hours, as they're most likely gone for good. -/datum/metric/proc/assess_player_activity(var/mob/M) +/datum/legacy_metric/proc/assess_player_activity(var/mob/M) . = 100 if(!M) . = 0 @@ -22,7 +22,7 @@ . = max(. , 0) // No negative numbers, or else people could drag other, non-afk players down. // This checks a whole department's collective activity. -/datum/metric/proc/assess_department(var/department) +/datum/legacy_metric/proc/assess_department(var/department) if(!department) return var/departmental_activity = 0 @@ -36,7 +36,7 @@ departmental_activity = departmental_activity / departmental_size // Average it out. return departmental_activity -/datum/metric/proc/assess_all_departments(var/cutoff_number = 3, var/list/department_blacklist = list()) +/datum/legacy_metric/proc/assess_all_departments(var/cutoff_number = 3, var/list/department_blacklist = list()) var/list/activity = list() for(var/department in departments) activity[department] = assess_department(department) @@ -63,7 +63,7 @@ //todo: finish return most_active_departments -/datum/metric/proc/assess_all_living_mobs() // Living refers to the type, not the stat variable. +/datum/legacy_metric/proc/assess_all_living_mobs() // Living refers to the type, not the stat variable. . = 0 var/num = 0 for(var/mob/living/L in GLOB.player_list) @@ -72,7 +72,7 @@ if(num) . = round(. / num, 0.1) -/datum/metric/proc/assess_all_dead_mobs() // Ditto. +/datum/legacy_metric/proc/assess_all_dead_mobs() // Ditto. . = 0 var/num = 0 for(var/mob/observer/dead/O in GLOB.player_list) @@ -81,7 +81,7 @@ if(num) . = round(. / num, 0.1) -/datum/metric/proc/assess_all_outdoor_mobs() +/datum/legacy_metric/proc/assess_all_outdoor_mobs() . = 0 var/num = 0 for(var/mob/living/L in GLOB.player_list) diff --git a/code/modules/metric/count.dm b/code/modules/legacy_metric/count.dm similarity index 77% rename from code/modules/metric/count.dm rename to code/modules/legacy_metric/count.dm index 2a5b0b9f1c72..55e68b5d4b85 100644 --- a/code/modules/metric/count.dm +++ b/code/modules/legacy_metric/count.dm @@ -2,7 +2,7 @@ * Procs for counting active players in different situations. Returns the number of active players within the given cutoff. */ -/datum/metric/proc/count_all_outdoor_mobs(var/cutoff = 75) +/datum/legacy_metric/proc/count_all_outdoor_mobs(var/cutoff = 75) var/num = 0 for(var/mob/living/L in GLOB.player_list) var/turf/T = get_turf(L) @@ -11,7 +11,7 @@ num++ return num -/datum/metric/proc/count_all_space_mobs(var/cutoff = 75, var/respect_z = TRUE) +/datum/legacy_metric/proc/count_all_space_mobs(var/cutoff = 75, var/respect_z = TRUE) var/num = 0 for(var/mob/living/L in GLOB.player_list) var/turf/T = get_turf(L) @@ -24,7 +24,7 @@ // Gives a count of how many human mobs of a specific species are on the station. // Note that `ignore_synths` makes this proc ignore posibrains and drones, but NOT cyborgs, as they are still the same species in-universe. -/datum/metric/proc/count_all_of_specific_species(species_name, ignore_synths = TRUE, cutoff = 75, respect_z = TRUE) +/datum/legacy_metric/proc/count_all_of_specific_species(species_name, ignore_synths = TRUE, cutoff = 75, respect_z = TRUE) var/num = 0 for(var/mob/living/carbon/human/H in GLOB.player_list) if(respect_z && !(H.z in (LEGACY_MAP_DATUM).station_levels)) @@ -37,7 +37,7 @@ return num // Gives a count of how many FBPs of a specific type there are on the station. -/datum/metric/proc/count_all_FBPs_of_kind(desired_FBP_class, cutoff = 75, respect_z = TRUE) +/datum/legacy_metric/proc/count_all_FBPs_of_kind(desired_FBP_class, cutoff = 75, respect_z = TRUE) var/num = 0 for(var/mob/living/carbon/human/H in GLOB.player_list) if(respect_z && !(H.z in (LEGACY_MAP_DATUM).station_levels)) @@ -49,19 +49,19 @@ return num // Like above, but for all FBPs. -/datum/metric/proc/count_all_FBPs(cutoff = 75, respect_z = TRUE) +/datum/legacy_metric/proc/count_all_FBPs(cutoff = 75, respect_z = TRUE) var/num = count_all_FBPs_of_kind(FBP_CYBORG, cutoff, respect_z) num += count_all_FBPs_of_kind(FBP_POSI, cutoff, respect_z) num += count_all_FBPs_of_kind(FBP_DRONE, cutoff, respect_z) return num -/datum/metric/proc/get_all_antags(cutoff = 75) +/datum/legacy_metric/proc/get_all_antags(cutoff = 75) . = list() for(var/mob/living/L in GLOB.player_list) if(L.mind && player_is_antag(L.mind) && assess_player_activity(L) >= cutoff) . += L -/datum/metric/proc/count_all_antags(cutoff = 75) +/datum/legacy_metric/proc/count_all_antags(cutoff = 75) var/list/L = get_all_antags(cutoff) return L.len diff --git a/code/modules/metric/department.dm b/code/modules/legacy_metric/department.dm similarity index 84% rename from code/modules/metric/department.dm rename to code/modules/legacy_metric/department.dm index 922cb3646dd4..62581f2b4e7c 100644 --- a/code/modules/metric/department.dm +++ b/code/modules/legacy_metric/department.dm @@ -1,6 +1,6 @@ // This proc tries to find the department of an arbitrary mob. -/datum/metric/proc/guess_department(var/mob/M) +/datum/legacy_metric/proc/guess_department(var/mob/M) var/list/found_roles = list() . = DEPARTMENT_UNKNOWN @@ -30,7 +30,7 @@ return DEPARTMENT_UNKNOWN // Welp. // Similar to above, but gets the actual job. Note that it returns the job datum itself, or null. -/datum/metric/proc/guess_job(mob/M) +/datum/legacy_metric/proc/guess_job(mob/M) // Like before, records are the most reliable way. var/datum/data/record/R = find_general_record("name", M.real_name) if(R) // They got a record, now find the job datum. @@ -53,19 +53,19 @@ // Feed this proc the name of a job, and it will try to figure out what department they are apart of. // Improved with the addition of SSjob, which has departments be an actual thing and not a virtual concept. -/datum/metric/proc/role_name_to_department(var/role_name) +/datum/legacy_metric/proc/role_name_to_department(var/role_name) var/datum/role/job/J = SSjob.get_job(role_name) if(istype(J)) if(LAZYLEN(J.departments)) return J.departments return list(DEPARTMENT_UNKNOWN) -/datum/metric/proc/count_people_in_department(var/department, cutoff = 75) +/datum/legacy_metric/proc/count_people_in_department(var/department, cutoff = 75) var/list/L = get_people_in_department(department, cutoff) return L.len -/datum/metric/proc/get_people_in_department(department, cutoff = 75) +/datum/legacy_metric/proc/get_people_in_department(department, cutoff = 75) . = list() if(!department) return @@ -81,7 +81,7 @@ continue . += M -/datum/metric/proc/get_people_with_job(job_type, cutoff = 75) +/datum/legacy_metric/proc/get_people_with_job(job_type, cutoff = 75) . = list() // First, get the name. var/datum/role/job/J = SSjob.job_by_type(job_type) @@ -99,13 +99,13 @@ continue . += M -/datum/metric/proc/count_people_with_job(job_type, cutoff = 75) +/datum/legacy_metric/proc/count_people_with_job(job_type, cutoff = 75) var/list/L = get_people_with_job(job_type, cutoff) return L.len -/datum/metric/proc/get_people_with_alt_title(job_type, alt_title_type, cutoff = 75) +/datum/legacy_metric/proc/get_people_with_alt_title(job_type, alt_title_type, cutoff = 75) . = list() var/list/people_with_jobs = get_people_with_job(job_type, cutoff) @@ -116,6 +116,6 @@ if(J.has_alt_title(M, null, A.title)) . += M -/datum/metric/proc/count_people_with_alt_title(job_type, alt_title_type, cutoff = 75) +/datum/legacy_metric/proc/count_people_with_alt_title(job_type, alt_title_type, cutoff = 75) var/list/L = get_people_with_alt_title(job_type, alt_title_type, cutoff) return L.len diff --git a/code/modules/metric/metric.dm b/code/modules/legacy_metric/metric.dm similarity index 94% rename from code/modules/metric/metric.dm rename to code/modules/legacy_metric/metric.dm index 4a9b6bf847e0..bacc8c9173ed 100644 --- a/code/modules/metric/metric.dm +++ b/code/modules/legacy_metric/metric.dm @@ -1,7 +1,7 @@ // This is a global datum used to retrieve certain information about the round, such as activity of a department or a specific // player. -/datum/metric +/datum/legacy_metric var/list/departments = list( DEPARTMENT_COMMAND, DEPARTMENT_SECURITY, diff --git a/code/modules/metrics/README.md b/code/modules/metrics/README.md new file mode 100644 index 000000000000..e58447e47898 --- /dev/null +++ b/code/modules/metrics/README.md @@ -0,0 +1,3 @@ +# Metrics + +In-dev feedback gathering system. diff --git a/code/modules/metrics/api.dm b/code/modules/metrics/api.dm new file mode 100644 index 000000000000..f8dd53c35b56 --- /dev/null +++ b/code/modules/metrics/api.dm @@ -0,0 +1,78 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/** + * counter metric + * + * * numerical, can only go up through a round + */ +/datum/metric/counter + +/** + * increments a counter metric by an amount, defaulting to 1 + * + * * The time at which this is called does matter. The recorded metric will be at the current + * time of the recording. + * + * This is what you can use for things like: + * + * * How many times an admin verb was pressed in a round + * * How many times a thing happened in a round + */ +/proc/metric_record_counter(datum/metric/counter/typepath, amount = 1) + return + +/** + * records a series of values at given times + * + * * Supports a number, string, or both. + * + * This is what you can use for things like: + * + * * Time dilation tracking + */ +/datum/metric/series + /// has numerical data to graph + /// + /// * doesn't limit the data, only determines if we try to pull a graph + var/graph_exists = FALSE + /// representation of numerical data in graph + /// + /// * valid values are ["average", "tally"] + var/graph_collate = "tally" + +/** + * records a number or a string in a series metric + * + * * The time at which this is called does matter. The recorded metric will be at the current + * time of the recording. + */ +/proc/metric_record_series(datum/metric/series/typepath, tally, comment) + return + +/** + * Records an event at a specific tile of a map + * + * * Supports a single tile event with annotation of number and/or string + * * Supports a rectangular event with annotation of number and/or string + * + * This is usually used for game-map purposes, but is actually usable as an arbitrary + * spatial metric if you need it for whatever reason. + * + * This is what you can use for things like: + * + * * Tracking where people died + * * Tracking what explodes + * * Tracking what goes wrong where + */ +/datum/metric/spatial + /// Whether the spatial metric corrosponds to the actual in-game map + var/is_game_world = FALSE + /// Don't render a tally of '1' + var/elide_singular_tally = TRUE + +/proc/metric_record_spatial_single(datum/metric/spatial/typepath, x, y, level_id, tally, comment) + return + +/proc/metric_record_spatial_box(datum/metric/spatial/typepath, x1, y1, x2, y2, level_id, tally, comment) + return diff --git a/code/modules/metrics/metric.dm b/code/modules/metrics/metric.dm new file mode 100644 index 000000000000..d288c3b96268 --- /dev/null +++ b/code/modules/metrics/metric.dm @@ -0,0 +1,24 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/** + * Describes a feedback variable. + * + * * All metrics should be well-formed and described in DM-code, as the DM + * code is the root of trust for what something actually is. + */ +/datum/metric + /// id + /// + /// * must be unique; this should never change + /// * changes require databaes migrations + var/id + /// fancy name + /// + /// * not recorded to database; external renders/access reserve the right to use + /// their own names + var/name + /// category; string value. + /// + /// * this corrosponds to database-level enums, be careful with this + var/category diff --git a/code/modules/metrics/metric_base.dm b/code/modules/metrics/metric_base.dm new file mode 100644 index 000000000000..54a36da9afb9 --- /dev/null +++ b/code/modules/metrics/metric_base.dm @@ -0,0 +1,28 @@ +//* This file is explicitly licensed under the MIT license. *// +//* Copyright (c) 2024 Citadel Station Developers *// + +/** + * Base metrics recorded every round. + * + * This includes things required to display metrics data, like round ID, + * server revision, and more. + * + * Any of these may be missing if we're unable to get the data. If round ID is + * missing, we should just bail on metrics reporting, as round ID is an + * identifying key in the metrics database. + * + * * Testmerges are intentionally not included as part of this. No metrics render solution + * should be performing full testmerges in general; testmerge data can always + * be recorded as a series data. + */ +/datum/metric_base + /// round ID as **string** + var/round_id + /// server revision hash + /// + /// * dependent on git; at time of writing this is SHA-1 + var/commit_hash + /// all valid metric ids, as a list + /// + /// * this is generated via typesof() + var/list/metric_ids