Code/net/minecraft/util/profiling/jfr/JfrProfiler.java

275 lines
11 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 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<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, NetworkSummaryEvent.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<NetworkSummaryEvent.SumAggregation> 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<Level> 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<Level> p_376556_, Holder<Structure> 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();
};
}
}
}