diff --git a/code/__DEFINES/profile.dm b/code/__DEFINES/profile.dm
index 3c9650876e2..5fc84789014 100644
--- a/code/__DEFINES/profile.dm
+++ b/code/__DEFINES/profile.dm
@@ -1,5 +1,5 @@
-#define PROFILE_START ;PROFILE_STORE = list();PROFILE_SET;
-#define PROFILE_STOP ;PROFILE_STORE = null;
+#define LINE_PROFILE_START ;PROFILE_STORE = list();PROFILE_SET;
+#define LINE_PROFILE_STOP ;PROFILE_STORE = null;
#define PROFILE_SET ;PROFILE_TIME = TICK_USAGE_REAL; PROFILE_LINE = __LINE__; PROFILE_FILE = __FILE__; PROFILE_SLEEPCHECK = world.time;
diff --git a/code/__DEFINES/subsystems.dm b/code/__DEFINES/subsystems.dm
index 6907a153bd6..efd8a80e9e2 100644
--- a/code/__DEFINES/subsystems.dm
+++ b/code/__DEFINES/subsystems.dm
@@ -98,6 +98,7 @@
// Subsystems shutdown in the reverse of the order they initialize in
// The numbers just define the ordering, they are meaningless otherwise.
+#define INIT_ORDER_PROFILER 101
#define INIT_ORDER_TITLE 100
#define INIT_ORDER_GARBAGE 99
#define INIT_ORDER_DBCORE 95
diff --git a/code/controllers/configuration/entries/general.dm b/code/controllers/configuration/entries/general.dm
index e8ff6697233..811937deb13 100644
--- a/code/controllers/configuration/entries/general.dm
+++ b/code/controllers/configuration/entries/general.dm
@@ -489,3 +489,4 @@
/datum/config_entry/flag/reopen_roundstart_suicide_roles_command_report
+/datum/config_entry/flag/auto_profile
diff --git a/code/controllers/subsystem/profiler.dm b/code/controllers/subsystem/profiler.dm
new file mode 100644
index 00000000000..2136f3cace0
--- /dev/null
+++ b/code/controllers/subsystem/profiler.dm
@@ -0,0 +1,50 @@
+#define PROFILER_FILENAME "profiler.json"
+
+SUBSYSTEM_DEF(profiler)
+ name = "Profiler"
+ init_order = INIT_ORDER_PROFILER
+ runlevels = RUNLEVELS_DEFAULT | RUNLEVEL_LOBBY
+ wait = 600
+
+/datum/controller/subsystem/profiler/Initialize()
+ if(CONFIG_GET(flag/auto_profile))
+ StartProfiling()
+ else
+ StopProfiling() //Stop the early start from world/New
+ return ..()
+
+/datum/controller/subsystem/profiler/fire()
+ if(CONFIG_GET(flag/auto_profile))
+ DumpFile()
+
+/datum/controller/subsystem/profiler/Shutdown()
+ if(CONFIG_GET(flag/auto_profile))
+ DumpFile()
+ return ..()
+
+/datum/controller/subsystem/profiler/proc/StartProfiling()
+#if DM_BUILD < 1506 || DM_VERSION < 513
+ stack_trace("Auto profiling unsupported on this byond version")
+ CONFIG_SET(flag/auto_profile, FALSE)
+#else
+ world.Profile(PROFILE_START)
+#endif
+
+/datum/controller/subsystem/profiler/proc/StopProfiling()
+#if DM_BUILD >= 1506 && DM_VERSION >= 513
+ world.Profile(PROFILE_STOP)
+#endif
+
+/datum/controller/subsystem/profiler/proc/DumpFile()
+#if DM_BUILD < 1506 || DM_VERSION < 513
+ stack_trace("Auto profiling unsupported on this byond version")
+ CONFIG_SET(flag/auto_profile, FALSE)
+#else
+ var/current_profile_data = world.Profile(PROFILE_REFRESH,format="json")
+ if(!length(current_profile_data)) //Would be nice to have explicit proc to check this
+ stack_trace("Warning, profiling stopped manually before dump.")
+ var/json_file = file("[GLOB.log_directory]/[PROFILER_FILENAME]")
+ if(fexists(json_file))
+ fdel(json_file)
+ WRITE_FILE(json_file, current_profile_data)
+#endif
diff --git a/code/game/world.dm b/code/game/world.dm
index 3746cad1d4a..322a77c7508 100644
--- a/code/game/world.dm
+++ b/code/game/world.dm
@@ -21,6 +21,11 @@ GLOBAL_VAR(restart_counter)
/world/New()
enable_debugger()
+ //Early profile for auto-profiler - will be stopped on profiler init if necessary.
+#if DM_VERSION >= 513 && DM_BUILD >= 1506
+ world.Profile(PROFILE_START)
+#endif
+
log_world("World loaded at [time_stamp()]!")
SetupExternalRSC()
diff --git a/code/modules/admin/verbs/debug.dm b/code/modules/admin/verbs/debug.dm
index 3d05b4e1f60..799e26378ef 100644
--- a/code/modules/admin/verbs/debug.dm
+++ b/code/modules/admin/verbs/debug.dm
@@ -839,7 +839,7 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that
set name = "Start Line Profiling"
set desc = "Starts tracking line by line profiling for code lines that support it"
- PROFILE_START
+ LINE_PROFILE_START
message_admins("[key_name_admin(src)] started line by line profiling.")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Start Line Profiling")
@@ -850,7 +850,7 @@ But you can call procs that are of type /mob/living/carbon/human/proc/ for that
set name = "Stops Line Profiling"
set desc = "Stops tracking line by line profiling for code lines that support it"
- PROFILE_STOP
+ LINE_PROFILE_STOP
message_admins("[key_name_admin(src)] stopped line by line profiling.")
SSblackbox.record_feedback("tally", "admin_verb", 1, "Stop Line Profiling")
diff --git a/config/config.txt b/config/config.txt
index 58058c2c58d..4cd17022a6a 100644
--- a/config/config.txt
+++ b/config/config.txt
@@ -502,3 +502,7 @@ DEFAULT_VIEW 15x15
## The alternative square viewport size if you're using a widescreen view size
## You probably shouldn't ever be changing this, but it's here if you want to.
DEFAULT_VIEW_SQUARE 15x15
+
+
+## Enable automatic profiling - Byond 513.1506 and newer only.
+#AUTO_PROFILE
diff --git a/tgstation.dme b/tgstation.dme
index ca23a270dfb..480b9d2d8b0 100644
--- a/tgstation.dme
+++ b/tgstation.dme
@@ -275,6 +275,7 @@
#include "code\controllers\subsystem\pathfinder.dm"
#include "code\controllers\subsystem\persistence.dm"
#include "code\controllers\subsystem\ping.dm"
+#include "code\controllers\subsystem\profiler.dm"
#include "code\controllers\subsystem\radiation.dm"
#include "code\controllers\subsystem\radio.dm"
#include "code\controllers\subsystem\research.dm"