minecraft-src/net/minecraft/util/profiling/FilledProfileResults.java
2025-07-04 01:41:11 +03:00

314 lines
10 KiB
Java

package net.minecraft.util.profiling;
import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.mojang.logging.LogUtils;
import it.unimi.dsi.fastutil.objects.Object2LongMap;
import it.unimi.dsi.fastutil.objects.Object2LongMaps;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import net.minecraft.ReportType;
import net.minecraft.SharedConstants;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.slf4j.Logger;
public class FilledProfileResults implements ProfileResults {
private static final Logger LOGGER = LogUtils.getLogger();
private static final ProfilerPathEntry EMPTY = new ProfilerPathEntry() {
@Override
public long getDuration() {
return 0L;
}
@Override
public long getMaxDuration() {
return 0L;
}
@Override
public long getCount() {
return 0L;
}
@Override
public Object2LongMap<String> getCounters() {
return Object2LongMaps.emptyMap();
}
};
private static final Splitter SPLITTER = Splitter.on('\u001e');
private static final Comparator<Entry<String, FilledProfileResults.CounterCollector>> COUNTER_ENTRY_COMPARATOR = Entry.comparingByValue(
Comparator.comparingLong(counterCollector -> counterCollector.totalValue)
)
.reversed();
private final Map<String, ? extends ProfilerPathEntry> entries;
private final long startTimeNano;
private final int startTimeTicks;
private final long endTimeNano;
private final int endTimeTicks;
private final int tickDuration;
public FilledProfileResults(Map<String, ? extends ProfilerPathEntry> entries, long startTimeNano, int startTimeTicks, long endTimeNano, int endTimeTicks) {
this.entries = entries;
this.startTimeNano = startTimeNano;
this.startTimeTicks = startTimeTicks;
this.endTimeNano = endTimeNano;
this.endTimeTicks = endTimeTicks;
this.tickDuration = endTimeTicks - startTimeTicks;
}
private ProfilerPathEntry getEntry(String key) {
ProfilerPathEntry profilerPathEntry = (ProfilerPathEntry)this.entries.get(key);
return profilerPathEntry != null ? profilerPathEntry : EMPTY;
}
@Override
public List<ResultField> getTimes(String sectionPath) {
String string = sectionPath;
ProfilerPathEntry profilerPathEntry = this.getEntry("root");
long l = profilerPathEntry.getDuration();
ProfilerPathEntry profilerPathEntry2 = this.getEntry(sectionPath);
long m = profilerPathEntry2.getDuration();
long n = profilerPathEntry2.getCount();
List<ResultField> list = Lists.<ResultField>newArrayList();
if (!sectionPath.isEmpty()) {
sectionPath = sectionPath + "\u001e";
}
long o = 0L;
for (String string2 : this.entries.keySet()) {
if (isDirectChild(sectionPath, string2)) {
o += this.getEntry(string2).getDuration();
}
}
float f = (float)o;
if (o < m) {
o = m;
}
if (l < o) {
l = o;
}
for (String string3 : this.entries.keySet()) {
if (isDirectChild(sectionPath, string3)) {
ProfilerPathEntry profilerPathEntry3 = this.getEntry(string3);
long p = profilerPathEntry3.getDuration();
double d = p * 100.0 / o;
double e = p * 100.0 / l;
String string4 = string3.substring(sectionPath.length());
list.add(new ResultField(string4, d, e, profilerPathEntry3.getCount()));
}
}
if ((float)o > f) {
list.add(new ResultField("unspecified", ((float)o - f) * 100.0 / o, ((float)o - f) * 100.0 / l, n));
}
Collections.sort(list);
list.add(0, new ResultField(string, 100.0, o * 100.0 / l, n));
return list;
}
private static boolean isDirectChild(String sectionPath, String entry) {
return entry.length() > sectionPath.length() && entry.startsWith(sectionPath) && entry.indexOf(30, sectionPath.length() + 1) < 0;
}
private Map<String, FilledProfileResults.CounterCollector> getCounterValues() {
Map<String, FilledProfileResults.CounterCollector> map = Maps.newTreeMap();
this.entries
.forEach(
(string, profilerPathEntry) -> {
Object2LongMap<String> object2LongMap = profilerPathEntry.getCounters();
if (!object2LongMap.isEmpty()) {
List<String> list = SPLITTER.splitToList(string);
object2LongMap.forEach(
(stringx, long_) -> ((FilledProfileResults.CounterCollector)map.computeIfAbsent(stringx, stringxx -> new FilledProfileResults.CounterCollector()))
.addValue(list.iterator(), long_)
);
}
}
);
return map;
}
@Override
public long getStartTimeNano() {
return this.startTimeNano;
}
@Override
public int getStartTimeTicks() {
return this.startTimeTicks;
}
@Override
public long getEndTimeNano() {
return this.endTimeNano;
}
@Override
public int getEndTimeTicks() {
return this.endTimeTicks;
}
@Override
public boolean saveResults(Path path) {
Writer writer = null;
boolean var4;
try {
Files.createDirectories(path.getParent());
writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8);
writer.write(this.getProfilerResults(this.getNanoDuration(), this.getTickDuration()));
return true;
} catch (Throwable var8) {
LOGGER.error("Could not save profiler results to {}", path, var8);
var4 = false;
} finally {
IOUtils.closeQuietly(writer);
}
return var4;
}
protected String getProfilerResults(long timeSpan, int tickSpan) {
StringBuilder stringBuilder = new StringBuilder();
ReportType.PROFILE.appendHeader(stringBuilder, List.of());
stringBuilder.append("Version: ").append(SharedConstants.getCurrentVersion().getId()).append('\n');
stringBuilder.append("Time span: ").append(timeSpan / 1000000L).append(" ms\n");
stringBuilder.append("Tick span: ").append(tickSpan).append(" ticks\n");
stringBuilder.append("// This is approximately ")
.append(String.format(Locale.ROOT, "%.2f", tickSpan / ((float)timeSpan / 1.0E9F)))
.append(" ticks per second. It should be ")
.append(20)
.append(" ticks per second\n\n");
stringBuilder.append("--- BEGIN PROFILE DUMP ---\n\n");
this.appendProfilerResults(0, "root", stringBuilder);
stringBuilder.append("--- END PROFILE DUMP ---\n\n");
Map<String, FilledProfileResults.CounterCollector> map = this.getCounterValues();
if (!map.isEmpty()) {
stringBuilder.append("--- BEGIN COUNTER DUMP ---\n\n");
this.appendCounters(map, stringBuilder, tickSpan);
stringBuilder.append("--- END COUNTER DUMP ---\n\n");
}
return stringBuilder.toString();
}
@Override
public String getProfilerResults() {
StringBuilder stringBuilder = new StringBuilder();
this.appendProfilerResults(0, "root", stringBuilder);
return stringBuilder.toString();
}
private static StringBuilder indentLine(StringBuilder builder, int indents) {
builder.append(String.format(Locale.ROOT, "[%02d] ", indents));
for (int i = 0; i < indents; i++) {
builder.append("| ");
}
return builder;
}
private void appendProfilerResults(int depth, String sectionPath, StringBuilder builder) {
List<ResultField> list = this.getTimes(sectionPath);
Object2LongMap<String> object2LongMap = ObjectUtils.firstNonNull((ProfilerPathEntry)this.entries.get(sectionPath), EMPTY).getCounters();
object2LongMap.forEach(
(string, long_) -> indentLine(builder, depth)
.append('#')
.append(string)
.append(' ')
.append(long_)
.append('/')
.append(long_ / this.tickDuration)
.append('\n')
);
if (list.size() >= 3) {
for (int i = 1; i < list.size(); i++) {
ResultField resultField = (ResultField)list.get(i);
indentLine(builder, depth)
.append(resultField.name)
.append('(')
.append(resultField.count)
.append('/')
.append(String.format(Locale.ROOT, "%.0f", (float)resultField.count / this.tickDuration))
.append(')')
.append(" - ")
.append(String.format(Locale.ROOT, "%.2f", resultField.percentage))
.append("%/")
.append(String.format(Locale.ROOT, "%.2f", resultField.globalPercentage))
.append("%\n");
if (!"unspecified".equals(resultField.name)) {
try {
this.appendProfilerResults(depth + 1, sectionPath + "\u001e" + resultField.name, builder);
} catch (Exception var9) {
builder.append("[[ EXCEPTION ").append(var9).append(" ]]");
}
}
}
}
}
private void appendCounterResults(int indents, String name, FilledProfileResults.CounterCollector collector, int tickSpan, StringBuilder builder) {
indentLine(builder, indents)
.append(name)
.append(" total:")
.append(collector.selfValue)
.append('/')
.append(collector.totalValue)
.append(" average: ")
.append(collector.selfValue / tickSpan)
.append('/')
.append(collector.totalValue / tickSpan)
.append('\n');
collector.children
.entrySet()
.stream()
.sorted(COUNTER_ENTRY_COMPARATOR)
.forEach(entry -> this.appendCounterResults(indents + 1, (String)entry.getKey(), (FilledProfileResults.CounterCollector)entry.getValue(), tickSpan, builder));
}
private void appendCounters(Map<String, FilledProfileResults.CounterCollector> counters, StringBuilder builder, int tickSpan) {
counters.forEach((string, counterCollector) -> {
builder.append("-- Counter: ").append(string).append(" --\n");
this.appendCounterResults(0, "root", (FilledProfileResults.CounterCollector)counterCollector.children.get("root"), tickSpan, builder);
builder.append("\n\n");
});
}
@Override
public int getTickDuration() {
return this.tickDuration;
}
static class CounterCollector {
long selfValue;
long totalValue;
final Map<String, FilledProfileResults.CounterCollector> children = Maps.<String, FilledProfileResults.CounterCollector>newHashMap();
public void addValue(Iterator<String> counters, long value) {
this.totalValue += value;
if (!counters.hasNext()) {
this.selfValue += value;
} else {
((FilledProfileResults.CounterCollector)this.children.computeIfAbsent((String)counters.next(), string -> new FilledProfileResults.CounterCollector()))
.addValue(counters, value);
}
}
}
}