2025-11-24 22:52:51 +03:00

311 lines
12 KiB
Java

/*
* Decompiled with CFR 0.152.
*
* Could not load the following classes:
* com.mojang.logging.LogUtils
* org.jspecify.annotations.Nullable
* org.slf4j.Logger
*/
package net.minecraft.util.profiling.jfr;
import com.mojang.logging.LogUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.SocketAddress;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.text.ParseException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeFormatterBuilder;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import jdk.jfr.Configuration;
import jdk.jfr.Event;
import jdk.jfr.FlightRecorder;
import jdk.jfr.FlightRecorderListener;
import jdk.jfr.Recording;
import net.minecraft.SharedConstants;
import net.minecraft.core.Holder;
import net.minecraft.network.ConnectionProtocol;
import net.minecraft.network.protocol.PacketType;
import net.minecraft.resources.ResourceKey;
import net.minecraft.util.FileUtil;
import net.minecraft.util.Util;
import net.minecraft.util.profiling.jfr.Environment;
import net.minecraft.util.profiling.jfr.JvmProfiler;
import net.minecraft.util.profiling.jfr.SummaryReporter;
import net.minecraft.util.profiling.jfr.callback.ProfiledDuration;
import net.minecraft.util.profiling.jfr.event.ChunkGenerationEvent;
import net.minecraft.util.profiling.jfr.event.ChunkRegionReadEvent;
import net.minecraft.util.profiling.jfr.event.ChunkRegionWriteEvent;
import net.minecraft.util.profiling.jfr.event.ClientFpsEvent;
import net.minecraft.util.profiling.jfr.event.NetworkSummaryEvent;
import net.minecraft.util.profiling.jfr.event.PacketReceivedEvent;
import net.minecraft.util.profiling.jfr.event.PacketSentEvent;
import net.minecraft.util.profiling.jfr.event.ServerTickTimeEvent;
import net.minecraft.util.profiling.jfr.event.StructureGenerationEvent;
import net.minecraft.util.profiling.jfr.event.WorldLoadFinishedEvent;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.chunk.storage.RegionFileVersion;
import net.minecraft.world.level.chunk.storage.RegionStorageInfo;
import net.minecraft.world.level.levelgen.structure.Structure;
import org.jspecify.annotations.Nullable;
import org.slf4j.Logger;
public class JfrProfiler
implements JvmProfiler {
private static final Logger LOGGER = LogUtils.getLogger();
public static final String ROOT_CATEGORY = "Minecraft";
public static final String WORLD_GEN_CATEGORY = "World Generation";
public static final String TICK_CATEGORY = "Ticking";
public static final String NETWORK_CATEGORY = "Network";
public static final String STORAGE_CATEGORY = "Storage";
private static final List<Class<? extends Event>> CUSTOM_EVENTS = List.of(ChunkGenerationEvent.class, ChunkRegionReadEvent.class, ChunkRegionWriteEvent.class, PacketReceivedEvent.class, PacketSentEvent.class, NetworkSummaryEvent.class, ServerTickTimeEvent.class, ClientFpsEvent.class, StructureGenerationEvent.class, WorldLoadFinishedEvent.class);
private static final String FLIGHT_RECORDER_CONFIG = "/flightrecorder-config.jfc";
private static final DateTimeFormatter DATE_TIME_FORMATTER = new DateTimeFormatterBuilder().appendPattern("yyyy-MM-dd-HHmmss").toFormatter(Locale.ROOT).withZone(ZoneId.systemDefault());
private static final JfrProfiler INSTANCE = new JfrProfiler();
private @Nullable Recording recording;
private int currentFPS;
private float currentAverageTickTimeServer;
private final Map<String, NetworkSummaryEvent.SumAggregation> networkTrafficByAddress = new ConcurrentHashMap<String, NetworkSummaryEvent.SumAggregation>();
private final Runnable periodicClientFps = () -> new ClientFpsEvent(this.currentFPS).commit();
private final Runnable periodicServerTickTime = () -> new ServerTickTimeEvent(this.currentAverageTickTimeServer).commit();
private final Runnable periodicNetworkSummary = () -> {
Iterator<NetworkSummaryEvent.SumAggregation> iterator = this.networkTrafficByAddress.values().iterator();
while (iterator.hasNext()) {
iterator.next().commitEvent();
iterator.remove();
}
};
private JfrProfiler() {
CUSTOM_EVENTS.forEach(FlightRecorder::register);
this.registerPeriodicEvents();
FlightRecorder.addListener(new FlightRecorderListener(){
@Override
public void recordingStateChanged(Recording rec) {
switch (rec.getState()) {
case STOPPED: {
JfrProfiler.this.registerPeriodicEvents();
break;
}
}
}
});
}
private void registerPeriodicEvents() {
JfrProfiler.addPeriodicEvent(ClientFpsEvent.class, this.periodicClientFps);
JfrProfiler.addPeriodicEvent(ServerTickTimeEvent.class, this.periodicServerTickTime);
JfrProfiler.addPeriodicEvent(NetworkSummaryEvent.class, this.periodicNetworkSummary);
}
private static void addPeriodicEvent(Class<? extends Event> eventClass, Runnable runnable) {
FlightRecorder.removePeriodicEvent(runnable);
FlightRecorder.addPeriodicEvent(eventClass, runnable);
}
public static JfrProfiler getInstance() {
return INSTANCE;
}
@Override
public boolean start(Environment environment) {
boolean bl;
URL resource = JfrProfiler.class.getResource(FLIGHT_RECORDER_CONFIG);
if (resource == null) {
LOGGER.warn("Could not find default flight recorder config at {}", (Object)FLIGHT_RECORDER_CONFIG);
return false;
}
BufferedReader reader = new BufferedReader(new InputStreamReader(resource.openStream(), StandardCharsets.UTF_8));
try {
bl = this.start(reader, environment);
}
catch (Throwable throwable) {
try {
try {
reader.close();
}
catch (Throwable throwable2) {
throwable.addSuppressed(throwable2);
}
throw throwable;
}
catch (IOException e) {
LOGGER.warn("Failed to start flight recorder using configuration at {}", (Object)resource, (Object)e);
return false;
}
}
reader.close();
return bl;
}
@Override
public Path stop() {
if (this.recording == null) {
throw new IllegalStateException("Not currently profiling");
}
this.networkTrafficByAddress.clear();
Path report = this.recording.getDestination();
this.recording.stop();
return report;
}
@Override
public boolean isRunning() {
return this.recording != null;
}
@Override
public boolean isAvailable() {
return FlightRecorder.isAvailable();
}
private boolean start(Reader configurationFile, Environment environment) {
if (this.isRunning()) {
LOGGER.warn("Profiling already in progress");
return false;
}
try {
Configuration jfrConfig = Configuration.create(configurationFile);
String startTimestamp = DATE_TIME_FORMATTER.format(Instant.now());
this.recording = Util.make(new Recording(jfrConfig), self -> {
CUSTOM_EVENTS.forEach(self::enable);
self.setDumpOnExit(true);
self.setToDisk(true);
self.setName(String.format(Locale.ROOT, "%s-%s-%s", environment.getDescription(), SharedConstants.getCurrentVersion().name(), startTimestamp));
});
Path destination = Paths.get(String.format(Locale.ROOT, "debug/%s-%s.jfr", environment.getDescription(), startTimestamp), new String[0]);
FileUtil.createDirectoriesSafe(destination.getParent());
this.recording.setDestination(destination);
this.recording.start();
this.setupSummaryListener();
}
catch (IOException | ParseException exception) {
LOGGER.warn("Failed to start jfr profiling", (Throwable)exception);
return false;
}
LOGGER.info("Started flight recorder profiling id({}):name({}) - will dump to {} on exit or stop command", new Object[]{this.recording.getId(), this.recording.getName(), this.recording.getDestination()});
return true;
}
private void setupSummaryListener() {
FlightRecorder.addListener(new FlightRecorderListener(){
final SummaryReporter summaryReporter = new SummaryReporter(() -> {
JfrProfiler.this.recording = null;
});
@Override
public void recordingStateChanged(Recording rec) {
if (rec != JfrProfiler.this.recording) {
return;
}
switch (rec.getState()) {
case STOPPED: {
this.summaryReporter.recordingStopped(rec.getDestination());
FlightRecorder.removeListener(this);
break;
}
}
}
});
}
@Override
public void onClientTick(int fps) {
if (ClientFpsEvent.TYPE.isEnabled()) {
this.currentFPS = fps;
}
}
@Override
public void onServerTick(float currentAverageTickTime) {
if (ServerTickTimeEvent.TYPE.isEnabled()) {
this.currentAverageTickTimeServer = currentAverageTickTime;
}
}
@Override
public void onPacketReceived(ConnectionProtocol protocol, PacketType<?> packetId, SocketAddress remoteAddress, int readableBytes) {
if (PacketReceivedEvent.TYPE.isEnabled()) {
new PacketReceivedEvent(protocol.id(), packetId.flow().id(), packetId.id().toString(), remoteAddress, readableBytes).commit();
}
if (NetworkSummaryEvent.TYPE.isEnabled()) {
this.networkStatFor(remoteAddress).trackReceivedPacket(readableBytes);
}
}
@Override
public void onPacketSent(ConnectionProtocol protocol, PacketType<?> packetId, SocketAddress remoteAddress, int writtenBytes) {
if (PacketSentEvent.TYPE.isEnabled()) {
new PacketSentEvent(protocol.id(), packetId.flow().id(), packetId.id().toString(), remoteAddress, writtenBytes).commit();
}
if (NetworkSummaryEvent.TYPE.isEnabled()) {
this.networkStatFor(remoteAddress).trackSentPacket(writtenBytes);
}
}
private NetworkSummaryEvent.SumAggregation networkStatFor(SocketAddress remoteAddress) {
return this.networkTrafficByAddress.computeIfAbsent(remoteAddress.toString(), NetworkSummaryEvent.SumAggregation::new);
}
@Override
public void onRegionFileRead(RegionStorageInfo info, ChunkPos pos, RegionFileVersion version, int readBytes) {
if (ChunkRegionReadEvent.TYPE.isEnabled()) {
new ChunkRegionReadEvent(info, pos, version, readBytes).commit();
}
}
@Override
public void onRegionFileWrite(RegionStorageInfo info, ChunkPos pos, RegionFileVersion version, int writtenBytes) {
if (ChunkRegionWriteEvent.TYPE.isEnabled()) {
new ChunkRegionWriteEvent(info, pos, version, writtenBytes).commit();
}
}
@Override
public @Nullable ProfiledDuration onWorldLoadedStarted() {
if (!WorldLoadFinishedEvent.TYPE.isEnabled()) {
return null;
}
WorldLoadFinishedEvent event = new WorldLoadFinishedEvent();
event.begin();
return ignored -> event.commit();
}
@Override
public @Nullable ProfiledDuration onChunkGenerate(ChunkPos pos, ResourceKey<Level> dimension, String name) {
if (!ChunkGenerationEvent.TYPE.isEnabled()) {
return null;
}
ChunkGenerationEvent event = new ChunkGenerationEvent(pos, dimension, name);
event.begin();
return ignored -> event.commit();
}
@Override
public @Nullable ProfiledDuration onStructureGenerate(ChunkPos sourceChunkPos, ResourceKey<Level> dimension, Holder<Structure> structure) {
if (!StructureGenerationEvent.TYPE.isEnabled()) {
return null;
}
StructureGenerationEvent event = new StructureGenerationEvent(sourceChunkPos, structure, dimension);
event.begin();
return success -> {
event.success = success;
event.commit();
};
}
}