Repository navigation
Coalesce repeated events so no program can flood trace (0.7.0) - #30
Merged
Merged
Conversation
One server whose /power command was dispatched every ~100 ms by another plugin sent ~425k command events in five days, one per run. The client now coalesces: the first report of an event (name, value and tags) is sent at once and opens a 60 s window; identical reports inside it are counted and sent as one event when it closes, tagged coalesced=<n> and with value=<n> when the event had none. A window that sent a count opens the next, so a steady flood is one event a minute; close() sends every open count. startup is never coalesced. At most 256 events are counted at once, so memory stays bounded. The sender becomes a single-thread ScheduledThreadPoolExecutor so the same daemon thread closes windows; the 256-report queue bound is kept by counting queued sends. The consumer API is unchanged; new public constants make this a minor release. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 of 2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
One third-party server runs Medieval Factions'
/powercommand roughly every 100 ms (most likely another plugin dispatching it). Medieval Factions reports acommandevent per execution, so trace received about 425k events in five days from that one server. The fix is made in the client, so no consumer can do this, with no change to the consumer API.What was changed
report(). An event is its name, value and tags (afterversion,installand server-wide tags are merged; tag order is ignored). The first report is sent at once and opens a 60 s window (COALESCE_WINDOW_MS). Identical reports inside the window are counted, and a count above zero is sent as one event when the window closes. A window that sent a count reopens, so a steady flood costs one event a minute per event; a window that counted nothing ends.close()flushes every open count before the existing bounded drain.startupis never coalesced or delayed.MAX_COALESCED_EVENTS(256) distinct events are counted at once. A new event beyond that is dropped silently, like a report that finds the queue full. The 256-report send queue bound is kept.ScheduledThreadPoolExecutor, so the same daemon thread (trace-client/<application>) closes windows. No second thread was added. The queue bound is now enforced by counting queued sends.VERSION, pom, README JitPack snippet). This is a minor bump: new public constants (COALESCE_WINDOW_MS,MAX_COALESCED_EVENTS,COALESCED_TAG) and changed send behaviour, with no API removed or changed.How the count is encoded
The trace server's
MetricDtohas a nullableDouble valueand a string→stringtagsmap (at most 32). The server counts rows today and never sumsvalue. A coalesced event carries:value= count, when the original event had no value (everycommandand most other events).valueis the only numeric field, soSUM(COALESCE(value, 1))gives true occurrences for valueless events without any string-to-number cast.coalesced= count, always (when there is room under 32 tags). This marks the row as a summary and carries the count for events that already had a value, which is kept unchanged because its meaning differs per program.coalescedwas chosen overcountso that it cannot collide with a consumer's owncounttag.Occurrences = Σ (
valuewhere taggedcoalesced, else 1). Row counts still answer "was it used, by how many servers". By design they now undercount "how many times".Tests
Eight tests were added: the first report goes out at once and the repeats become one counted event; a steady flood gives about one event per window, the counts add up to every report, and a quiet window ends;
close()sends open counts without waiting for the window; the key includes name, value and tags but not tag order, and NaN counts as no value;startupis never held back; the distinct-event cap holds; nothing is counted afterclose(); andwithCountrespects an own tag andMAX_TAGS. The queue-bound test now floodsstartup, because that is the one event that still goes straight to the queue. The tests use a package-privateBuilder.coalesceWindowMillisto shorten the window, which is not public API.mvn -B verifywas run locally on Java 17 and 21: 94 tests, 0 failures, repeated three times. Java 8 was not available locally, so CI covers it.Releasing and re-vendoring into consumers are left to the owner.
drafted by Claude on behalf of Daniel Stephenson
🤖 Generated with Claude Code