From 155faefe81e989a078c756bc03f95fb89176e280 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 | 69 ++++++++++++++++++ .../ServerFunctionManagerMixin.java | 72 +++++++++++++++++++ .../duck/IProfilingServerFunctionManager.java | 5 ++ 3 files changed, 146 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 cafbe0af..1e6630c7 100644 --- a/common/src/main/java/org/embeddedt/modernfix/command/ModernFixCommands.java +++ b/common/src/main/java/org/embeddedt/modernfix/command/ModernFixCommands.java @@ -2,9 +2,78 @@ package org.embeddedt.modernfix.command; import com.mojang.brigadier.CommandDispatcher; import net.minecraft.commands.CommandSourceStack; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +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; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import static net.minecraft.commands.Commands.literal; public class ModernFixCommands { public static void register(CommandDispatcher dispatcher) { + dispatcher.register(literal("modernfix") + .then(literal("upgradeStructures") + .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; + } + ResourceManager manager = level.getServer().resources.resourceManager(); + Map structures = manager.listResources("structures", p -> p.getPath().endsWith(".nbt")); + int upgradedNum = 0; + Pattern pathPattern = Pattern.compile("^structures/(.*)\\.nbt$"); + for(Map.Entry entry : structures.entrySet()) { + upgradedNum++; + ResourceLocation found = entry.getKey(); + Matcher matcher = pathPattern.matcher(found.getPath()); + if(!matcher.matches()) + continue; + ResourceLocation structureLocation = new ResourceLocation(found.getNamespace(), matcher.group(1)); + try(InputStream resource = entry.getValue().open()) { + CachingStructureManager.readStructureTag(structureLocation, level.getServer().getFixerUpper(), resource); + Component msg = Component.literal("checked " + structureLocation + " (" + upgradedNum + "/" + structures.size() + ")"); + context.getSource().sendSuccess(() -> msg, false); + } catch(Throwable e) { + ModernFix.LOGGER.error("Couldn't upgrade structure " + found, e); + context.getSource().sendFailure(Component.literal("error reading " + structureLocation + " (" + upgradedNum + "/" + structures.size() + ")")); + } + } + + context.getSource().sendSuccess(() -> Component.literal("All structures upgraded"), false); + + 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(); +}