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.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 javax.annotation.Nullable; import jdk.jfr.Configuration; import jdk.jfr.Event; import jdk.jfr.FlightRecorder; import jdk.jfr.FlightRecorderListener; import jdk.jfr.Recording; import jdk.jfr.RecordingState; import net.minecraft.FileUtil; import net.minecraft.SharedConstants; import net.minecraft.Util; 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.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.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.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> CUSTOM_EVENTS = List.of( ChunkGenerationEvent.class, ChunkRegionReadEvent.class, ChunkRegionWriteEvent.class, PacketReceivedEvent.class, PacketSentEvent.class, NetworkSummaryEvent.class, ServerTickTimeEvent.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() .withZone(ZoneId.systemDefault()); private static final JfrProfiler INSTANCE = new JfrProfiler(); @Nullable Recording recording; private float currentAverageTickTime; private final Map networkTrafficByAddress = new ConcurrentHashMap<>(); private JfrProfiler() { CUSTOM_EVENTS.forEach(FlightRecorder::register); FlightRecorder.addPeriodicEvent(ServerTickTimeEvent.class, () -> new ServerTickTimeEvent(this.currentAverageTickTime).commit()); FlightRecorder.addPeriodicEvent(NetworkSummaryEvent.class, () -> { Iterator iterator = this.networkTrafficByAddress.values().iterator(); while (iterator.hasNext()) { iterator.next().commitEvent(); iterator.remove(); } }); } public static JfrProfiler getInstance() { return INSTANCE; } @Override public boolean start(Environment p_185307_) { URL url = JfrProfiler.class.getResource("/flightrecorder-config.jfc"); if (url == null) { LOGGER.warn("Could not find default flight recorder config at {}", "/flightrecorder-config.jfc"); return false; } else { try { boolean flag; try (BufferedReader bufferedreader = new BufferedReader(new InputStreamReader(url.openStream()))) { flag = this.start(bufferedreader, p_185307_); } return flag; } catch (IOException ioexception) { LOGGER.warn("Failed to start flight recorder using configuration at {}", url, ioexception); return false; } } } @Override public Path stop() { if (this.recording == null) { throw new IllegalStateException("Not currently profiling"); } else { this.networkTrafficByAddress.clear(); Path path = this.recording.getDestination(); this.recording.stop(); return path; } } @Override public boolean isRunning() { return this.recording != null; } @Override public boolean isAvailable() { return FlightRecorder.isAvailable(); } private boolean start(Reader p_185317_, Environment p_185318_) { if (this.isRunning()) { LOGGER.warn("Profiling already in progress"); return false; } else { try { Configuration configuration = Configuration.create(p_185317_); String s = DATE_TIME_FORMATTER.format(Instant.now()); this.recording = Util.make(new Recording(configuration), p_185311_ -> { CUSTOM_EVENTS.forEach(p_185311_::enable); p_185311_.setDumpOnExit(true); p_185311_.setToDisk(true); p_185311_.setName(String.format(Locale.ROOT, "%s-%s-%s", p_185318_.getDescription(), SharedConstants.getCurrentVersion().getName(), s)); }); Path path = Paths.get(String.format(Locale.ROOT, "debug/%s-%s.jfr", p_185318_.getDescription(), s)); FileUtil.createDirectoriesSafe(path.getParent()); this.recording.setDestination(path); this.recording.start(); this.setupSummaryListener(); } catch (ParseException | IOException ioexception) { LOGGER.warn("Failed to start jfr profiling", (Throwable)ioexception); return false; } LOGGER.info( "Started flight recorder profiling id({}):name({}) - will dump to {} on exit or stop command", 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 p_185339_) { if (p_185339_ == JfrProfiler.this.recording && p_185339_.getState() == RecordingState.STOPPED) { this.summaryReporter.recordingStopped(p_185339_.getDestination()); FlightRecorder.removeListener(this); } } }); } @Override public void onServerTick(float p_185300_) { if (ServerTickTimeEvent.TYPE.isEnabled()) { this.currentAverageTickTime = p_185300_; } } @Override public void onPacketReceived(ConnectionProtocol p_300094_, PacketType p_335626_, SocketAddress p_185304_, int p_185302_) { if (PacketReceivedEvent.TYPE.isEnabled()) { new PacketReceivedEvent(p_300094_.id(), p_335626_.flow().id(), p_335626_.id().toString(), p_185304_, p_185302_).commit(); } if (NetworkSummaryEvent.TYPE.isEnabled()) { this.networkStatFor(p_185304_).trackReceivedPacket(p_185302_); } } @Override public void onPacketSent(ConnectionProtocol p_299489_, PacketType p_334491_, SocketAddress p_185325_, int p_185323_) { if (PacketSentEvent.TYPE.isEnabled()) { new PacketSentEvent(p_299489_.id(), p_334491_.flow().id(), p_334491_.id().toString(), p_185325_, p_185323_).commit(); } if (NetworkSummaryEvent.TYPE.isEnabled()) { this.networkStatFor(p_185325_).trackSentPacket(p_185323_); } } private NetworkSummaryEvent.SumAggregation networkStatFor(SocketAddress p_185320_) { return this.networkTrafficByAddress.computeIfAbsent(p_185320_.toString(), NetworkSummaryEvent.SumAggregation::new); } @Override public void onRegionFileRead(RegionStorageInfo p_332602_, ChunkPos p_331074_, RegionFileVersion p_332565_, int p_334299_) { if (ChunkRegionReadEvent.TYPE.isEnabled()) { new ChunkRegionReadEvent(p_332602_, p_331074_, p_332565_, p_334299_).commit(); } } @Override public void onRegionFileWrite(RegionStorageInfo p_334895_, ChunkPos p_330898_, RegionFileVersion p_334334_, int p_334429_) { if (ChunkRegionWriteEvent.TYPE.isEnabled()) { new ChunkRegionWriteEvent(p_334895_, p_330898_, p_334334_, p_334429_).commit(); } } @Nullable @Override public ProfiledDuration onWorldLoadedStarted() { if (!WorldLoadFinishedEvent.TYPE.isEnabled()) { return null; } else { WorldLoadFinishedEvent worldloadfinishedevent = new WorldLoadFinishedEvent(); worldloadfinishedevent.begin(); return p_374913_ -> worldloadfinishedevent.commit(); } } @Nullable @Override public ProfiledDuration onChunkGenerate(ChunkPos p_185313_, ResourceKey p_185314_, String p_185315_) { if (!ChunkGenerationEvent.TYPE.isEnabled()) { return null; } else { ChunkGenerationEvent chunkgenerationevent = new ChunkGenerationEvent(p_185313_, p_185314_, p_185315_); chunkgenerationevent.begin(); return p_374911_ -> chunkgenerationevent.commit(); } } @Nullable @Override public ProfiledDuration onStructureGenerate(ChunkPos p_376237_, ResourceKey p_376556_, Holder p_377441_) { if (!StructureGenerationEvent.TYPE.isEnabled()) { return null; } else { StructureGenerationEvent structuregenerationevent = new StructureGenerationEvent(p_376237_, p_377441_, p_376556_); structuregenerationevent.begin(); return p_374909_ -> { structuregenerationevent.success = p_374909_; structuregenerationevent.commit(); }; } } }