diff --git a/TODO.md b/TODO.md index 88f10bf..a96d6cc 100644 --- a/TODO.md +++ b/TODO.md @@ -151,6 +151,23 @@ and the p90 tail fell from ~470ms to ~330ms. The proxy was ruled out early run can also finish before the client JVM has a session to flush, and then only the host profile is collected, with nothing said about the missing one. +- [ ] **Game-summary images cost ~18% of the EDT's working time.** Measured on + the first spectated profile (2026-08-07): `BoardView$1.gamePhaseChange` + calls `ImageIO.write` on every phase change, on the EDT, because we set + `GameSummaryBoardView` in `megamek/clientsettings.xml.template`. That is + 1541 of 8383 running EDT samples going into PNGs nobody has looked at - + and the sibling `GameSummaryMinimap` is the one already doubted above, + since `MinimapEnabled` is false. + + It is also a second PNG encoder: the summary goes through the JDK's + `com.sun.imageio.plugins.png.PNGImageWriter`, not Suramadu's native ZPNG, + so the two compete for the same EDT. + + Caveat before acting: a bot-vs-bot match changes phase far more often per + minute than a human one, so 18% is the fast end. The fix is to decide + whether these images are wanted at all - `container/exit/finalize.sh` + collects them - and if they are, whether they can be written off the EDT. + - [ ] **Frame tearing.** A white or half-drawn band at the board's top edge, phase-independent, survives patches 0006 and 0007 (0006 confirmed effective on the 2026-08-05 retest). MegaMek's paint paths audit clean: diff --git a/tests/profile-summary.py b/tests/profile-summary.py index 8125ff8..a8c100b 100755 --- a/tests/profile-summary.py +++ b/tests/profile-summary.py @@ -27,8 +27,30 @@ from collections import Counter # matches, because they nest - an encode happens inside a paint. BUCKETS = ( ("paint", ("BoardView", "paintComponent", "RepaintManager")), - ("encode+hash", ("ZPNG", "libzpng", "EncoderBase", "xxhash")), + # Everything that turns pixels into PNG bytes, including zlib's own + # internals. Naming only the entry points undercounted this by nearly 3x: + # the encoders hand off to deflate and the samples land in longest_match + # and compress_block, which named neither ZPNG nor PNG. + ( + "png+deflate", + ( + "ZPNG", + "libzpng", + "EncoderBase", + "PNGImageWriter", + "ImageIO", + "deflate", + "longest_match", + "compress_block", + "slide_hash", + ), + ), + # A subset of the above, called out because it is not frame traffic at all: + # MegaMek's GameSummaryBoardView preference writes a board PNG through + # ImageIO on every phase change, on the EDT. Nobody has ever looked at one. + (" of which game-summary images", ("ImageIO", "PNGImageWriter")), ("directdraw", ("ImageConst", "DrawConstantPool", "DrawInstructionFactory")), + ("hash", ("xxhash", "Coords.hashCode")), ("gc/alloc", ("GCTaskThread", "G1", "CollectedHeap")), ) diff --git a/tests/shell/test-profile-summary.sh b/tests/shell/test-profile-summary.sh index 43835b4..73feedc 100755 --- a/tests/shell/test-profile-summary.sh +++ b/tests/shell/test-profile-summary.sh @@ -40,8 +40,8 @@ check " and the rest is working" grep -q "100 running" <<<"$out" # The buckets nest: an encode happens inside a paint, so 100% paint and 70% # encode is the correct reading of this fixture, not a double count. check " everything running was under paint" grep -q "100.0% paint" <<<"$out" -check " and the encode share is of the working samples" \ - grep -q "70.0% encode+hash" <<<"$out" +check " and the png share is of the working samples" \ + grep -q "70.0% png+deflate" <<<"$out" check " a thread that is not the EDT is left out" \ bash -c '! grep -q "ThreadPoolExecutor" <<<"$1"' _ "$out" @@ -52,6 +52,14 @@ check "--thread '' counts every thread" \ bash -c 'python3 "$1/tests/profile-summary.py" --thread "" "$2" | grep -q "1,400 waiting"' \ _ "$ROOT" "$TMP/p.collapsed" +# zlib internals name neither ZPNG nor PNG, and missing them undercounted the +# encode share by nearly 3x on the first real profile. +printf 'java/awt/EventDispatchThread.run;megamek/BoardView.drawHex;deflate_slow;longest_match 40\n' \ + >> "$TMP/p.collapsed" +check " zlib internals count as encode, not as something else" \ + bash -c 'python3 "$1/tests/profile-summary.py" "$2" | grep -q "78.6% png+deflate"' \ + _ "$ROOT" "$TMP/p.collapsed" + # A profile with nothing running says so rather than dividing by zero. printf 'java/awt/EventDispatchThread.run;java/awt/EventQueue.getNextEvent 10\n' \ > "$TMP/idle.collapsed"