minecraft-src/net/minecraft/util/profiling/jfr/JfrProfiler.java
2025-07-04 03:15:13 +03:00

286 lines
9.8 KiB
Java

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<Class<? extends Event>> 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<String, SumAggregation> networkTrafficByAddress = new ConcurrentHashMap();
private JfrProfiler() {
CUSTOM_EVENTS.forEach(FlightRecorder::register);
FlightRecorder.addPeriodicEvent(ServerTickTimeEvent.class, () -> new ServerTickTimeEvent(this.currentAverageTickTime).commit());
FlightRecorder.addPeriodicEvent(NetworkSummaryEvent.class, () -> {
Iterator<SumAggregation> 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> 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> level, Holder<Structure> structure) {
if (!StructureGenerationEvent.TYPE.isEnabled()) {
return null;
} else {
StructureGenerationEvent structureGenerationEvent = new StructureGenerationEvent(chunkPos, structure, level);
structureGenerationEvent.begin();
return bl -> {
structureGenerationEvent.success = bl;
structureGenerationEvent.commit();
};
}
}
}