fix(perf): normalize entity hotspot lifetimes
This commit is contained in:
1 parent
fcd53a9668
commit
d621322018
21 files changed
+190
-42
No files matched your search
@@ -51,8 +51,15 @@ public final class ClientPerfWorldState {
|
||||
long tickCount,
|
||||
long nanosPerSample) {
|
||||
var cost = new PerfWorldHotspotIndex.Cost(entry.samples(), entry.maxSamplesQ8());
|
||||
PerfReportData.Metric metric = cost.metric(tickCount, nanosPerSample);
|
||||
return new Hotspot(entry.key(), entry.detailKind(), entry.registryId(), metric);
|
||||
long measurementTicks = measurementTicks(tickCount, entry.measurementStartTick());
|
||||
PerfReportData.Metric metric = cost.metric(measurementTicks, nanosPerSample);
|
||||
return new Hotspot(entry.key(), entry.detailKind(), entry.registryId(),
|
||||
measurementTicks, metric);
|
||||
}
|
||||
|
||||
static long measurementTicks(long tickCount, long measurementStartTick) {
|
||||
return tickCount <= 0L ? 0L
|
||||
: Math.max(1L, tickCount - measurementStartTick + 1L);
|
||||
}
|
||||
|
||||
public static Snapshot snapshot() {
|
||||
@@ -67,6 +74,7 @@ public final class ClientPerfWorldState {
|
||||
public record Hotspot(PerfWorldHotspotIndex.Key key,
|
||||
TickDetailKind detailKind,
|
||||
int registryId,
|
||||
long measurementTicks,
|
||||
PerfReportData.Metric metric) {
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,8 @@ final class WorldEntityFlameClusters {
|
||||
mspt, Math.max(mspt, maximum), samples);
|
||||
var anchor = new Anchor(centerX(input.bounds()), input.bounds().maxY,
|
||||
centerZ(input.bounds()));
|
||||
var visual = new Visual(ObjectPerformanceScale.effectiveSamples(metric, tickCount),
|
||||
var visual = new Visual(ObjectPerformanceScale.effectiveSamples(
|
||||
metric, measurementTicks(input, tickCount)),
|
||||
updatePhase(0x811C9DC5, input.stableId()) & 0x7FFF, 1);
|
||||
return new Marker(anchor, metric, visual);
|
||||
}
|
||||
@@ -54,6 +55,8 @@ final class WorldEntityFlameClusters {
|
||||
double totalMspt = 0.0D;
|
||||
double totalMax = 0.0D;
|
||||
long totalSamples = 0L;
|
||||
double averageSamples = 0.0D;
|
||||
double maximumSamples = 0.0D;
|
||||
double totalWeight = 0.0D;
|
||||
double x = 0.0D;
|
||||
double z = 0.0D;
|
||||
@@ -66,6 +69,9 @@ final class WorldEntityFlameClusters {
|
||||
totalMspt += mspt;
|
||||
totalMax += maximum;
|
||||
totalSamples = saturatedAdd(totalSamples, Math.max(0L, input.metric().samples()));
|
||||
long inputTicks = measurementTicks(input, tickCount);
|
||||
averageSamples += ObjectPerformanceScale.averageSamples(input.metric(), inputTicks);
|
||||
maximumSamples += ObjectPerformanceScale.estimatedMaxSamples(input.metric(), inputTicks);
|
||||
totalWeight += weight;
|
||||
x += centerX(input.bounds()) * weight;
|
||||
z += centerZ(input.bounds()) * weight;
|
||||
@@ -75,10 +81,14 @@ final class WorldEntityFlameClusters {
|
||||
PerfReportData.Metric metric = new PerfReportData.Metric(
|
||||
totalMspt, Math.max(totalMspt, totalMax), totalSamples);
|
||||
return new Marker(new Anchor(x / totalWeight, y, z / totalWeight), metric,
|
||||
new Visual(ObjectPerformanceScale.effectiveSamples(metric, tickCount),
|
||||
new Visual(ObjectPerformanceScale.effectiveSamples(averageSamples, maximumSamples),
|
||||
phase & 0x7FFF, group.size()));
|
||||
}
|
||||
|
||||
private static long measurementTicks(Input input, long fallback) {
|
||||
return input.measurementTicks() > 0L ? input.measurementTicks() : fallback;
|
||||
}
|
||||
|
||||
private static double positiveFinite(double value) {
|
||||
return Double.isFinite(value) && value > 0.0D ? value : 0.0D;
|
||||
}
|
||||
@@ -103,7 +113,11 @@ final class WorldEntityFlameClusters {
|
||||
return (int) Math.floor(coordinate / CELL_SIZE);
|
||||
}
|
||||
|
||||
record Input(AABB bounds, PerfReportData.Metric metric, int stableId) {
|
||||
record Input(AABB bounds, PerfReportData.Metric metric,
|
||||
long measurementTicks, int stableId) {
|
||||
Input(AABB bounds, PerfReportData.Metric metric, int stableId) {
|
||||
this(bounds, metric, 0L, stableId);
|
||||
}
|
||||
}
|
||||
|
||||
record Anchor(double x, double y, double z) {
|
||||
|
||||
@@ -78,7 +78,7 @@ final class WorldPerfObjectRenderer {
|
||||
}
|
||||
for (ClientPerfWorldState.Hotspot hotspot : snapshot.hotspots()) {
|
||||
double effective = ObjectPerformanceScale.effectiveSamples(
|
||||
hotspot.metric(), snapshot.tickCount());
|
||||
hotspot.metric(), hotspot.measurementTicks());
|
||||
if (WorldOverlayMath.shouldRender(effective, currentFilter)) {
|
||||
next.add(new Candidate(hotspot, hotspot.metric(), effective));
|
||||
if (next.size() == WorldPerfObjectBuffers.MAX_MARKER_COUNT) {
|
||||
@@ -109,7 +109,8 @@ final class WorldPerfObjectRenderer {
|
||||
}
|
||||
if (candidate.hotspot().key().kind() == PerfWorldHotspotIndex.Kind.ENTITY) {
|
||||
var input = new WorldEntityFlameClusters.Input(
|
||||
bounds, candidate.metric(), candidate.hotspot().key().entityId());
|
||||
bounds, candidate.metric(), candidate.hotspot().measurementTicks(),
|
||||
candidate.hotspot().key().entityId());
|
||||
if (shouldClusterEntity(candidate.hotspot().detailKind())) {
|
||||
itemEntities.add(input);
|
||||
} else {
|
||||
|
||||
+13
-3
@@ -26,7 +26,13 @@ public final class ObjectPerformanceScale {
|
||||
|
||||
public static double effectiveSamples(PerfReportData.Metric metric, long tickCount) {
|
||||
double average = averageSamples(metric, tickCount);
|
||||
double burst = Math.max(0.0D, estimatedMaxSamples(metric, tickCount) - average);
|
||||
return effectiveSamples(average, estimatedMaxSamples(metric, tickCount));
|
||||
}
|
||||
|
||||
public static double effectiveSamples(double averageSamples, double estimatedMaxSamples) {
|
||||
double average = bounded(averageSamples);
|
||||
double maximum = bounded(estimatedMaxSamples);
|
||||
double burst = Math.max(0.0D, maximum - average);
|
||||
return Math.min(MAX_SAMPLE_VALUE, average + Math.log1p(burst) / LOG_TWO);
|
||||
}
|
||||
|
||||
@@ -36,7 +42,11 @@ public final class ObjectPerformanceScale {
|
||||
}
|
||||
double averageSamples = samples / (double) tickCount;
|
||||
double estimatedMaxSamples = maxSamplesQ8 / (double) PerfWorldHotspotIndex.MAX_SAMPLES_SCALE;
|
||||
double excess = Math.max(0.0D, estimatedMaxSamples - averageSamples);
|
||||
return averageSamples + Math.log1p(excess) / Math.log(2.0D);
|
||||
return effectiveSamples(averageSamples, estimatedMaxSamples);
|
||||
}
|
||||
|
||||
private static double bounded(double value) {
|
||||
return Double.isFinite(value) && value > 0.0D
|
||||
? Math.min(MAX_SAMPLE_VALUE, value) : 0.0D;
|
||||
}
|
||||
}
|
||||
+11
-2
@@ -20,6 +20,9 @@ public record PerfWorldHotspotIndex(long streamEpoch,
|
||||
throw new IllegalArgumentException("Hotspot index scalars must be non-negative");
|
||||
}
|
||||
entries = List.copyOf(entries);
|
||||
if (entries.stream().anyMatch(entry -> entry.measurementStartTick() > tickCount)) {
|
||||
throw new IllegalArgumentException("Hotspot measurement starts after the snapshot");
|
||||
}
|
||||
}
|
||||
|
||||
public static PerfWorldHotspotIndex empty(long streamEpoch, long revision, long tickCount) {
|
||||
@@ -52,6 +55,7 @@ public record PerfWorldHotspotIndex(long streamEpoch,
|
||||
int z,
|
||||
TickDetailKind detailKind,
|
||||
ResourceLocation detailId,
|
||||
long measurementStartTick,
|
||||
long samples,
|
||||
int maxSamplesQ8) {
|
||||
public Entry {
|
||||
@@ -59,8 +63,8 @@ public record PerfWorldHotspotIndex(long streamEpoch,
|
||||
Objects.requireNonNull(dimension, "dimension");
|
||||
Objects.requireNonNull(detailKind, "detailKind");
|
||||
Objects.requireNonNull(detailId, "detailId");
|
||||
if (samples < 0L || maxSamplesQ8 < 0) {
|
||||
throw new IllegalArgumentException("Hotspot samples must be non-negative");
|
||||
if (measurementStartTick <= 0L || samples < 0L || maxSamplesQ8 < 0) {
|
||||
throw new IllegalArgumentException("Invalid hotspot measurement data");
|
||||
}
|
||||
key.validateDetailKind(detailKind);
|
||||
if (key.kind() == Kind.BLOCK_ENTITY
|
||||
@@ -69,6 +73,11 @@ public record PerfWorldHotspotIndex(long streamEpoch,
|
||||
}
|
||||
}
|
||||
|
||||
public long measurementTicks(long totalTicks) {
|
||||
return totalTicks <= 0L ? 0L
|
||||
: Math.max(1L, totalTicks - measurementStartTick + 1L);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public record Key(Kind kind, int entityId, long blockPosition) {
|
||||
|
||||
+3
-2
@@ -177,7 +177,8 @@ final class PerfObjectSnapshotBuilder {
|
||||
PerfReportData.Metric metric,
|
||||
long tickCount) {
|
||||
return new ObjectCandidate(key, aggregate, metric,
|
||||
ObjectPerformanceScale.effectiveSamples(metric, tickCount),
|
||||
ObjectPerformanceScale.effectiveSamples(
|
||||
metric, aggregate.measurementTicks(tickCount)),
|
||||
StableObjectId.from(key, aggregate), tickCount);
|
||||
}
|
||||
|
||||
@@ -203,7 +204,7 @@ final class PerfObjectSnapshotBuilder {
|
||||
PerfWorldHotspotIndex.Cost cost = aggregate.stats.hotspotCost(tickCount);
|
||||
return new PerfWorldHotspotIndex.Entry(hotspotKey(), key.dimension(),
|
||||
aggregate.x, aggregate.y, aggregate.z, key.detailKind(), key.detailId(),
|
||||
cost.samples(), cost.maxSamplesQ8());
|
||||
aggregate.measurementStartTick, cost.samples(), cost.maxSamplesQ8());
|
||||
}
|
||||
|
||||
static ObjectCandidate newer(ObjectCandidate left, ObjectCandidate right) {
|
||||
|
||||
+10
-1
@@ -251,6 +251,7 @@ final class PerfSampleAggregator {
|
||||
boolean chunkPosition;
|
||||
int x, y, z;
|
||||
long lastSeenTick;
|
||||
long measurementStartTick = 1L;
|
||||
|
||||
void record(TickSamples.TickCount count, long tick) {
|
||||
positioned = count.positioned;
|
||||
@@ -259,6 +260,10 @@ final class PerfSampleAggregator {
|
||||
y = count.y;
|
||||
z = count.z;
|
||||
lastSeenTick = tick;
|
||||
if (count.entityAgeTicks >= 0) {
|
||||
long age = Math.max(1L, count.entityAgeTicks);
|
||||
measurementStartTick = Math.max(1L, tick - age + 1L);
|
||||
}
|
||||
stats.record(count.count, tick);
|
||||
}
|
||||
|
||||
@@ -283,7 +288,11 @@ final class PerfSampleAggregator {
|
||||
}
|
||||
|
||||
PerfReportData.Metric metric(long ticks, double nanosPerSample) {
|
||||
return stats.metric(ticks, nanosPerSample);
|
||||
return stats.metric(ticks, measurementTicks(ticks), nanosPerSample);
|
||||
}
|
||||
|
||||
long measurementTicks(long ticks) {
|
||||
return ticks <= 0L ? 0L : Math.max(1L, ticks - measurementStartTick + 1L);
|
||||
}
|
||||
|
||||
TickSamples.ChunkKey chunkKey(TickSamples.TickableKey key) {
|
||||
|
||||
+26
-3
@@ -174,7 +174,25 @@ public final class PerfWorkTracker {
|
||||
int z,
|
||||
boolean chunkPosition,
|
||||
String eventLoop,
|
||||
String displayName) {
|
||||
String displayName,
|
||||
int entityAgeTicks) {
|
||||
public Snapshot(TickType type,
|
||||
ResourceLocation dimension,
|
||||
Class<?> workClass,
|
||||
ResourceLocation detailId,
|
||||
TickDetailKind detailKind,
|
||||
int entityId,
|
||||
boolean positioned,
|
||||
int x,
|
||||
int y,
|
||||
int z,
|
||||
boolean chunkPosition,
|
||||
String eventLoop,
|
||||
String displayName) {
|
||||
this(type, dimension, workClass, detailId, detailKind, entityId,
|
||||
positioned, x, y, z, chunkPosition, eventLoop, displayName, -1);
|
||||
}
|
||||
|
||||
public Snapshot(TickType type,
|
||||
ResourceLocation dimension,
|
||||
Class<?> workClass,
|
||||
@@ -188,7 +206,7 @@ public final class PerfWorkTracker {
|
||||
boolean chunkPosition,
|
||||
String eventLoop) {
|
||||
this(type, dimension, workClass, detailId, detailKind, entityId,
|
||||
positioned, x, y, z, chunkPosition, eventLoop, "");
|
||||
positioned, x, y, z, chunkPosition, eventLoop, "", -1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,6 +226,7 @@ public final class PerfWorkTracker {
|
||||
private boolean chunkPosition;
|
||||
private String eventLoop;
|
||||
private String displayName;
|
||||
private int entityAgeTicks;
|
||||
|
||||
void publishChunk(ResourceLocation dimension, LevelChunk chunk) {
|
||||
var pos = chunk.getPos();
|
||||
@@ -222,6 +241,7 @@ public final class PerfWorkTracker {
|
||||
z = pos.z;
|
||||
chunkPosition = true;
|
||||
eventLoop = null;
|
||||
entityAgeTicks = -1;
|
||||
finishPublish();
|
||||
}
|
||||
|
||||
@@ -242,6 +262,7 @@ public final class PerfWorkTracker {
|
||||
z = entity.getBlockZ();
|
||||
chunkPosition = false;
|
||||
eventLoop = null;
|
||||
entityAgeTicks = Math.max(1, entity.tickCount + 1);
|
||||
finishPublish();
|
||||
}
|
||||
|
||||
@@ -258,6 +279,7 @@ public final class PerfWorkTracker {
|
||||
z = pos.getZ();
|
||||
chunkPosition = false;
|
||||
eventLoop = null;
|
||||
entityAgeTicks = -1;
|
||||
finishPublish();
|
||||
}
|
||||
|
||||
@@ -273,6 +295,7 @@ public final class PerfWorkTracker {
|
||||
z = 0;
|
||||
chunkPosition = false;
|
||||
this.eventLoop = eventLoop;
|
||||
entityAgeTicks = -1;
|
||||
finishPublish();
|
||||
}
|
||||
|
||||
@@ -301,7 +324,7 @@ public final class PerfWorkTracker {
|
||||
}
|
||||
Snapshot result = new Snapshot(type, dimension, workClass, detailId, detailKind,
|
||||
entityId, positioned,
|
||||
x, y, z, chunkPosition, eventLoop, displayName);
|
||||
x, y, z, chunkPosition, eventLoop, displayName, entityAgeTicks);
|
||||
return before == version ? result : null;
|
||||
}
|
||||
}
|
||||
|
||||
+8
-3
@@ -35,9 +35,14 @@ final class SampleStatistics {
|
||||
}
|
||||
|
||||
PerfReportData.Metric metric(long totalTicks, double nanosPerSample) {
|
||||
advanceWindows(totalTicks);
|
||||
double mspt = totalTicks == 0 ? 0.0D
|
||||
: totalSamples * nanosPerSample / NANOS_PER_MILLISECOND / totalTicks;
|
||||
return metric(totalTicks, totalTicks, nanosPerSample);
|
||||
}
|
||||
|
||||
PerfReportData.Metric metric(long currentTick, long measurementTicks,
|
||||
double nanosPerSample) {
|
||||
advanceWindows(currentTick);
|
||||
double mspt = measurementTicks == 0L ? 0.0D
|
||||
: totalSamples * nanosPerSample / NANOS_PER_MILLISECOND / measurementTicks;
|
||||
double estimatedMaxSamples = estimatedMaxSamples();
|
||||
double estimatedMax = estimatedMaxSamples * nanosPerSample / NANOS_PER_MILLISECOND;
|
||||
return new PerfReportData.Metric(mspt, Math.max(mspt, estimatedMax), Math.round(totalSamples));
|
||||
|
||||
+3
@@ -104,6 +104,7 @@ final class TickSamples {
|
||||
int x;
|
||||
int y;
|
||||
int z;
|
||||
int entityAgeTicks = -1;
|
||||
|
||||
void record(PerfWorkTracker.Snapshot snapshot) {
|
||||
count++;
|
||||
@@ -112,6 +113,7 @@ final class TickSamples {
|
||||
x = snapshot.x();
|
||||
y = snapshot.y();
|
||||
z = snapshot.z();
|
||||
entityAgeTicks = snapshot.entityAgeTicks();
|
||||
}
|
||||
|
||||
void merge(TickCount source) {
|
||||
@@ -121,6 +123,7 @@ final class TickSamples {
|
||||
x = source.x;
|
||||
y = source.y;
|
||||
z = source.z;
|
||||
entityAgeTicks = Math.max(entityAgeTicks, source.entityAgeTicks);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,9 @@ public record PerfWorldHotspotPayload(long streamEpoch,
|
||||
removals = List.copyOf(removals);
|
||||
validateSize(upserts.size());
|
||||
validateSize(removals.size());
|
||||
if (upserts.stream().anyMatch(entry -> entry.measurementStartTick() > tickCount)) {
|
||||
throw new IllegalArgumentException("Hotspot measurement starts after the payload");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -84,6 +87,7 @@ public record PerfWorldHotspotPayload(long streamEpoch,
|
||||
writeKey(buffer, entry.key);
|
||||
buffer.writeByte(entry.detailKind.ordinal());
|
||||
buffer.writeVarInt(entry.registryId);
|
||||
buffer.writeVarLong(entry.measurementStartTick);
|
||||
buffer.writeVarLong(entry.samples);
|
||||
buffer.writeVarInt(entry.maxSamplesQ8);
|
||||
}
|
||||
@@ -96,7 +100,7 @@ public record PerfWorldHotspotPayload(long streamEpoch,
|
||||
throw new IllegalArgumentException("Unknown hotspot detail kind: " + detailOrdinal);
|
||||
}
|
||||
return new Entry(key, kinds[detailOrdinal], buffer.readVarInt(),
|
||||
buffer.readVarLong(), buffer.readVarInt());
|
||||
buffer.readVarLong(), buffer.readVarLong(), buffer.readVarInt());
|
||||
}
|
||||
|
||||
private static void writeKey(
|
||||
@@ -140,12 +144,14 @@ public record PerfWorldHotspotPayload(long streamEpoch,
|
||||
public record Entry(PerfWorldHotspotIndex.Key key,
|
||||
TickDetailKind detailKind,
|
||||
int registryId,
|
||||
long measurementStartTick,
|
||||
long samples,
|
||||
int maxSamplesQ8) {
|
||||
public Entry {
|
||||
Objects.requireNonNull(key, "key");
|
||||
Objects.requireNonNull(detailKind, "detailKind");
|
||||
if (registryId < 0 || samples < 0L || maxSamplesQ8 < 0) {
|
||||
if (registryId < 0 || measurementStartTick <= 0L
|
||||
|| samples < 0L || maxSamplesQ8 < 0) {
|
||||
throw new IllegalArgumentException("Invalid compact hotspot entry");
|
||||
}
|
||||
key.validateDetailKind(detailKind);
|
||||
|
||||
@@ -45,7 +45,8 @@ final class PerfWorldHotspotResolver {
|
||||
String dimension = entity.level().dimension().location().toString();
|
||||
return new PerfWorldHotspotIndex.Entry(entry.key(), dimension,
|
||||
entity.getBlockX(), entity.getBlockY(), entity.getBlockZ(),
|
||||
entry.detailKind(), entry.detailId(), entry.samples(), entry.maxSamplesQ8());
|
||||
entry.detailKind(), entry.detailId(), entry.measurementStartTick(),
|
||||
entry.samples(), entry.maxSamplesQ8());
|
||||
}
|
||||
|
||||
private static Entity findEntity(PerfWorldHotspotIndex.Entry entry,
|
||||
|
||||
@@ -120,7 +120,7 @@ final class PerfWorldHotspotStream {
|
||||
int registryId = registryIds.resolve(entry.detailKind(), entry.detailId());
|
||||
return registryId < 0 ? null : new PerfWorldHotspotPayload.Entry(
|
||||
entry.key(), entry.detailKind(), registryId,
|
||||
entry.samples(), entry.maxSamplesQ8());
|
||||
entry.measurementStartTick(), entry.samples(), entry.maxSamplesQ8());
|
||||
}
|
||||
|
||||
private static int builtInRegistryId(TickDetailKind kind, ResourceLocation id) {
|
||||
|
||||
@@ -15,7 +15,7 @@ import net.neoforged.neoforge.network.registration.NetworkRegistry;
|
||||
|
||||
@EventBusSubscriber(modid = ServerWarashi.MODID, bus = EventBusSubscriber.Bus.MOD)
|
||||
public final class WarashiNetwork {
|
||||
static final String NETWORK_VERSION = "6";
|
||||
static final String NETWORK_VERSION = "7";
|
||||
|
||||
private WarashiNetwork() {
|
||||
}
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ class ClientPerfStateCoordinatorTest {
|
||||
void clearAllResetsReportMapAndWorldStores() {
|
||||
ClientPerfStateCoordinator.applyFullReport(report(), true);
|
||||
var entry = new PerfWorldHotspotPayload.Entry(
|
||||
PerfWorldHotspotIndex.Key.entity(7), TickDetailKind.ENTITY, 1, 2L, 256);
|
||||
PerfWorldHotspotIndex.Key.entity(7), TickDetailKind.ENTITY, 1, 1L, 2L, 256);
|
||||
ClientPerfStateCoordinator.applyWorldDelta(new PerfWorldHotspotPayload(
|
||||
1L, 0L, 1L, 20L, 1_000_000L,
|
||||
ResourceLocation.parse("minecraft:overworld"), true,
|
||||
|
||||
@@ -26,7 +26,7 @@ class ClientPerfWorldStateTest {
|
||||
@Test
|
||||
void scalarOnlyDeltaRefreshesMetricWithoutRepeatingEntry() {
|
||||
var entry = new PerfWorldHotspotPayload.Entry(
|
||||
KEY, TickDetailKind.ENTITY, 4, 20L, 512);
|
||||
KEY, TickDetailKind.ENTITY, 4, 1L, 20L, 512);
|
||||
assertTrue(ClientPerfWorldState.apply(payload(0L, 20L, 20L, 1_000_000L,
|
||||
true, List.of(entry), List.of())));
|
||||
assertEquals(1.0D, metric().mspt(), 1.0E-9D);
|
||||
@@ -40,7 +40,7 @@ class ClientPerfWorldStateTest {
|
||||
@Test
|
||||
void rejectsDeltaWithWrongBaseAndAppliesRemoval() {
|
||||
var entry = new PerfWorldHotspotPayload.Entry(
|
||||
KEY, TickDetailKind.ENTITY, 4, 2L, 256);
|
||||
KEY, TickDetailKind.ENTITY, 4, 1L, 2L, 256);
|
||||
ClientPerfWorldState.apply(payload(
|
||||
0L, 20L, 20L, 1_000_000L, true, List.of(entry), List.of()));
|
||||
assertFalse(ClientPerfWorldState.apply(payload(
|
||||
@@ -53,7 +53,7 @@ class ClientPerfWorldStateTest {
|
||||
@Test
|
||||
void rejectsOlderFullSnapshotButAcceptsANewStreamEpoch() {
|
||||
var entry = new PerfWorldHotspotPayload.Entry(
|
||||
KEY, TickDetailKind.ENTITY, 4, 2L, 256);
|
||||
KEY, TickDetailKind.ENTITY, 4, 1L, 2L, 256);
|
||||
assertTrue(ClientPerfWorldState.apply(new PerfWorldHotspotPayload(
|
||||
7L, 0L, 20L, 20L, 1_000_000L, DIMENSION,
|
||||
true, List.of(entry), List.of())));
|
||||
@@ -70,6 +70,18 @@ class ClientPerfWorldStateTest {
|
||||
assertEquals(1L, ClientPerfWorldState.snapshot().protocolRevision());
|
||||
}
|
||||
|
||||
@Test
|
||||
void dividesEntityCostByTicksSinceItAppeared() {
|
||||
var entry = new PerfWorldHotspotPayload.Entry(
|
||||
KEY, TickDetailKind.ENTITY, 4, 91L, 20L, 512);
|
||||
|
||||
assertTrue(ClientPerfWorldState.apply(payload(
|
||||
0L, 100L, 100L, 1_000_000L, true, List.of(entry), List.of())));
|
||||
|
||||
assertEquals(2.0D, metric().mspt(), 1.0E-9D);
|
||||
assertEquals(10L, ClientPerfWorldState.snapshot().hotspots().get(0).measurementTicks());
|
||||
}
|
||||
|
||||
private static com.moepus.serverwarashi.common.modules.performance.report.PerfReportData.Metric metric() {
|
||||
return ClientPerfWorldState.snapshot().hotspots().get(0).metric();
|
||||
}
|
||||
|
||||
+20
-4
@@ -69,6 +69,20 @@ class WorldEntityFlameClustersTest {
|
||||
assertEquals(clustered.z(), single.z(), 0.000_001D);
|
||||
}
|
||||
|
||||
@Test
|
||||
void combinesRatesFromMembersWithDifferentMeasurementWindows() {
|
||||
var recent = new WorldEntityFlameClusters.Input(
|
||||
bounds(new Point(0.0D, 0.0D)), metric(1.0D, 1.0D, 10L), 10L, 1);
|
||||
var old = new WorldEntityFlameClusters.Input(
|
||||
bounds(new Point(0.2D, 0.0D)), metric(1.0D, 1.0D, 100L), 100L, 2);
|
||||
|
||||
WorldEntityFlameClusters.Marker marker =
|
||||
WorldEntityFlameClusters.cluster(List.of(recent, old), 100L).get(0);
|
||||
|
||||
assertEquals(2.0D, marker.metric().mspt(), 0.000_001D);
|
||||
assertEquals(2.0D, marker.effectiveSamples(), 0.000_001D);
|
||||
}
|
||||
|
||||
@Test
|
||||
void clustersTheMaximumCoincidentInputBudgetWithoutPairMaterialization() {
|
||||
List<WorldEntityFlameClusters.Input> inputs = new ArrayList<>();
|
||||
@@ -85,10 +99,12 @@ class WorldEntityFlameClustersTest {
|
||||
|
||||
private static WorldEntityFlameClusters.Input input(
|
||||
int id, Point point, PerfReportData.Metric metric) {
|
||||
return new WorldEntityFlameClusters.Input(
|
||||
new AABB(point.x() - 0.3D, 64.0D, point.z() - 0.3D,
|
||||
point.x() + 0.3D, 65.8D, point.z() + 0.3D),
|
||||
metric, id);
|
||||
return new WorldEntityFlameClusters.Input(bounds(point), metric, id);
|
||||
}
|
||||
|
||||
private static AABB bounds(Point point) {
|
||||
return new AABB(point.x() - 0.3D, 64.0D, point.z() - 0.3D,
|
||||
point.x() + 0.3D, 65.8D, point.z() + 0.3D);
|
||||
}
|
||||
|
||||
private static WorldEntityFlameClusters.Input input(int id, AABB bounds) {
|
||||
|
||||
+29
@@ -124,6 +124,35 @@ class PerfSampleAggregatorTest {
|
||||
assertTrue(report.chunks().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void limitsEntityAverageToItsLifetimeInsideTheSamplingSession() {
|
||||
PerfSampleAggregator aggregator = new PerfSampleAggregator(23L);
|
||||
for (int tick = 1; tick <= 90; tick++) {
|
||||
aggregator.accept(new TickSamples(null));
|
||||
}
|
||||
TickSamples spawned = new TickSamples(null);
|
||||
spawned.sample(new PerfWorkTracker.Snapshot(TickType.ENTITY,
|
||||
ResourceLocation.parse("test:level"), Cow.class,
|
||||
ResourceLocation.parse("minecraft:cow"), TickDetailKind.ENTITY, 7,
|
||||
true, 8, 64, 8, false, null, "", 1));
|
||||
aggregator.accept(spawned);
|
||||
for (int tick = 92; tick <= 100; tick++) {
|
||||
aggregator.accept(new TickSamples(null));
|
||||
}
|
||||
|
||||
PerfSampleAggregator.BuildSnapshot build =
|
||||
aggregator.buildSnapshot(milliseconds(100L), 1L, 1_000);
|
||||
double sampleMillis = observedMillis(100L, 1);
|
||||
PerfReportData.TickableEntry entity = build.report().tickables().get(0);
|
||||
PerfWorldHotspotIndex.Entry hotspot = build.hotspots().entries().get(0);
|
||||
|
||||
assertEquals(sampleMillis / 10.0D, entity.metric().mspt(), 0.000_001D);
|
||||
assertEquals(sampleMillis / 100.0D,
|
||||
build.report().chunks().get(0).metric().mspt(), 0.000_001D);
|
||||
assertEquals(91L, hotspot.measurementStartTick());
|
||||
assertEquals(10L, hotspot.measurementTicks(build.hotspots().tickCount()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void capturesRareMultiSampleEntityTickAsBurstMax() {
|
||||
PerfSampleAggregator aggregator = new PerfSampleAggregator(8L);
|
||||
|
||||
+7
-6
@@ -18,10 +18,11 @@ class PerfWorldHotspotPayloadTest {
|
||||
@Test
|
||||
void roundTripsCompactEntriesWithoutUuidIdentity() {
|
||||
var entity = new PerfWorldHotspotPayload.Entry(
|
||||
PerfWorldHotspotIndex.Key.entity(42), TickDetailKind.ENTITY, 7, 123L, 640);
|
||||
PerfWorldHotspotIndex.Key.entity(42), TickDetailKind.ENTITY,
|
||||
7, 17L, 123L, 640);
|
||||
var blockEntity = new PerfWorldHotspotPayload.Entry(
|
||||
PerfWorldHotspotIndex.Key.blockEntity(3, 64, -2),
|
||||
TickDetailKind.BLOCK, 11, 9L, 384);
|
||||
TickDetailKind.BLOCK, 11, 1L, 9L, 384);
|
||||
var expected = new PerfWorldHotspotPayload(5L, 0L, 20L, 20L, 1_250_000L,
|
||||
ResourceLocation.parse("minecraft:overworld"), true,
|
||||
List.of(entity, blockEntity), List.of());
|
||||
@@ -65,16 +66,16 @@ class PerfWorldHotspotPayloadTest {
|
||||
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
new PerfWorldHotspotIndex.Entry(blockKey, "minecraft:overworld",
|
||||
4, 64, -2, TickDetailKind.BLOCK, stone, 1L, 256));
|
||||
4, 64, -2, TickDetailKind.BLOCK, stone, 1L, 1L, 256));
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
new PerfWorldHotspotIndex.Entry(blockKey, "minecraft:overworld",
|
||||
3, 64, -2, TickDetailKind.ENTITY, stone, 1L, 256));
|
||||
3, 64, -2, TickDetailKind.ENTITY, stone, 1L, 1L, 256));
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
new PerfWorldHotspotPayload.Entry(
|
||||
blockKey, TickDetailKind.ENTITY, 1, 1L, 256));
|
||||
blockKey, TickDetailKind.ENTITY, 1, 1L, 1L, 256));
|
||||
assertThrows(IllegalArgumentException.class, () ->
|
||||
new PerfWorldHotspotPayload.Entry(
|
||||
PerfWorldHotspotIndex.Key.entity(4),
|
||||
TickDetailKind.BLOCK, 1, 1L, 256));
|
||||
TickDetailKind.BLOCK, 1, 1L, 1L, 256));
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -122,6 +122,6 @@ class PerfWorldHotspotStreamTest {
|
||||
int x, int y, int z, long samples, ResourceLocation detailId) {
|
||||
return new PerfWorldHotspotIndex.Entry(
|
||||
PerfWorldHotspotIndex.Key.blockEntity(x, y, z), DIMENSION.toString(), x, y, z,
|
||||
TickDetailKind.BLOCK, detailId, samples, (int) samples * 256);
|
||||
TickDetailKind.BLOCK, detailId, 1L, samples, (int) samples * 256);
|
||||
}
|
||||
}
|
||||
@@ -77,7 +77,7 @@ class WeakChunkPayloadTest {
|
||||
|
||||
@Test
|
||||
void networkProtocolIsVersionSix() {
|
||||
assertEquals("6", WarashiNetwork.NETWORK_VERSION);
|
||||
assertEquals("7", WarashiNetwork.NETWORK_VERSION);
|
||||
}
|
||||
|
||||
private static RegistryFriendlyByteBuf buffer() {
|
||||
|
||||
Reference in new issue
Block a user