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