Repository navigation
perf(rum): spread bursts of RUM calls over later turns - #86
Merged
Merged
Conversation
Every RUM event was assembled, serialized and written to disk synchronously on the caller's thread, usually the UI thread. A host reporting thousands of events in a row (for example one action per synced message) held that thread for as long as the burst took: about 1 s for 3,700 actions on a current phone. Within one turn of the event loop the monitor now processes events on the caller's turn for at most 4 ms. Calls past that budget are queued and processed in slices of the same size on later turns (setTimeout 0), so the thread yields between slices. The queue is bounded at 5,000 events; at the bound a slice runs before the event is queued. - Events are built at call time and keep the timestamp and attributes of their call, and their order. - The queue is processed synchronously before a crash is counted (markCrash, reportError) or a launch view is opened for a JS fault (openLaunchView), before keep-alive (background, flush and stop), before session queries and withheld-session changes, and before the user or the tracking consent changes: the core sends RUM a drain_pending message before it applies either. - After stop, calls are processed on the caller's turn as before. - While queued events are processed, the view document an action updates is written once per slice instead of once per action. A burst writes about half as much, so a few thousand actions in a row no longer exceed the 4 MB disk quota and evict batches before they are uploaded. - A failing deferred view write is logged and does not stop later slices or throw into the timer. Measured on a HarmonyOS phone (API 26, debug build) with 350-character action attributes: - longest main-thread stall for 3,700 actions in one loop: 1,038 ms -> 61 ms; - frames over 16 ms at 300 actions/s for 12 s: 122 -> 2; - SDK CPU for the 3,700-action burst: about one third lower.
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.
Problem
Every RUM event is assembled, serialized and written to disk synchronously on the caller's thread, usually the UI thread. A host that reports thousands of events in a row, for example one custom action per synced message, holds that thread for as long as the burst takes. On a current phone that is about 1 s for 3,700 actions with ~350-character attributes. A burst like that also writes a full view document per action. A few thousand actions in a row exceed the 4 MB disk quota before the uploader catches up, and the oldest batches are evicted.
Change
DefaultRumMonitor.dispatchbecomes adaptive:setTimeout(0)), so the thread yields between slices.markCrash,reportErrorandopenLaunchView, so a persisted crash incident sees every earlier call;drawDetachedSession;drain_pendingmessage.FlashcatCoresends it beforesetUserInfo,clearUserInfoand a tracking-consent change, so queued events keep the user and consent of their call.RumViewScope): while queued events are processed, the view document an action updates is written once per slice instead of once per action. A failing deferred write is logged; it does not stop later slices or throw into the timer.Measurements
HarmonyOS phone (API 26), debug build, 350-character action attributes. Before is
main, after is this branch. One run per scenario.Per action on
main: ~60 µs to serialize the three documents and ~100 µs for three open/append/close calls.Note for integrators: wrapping each call in its own
setTimeout(0)on the host side does not help. Thousands of timers that are due together run back to back in one turn: 3,700 timers produced a 1,028 ms stall.Tests
node scripts/unit-node/run.mjs: 622 passed.flashcat-rum/src/test/DispatchQueue.test.etsand a core test inAppForeground.test.etscover:drain_pending;drain_pendingbefore user and consent changes.hvigorw assembleHarpasses for all modules, and a HAP that compiles the SDK sources passes too.Known limits
addError, the session queries and the other synchronous drains process the whole queue on the caller's turn. The 4 ms per-turn bound applies to the queued path.