diff --git a/aurorastation.dme b/aurorastation.dme index b1dd88d38c5..d2e4c19a49e 100644 --- a/aurorastation.dme +++ b/aurorastation.dme @@ -15,6 +15,7 @@ #include "code\names.dm" #include "code\stylesheet.dm" #include "code\world.dm" +#include "code\__defines\_common.dm" #include "code\__defines\_compile_options.dm" #include "code\__defines\_layers.dm" #include "code\__defines\_macros.dm" @@ -27,6 +28,7 @@ #include "code\__defines\damage_organs.dm" #include "code\__defines\dna.dm" #include "code\__defines\dview.dm" +#include "code\__defines\extools.dm" #include "code\__defines\flags.dm" #include "code\__defines\gamemode.dm" #include "code\__defines\guns.dm" diff --git a/byond-extools.dll b/byond-extools.dll new file mode 100644 index 00000000000..e6d9b553490 Binary files /dev/null and b/byond-extools.dll differ diff --git a/code/__defines/_common.dm b/code/__defines/_common.dm new file mode 100644 index 00000000000..21ce39444ca --- /dev/null +++ b/code/__defines/_common.dm @@ -0,0 +1 @@ +#define GLOBAL_PROC "some_magic_bullshit" diff --git a/code/__defines/callback.dm b/code/__defines/callback.dm index 18059a60839..8649e131ded 100644 --- a/code/__defines/callback.dm +++ b/code/__defines/callback.dm @@ -1,4 +1,2 @@ -#define GLOBAL_PROC "some_magic_bullshit" - #define CALLBACK new /datum/callback #define INVOKE_ASYNC ImmediateInvokeAsync diff --git a/code/__defines/extools.dm b/code/__defines/extools.dm new file mode 100644 index 00000000000..2fc3a4cef54 --- /dev/null +++ b/code/__defines/extools.dm @@ -0,0 +1,178 @@ +#define EXTOOLS (world.system_type == MS_WINDOWS ? "byond-extools.dll" : "byond-extools") +#define EXTOOLS_SUCCESS "SUCCESS" +#define EXTOOLS_FAILED "FAIL" + +/* + Core - Provides necessary functionality for other modules. + + Initializing any other modules also initializes this so it shouldn't be necessary to call this. + +*/ + +/proc/extools_initialize() + return call(EXTOOLS, "core_initialize")() == EXTOOLS_SUCCESS + +/* + TFFI - Threaded FFI + + All DLL calls are automatically threaded off. + Black magic is used to suspend (sleep) the currently executing proc, allowing non-blocking FFI. + + You may call a DLL function and sleep until it returns, pass a callback to be called with the result, + or call resolve() on the /datum/promise to receive the return value at any time. + + Example: + + + var/x = call_wait("sample.dll", "do_work", "arg1", "arg2", "arg3") + + - Calls the do_work function from sample.dll with 3 arguments. The proc sleeps until do_work returns. + + + + var/datum/promise/P = call_async("sample.dll", "do_work", "arg1") + ... do something else ... + var/result = P.resolve() + + - Calls do_work with 1 argument. Returns a promise object. Runs some other code before calling P.resolve() to obtain the result. + + + + /proc/print_result(result) + world << result + + call_cb("sample.dll", "do_work", /proc/print_result, "arg1", "arg2") + + - Calls do_work with 2 arguments. The callback is invoked with the result as the single argument. Execution resumes immediately. + + +*/ + +#if 0 + +/proc/tffi_initialize() + return call(EXTOOLS, "tffi_initialize")() == EXTOOLS_SUCCESS + +var/fallback_alerted = FALSE +var/next_promise_id = 0 + +/datum/promise + var/completed = FALSE + var/result = "" + var/callback_context = GLOBAL_PROC + var/callback_proc = null + var/__id = 0 + +/datum/promise/New() + __id = next_promise_id++ //please don't create more than 10^38 promises in a single tick + +//This proc's bytecode is overwritten to allow suspending and resuming on demand. +//None of the code here should run. +/datum/promise/proc/__internal_resolve(ref, id) + if(!fallback_alerted && world.system_type != UNIX) // the rewriting is currently broken on Linux. + world << "TFFI: __internal_resolve has not been rewritten, the TFFI DLL was not loaded correctly." + world.log << "TFFI: __internal_resolve has not been rewritten, the TFFI DLL was not loaded correctly." + fallback_alerted = TRUE + while(!completed) + sleep(1) + //It might be better to just fail and notify the user that something went wrong. + +/datum/promise/proc/__resolve_callback() + __internal_resolve("\ref[src]", __id) + if(callback_context == GLOBAL_PROC) + call(callback_proc)(result) + else + call(callback_context, callback_proc)(result) + +/datum/promise/proc/resolve() + __internal_resolve("\ref[src]", __id) + return result + +/proc/call_async() + var/list/arguments = args.Copy() + var/datum/promise/P = new + arguments.Insert(1, "\ref[P]") + call(EXTOOLS, "call_async")(arglist(arguments)) + return P + +/proc/call_cb() + var/list/arguments = args.Copy() + var/context = arguments[3] + var/callback = arguments[4] + arguments.Cut(3, 5) + var/datum/promise/P = new + P.callback_context = context + P.callback_proc = callback + arguments.Insert(1, "\ref[P]") + call(EXTOOLS, "call_async")(arglist(arguments)) + spawn(0) + P.__resolve_callback() + +/proc/call_wait() + return call_async(arglist(args)).resolve() + +#endif + +/* + Extended Profiling - High precision in-depth performance profiling. + + Turning on extended profiling for a proc will cause each execution of it to generate a file in the ./profiles directory + containing a breakdown of time spent executing the proc and each sub-proc it calls. Import the file into https://www.speedscope.app/ to + view a good visual representation. + + Be aware that sleeping counts as stopping and restarting the execution of the proc, which will generate multiple files, one between each sleep. + + For large procs the profiles may become unusably large. Optimizations pending. + + Example: + + start_profiling(/datum/explosion/New) + + - Enables profiling for /datum/explosion/New(), which will produce a detailed breakdown of each explosion that occurs afterwards. + + stop_profiling(/datum/explosion/New) + + - Disables profiling for explosions. Any currently running profiles will stop when the proc finishes executing or enters a sleep. + +*/ + +/proc/profiling_initialize() + return call(EXTOOLS, "extended_profiling_initialize")() == EXTOOLS_SUCCESS + +/proc/start_profiling(procpath) + call(EXTOOLS, "enable_extended_profiling")("[procpath]") + +/proc/stop_profiling(procpath) + call(EXTOOLS, "disable_extended_profiling")("[procpath]") + +/* + + Debug Server - High and low level debugging of DM code. + + Calling debugger_initialize will start a debug server that allows connections from frontends, + such as SpaceManiac's VSCode extension for line-by-line debugging (and more), or Steamport's + Somnium for bytecode inspection. + + Call with pause = TRUE to wait until the debugger connected and immediately break on the next instruction after the call. + +*/ + +/proc/debugger_initialize(pause = FALSE) + return call(EXTOOLS, "debug_initialize")(pause ? "pause" : "") == EXTOOLS_SUCCESS + +/* + + Misc + +*/ + +//Programatically enable and disable the built-in byond profiler. Useful if you want to, for example, profile subsystem initializations. +/proc/enable_profiling() + return call(EXTOOLS, "enable_profiling")() == EXTOOLS_SUCCESS + +/proc/disable_profiling() + return call(EXTOOLS, "disable_profiling")() == EXTOOLS_SUCCESS + +// Will dump the server's in-depth memory profile into the file specified. +/proc/dump_memory_profile(file_name) + return call(EXTOOLS, "dump_memory_usage")(file_name) == EXTOOLS_SUCCESS diff --git a/code/modules/admin/admin_verbs.dm b/code/modules/admin/admin_verbs.dm index c6322365a2a..10a75164874 100644 --- a/code/modules/admin/admin_verbs.dm +++ b/code/modules/admin/admin_verbs.dm @@ -176,7 +176,8 @@ var/list/admin_verbs_server = list( /client/proc/restart_controller, /client/proc/cmd_ss_panic, /client/proc/configure_access_control, - /datum/admins/proc/togglehubvisibility //toggles visibility on the BYOND Hub + /datum/admins/proc/togglehubvisibility, //toggles visibility on the BYOND Hub + /client/proc/dump_memory_usage ) var/list/admin_verbs_debug = list( /client/proc/getruntimelog, // allows us to access runtime logs to somebody, @@ -1283,3 +1284,29 @@ var/list/admin_verbs_cciaa = list( to_chat(usr, "The sunlight system is disabled.") #endif + +/client/proc/dump_memory_usage() + set name = "Dump Server Memory Usage" + set category = "Server" + + if (!check_rights(R_SERVER)) + return + + if (alert(usr, "This will momentarily block the server. Proceed?", "Alert", "Yes", "No") != "Yes") + return + + var/fname = "[game_id]-[time2text(world.timeofday, "MM-DD-hhmm")].json" + + to_world(SPAN_DANGER("The server will momentarily freeze in 2 seconds!")) + log_and_message_admins("has initiated a memory dump into \"[fname]\".", usr) + + sleep(20) + + if (!dump_memory_profile("data/logs/memory/[fname]")) + to_chat(usr, SPAN_WARNING("Dumping memory failed at dll call.")) + return + + if (!fexists("data/logs/memory/[fname]")) + to_chat(usr, SPAN_WARNING("File creation failed. Please check to see if the data/logs/memory folder actually exists.")) + else + to_chat(usr, SPAN_NOTICE("Memory dump completed.")) diff --git a/html/changelogs/skull132_memory-dumps.yml b/html/changelogs/skull132_memory-dumps.yml new file mode 100644 index 00000000000..e0485fe0937 --- /dev/null +++ b/html/changelogs/skull132_memory-dumps.yml @@ -0,0 +1,5 @@ +author: Skull132 +delete-after: True + +changes: + - backend: "Added the ability for admins to dump process memory."