Skip to content

Commit 74d17f4

Browse files
committed
Split direct-write mutation phases
1 parent 3e23f2e commit 74d17f4

4 files changed

Lines changed: 104 additions & 14 deletions

File tree

common/src/main/java/com/loohp/interactionvisualizer/Commands.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646

4747
import java.util.LinkedList;
4848
import java.util.List;
49+
import java.util.Locale;
4950
import java.util.UUID;
5051

5152
public class Commands implements CommandExecutor, TabCompleter {
@@ -474,7 +475,10 @@ private static void mutatePerformanceBlockScene(CommandSender sender, String[] a
474475
"state=invalid reason=online_player_or_owner_uuid_required");
475476
return;
476477
}
478+
long mutationCommandStartNanos = System.nanoTime();
477479
PerformanceBlockScene.Snapshot current = PerformanceBlockScene.snapshot(owner.ownerId());
480+
long mutationPreflightElapsedNanos = Math.max(
481+
0L, System.nanoTime() - mutationCommandStartNanos);
478482
if (current.state() == PerformanceBlockScene.SceneState.ABSENT) {
479483
sendBlockSceneRecordForOwner(sender, "mutate", owner.displayName(), current.summary());
480484
return;
@@ -495,7 +499,14 @@ private static void mutatePerformanceBlockScene(CommandSender sender, String[] a
495499
snapshot = PerformanceBlockScene.mutate(owner.ownerId(),
496500
count == null ? current.placedCount() : count);
497501
}
498-
sendBlockSceneRecordForOwner(sender, "mutate", owner.displayName(), snapshot.summary());
502+
long mutationCommandElapsedNanos = Math.max(
503+
0L, System.nanoTime() - mutationCommandStartNanos);
504+
String commandTiming = " mutationPreflightMs="
505+
+ String.format(Locale.ROOT, "%.6f", mutationPreflightElapsedNanos / 1_000_000.0D)
506+
+ " mutationCommandMs="
507+
+ String.format(Locale.ROOT, "%.6f", mutationCommandElapsedNanos / 1_000_000.0D);
508+
sendBlockSceneRecordForOwner(sender, "mutate", owner.displayName(),
509+
snapshot.summary() + commandTiming);
499510
}
500511

501512
private static void inspectPerformanceBlockScene(CommandSender sender, String[] args, boolean clear) {

common/src/main/java/com/loohp/interactionvisualizer/debug/PerformanceBlockScene.java

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ public record Snapshot(
127127
long lastMutationStartBukkitTick,
128128
long lastMutationEndBukkitTick,
129129
long lastMutationElapsedNanos,
130+
long lastMutationWriteElapsedNanos,
131+
long lastMutationInspectionElapsedNanos,
130132
int restoredCount,
131133
int skippedExternalCount,
132134
int restoreFailureCount,
@@ -156,6 +158,8 @@ public String summary() {
156158
+ " mutationStartBukkitTick=" + lastMutationStartBukkitTick
157159
+ " mutationEndBukkitTick=" + lastMutationEndBukkitTick
158160
+ " mutationElapsedMs=" + String.format(Locale.ROOT, "%.6f", lastMutationElapsedNanos / 1_000_000.0D)
161+
+ " mutationWriteMs=" + String.format(Locale.ROOT, "%.6f", lastMutationWriteElapsedNanos / 1_000_000.0D)
162+
+ " mutationInspectionMs=" + String.format(Locale.ROOT, "%.6f", lastMutationInspectionElapsedNanos / 1_000_000.0D)
159163
+ " restored=" + restoredCount
160164
+ " skippedExternal=" + skippedExternalCount
161165
+ " restoreFailures=" + restoreFailureCount
@@ -482,6 +486,10 @@ private static Snapshot mutate(Session session, int requestedOperations, Mode mo
482486
applied++;
483487
}
484488
}
489+
// Keep the synchronous write loop separate from the ownership/PDC
490+
// verification performed by snapshot(). Total mutation time still
491+
// includes the small bookkeeping gap between both phases.
492+
long writeEndNanos = System.nanoTime();
485493
session.lastMutationRequested = operations;
486494
session.lastMutationApplied = applied;
487495
session.skippedExternalCount = skipped;
@@ -492,7 +500,7 @@ private static Snapshot mutate(Session session, int requestedOperations, Mode mo
492500
mode == Mode.ACTIVE ? "vanilla_furnace_events_primed"
493501
: mode == Mode.DIRECT_WRITE ? "eventless_direct_write"
494502
: "idle_normalized",
495-
true, startTick, startNanos);
503+
true, startTick, startNanos, writeEndNanos);
496504
}
497505

498506
private static List<Entry> plan(Player owner, int count) {
@@ -762,14 +770,16 @@ private static List<Block> blocks(List<Entry> entries) {
762770
}
763771

764772
private static Snapshot snapshot(Session session, SceneState state, String detail) {
765-
return snapshot(session, state, detail, false, NO_MUTATION_TICK, 0L);
773+
return snapshot(session, state, detail, false, NO_MUTATION_TICK, 0L, 0L);
766774
}
767775

768776
private static Snapshot snapshot(Session session, SceneState state, String detail,
769777
boolean captureMutationTiming,
770-
long mutationStartBukkitTick, long mutationStartNanos) {
778+
long mutationStartBukkitTick, long mutationStartNanos,
779+
long mutationWriteEndNanos) {
771780
int owned = 0;
772781
int unloaded = 0;
782+
long inspectionStartNanos = captureMutationTiming ? System.nanoTime() : 0L;
773783
try {
774784
for (Entry entry : session.entries) {
775785
if (!isLoaded(entry)) {
@@ -780,9 +790,12 @@ private static Snapshot snapshot(Session session, SceneState state, String detai
780790
}
781791
} finally {
782792
if (captureMutationTiming) {
793+
long endNanos = System.nanoTime();
783794
session.lastMutationStartBukkitTick = mutationStartBukkitTick;
784795
session.lastMutationEndBukkitTick = Bukkit.getCurrentTick();
785-
session.lastMutationElapsedNanos = Math.max(0L, System.nanoTime() - mutationStartNanos);
796+
session.lastMutationElapsedNanos = Math.max(0L, endNanos - mutationStartNanos);
797+
session.lastMutationWriteElapsedNanos = Math.max(0L, mutationWriteEndNanos - mutationStartNanos);
798+
session.lastMutationInspectionElapsedNanos = Math.max(0L, endNanos - inspectionStartNanos);
786799
}
787800
}
788801
Counts counts = session.counts;
@@ -791,14 +804,15 @@ private static Snapshot snapshot(Session session, SceneState state, String detai
791804
owned, unloaded, counts.furnace, counts.blastFurnace, counts.smoker,
792805
counts.beeHive, counts.beeNest, session.revision, session.lastMutationRequested,
793806
session.lastMutationApplied, session.lastMutationStartBukkitTick, session.lastMutationEndBukkitTick,
794-
session.lastMutationElapsedNanos, session.restoredCount, session.skippedExternalCount,
807+
session.lastMutationElapsedNanos, session.lastMutationWriteElapsedNanos,
808+
session.lastMutationInspectionElapsedNanos, session.restoredCount, session.skippedExternalCount,
795809
session.restoreFailureCount, session.inspectionFailureCount, detail);
796810
}
797811

798812
private static Snapshot absent(UUID ownerId, String detail) {
799813
return new Snapshot(ownerId, SceneState.ABSENT, null, 0, 0, 0, 0, 0, 0,
800814
0, 0, 0, 0, 0, 0L, 0, 0, NO_MUTATION_TICK, NO_MUTATION_TICK,
801-
0L, 0, 0, 0, 0, detail);
815+
0L, 0L, 0L, 0, 0, 0, 0, detail);
802816
}
803817

804818
private static Session requireSession(UUID ownerId) {
@@ -877,6 +891,8 @@ private static final class Session {
877891
private long lastMutationStartBukkitTick;
878892
private long lastMutationEndBukkitTick;
879893
private long lastMutationElapsedNanos;
894+
private long lastMutationWriteElapsedNanos;
895+
private long lastMutationInspectionElapsedNanos;
880896
private int restoredCount;
881897
private int skippedExternalCount;
882898
private int restoreFailureCount;

common/src/test/java/com/loohp/interactionvisualizer/debug/PerformanceBlockSceneSnapshotTest.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,36 +24,45 @@ class PerformanceBlockSceneSnapshotTest {
2424

2525
@Test
2626
void summaryPublishesLastMutationTickWindowAndElapsedNanos() {
27-
PerformanceBlockScene.Snapshot snapshot = snapshot(410L, 411L, 12_345_678L);
27+
PerformanceBlockScene.Snapshot snapshot = snapshot(
28+
410L, 411L, 12_345_678L, 8_000_000L, 3_000_000L);
2829

2930
assertEquals(410L, snapshot.lastMutationStartBukkitTick());
3031
assertEquals(411L, snapshot.lastMutationEndBukkitTick());
3132
assertEquals(12_345_678L, snapshot.lastMutationElapsedNanos());
33+
assertEquals(8_000_000L, snapshot.lastMutationWriteElapsedNanos());
34+
assertEquals(3_000_000L, snapshot.lastMutationInspectionElapsedNanos());
3235

3336
Map<String, String> fields = summaryFields(snapshot);
3437
assertEquals("410", fields.get("mutationStartBukkitTick"));
3538
assertEquals("411", fields.get("mutationEndBukkitTick"));
3639
assertEquals("12.345678", fields.get("mutationElapsedMs"));
40+
assertEquals("8.000000", fields.get("mutationWriteMs"));
41+
assertEquals("3.000000", fields.get("mutationInspectionMs"));
3742
}
3843

3944
@Test
4045
void summaryKeepsNoMutationSentinelsObservable() {
41-
Map<String, String> fields = summaryFields(snapshot(-1L, -1L, 0L));
46+
Map<String, String> fields = summaryFields(snapshot(-1L, -1L, 0L, 0L, 0L));
4247

4348
assertEquals("-1", fields.get("mutationStartBukkitTick"));
4449
assertEquals("-1", fields.get("mutationEndBukkitTick"));
4550
assertEquals("0.000000", fields.get("mutationElapsedMs"));
51+
assertEquals("0.000000", fields.get("mutationWriteMs"));
52+
assertEquals("0.000000", fields.get("mutationInspectionMs"));
4653
}
4754

48-
private static PerformanceBlockScene.Snapshot snapshot(long startTick, long endTick, long elapsedNanos) {
55+
private static PerformanceBlockScene.Snapshot snapshot(long startTick, long endTick, long elapsedNanos,
56+
long writeElapsedNanos,
57+
long inspectionElapsedNanos) {
4958
return new PerformanceBlockScene.Snapshot(
5059
UUID.fromString("11111111-2222-3333-4444-555555555555"),
5160
PerformanceBlockScene.SceneState.READY,
5261
PerformanceBlockScene.Mode.DIRECT_WRITE,
5362
5, 5, 5, 0, 5, 0,
5463
1, 1, 1, 1, 1,
5564
7L, 5, 5,
56-
startTick, endTick, elapsedNanos,
65+
startTick, endTick, elapsedNanos, writeElapsedNanos, inspectionElapsedNanos,
5766
0, 0, 0, 0,
5867
"eventless_direct_write");
5968
}

tools/perf/run-phase2-runtime-once.sh

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -828,26 +828,68 @@ if scenario == "block-direct-write":
828828
mutation_start_tick = int(mutate["fields"]["mutationStartBukkitTick"])
829829
mutation_end_tick = int(mutate["fields"]["mutationEndBukkitTick"])
830830
mutation_elapsed_ms = float(mutate["fields"]["mutationElapsedMs"])
831+
mutation_write_ms = float(mutate["fields"]["mutationWriteMs"])
832+
mutation_inspection_ms = float(mutate["fields"]["mutationInspectionMs"])
833+
mutation_preflight_ms = float(mutate["fields"]["mutationPreflightMs"])
834+
mutation_command_ms = float(mutate["fields"]["mutationCommandMs"])
831835
if mutation_start_tick < 0 or mutation_end_tick != mutation_start_tick:
832836
raise SystemExit(
833837
f"direct-write mutation did not complete synchronously in one Bukkit tick: "
834838
f"start={mutation_start_tick} end={mutation_end_tick}"
835839
)
836840
if not math.isfinite(mutation_elapsed_ms) or mutation_elapsed_ms <= 0:
837841
raise SystemExit(f"invalid direct-write mutationElapsedMs={mutation_elapsed_ms!r}")
838-
for field in ("mutationStartBukkitTick", "mutationEndBukkitTick", "mutationElapsedMs"):
842+
if (not math.isfinite(mutation_write_ms) or mutation_write_ms <= 0
843+
or mutation_write_ms > mutation_elapsed_ms):
844+
raise SystemExit(f"invalid direct-write mutationWriteMs={mutation_write_ms!r}")
845+
if (not math.isfinite(mutation_inspection_ms) or mutation_inspection_ms <= 0
846+
or mutation_inspection_ms > mutation_elapsed_ms):
847+
raise SystemExit(f"invalid direct-write mutationInspectionMs={mutation_inspection_ms!r}")
848+
if (not math.isfinite(mutation_command_ms) or mutation_command_ms <= 0
849+
or mutation_elapsed_ms > mutation_command_ms):
850+
raise SystemExit(f"invalid direct-write mutationCommandMs={mutation_command_ms!r}")
851+
if (not math.isfinite(mutation_preflight_ms) or mutation_preflight_ms <= 0
852+
or mutation_preflight_ms > mutation_command_ms):
853+
raise SystemExit(f"invalid direct-write mutationPreflightMs={mutation_preflight_ms!r}")
854+
timing_fields = (
855+
"mutationStartBukkitTick",
856+
"mutationEndBukkitTick",
857+
"mutationElapsedMs",
858+
"mutationWriteMs",
859+
"mutationInspectionMs",
860+
)
861+
for field in timing_fields:
839862
if clear["fields"].get(field) != mutate["fields"].get(field):
840863
raise SystemExit(f"clear record did not preserve direct-write timing field {field}")
841864
slowest_tick = int(metrics["msptMaxBukkitTick"])
842865
slowest_epoch_ms = int(metrics["msptMaxEndEpochMillis"])
843866
slowest_block_checks = int(metrics["msptMaxBlockUpdateChecks"])
844867
slowest_block_ms = float(metrics["msptMaxBlockUpdateMs"])
845868
slowest_ms = float(metrics["msptMax"])
869+
if not math.isfinite(slowest_ms) or slowest_ms <= 0:
870+
raise SystemExit(f"invalid direct-write msptMax={slowest_ms!r}")
871+
if mutation_command_ms > slowest_ms:
872+
raise SystemExit(
873+
f"direct-write command duration exceeds its completed tick: "
874+
f"command={mutation_command_ms!r} tick={slowest_ms!r}"
875+
)
876+
mutation_unattributed_ms = max(
877+
0.0, mutation_elapsed_ms - mutation_write_ms - mutation_inspection_ms
878+
)
879+
mutation_command_unattributed_ms = max(
880+
0.0, mutation_command_ms - mutation_preflight_ms - mutation_elapsed_ms
881+
)
846882
direct_write_diagnostics = {
847-
"schemaVersion": 1,
883+
"schemaVersion": 2,
848884
"mutationStartBukkitTick": mutation_start_tick,
849885
"mutationEndBukkitTick": mutation_end_tick,
850886
"mutationElapsedMs": mutation_elapsed_ms,
887+
"mutationWriteMs": mutation_write_ms,
888+
"mutationInspectionMs": mutation_inspection_ms,
889+
"mutationUnattributedMs": mutation_unattributed_ms,
890+
"mutationPreflightMs": mutation_preflight_ms,
891+
"mutationCommandMs": mutation_command_ms,
892+
"mutationCommandUnattributedMs": mutation_command_unattributed_ms,
851893
"slowestBukkitTick": slowest_tick,
852894
"slowestTickEndEpochMillis": slowest_epoch_ms,
853895
"slowestTickMs": slowest_ms,
@@ -857,6 +899,18 @@ if scenario == "block-direct-write":
857899
"mutationFractionOfSlowestTick": (
858900
mutation_elapsed_ms / slowest_ms if slowest_ms > 0 else None
859901
),
902+
"mutationWriteFractionOfSlowestTick": (
903+
mutation_write_ms / slowest_ms if slowest_ms > 0 else None
904+
),
905+
"mutationInspectionFractionOfSlowestTick": (
906+
mutation_inspection_ms / slowest_ms if slowest_ms > 0 else None
907+
),
908+
"mutationPreflightFractionOfSlowestTick": (
909+
mutation_preflight_ms / slowest_ms if slowest_ms > 0 else None
910+
),
911+
"mutationCommandFractionOfSlowestTick": (
912+
mutation_command_ms / slowest_ms if slowest_ms > 0 else None
913+
),
860914
"blockUpdateFractionOfSlowestTick": (
861915
slowest_block_ms / slowest_ms if slowest_ms > 0 else None
862916
),
@@ -869,7 +923,7 @@ elif clear_revision != create_revision:
869923
)
870924
871925
evidence = {
872-
"schemaVersion": 2,
926+
"schemaVersion": 3,
873927
"scenario": scenario,
874928
"formalEvidenceReady": True,
875929
"records": {"create": create, "mutate": mutate, "clear": clear},

0 commit comments

Comments
 (0)