Harden simulation profile snapshots
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
package com.livingworld.debug;
|
package com.livingworld.debug;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.StringJoiner;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Immutable snapshot of one simulation profiling cycle.
|
* Immutable snapshot of one simulation profiling cycle.
|
||||||
@@ -24,12 +26,26 @@ public record SimulationProfileSnapshot(
|
|||||||
if (eventsPublished < 0 || regionsUpdated < 0 || savesPerformed < 0) {
|
if (eventsPublished < 0 || regionsUpdated < 0 || savesPerformed < 0) {
|
||||||
throw new IllegalArgumentException("profile counts must not be negative");
|
throw new IllegalArgumentException("profile counts must not be negative");
|
||||||
}
|
}
|
||||||
moduleTimings = Map.copyOf(new LinkedHashMap<>(moduleTimings));
|
LinkedHashMap<String, Long> timingCopy = new LinkedHashMap<>();
|
||||||
|
moduleTimings.forEach((moduleId, timingNanos) -> {
|
||||||
|
if (moduleId == null || moduleId.isBlank()) {
|
||||||
|
throw new IllegalArgumentException("Module IDs must not be blank");
|
||||||
|
}
|
||||||
|
if (timingNanos == null || timingNanos < 0) {
|
||||||
|
throw new IllegalArgumentException("Module timings must be non-negative");
|
||||||
|
}
|
||||||
|
timingCopy.put(moduleId, timingNanos);
|
||||||
|
});
|
||||||
|
moduleTimings = Collections.unmodifiableMap(timingCopy);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String toHumanReadableString() {
|
public String toHumanReadableString() {
|
||||||
|
StringJoiner formattedTimings = new StringJoiner(", ", "{", "}");
|
||||||
|
moduleTimings.forEach((moduleId, timingNanos) ->
|
||||||
|
formattedTimings.add(moduleId + "=" + timingNanos / 1_000_000.0 + "ms"));
|
||||||
|
|
||||||
return "cycle=" + (totalCycleNanos / 1_000_000.0) + "ms"
|
return "cycle=" + (totalCycleNanos / 1_000_000.0) + "ms"
|
||||||
+ ", modules=" + moduleTimings
|
+ ", modules=" + formattedTimings
|
||||||
+ ", events=" + eventsPublished
|
+ ", events=" + eventsPublished
|
||||||
+ ", regions=" + regionsUpdated
|
+ ", regions=" + regionsUpdated
|
||||||
+ ", saves=" + savesPerformed
|
+ ", saves=" + savesPerformed
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package com.livingworld.debug;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
class SimulationProfileSnapshotTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void defensivelyCopiesAndLocksModuleTimings() {
|
||||||
|
Map<String, Long> timings = new LinkedHashMap<>();
|
||||||
|
timings.put("ecology", 2_000_000L);
|
||||||
|
|
||||||
|
SimulationProfileSnapshot snapshot =
|
||||||
|
new SimulationProfileSnapshot(3_000_000L, timings, 2, 3, 1, false);
|
||||||
|
timings.put("settlements", 1_000_000L);
|
||||||
|
|
||||||
|
assertEquals(Map.of("ecology", 2_000_000L), snapshot.moduleTimings());
|
||||||
|
assertThrows(
|
||||||
|
UnsupportedOperationException.class,
|
||||||
|
() -> snapshot.moduleTimings().put("weather", 500_000L));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void rejectsInvalidValues() {
|
||||||
|
assertThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() -> new SimulationProfileSnapshot(-1, Map.of(), 0, 0, 0, false));
|
||||||
|
assertThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() -> new SimulationProfileSnapshot(0, Map.of("", 1L), 0, 0, 0, false));
|
||||||
|
assertThrows(
|
||||||
|
IllegalArgumentException.class,
|
||||||
|
() -> new SimulationProfileSnapshot(0, Map.of("test", -1L), 0, 0, 0, false));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void formatsMeasurementsForDebugOutput() {
|
||||||
|
SimulationProfileSnapshot snapshot = new SimulationProfileSnapshot(
|
||||||
|
3_000_000L, Map.of("test", 2_000_000L), 2, 3, 1, true);
|
||||||
|
|
||||||
|
String output = snapshot.toHumanReadableString();
|
||||||
|
|
||||||
|
assertTrue(output.contains("cycle=3.0ms"));
|
||||||
|
assertTrue(output.contains("test=2.0ms"));
|
||||||
|
assertTrue(output.contains("events=2"));
|
||||||
|
assertTrue(output.contains("budgetExceeded=true"));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user