Skip to content

Commit

Permalink
Implements extool's memory profiler (#9051)
Browse files Browse the repository at this point in the history
* le commit

* log da change
  • Loading branch information
skull132 authored Jun 9, 2020
1 parent 5b67d93 commit da362ec
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 3 deletions.
2 changes: 2 additions & 0 deletions aurorastation.dme
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down
Binary file added byond-extools.dll
Binary file not shown.
1 change: 1 addition & 0 deletions code/__defines/_common.dm
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
#define GLOBAL_PROC "some_magic_bullshit"
2 changes: 0 additions & 2 deletions code/__defines/callback.dm
Original file line number Diff line number Diff line change
@@ -1,4 +1,2 @@
#define GLOBAL_PROC "some_magic_bullshit"

#define CALLBACK new /datum/callback
#define INVOKE_ASYNC ImmediateInvokeAsync
178 changes: 178 additions & 0 deletions code/__defines/extools.dm
Original file line number Diff line number Diff line change
@@ -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 << "<b>TFFI: __internal_resolve has not been rewritten, the TFFI DLL was not loaded correctly.</b>"
world.log << "<b>TFFI: __internal_resolve has not been rewritten, the TFFI DLL was not loaded correctly.</b>"
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
29 changes: 28 additions & 1 deletion code/modules/admin/admin_verbs.dm
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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."))
5 changes: 5 additions & 0 deletions html/changelogs/skull132_memory-dumps.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
author: Skull132
delete-after: True

changes:
- backend: "Added the ability for admins to dump process memory."

0 comments on commit da362ec

Please sign in to comment.