Skip to content

Commit 60525ad

Browse files
committed
Add option to profile launch using Spark
1 parent b611830 commit 60525ad

File tree

14 files changed

+421
-1
lines changed

14 files changed

+421
-1
lines changed

common/src/main/java/org/embeddedt/modernfix/ModernFix.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ public void onServerStarted() {
7171
if(ModernFixPlatformHooks.isDedicatedServer()) {
7272
float gameStartTime = ManagementFactory.getRuntimeMXBean().getUptime() / 1000f;
7373
ModernFix.LOGGER.warn("Dedicated server took " + gameStartTime + " seconds to load");
74+
ModernFixPlatformHooks.onLaunchComplete();
7475
}
7576
ClassInfoManager.clear();
7677
}

common/src/main/java/org/embeddedt/modernfix/ModernFixClient.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ public void onScreenOpening(Screen openingScreen) {
6565
} else if (openingScreen instanceof TitleScreen && gameStartTimeSeconds < 0) {
6666
gameStartTimeSeconds = ManagementFactory.getRuntimeMXBean().getUptime() / 1000f;
6767
ModernFix.LOGGER.warn("Game took " + gameStartTimeSeconds + " seconds to start");
68+
ModernFixPlatformHooks.onLaunchComplete();
6869
}
6970
}
7071

common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ else if(isClientOnly && !ModernFixPlatformHooks.isClient())
153153
.put("mixin.perf.faster_item_rendering", false)
154154
.put("mixin.feature.spam_thread_dump", false)
155155
.put("mixin.feature.snapshot_easter_egg", true)
156+
.put("mixin.feature.spark_profile_launch", false)
156157
.put("mixin.perf.blast_search_trees", shouldReplaceSearchTrees)
157158
.put("mixin.devenv", isDevEnv)
158159
.put("mixin.perf.remove_spawn_chunks", isDevEnv)

common/src/main/java/org/embeddedt/modernfix/platform/ModernFixPlatformHooks.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,4 +91,9 @@ public static void onServerCommandRegister(Consumer<CommandDispatcher<CommandSou
9191
public static Multimap<String, String> getCustomModOptions() {
9292
throw new AssertionError();
9393
}
94+
95+
@ExpectPlatform
96+
public static void onLaunchComplete() {
97+
98+
}
9499
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.embeddedt.modernfix.util;
2+
3+
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
4+
5+
public class CommonModUtil {
6+
/**
7+
* Avoid using this, it's bad practice but cleanest way of suppressing errors for nonessential mod-dependent
8+
* functionality.
9+
*/
10+
public static void runWithoutCrash(Runnable r, String errorMsg) {
11+
try {
12+
r.run();
13+
} catch(Throwable e) {
14+
ModernFixMixinPlugin.instance.logger.error(errorMsg, e);
15+
}
16+
}
17+
}

fabric/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ dependencies {
3333
modIncludeImplementation(fabricApi.module("fabric-models-v0", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
3434
modImplementation(fabricApi.module("fabric-resource-loader-v0", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' }
3535
modCompileOnly("com.terraformersmc:modmenu:${rootProject.modmenu_version}") { transitive false }
36+
modImplementation "curse.maven:spark-361579:${rootProject.spark_fabric_version}"
3637
modRuntimeOnly("net.fabricmc.fabric-api:fabric-api:${rootProject.fabric_api_version}") { exclude group: 'net.fabricmc', module: 'fabric-loader' }
3738
// Remove the next line if you don't want to depend on the API
3839
// modApi "me.shedaniel:architectury-fabric:${rootProject.architectury_version}"
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.embeddedt.modernfix;
2+
3+
import net.fabricmc.loader.api.entrypoint.PreLaunchEntrypoint;
4+
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
5+
import org.embeddedt.modernfix.fabric.spark.SparkLaunchProfiler;
6+
import org.embeddedt.modernfix.util.CommonModUtil;
7+
8+
public class ModernFixPreLaunchFabric implements PreLaunchEntrypoint {
9+
@Override
10+
public void onPreLaunch() {
11+
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.spark_profile_launch.OnFabric")) {
12+
CommonModUtil.runWithoutCrash(() -> SparkLaunchProfiler.start("launch"), "Failed to start profiler");
13+
}
14+
}
15+
}
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
package org.embeddedt.modernfix.fabric.spark;
2+
3+
import com.google.common.util.concurrent.ThreadFactoryBuilder;
4+
import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
5+
import me.lucko.spark.common.SparkPlatform;
6+
import me.lucko.spark.common.SparkPlugin;
7+
import me.lucko.spark.common.command.sender.CommandSender;
8+
import me.lucko.spark.common.platform.AbstractPlatformInfo;
9+
import me.lucko.spark.common.platform.PlatformInfo;
10+
import me.lucko.spark.common.sampler.Sampler;
11+
import me.lucko.spark.common.sampler.ThreadDumper;
12+
import me.lucko.spark.common.sampler.ThreadGrouper;
13+
import me.lucko.spark.common.sampler.ThreadNodeOrder;
14+
import me.lucko.spark.common.sampler.async.AsyncProfilerAccess;
15+
import me.lucko.spark.common.sampler.async.AsyncSampler;
16+
import me.lucko.spark.common.sampler.java.JavaSampler;
17+
import me.lucko.spark.common.sampler.node.MergeMode;
18+
import me.lucko.spark.common.util.MethodDisambiguator;
19+
import me.lucko.spark.lib.adventure.text.Component;
20+
import me.lucko.spark.lib.okhttp3.MediaType;
21+
import net.fabricmc.api.EnvType;
22+
import net.fabricmc.loader.api.FabricLoader;
23+
import net.fabricmc.loader.api.ModContainer;
24+
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
25+
26+
import java.nio.file.Path;
27+
import java.util.Map;
28+
import java.util.Optional;
29+
import java.util.UUID;
30+
import java.util.concurrent.ExecutorService;
31+
import java.util.concurrent.Executors;
32+
import java.util.stream.Stream;
33+
34+
/* Inspired by CensoredASM */
35+
public class SparkLaunchProfiler {
36+
private static PlatformInfo platformInfo = new ModernFixPlatformInfo();
37+
private static CommandSender commandSender = new ModernFixCommandSender();
38+
private static Map<String, Sampler> ongoingSamplers = new Object2ReferenceOpenHashMap<>();
39+
private static MediaType mediaType = MediaType.parse("application/x-spark-sampler");
40+
private static ExecutorService executor = Executors.newSingleThreadScheduledExecutor((new ThreadFactoryBuilder()).setNameFormat("spark-modernfix-async-worker").build());
41+
private static final SparkPlatform platform = new SparkPlatform(new ModernFixSparkPlugin());
42+
43+
private static final boolean USE_JAVA_SAMPLER_FOR_LAUNCH = true; //Boolean.getBoolean("modernfix.profileLaunchWithJavaSampler");
44+
45+
public static void start(String key) {
46+
if (!ongoingSamplers.containsKey(key)) {
47+
Sampler sampler;
48+
try {
49+
if(USE_JAVA_SAMPLER_FOR_LAUNCH) {
50+
throw new UnsupportedOperationException();
51+
}
52+
AsyncProfilerAccess.INSTANCE.getProfiler();
53+
sampler = new AsyncSampler(4000, ThreadDumper.ALL, ThreadGrouper.BY_NAME);
54+
} catch (UnsupportedOperationException e) {
55+
sampler = new JavaSampler(4000, ThreadDumper.ALL, ThreadGrouper.BY_NAME, -1, true, true);
56+
}
57+
ongoingSamplers.put(key, sampler);
58+
ModernFixMixinPlugin.instance.logger.warn("Profiler has started for stage [{}]...", key);
59+
sampler.start();
60+
}
61+
}
62+
63+
public static void stop(String key) {
64+
Sampler sampler = ongoingSamplers.remove(key);
65+
if (sampler != null) {
66+
sampler.stop();
67+
output(key, sampler);
68+
}
69+
}
70+
71+
private static void output(String key, Sampler sampler) {
72+
executor.execute(() -> {
73+
ModernFixMixinPlugin.instance.logger.warn("Stage [{}] profiler has stopped! Uploading results...", key);
74+
byte[] output = sampler.formCompressedDataPayload(new Sampler.ExportProps(platformInfo, commandSender, ThreadNodeOrder.BY_TIME, "Stage: " + key, MergeMode.separateParentCalls(new MethodDisambiguator()), platform.getClassSourceLookup()));
75+
try {
76+
String urlKey = SparkPlatform.BYTEBIN_CLIENT.postContent(output, mediaType).key();
77+
String url = "https://spark.lucko.me/" + urlKey;
78+
ModernFixMixinPlugin.instance.logger.warn("Profiler results for Stage [{}]: {}", key, url);
79+
} catch (Exception e) {
80+
ModernFixMixinPlugin.instance.logger.fatal("An error occurred whilst uploading the results.", e);
81+
}
82+
});
83+
}
84+
85+
static class ModernFixPlatformInfo extends AbstractPlatformInfo {
86+
87+
@Override
88+
public Type getType() {
89+
return FabricLoader.getInstance().getEnvironmentType() == EnvType.CLIENT ? Type.CLIENT : Type.SERVER;
90+
}
91+
92+
@Override
93+
public String getName() {
94+
return "ModernFix";
95+
}
96+
97+
private static String versionOfModContainer(Optional<ModContainer> containerOpt) {
98+
return containerOpt.map(container -> container.getMetadata().getVersion().toString()).orElse("unknown");
99+
}
100+
101+
@Override
102+
public String getVersion() {
103+
return versionOfModContainer(FabricLoader.getInstance().getModContainer("modernfix"));
104+
}
105+
106+
@Override
107+
public String getMinecraftVersion() {
108+
return versionOfModContainer(FabricLoader.getInstance().getModContainer("minecraft"));
109+
}
110+
}
111+
112+
public static class ModernFixCommandSender implements CommandSender {
113+
114+
private final UUID uuid = UUID.randomUUID();
115+
private final String name;
116+
117+
public ModernFixCommandSender() {
118+
this.name = "ModernFix";
119+
}
120+
121+
@Override
122+
public String getName() {
123+
return name;
124+
}
125+
126+
@Override
127+
public UUID getUniqueId() {
128+
return uuid;
129+
}
130+
131+
@Override
132+
public boolean hasPermission(String s) {
133+
return true;
134+
}
135+
136+
@Override
137+
public void sendMessage(Component component) {
138+
139+
}
140+
}
141+
142+
static class ModernFixSparkPlugin implements SparkPlugin {
143+
144+
@Override
145+
public String getVersion() {
146+
return "1.0";
147+
}
148+
149+
@Override
150+
public Path getPluginDirectory() {
151+
return FabricLoader.getInstance().getGameDir().resolve("spark-modernfix");
152+
}
153+
154+
@Override
155+
public String getCommandName() {
156+
return "spark-modernfix";
157+
}
158+
159+
@Override
160+
public Stream<? extends CommandSender> getCommandSenders() {
161+
return Stream.of();
162+
}
163+
164+
@Override
165+
public void executeAsync(Runnable runnable) {
166+
executor.execute(runnable);
167+
}
168+
169+
@Override
170+
public PlatformInfo getPlatformInfo() {
171+
return platformInfo;
172+
}
173+
}
174+
}

fabric/src/main/java/org/embeddedt/modernfix/platform/fabric/ModernFixPlatformHooksImpl.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919
import net.minecraft.server.packs.resources.ResourceManager;
2020
import org.embeddedt.modernfix.ModernFixFabric;
2121
import org.embeddedt.modernfix.api.constants.IntegrationConstants;
22+
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
23+
import org.embeddedt.modernfix.fabric.spark.SparkLaunchProfiler;
24+
import org.embeddedt.modernfix.util.CommonModUtil;
2225
import org.objectweb.asm.tree.*;
2326

2427
import java.nio.file.Path;
@@ -109,4 +112,10 @@ public static Multimap<String, String> getCustomModOptions() {
109112
}
110113
return modOptions;
111114
}
115+
116+
public static void onLaunchComplete() {
117+
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.spark_profile_launch.OnFabric")) {
118+
CommonModUtil.runWithoutCrash(() -> SparkLaunchProfiler.stop("launch"), "Failed to stop profiler");
119+
}
120+
}
112121
}

fabric/src/main/resources/fabric.mod.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
"client": [
2323
"org.embeddedt.modernfix.ModernFixClientFabric"
2424
],
25+
"preLaunch": [
26+
"org.embeddedt.modernfix.ModernFixPreLaunchFabric"
27+
],
2528
"modmenu": [ "org.embeddedt.modernfix.fabric.modmenu.ModernFixModMenuApiImpl" ]
2629
},
2730
"mixins": [

0 commit comments

Comments
 (0)