Add option to profile launch using Spark

This commit is contained in:
embeddedt 2023-06-26 13:58:12 -04:00
parent b611830b7e
commit 60525ad594
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
14 changed files with 421 additions and 1 deletions

View File

@ -71,6 +71,7 @@ public class ModernFix {
if(ModernFixPlatformHooks.isDedicatedServer()) {
float gameStartTime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000f;
ModernFix.LOGGER.warn("Dedicated server took " + gameStartTime + " seconds to load");
ModernFixPlatformHooks.onLaunchComplete();
}
ClassInfoManager.clear();
}

View File

@ -65,6 +65,7 @@ public class ModernFixClient {
} else if (openingScreen instanceof TitleScreen && gameStartTimeSeconds < 0) {
gameStartTimeSeconds = ManagementFactory.getRuntimeMXBean().getUptime() / 1000f;
ModernFix.LOGGER.warn("Game took " + gameStartTimeSeconds + " seconds to start");
ModernFixPlatformHooks.onLaunchComplete();
}
}

View File

@ -153,6 +153,7 @@ public class ModernFixEarlyConfig {
.put("mixin.perf.faster_item_rendering", false)
.put("mixin.feature.spam_thread_dump", false)
.put("mixin.feature.snapshot_easter_egg", true)
.put("mixin.feature.spark_profile_launch", false)
.put("mixin.perf.blast_search_trees", shouldReplaceSearchTrees)
.put("mixin.devenv", isDevEnv)
.put("mixin.perf.remove_spawn_chunks", isDevEnv)

View File

@ -91,4 +91,9 @@ public class ModernFixPlatformHooks {
public static Multimap<String, String> getCustomModOptions() {
throw new AssertionError();
}
@ExpectPlatform
public static void onLaunchComplete() {
}
}

View File

@ -0,0 +1,17 @@
package org.embeddedt.modernfix.util;
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
public class CommonModUtil {
/**
* Avoid using this, it's bad practice but cleanest way of suppressing errors for nonessential mod-dependent
* functionality.
*/
public static void runWithoutCrash(Runnable r, String errorMsg) {
try {
r.run();
} catch(Throwable e) {
ModernFixMixinPlugin.instance.logger.error(errorMsg, e);
}
}
}

View File

@ -33,6 +33,7 @@ dependencies {
modIncludeImplementation(fabricApi.module("fabric-models-v0", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
modImplementation(fabricApi.module("fabric-resource-loader-v0", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
modCompileOnly("com.terraformersmc:modmenu:${rootProject.modmenu_version}") { transitive false }
modImplementation "curse.maven:spark-361579:${rootProject.spark_fabric_version}"
modRuntimeOnly("net.fabricmc.fabric-api:fabric-api:${rootProject.fabric_api_version}") { exclude group: 'net.fabricmc', module: 'fabric-loader' }
// Remove the next line if you don't want to depend on the API
// modApi "me.shedaniel:architectury-fabric:${rootProject.architectury_version}"

View File

@ -0,0 +1,15 @@
package org.embeddedt.modernfix;
import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint;
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
import org.embeddedt.modernfix.fabric.spark.SparkLaunchProfiler;
import org.embeddedt.modernfix.util.CommonModUtil;
public class ModernFixPreLaunchFabric implements PreLaunchEntrypoint {
@Override
public void onPreLaunch() {
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.spark_profile_launch.OnFabric")) {
CommonModUtil.runWithoutCrash(() -> SparkLaunchProfiler.start("launch"), "Failed to start profiler");
}
}
}

View File

@ -0,0 +1,174 @@
package org.embeddedt.modernfix.fabric.spark;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
import me.lucko.spark.common.SparkPlatform;
import me.lucko.spark.common.SparkPlugin;
import me.lucko.spark.common.command.sender.CommandSender;
import me.lucko.spark.common.platform.AbstractPlatformInfo;
import me.lucko.spark.common.platform.PlatformInfo;
import me.lucko.spark.common.sampler.Sampler;
import me.lucko.spark.common.sampler.ThreadDumper;
import me.lucko.spark.common.sampler.ThreadGrouper;
import me.lucko.spark.common.sampler.ThreadNodeOrder;
import me.lucko.spark.common.sampler.async.AsyncProfilerAccess;
import me.lucko.spark.common.sampler.async.AsyncSampler;
import me.lucko.spark.common.sampler.java.JavaSampler;
import me.lucko.spark.common.sampler.node.MergeMode;
import me.lucko.spark.common.util.MethodDisambiguator;
import me.lucko.spark.lib.adventure.text.Component;
import me.lucko.spark.lib.okhttp3.MediaType;
import net.fabricmc.api.EnvType;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Stream;
/* Inspired by CensoredASM */
public class SparkLaunchProfiler {
private static PlatformInfo platformInfo = new ModernFixPlatformInfo();
private static CommandSender commandSender = new ModernFixCommandSender();
private static Map<String, Sampler> ongoingSamplers = new Object2ReferenceOpenHashMap<>();
private static MediaType mediaType = MediaType.parse("application/x-spark-sampler");
private static ExecutorService executor = Executors.newSingleThreadScheduledExecutor((new ThreadFactoryBuilder()).setNameFormat("spark-modernfix-async-worker").build());
private static final SparkPlatform platform = new SparkPlatform(new ModernFixSparkPlugin());
private static final boolean USE_JAVA_SAMPLER_FOR_LAUNCH = true; //Boolean.getBoolean("modernfix.profileLaunchWithJavaSampler");
public static void start(String key) {
if (!ongoingSamplers.containsKey(key)) {
Sampler sampler;
try {
if(USE_JAVA_SAMPLER_FOR_LAUNCH) {
throw new UnsupportedOperationException();
}
AsyncProfilerAccess.INSTANCE.getProfiler();
sampler = new AsyncSampler(4000, ThreadDumper.ALL, ThreadGrouper.BY_NAME);
} catch (UnsupportedOperationException e) {
sampler = new JavaSampler(4000, ThreadDumper.ALL, ThreadGrouper.BY_NAME, -1, true, true);
}
ongoingSamplers.put(key, sampler);
ModernFixMixinPlugin.instance.logger.warn("Profiler has started for stage [{}]...", key);
sampler.start();
}
}
public static void stop(String key) {
Sampler sampler = ongoingSamplers.remove(key);
if (sampler != null) {
sampler.stop();
output(key, sampler);
}
}
private static void output(String key, Sampler sampler) {
executor.execute(() -> {
ModernFixMixinPlugin.instance.logger.warn("Stage [{}] profiler has stopped! Uploading results...", key);
byte[] output = sampler.formCompressedDataPayload(new Sampler.ExportProps(platformInfo, commandSender, ThreadNodeOrder.BY_TIME, "Stage: " + key, MergeMode.separateParentCalls(new MethodDisambiguator()), platform.getClassSourceLookup()));
try {
String urlKey = SparkPlatform.BYTEBIN_CLIENT.postContent(output, mediaType).key();
String url = "https://spark.lucko.me/" + urlKey;
ModernFixMixinPlugin.instance.logger.warn("Profiler results for Stage [{}]: {}", key, url);
} catch (Exception e) {
ModernFixMixinPlugin.instance.logger.fatal("An error occurred whilst uploading the results.", e);
}
});
}
static class ModernFixPlatformInfo extends AbstractPlatformInfo {
@Override
public Type getType() {
return FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT ? Type.CLIENT : Type.SERVER;
}
@Override
public String getName() {
return "ModernFix";
}
private static String versionOfModContainer(Optional<ModContainer> containerOpt) {
return containerOpt.map(container -> container.getMetadata().getVersion().toString()).orElse("unknown");
}
@Override
public String getVersion() {
return versionOfModContainer(FabricLoader.getInstance().getModContainer("modernfix"));
}
@Override
public String getMinecraftVersion() {
return versionOfModContainer(FabricLoader.getInstance().getModContainer("minecraft"));
}
}
public static class ModernFixCommandSender implements CommandSender {
private final UUID uuid = UUID.randomUUID();
private final String name;
public ModernFixCommandSender() {
this.name = "ModernFix";
}
@Override
public String getName() {
return name;
}
@Override
public UUID getUniqueId() {
return uuid;
}
@Override
public boolean hasPermission(String s) {
return true;
}
@Override
public void sendMessage(Component component) {
}
}
static class ModernFixSparkPlugin implements SparkPlugin {
@Override
public String getVersion() {
return "1.0";
}
@Override
public Path getPluginDirectory() {
return FabricLoader.getInstance().getGameDir().resolve("spark-modernfix");
}
@Override
public String getCommandName() {
return "spark-modernfix";
}
@Override
public Stream<? extends CommandSender> getCommandSenders() {
return Stream.of();
}
@Override
public void executeAsync(Runnable runnable) {
executor.execute(runnable);
}
@Override
public PlatformInfo getPlatformInfo() {
return platformInfo;
}
}
}

View File

@ -19,6 +19,9 @@ import net.minecraft.server.packs.resources.Resource;
import net.minecraft.server.packs.resources.ResourceManager;
import org.embeddedt.modernfix.ModernFixFabric;
import org.embeddedt.modernfix.api.constants.IntegrationConstants;
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
import org.embeddedt.modernfix.fabric.spark.SparkLaunchProfiler;
import org.embeddedt.modernfix.util.CommonModUtil;
import org.objectweb.asm.tree.*;
import java.nio.file.Path;
@ -109,4 +112,10 @@ public class ModernFixPlatformHooksImpl {
}
return modOptions;
}
public static void onLaunchComplete() {
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.spark_profile_launch.OnFabric")) {
CommonModUtil.runWithoutCrash(() -> SparkLaunchProfiler.stop("launch"), "Failed to stop profiler");
}
}
}

View File

@ -22,6 +22,9 @@
"client": [
"org.embeddedt.modernfix.ModernFixClientFabric"
],
"preLaunch": [
"org.embeddedt.modernfix.ModernFixPreLaunchFabric"
],
"modmenu": [ "org.embeddedt.modernfix.fabric.modmenu.ModernFixModMenuApiImpl" ]
},
"mixins": [

View File

@ -50,6 +50,7 @@ dependencies {
modCompileOnly files("deps/starlight-1.2.jar")
modCompileOnly("appeng:appliedenergistics2:8.4.7")
modCompileOnly("vazkii.patchouli:Patchouli:1.16.4-53.3")
modImplementation "curse.maven:spark-361579:${rootProject.spark_forge_version}"
common(project(path: ":common", configuration: "namedElements")) { transitive false }
shadowCommon(project(path: ":common", configuration: "transformProductionForge")) { transitive = false }

View File

@ -0,0 +1,177 @@
package org.embeddedt.modernfix.forge.spark;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
import me.lucko.spark.common.SparkPlatform;
import me.lucko.spark.common.SparkPlugin;
import me.lucko.spark.common.command.sender.CommandSender;
import me.lucko.spark.common.platform.PlatformInfo;
import me.lucko.spark.common.sampler.Sampler;
import me.lucko.spark.common.sampler.ThreadDumper;
import me.lucko.spark.common.sampler.ThreadGrouper;
import me.lucko.spark.common.sampler.ThreadNodeOrder;
import me.lucko.spark.common.sampler.async.AsyncProfilerAccess;
import me.lucko.spark.common.sampler.async.AsyncSampler;
import me.lucko.spark.common.sampler.java.JavaSampler;
import me.lucko.spark.common.sampler.node.MergeMode;
import me.lucko.spark.common.util.MethodDisambiguator;
import me.lucko.spark.lib.adventure.text.Component;
import me.lucko.spark.lib.okhttp3.MediaType;
import me.lucko.spark.proto.SparkSamplerProtos;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.FMLPaths;
import net.minecraftforge.fml.loading.LoadingModList;
import net.minecraftforge.versions.mcp.MCPVersion;
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
import java.nio.file.Path;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.stream.Stream;
/* Inspired by CensoredASM */
public class SparkLaunchProfiler {
private static PlatformInfo platformInfo = new ModernFixPlatformInfo();
private static CommandSender commandSender = new ModernFixCommandSender();
private static Map<String, Sampler> ongoingSamplers = new Object2ReferenceOpenHashMap<>();
private static MediaType mediaType = MediaType.parse("application/x-spark-sampler");
private static ExecutorService executor = Executors.newSingleThreadScheduledExecutor((new ThreadFactoryBuilder()).setNameFormat("spark-modernfix-async-worker").build());
private static final SparkPlatform platform = new SparkPlatform(new ModernFixSparkPlugin());
private static final boolean USE_JAVA_SAMPLER_FOR_LAUNCH = true; //Boolean.getBoolean("modernfix.profileLaunchWithJavaSampler");
public static void start(String key) {
if (!ongoingSamplers.containsKey(key)) {
Sampler sampler;
try {
if(USE_JAVA_SAMPLER_FOR_LAUNCH) {
throw new UnsupportedOperationException();
}
AsyncProfilerAccess.INSTANCE.getProfiler();
sampler = new AsyncSampler(4000, ThreadDumper.ALL, ThreadGrouper.BY_NAME, -1);
} catch (UnsupportedOperationException e) {
sampler = new JavaSampler(4000, ThreadDumper.ALL, ThreadGrouper.BY_NAME, -1, true, true);
}
ongoingSamplers.put(key, sampler);
ModernFixMixinPlugin.instance.logger.warn("Profiler has started for stage [{}]...", key);
sampler.start();
}
}
public static void stop(String key) {
Sampler sampler = ongoingSamplers.remove(key);
if (sampler != null) {
sampler.stop();
output(key, sampler);
}
}
private static void output(String key, Sampler sampler) {
executor.execute(() -> {
ModernFixMixinPlugin.instance.logger.warn("Stage [{}] profiler has stopped! Uploading results...", key);
SparkSamplerProtos.SamplerData output = sampler.toProto(platform, commandSender, ThreadNodeOrder.BY_TIME, "Stage: " + key, MergeMode.separateParentCalls(new MethodDisambiguator()), platform.createClassSourceLookup());
try {
String urlKey = platform.getBytebinClient().postContent(output, mediaType).key();
String url = "https://spark.lucko.me/" + urlKey;
ModernFixMixinPlugin.instance.logger.warn("Profiler results for Stage [{}]: {}", key, url);
} catch (Exception e) {
ModernFixMixinPlugin.instance.logger.fatal("An error occurred whilst uploading the results.", e);
}
});
}
static class ModernFixPlatformInfo implements PlatformInfo {
@Override
public Type getType() {
return FMLLoader.getDist().isClient() ? Type.CLIENT : Type.SERVER;
}
@Override
public String getName() {
return "ModernFix";
}
@Override
public String getVersion() {
return LoadingModList.get().getModFileById("modernfix").getMods().get(0).getVersion().toString();
}
@Override
public String getMinecraftVersion() {
return MCPVersion.getMCVersion();
}
}
public static class ModernFixCommandSender implements CommandSender {
private final UUID uuid = UUID.randomUUID();
private final String name;
public ModernFixCommandSender() {
this.name = "ModernFix";
}
@Override
public String getName() {
return name;
}
@Override
public UUID getUniqueId() {
return uuid;
}
@Override
public boolean hasPermission(String s) {
return true;
}
@Override
public void sendMessage(Component component) {
}
}
static class ModernFixSparkPlugin implements SparkPlugin {
@Override
public String getVersion() {
return "1.0";
}
@Override
public Path getPluginDirectory() {
return FMLPaths.GAMEDIR.get().resolve("spark-modernfix");
}
@Override
public String getCommandName() {
return "spark-modernfix";
}
@Override
public Stream<? extends CommandSender> getCommandSenders() {
return Stream.of();
}
@Override
public void executeAsync(Runnable runnable) {
executor.execute(runnable);
}
@Override
public void log(Level level, String s) {
ModernFixMixinPlugin.instance.logger.warn(s);
}
@Override
public PlatformInfo getPlatformInfo() {
return platformInfo;
}
}
}

View File

@ -30,6 +30,8 @@ import org.embeddedt.modernfix.api.constants.IntegrationConstants;
import org.embeddedt.modernfix.forge.classloading.FastAccessTransformerList;
import org.embeddedt.modernfix.forge.classloading.ModernFixResourceFinder;
import org.embeddedt.modernfix.forge.packet.PacketHandler;
import org.embeddedt.modernfix.forge.spark.SparkLaunchProfiler;
import org.embeddedt.modernfix.util.CommonModUtil;
import org.embeddedt.modernfix.util.DummyList;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.ClassNode;
@ -174,6 +176,10 @@ public class ModernFixPlatformHooksImpl {
} catch(RuntimeException | ReflectiveOperationException e) {
ModernFixMixinPlugin.instance.logger.error("Failed to patch mixin memory leak", e);
}
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.spark_profile_launch.OnForge")) {
CommonModUtil.runWithoutCrash(() -> SparkLaunchProfiler.start("launch"), "Failed to start profiler");
}
}
private Method defineClassMethod = null;
@ -245,4 +251,10 @@ public class ModernFixPlatformHooksImpl {
}
return modOptions;
}
public static void onLaunchComplete() {
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.spark_profile_launch.OnForge")) {
CommonModUtil.runWithoutCrash(() -> SparkLaunchProfiler.stop("launch"), "Failed to stop profiler");
}
}
}

View File

@ -17,4 +17,7 @@ supported_minecraft_versions=1.16.4,1.16.5
fabric_loader_version=0.14.18
fabric_api_version=0.42.0+1.16
modmenu_version=1.16.23
modmenu_version=1.16.23
spark_forge_version=3767277
spark_fabric_version=3337642