From 211e404687e38f290ea0aaa4dfdc5cb33d086875 Mon Sep 17 00:00:00 2001 From: embeddedt <42941056+embeddedt@users.noreply.github.com> Date: Sun, 27 Apr 2025 19:38:23 -0400 Subject: [PATCH] Implement a very primitive datapack function profiler --- .../modernfix/command/ModernFixCommands.java | 20 ++++++ .../ServerFunctionManagerMixin.java | 72 +++++++++++++++++++ .../duck/IProfilingServerFunctionManager.java | 5 ++ 3 files changed, 97 insertions(+) create mode 100644 common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/mcfunction_profiling/ServerFunctionManagerMixin.java create mode 100644 common/src/main/java/org/embeddedt/modernfix/duck/IProfilingServerFunctionManager.java diff --git a/common/src/main/java/org/embeddedt/modernfix/command/ModernFixCommands.java b/common/src/main/java/org/embeddedt/modernfix/command/ModernFixCommands.java index 397b0faf..1e6630c7 100644 --- a/common/src/main/java/org/embeddedt/modernfix/command/ModernFixCommands.java +++ b/common/src/main/java/org/embeddedt/modernfix/command/ModernFixCommands.java @@ -8,6 +8,7 @@ import net.minecraft.server.level.ServerLevel; import net.minecraft.server.packs.resources.Resource; import net.minecraft.server.packs.resources.ResourceManager; import org.embeddedt.modernfix.ModernFix; +import org.embeddedt.modernfix.duck.IProfilingServerFunctionManager; import org.embeddedt.modernfix.structure.CachingStructureManager; import java.io.InputStream; @@ -54,6 +55,25 @@ public class ModernFixCommands { return 1; })) + .then(literal("mcfunctions").requires(source -> source.hasPermission(3)) + .executes(context -> { + ServerLevel level = context.getSource().getLevel(); + if(level == null) { + context.getSource().sendFailure(Component.literal("Couldn't find server level")); + return 0; + } + if (level.getServer().getFunctions() instanceof IProfilingServerFunctionManager profiler) { + context.getSource().sendSuccess(() -> Component.literal("mcfunction runtime breakdown:"), false); + for(String line : profiler.mfix$getProfilingResults().split("\n")) { + context.getSource().sendSuccess(() -> Component.literal(line), false); + } + + return 1; + } else { + context.getSource().sendFailure(Component.literal("ModernFix mcfunction profiling is not enabled on this server.")); + return 0; + } + })) ); } } diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/mcfunction_profiling/ServerFunctionManagerMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/mcfunction_profiling/ServerFunctionManagerMixin.java new file mode 100644 index 00000000..7173ad2e --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/feature/mcfunction_profiling/ServerFunctionManagerMixin.java @@ -0,0 +1,72 @@ +package org.embeddedt.modernfix.common.mixin.feature.mcfunction_profiling; + +import com.google.common.base.Stopwatch; +import com.llamalad7.mixinextras.sugar.Local; +import com.llamalad7.mixinextras.sugar.Share; +import com.llamalad7.mixinextras.sugar.ref.LocalRef; +import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; +import net.minecraft.commands.CommandFunction; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.ServerFunctionManager; +import org.embeddedt.modernfix.duck.IProfilingServerFunctionManager; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Comparator; +import java.util.Map; + +@Mixin(ServerFunctionManager.class) +public class ServerFunctionManagerMixin implements IProfilingServerFunctionManager { + @Shadow @Final private static ResourceLocation TICK_FUNCTION_TAG; + + private final Map mfix$functionWatches = new Object2ObjectOpenHashMap<>(); + + @Inject(method = "executeTagFunctions", at = @At("HEAD")) + private void resetWatches(Collection functionObjects, ResourceLocation identifier, CallbackInfo ci) { + mfix$functionWatches.values().forEach(Stopwatch::reset); + } + + @Inject(method = "executeTagFunctions", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/ServerFunctionManager;execute(Lnet/minecraft/commands/CommandFunction;Lnet/minecraft/commands/CommandSourceStack;)I")) + private void startWatch(Collection functionObjects, ResourceLocation identifier, CallbackInfo ci, @Local(ordinal = 0) CommandFunction function, @Share("stopwatch") LocalRef watchRef) { + watchRef.set(null); + if (identifier == TICK_FUNCTION_TAG) { + var watch = mfix$functionWatches.computeIfAbsent(function.getId(), i -> Stopwatch.createUnstarted()); + watch.start(); + watchRef.set(watch); + } + } + + @Inject(method = "executeTagFunctions", at = @At(value = "INVOKE", target = "Lnet/minecraft/server/ServerFunctionManager;execute(Lnet/minecraft/commands/CommandFunction;Lnet/minecraft/commands/CommandSourceStack;)I", shift = At.Shift.AFTER)) + private void stopWatch(Collection functionObjects, ResourceLocation identifier, CallbackInfo ci, @Share("stopwatch") LocalRef watchRef) { + var watch = watchRef.get(); + if (watch != null && watch.isRunning()) { + watch.stop(); + } + } + + @Inject(method = "executeTagFunctions", at = @At("RETURN")) + private void pruneUnusedWatches(Collection functionObjects, ResourceLocation identifier, CallbackInfo ci) { + mfix$functionWatches.values().removeIf(watch -> watch.elapsed().isZero()); + } + + @Override + public String mfix$getProfilingResults() { + var list = new ArrayList<>(mfix$functionWatches.entrySet()); + list.sort(Comparator., Duration>comparing(e -> e.getValue().elapsed()).reversed()); + StringBuilder sb = new StringBuilder(); + for (var entry : list) { + sb.append(entry.getKey().toString()); + sb.append(" - "); + sb.append(entry.getValue().toString()); + sb.append('\n'); + } + return sb.toString(); + } +} diff --git a/common/src/main/java/org/embeddedt/modernfix/duck/IProfilingServerFunctionManager.java b/common/src/main/java/org/embeddedt/modernfix/duck/IProfilingServerFunctionManager.java new file mode 100644 index 00000000..f1337e6f --- /dev/null +++ b/common/src/main/java/org/embeddedt/modernfix/duck/IProfilingServerFunctionManager.java @@ -0,0 +1,5 @@ +package org.embeddedt.modernfix.duck; + +public interface IProfilingServerFunctionManager { + String mfix$getProfilingResults(); +}