Repository navigation
feat(research): memory probe for browser push listening and the resident bridge [S6] - #205
Merged
Merged
Conversation
…listening [S6] scripts/push_memory_probe.py runs one configuration per call and samples, every 10 s, PSS/USS/RSS of each browser process group from /proc smaps_rollup, the process and renderer counts, the Python process, incoming NATS MSG frames per subject, handshakes and reconnects, and the pages' own API requests. Every SofaScore request takes a slot from the shared research lock. Configs: baseline, shared-browser, multi-sport, flags, block-third-party, live-filter, reload-every, gc-pressure, hidden and three optional engines (chrome headless shell, Camoufox, Lightpanda). scripts/push_memory_matrix.py runs a list of configs one after another with a cool-down. scripts/push_memory_engines.sh installs the optional engines with pinned versions under ~/.cache/s6-engines only.
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.
Item S6: research tooling that measures ways to lower the memory of browser-based push listening (the
pagesource) and of the resident bridge. Nothing in this PR touches SofaScore. Everything was built and tested offline against a local fake site on a.testhost, reached through the browser's proxy, with a fake NATS-over-WebSocket server. The orchestrator runs the live matrix afterwards.What is added
scripts/push_memory_probe.py: runs one configuration per call, for--minutesof steady state after the last tab opens. It writesresearch/push_memory/<run-id>/{samples.jsonl,events.jsonl,summary.json}.scripts/push_memory_matrix.py: runs a list of configurations one after another, with a cool-down between runs.--settleseconds, then terminates any leftover and records it.research/push_memory/matrix-<id>.jsonl.scripts/push_memory_engines.sh: installs the optional engines with pinned versions under~/.cache/s6-enginesonly.tests/test_push_memory_probe.py: 38 offline unit tests and 5 browser tests markedbrowser, which run in the "browser bridge (offline)" CI job.Usage
Useful options:
--interval 10,--warmup 60(per idle phase),--tab-gap 120.--sports,--no-bridge,--flags-target all|live|bridge.--reload-minutes 10,--gc-minutes 5.--hidden-mode window|js|both,--hidden-allow-backgrounding.--live-filter-selector.--max-silence 300.--page-counter,--cdp-driver scrapling|playwright.--keep-profiles,--run-id.What is recorded
Every sample (10 s by default):
bridge,live, Playwrightdriver,other): PSS, USS and RSS summed from/proc/<pid>/smaps_rollup. RSS is kept to compare with the old README numbers. USS is Private_Clean + Private_Dirty + Private_Hugetlb.sport.x,event.{id},odds.{id}…), the number of distinctevent.*subjects, whether the NATS connection is open, and the JS heap.Processes are attributed to a browser through the profile path in their command line (
--user-data-dir=for Chromium,-profilefor Firefox) or through the PID (Lightpanda). Chromium rewrites its process title (setproctitle), so the reader also handles a command line packed into one string.Events:
/proc(only the relevant ones, no paths) and the applied launch options.-ERR, reported only as a known NATS error text.bridge,live-idle,tabs-N), reloads (time until the next handshake), GC rounds, hidden marking, live-filter clicks, and page crashes.Summary:
ok/failed(with reasons) /skipped.What fails a run:
sport.{sport}frames at all.--max-silence.Rate: every SofaScore request goes through the research lock in
scripts/_research_common.py(≤ 1 req/s across research processes). This covers the pages' own API calls, which are throttled inside the load window and blocked after it, exactly like the product. The bridge's reads go through it too, because the bridge's_wait_for_slotis pointed at the same lock. The product's throttle file under~/.cache/sofascore_scraperis never written.Privacy:
NatsReaderdrops it.redact(IPs and geo keys) and are truncated.client_ipandconnect_urls, and a CONNECT credential, never appear in the output, andtests/test_research_privacy.scanis clean.Profiles: each run uses fresh temporary profiles under
~/.cache/sofascore_research/push_memory_probe/<run-id>/{bridge,bridge-live}and deletes them at the end. A lock in that root keeps two probes from ever running at the same time.Configurations and what each measures
baselinepagesource as it is: the resident bridge (quietrobots.txttab), a separate<profile>-livebrowser and one tab per sport, with context-levelroute_actionrules. Phases: bridge → live-idle → tabs.shared-browsermulti-sport--tab-gapapart. Gives the marginal cost per extra sport. Combines withshared-browser.flagsrenderer-limit=N,no-site-isolation(--disable-site-isolation-trials+IsolateOrigins,site-per-process,SitePerProcess),no-bfcache,lite-mode,max-old-space=N,disable-gpu,no-images(imagesEnabled=false),small-viewport(400x300 viewport; the screen fingerprint is unchanged). The challenge gate is recorded for every browser that gets the flags.block-third-partychallenges.cloudflare.com, plus SofaScore's CSS, font, media, image, beacon and manifest requests. The challenge host is treated as in the product.live-filterevent.*subjects, to answer whether it subscribes tosport.*and whether it is lighter.reload-everypage.reload()every N minutes. Records memory and the gap until the next handshake (reload_gaps_s).gc-pressureHeapProfiler.collectGarbage+Memory.simulatePressureNotification(critical).hiddenBrowser.setWindowBounds) and/or overrides the page'svisibilityState/hiddenand firesvisibilitychange. It never freezes the page. Each sample records the visibility the browser itself reports.--hidden-allow-backgroundingalso removes the switches that keep background renderers at full priority, from Scrapling's args and from Playwright's defaults (ignore_default_args).engine-headless-shellchrome-headless-shell, matching patchright's revision 1243 (channel=chromium-headless-shell+executable_path).engine-camoufoxstart()opens Camoufox's persistent context. Scrapling's own Turnstile solver runs on the Firefox pages. Runs with its own virtualenv Python (the matrix picks it automatically).engine-lightpandacdp_url; if that fails, through plain Playwrightconnect_over_cdp. Also counts frames inside the page (see the limits).Which browser Scrapling 0.4.15 / patchright 1.63 launch today
Scrapling sets
channel="chromium". Patchright resolves that to the full Chromium binary (Chrome for Testing 153.0.8010.12, rev 1243) in new headless mode. It would only pickchrome-headless-shellwith no channel andheadless=True. Theengine-headless-shellconfig makes that choice explicit and records the executable that actually ran.Scrapling stealth flags are kept
Scrapling 0.4.15 has a pitfall: passing
extra_flagsreplaces its stealth flags (STEALTH_ARGS and the WebRTC policy), because ofconfig.extra_flags or extra_flags. So the probe never usesextra_flags.Instead, the session is built with exactly the product's arguments and the flags are merged into
session._browser_options["args"]beforestart().--disable-features,--js-flagsand--blink-settingsare merged into a single switch that carries Scrapling's values plus ours. This matters because Playwright also adds its own--disable-featuresand headless--blink-settingsearlier on the command line, and the merged switch comes last.A unit test checks that every stealth switch survives. Another checks that the baseline session is byte-for-byte the product's. A test also runs the product's own
BrowserBridge._launchand checks it receives the same constructor arguments.Optional engines (pinned; the probe marks a missing engine
skipped)camoufox==0.5.8(MIT); browserv156.0.1-beta.36(MPL-2.0), pinned by the package;playwright==1.62.0in its own venv (camoufox needs<1.63, the repo pins 1.63.0)camoufox-156.0.1-beta.36-lin.x86_64.zip)…-lin.arm64.zip, sha2568b5fd083…)1.0.0(sha256 pinned per arch in the script)lightpanda-x86_64-linux)lightpanda-aarch64-linux)Scrapling 0.4.15 no longer ships a Camoufox session. Camoufox comes from the
camoufoxpackage, installed in~/.cache/s6-engines/camoufox-venvtogether with the repo's requirements, with playwright overridden to 1.62.0. The browser archive (1.3 GB) is fetched withXDG_CACHE_HOME=~/.cache/s6-engines/xdg. The probe points Camoufox's cache there only while it imports Camoufox. Lightpanda's telemetry is always disabled (LIGHTPANDA_DISABLE_TELEMETRY=true).Offline results (fake site, mechanics only; not a memory verdict)
chrome-headless-shell. Status ok.websocketevents.Lightpanda 1.0.0 feasibility:
connect_over_cdp+new_contextfails ("Connection closed while reading from the driver").Target.createTarget: TargetAlreadyLoaded). In the separate layout the live browser's own page becomes the tab.Known limits
robots.txtpage, as in the earlier probe and the README's 1.1 GB baseline. The product's resident bridge sits on/tr, whose own API calls cannot be told apart from the bridge'sfetch(), so/tris not measured.live_eventsfrom its API reads so frames per live event can be normalised.hidden: in headless Chromium, minimising the window left the browser-reportedvisibilityStateasvisible(offline). The page's JS still seeshidden. The live run will show whether SofaScore's page does less work.--disable-features/--blink-settingsduplicates come from Playwright, earlier on the command line. Chromium takes the last value, so the merged switch is the one in effect. This was already true of Scrapling's own switch.small-viewportmay move the Turnstile widget outside the 400x300 viewport. The challenge gate will show it live.gc-pressureand the window mode ofhiddenneed CDP (Chromium engines only). The probe rejects them for Camoufox and Lightpanda.Checks
ruff check .passes.STORE_SHADOW_CHECK=1: 9826 passed, 39 skipped, 27 deselected (8 min).tests/test_push_memory_probe.pypass: 4 passed in the repo venv, plus the Camoufox test in the Camoufox venv, which is skipped where Camoufox is not installed (CI).CHANGELOG.mdanddocs/design/**are untouched.