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 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.util.profiling.jfr.event.NetworkSummaryEvent.SumAggregation; 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.jetbrains.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> 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()) { ((SumAggregation)iterator.next()).commitEvent(); iterator.remove(); } }); } public static JfrProfiler getInstance() { return INSTANCE; } @Override public boolean start(Environment environment) { 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 { BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(uRL.openStream())); boolean var4; try { var4 = this.start(bufferedReader, environment); } catch (Throwable var7) { try { bufferedReader.close(); } catch (Throwable var6) { var7.addSuppressed(var6); } throw var7; } bufferedReader.close(); return var4; } catch (IOException var8) { LOGGER.warn("Failed to start flight recorder using configuration at {}", uRL, var8); 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 reader, Environment environment) { if (this.isRunning()) { LOGGER.warn("Profiling already in progress"); return false; } else { try { Configuration configuration = Configuration.create(reader); String string = DATE_TIME_FORMATTER.format(Instant.now()); this.recording = Util.make(new Recording(configuration), recording -> { CUSTOM_EVENTS.forEach(recording::enable); recording.setDumpOnExit(true); recording.setToDisk(true); recording.setName(String.format(Locale.ROOT, "%s-%s-%s", environment.getDescription(), SharedConstants.getCurrentVersion().getName(), string)); }); Path path = Paths.get(String.format(Locale.ROOT, "debug/%s-%s.jfr", environment.getDescription(), string)); FileUtil.createDirectoriesSafe(path.getParent()); this.recording.setDestination(path); this.recording.start(); this.setupSummaryListener(); } catch (ParseException | IOException var6) { LOGGER.warn("Failed to start jfr profiling", (Throwable)var6); 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); public void recordingStateChanged(Recording recording) { if (recording == JfrProfiler.this.recording && recording.getState() == RecordingState.STOPPED) { this.summaryReporter.recordingStopped(recording.getDestination()); FlightRecorder.removeListener(this); } } }); } @Override public void onServerTick(float currentAverageTickTime) { if (ServerTickTimeEvent.TYPE.isEnabled()) { this.currentAverageTickTime = currentAverageTickTime; } } @Override public void onPacketReceived(ConnectionProtocol protocol, PacketType packetType, SocketAddress address, int size) { if (PacketReceivedEvent.TYPE.isEnabled()) { new PacketReceivedEvent(protocol.id(), packetType.flow().id(), packetType.id().toString(), address, size).commit(); } if (NetworkSummaryEvent.TYPE.isEnabled()) { this.networkStatFor(address).trackReceivedPacket(size); } } @Override public void onPacketSent(ConnectionProtocol protocol, PacketType packetType, SocketAddress address, int size) { if (PacketSentEvent.TYPE.isEnabled()) { new PacketSentEvent(protocol.id(), packetType.flow().id(), packetType.id().toString(), address, size).commit(); } if (NetworkSummaryEvent.TYPE.isEnabled()) { this.networkStatFor(address).trackSentPacket(size); } } private SumAggregation networkStatFor(SocketAddress remoteAddress) { return (SumAggregation)this.networkTrafficByAddress.computeIfAbsent(remoteAddress.toString(), SumAggregation::new); } @Override public void onRegionFileRead(RegionStorageInfo regionStorageInfo, ChunkPos chunkPos, RegionFileVersion version, int bytes) { if (ChunkRegionReadEvent.TYPE.isEnabled()) { new ChunkRegionReadEvent(regionStorageInfo, chunkPos, version, bytes).commit(); } } @Override public void onRegionFileWrite(RegionStorageInfo regionStorageInfo, ChunkPos chunkPos, RegionFileVersion version, int bytes) { if (ChunkRegionWriteEvent.TYPE.isEnabled()) { new ChunkRegionWriteEvent(regionStorageInfo, chunkPos, version, bytes).commit(); } } @Nullable @Override public ProfiledDuration onWorldLoadedStarted() { if (!WorldLoadFinishedEvent.TYPE.isEnabled()) { return null; } else { WorldLoadFinishedEvent worldLoadFinishedEvent = new WorldLoadFinishedEvent(); worldLoadFinishedEvent.begin(); return bl -> worldLoadFinishedEvent.commit(); } } @Nullable @Override public ProfiledDuration onChunkGenerate(ChunkPos chunkPos, ResourceKey level, String name) { if (!ChunkGenerationEvent.TYPE.isEnabled()) { return null; } else { ChunkGenerationEvent chunkGenerationEvent = new ChunkGenerationEvent(chunkPos, level, name); chunkGenerationEvent.begin(); return bl -> chunkGenerationEvent.commit(); } } @Nullable @Override public ProfiledDuration onStructureGenerate(ChunkPos chunkPos, ResourceKey level, Holder structure) { if (!StructureGenerationEvent.TYPE.isEnabled()) { return null; } else { StructureGenerationEvent structureGenerationEvent = new StructureGenerationEvent(chunkPos, structure, level); structureGenerationEvent.begin(); return bl -> { structureGenerationEvent.success = bl; structureGenerationEvent.commit(); }; } } }