diff --git a/EXILED/Exiled.Events/Config.cs b/EXILED/Exiled.Events/Config.cs
index fd244f699..79b78e81a 100644
--- a/EXILED/Exiled.Events/Config.cs
+++ b/EXILED/Exiled.Events/Config.cs
@@ -110,5 +110,23 @@ public sealed class Config : IConfig
///
[Description("Whether to log RA commands.")]
public bool LogRaCommands { get; set; } = true;
+
+ ///
+ /// Gets or sets a value indicating whether the Event Profiler is enabled.
+ ///
+ [Description("Indicates whether to enable the event profiler. This detects and logs plugins that cause lag by taking too long to handle events.")]
+ public bool EventProfiler { get; set; } = false;
+
+ ///
+ /// Gets or sets the threshold in milliseconds for the Event Profiler.
+ ///
+ [Description("The threshold in milliseconds. If a plugin takes longer than this to handle an event, a warning will be logged.(For 60 fps 1 frame time is 16.6 ms)")]
+ public double EventProfilerThreshold { get; set; } = 16.6;
+
+ ///
+ /// Gets or sets the allocation threshold in bytes.
+ ///
+ [Description("If a plugin allocates more memory than this (bytes) in a single event, it will be logged. Default: 16KB")]
+ public long EventProfilerAllocationThreshold { get; set; } = 16384;
}
}
diff --git a/EXILED/Exiled.Events/Patches/Generic/EventProfiler.cs b/EXILED/Exiled.Events/Patches/Generic/EventProfiler.cs
new file mode 100644
index 000000000..1325406cc
--- /dev/null
+++ b/EXILED/Exiled.Events/Patches/Generic/EventProfiler.cs
@@ -0,0 +1,256 @@
+// -----------------------------------------------------------------------
+//
+// Copyright (c) ExMod Team. All rights reserved.
+// Licensed under the CC BY-SA 3.0 license.
+//
+// -----------------------------------------------------------------------
+
+namespace Exiled.Events.Patches.Generic
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Reflection;
+ using System.Reflection.Emit;
+ using System.Runtime.InteropServices;
+
+ using Exiled.API.Features;
+ using Exiled.API.Features.Pools;
+ using Exiled.Events.Features;
+
+ using HarmonyLib;
+
+ using static HarmonyLib.AccessTools;
+
+ ///
+ /// Patch for adding profiler to .
+ ///
+ [HarmonyPatch]
+ internal static class EventProfiler
+ {
+ private static float profilerThreshold;
+
+ private static long allocationThreshold;
+
+ private static Dictionary handlerPropCache;
+
+ private static bool Prepare()
+ {
+ Config config = Exiled.Events.Events.Instance?.Config;
+
+ if (config == null || !config.EventProfiler)
+ return false;
+
+ handlerPropCache = new Dictionary();
+ profilerThreshold = (float)config.EventProfilerThreshold;
+ allocationThreshold = config.EventProfilerAllocationThreshold;
+
+ return true;
+ }
+
+ private static IEnumerable TargetMethods()
+ {
+ Assembly exiledAssembly = typeof(Exiled.Events.Events).Assembly;
+
+ foreach (Type type in exiledAssembly.GetExportedTypes())
+ {
+ foreach (PropertyInfo property in type.GetProperties(BindingFlags.Static | BindingFlags.Public))
+ {
+ Type currentType = property.PropertyType;
+
+ while (currentType != null && currentType != typeof(object))
+ {
+ // if (currentType == typeof(Event) || (currentType.IsGenericType && currentType.GetGenericTypeDefinition() == typeof(Event<>)))
+ if (currentType.IsGenericType && currentType.GetGenericTypeDefinition() == typeof(Event<>))
+ {
+ MethodInfo method = property.PropertyType.GetMethod("BlendedInvoke", BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.FlattenHierarchy);
+
+ if (method != null)
+ yield return method;
+
+ break;
+ }
+
+ currentType = currentType.BaseType;
+ }
+ }
+ }
+ }
+
+ private static IEnumerable Transpiler(IEnumerable instructions, ILGenerator generator, MethodBase originalMethod)
+ {
+ List newInstructions = ListPool.Pool.Get(instructions);
+
+ bool isGenericEvent = originalMethod.DeclaringType.IsGenericType;
+
+ List