| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Sorry, something went wrong.
…xpire Pin the cost of RFC 6762 §10.2 paragraph 2 cache-expiry path (_async_set_created_ttl in-place mutation) so a copy-on-expire follow-up (issue python-zeroconf#1780) has a baseline to measure against.
Codecov Report✅ All modified and coverable lines are covered by tests. @@ Coverage Diff @@
## master #1785 +/- ##
=======================================
Coverage 99.77% 99.77%
=======================================
Files 33 33
Lines 3536 3536
Branches 498 498
=======================================
Hits 3528 3528
Misses 5 5
Partials 3 3 ☔ View full report in Codecov by Sentry.
|
Sorry, something went wrong.
Merging this PR will not alter performance✅ 18 untouched benchmarks Performance Changes
Comparing bluetoothbot:koan/bench-mark-records-to-expire (bd36427) with master (44433dd) |
Sorry, something went wrong.
There was a problem hiding this comment.
This looks wrong. It needs to make sure it only iterates the benchmark once since it will only measure the first clear. And be careful not to measure cost of adding
Sorry, something went wrong.
Sorry, something went wrong.
Rebase with requested adjustmentsBranch koan/bench-mark-records-to-expire was rebased onto master and review feedback was applied. Stats1 file changed, 123 insertions(+)
CI statusCI will be checked asynchronously. Automated by Kōan |
Sorry, something went wrong.
Sorry, something went wrong.
PR Review — test: add benchmarks for cache mark-to-expire path (#1780)The three benchmarks have a load-bearing correctness bug that the CodSpeed report itself surfaces: all_stale and none_stale measure identical 120.2 µs because async_mark_unique_records_older_than_1s_to_expire mutates each cached record's created field on the first call, and CodSpeed re-runs the closure many times per measurement against the shared outer-scope cache. Iterations 2..N hit the cheap age-check-and-skip path, so the "worst case" benchmark is really measuring the scan-only path, and there's no actual baseline for the copy-on-expire follow-up (#1780) to compare against. @bdraco's review identified both halves of this — reset state between iterations, and don't pay the async_add_records cost in the measured body. The fix is benchmark.pedantic(setup=..., rounds=N, iterations=1). Docstrings also carry more narrative than CLAUDE.md asks for. Blocking until the pedantic shape lands and the new CodSpeed numbers actually diverge between stale and fresh. 🔴 Blocking1. all_stale benchmark only measures the first iteration's mutation path (`tests/benchmarks/test_cache_mark_expire.py`, L21-51)This benchmark doesn't measure what its docstring claims. async_mark_unique_records_older_than_1s_to_expire calls _async_set_created_ttl(record, now, 1), which mutates the cached record in place to created=now, ttl=1 (see _cache.py:343-348). CodSpeed's walltime harness runs the decorated closure many times per measurement — and since cache, the records, and now are all captured from the outer scope, on iterations 2..N the age check (now - created_double > _ONE_SECOND) is (now - now > 1000) == False, so the mutation branch is skipped entirely. Only the first call hits the worst case. The CodSpeed report on this PR is the smoking gun: test_mark_to_expire_1000_records_all_stale and test_mark_to_expire_1000_records_none_stale both report 120.2 µs — identical to the tenth of a microsecond. If the all-stale path were actually being measured it would be substantially slower (1000 extra _async_add calls per iteration). The reported number is essentially just the scan + age-check cost in both cases, which means the "baseline for copy-on-expire follow-up" this PR is meant to provide doesn't exist. Fix: reset cache state before each measured iteration without including setup in the timing. The standard pytest-benchmark/pytest-codspeed idiom is benchmark.pedantic with a setup callable, e.g. records = [DNSAddress(name, _TYPE_A, _UNIQUE_CLASS, 120, _ipv4_bytes(i), created=now - 5_000) for i in range(1000)]
def _setup():
cache = DNSCache()
cache.async_add_records(records)
return (cache, unique_types, answers, now), {}
benchmark.pedantic(
DNSCache.async_mark_unique_records_older_than_1s_to_expire,
setup=_setup,
rounds=50,
iterations=1,
)This keeps the async_add_records cost out of the measurement (per @bdraco's second point) while ensuring every measured call sees fresh, stale records. Apply the same pattern to test_mark_to_expire_many_unique_types. test_mark_to_expire_1000_records_none_stale is the only one of the three that's stable across iterations as written — but for consistency it should use the same pedantic shape. @benchmark
def _mark() -> None:
cache.async_mark_unique_records_older_than_1s_to_expire(unique_types, answers, now)
Same root cause as the first benchmark: the 100 cached PTR records start with created=now - 5_000, the first call mutates all of them to created=now, ttl=1, and every subsequent measured iteration falls out of the mutation branch. The 135.1 µs CodSpeed number is dominated by the outer loop over 100 unique_types triplets and the scan, not by the mutation cost the test is meant to expose. Same fix — benchmark.pedantic with a setup callable that rebuilds the cache between rounds. 🟢 Suggestions1. Docstring carries narrative that belongs in the PR body (`tests/benchmarks/test_cache_mark_expire.py`, L1-8)Per CLAUDE.md, test docstrings should pin what the test asserts in one sentence — not retell the production-side story. The line-number reference (_cache.py line ~345) will rot the moment the file shifts, and the issue cross-reference ("follow-up to issue #1780") belongs in the PR description rather than the source. Consider trimming to a single line: "Benchmarks for DNSCache.async_mark_unique_records_older_than_1s_to_expire." Same goes for the per-test docstrings — they each include rationale ("the delta to the all-stale case is the mutation+re-add tax we'd avoid by switching to copy-on-expire") that's PR-body material. Checklist
SummaryThe three benchmarks have a load-bearing correctness bug that the CodSpeed report itself surfaces: all_stale and none_stale measure identical 120.2 µs because async_mark_unique_records_older_than_1s_to_expire mutates each cached record's created field on the first call, and CodSpeed re-runs the closure many times per measurement against the shared outer-scope cache. Iterations 2..N hit the cheap age-check-and-skip path, so the "worst case" benchmark is really measuring the scan-only path, and there's no actual baseline for the copy-on-expire follow-up (#1780) to compare against. @bdraco's review identified both halves of this — reset state between iterations, and don't pay the async_add_records cost in the measured body. The fix is benchmark.pedantic(setup=..., rounds=N, iterations=1). Docstrings also carry more narrative than CLAUDE.md asks for. Blocking until the pedantic shape lands and the new CodSpeed numbers actually diverge between stale and fresh. To rebase specific severity levels, mention me: @bluetoothbot rebase critical (fixes 🔴 only), @bluetoothbot rebase important (fixes 🔴 + 🟡), or just @bluetoothbot rebase for all. Automated review by Kōanf6f4a8e |
Sorry, something went wrong.
Rebase with requested adjustmentsBranch koan/bench-mark-records-to-expire was rebased onto master and review feedback was applied. Changes applied
Stats1 file changed, 124 insertions(+)
CI statusCI will be checked asynchronously. Automated by Kōan |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
What
Three CodSpeed benchmarks covering DNSCache.async_mark_unique_records_older_than_1s_to_expire — the RFC 6762 §10.2 paragraph 2 path that today mutates cached DNSRecord instances in place via _async_set_created_ttl.
Why
Issue #1780 proposes switching from in-place mutation to copy-on-expire so listeners and ServiceInfo consumers that hold a reference to a cached record don't see its TTL/created flip out from under them mid-dispatch. The follow-up decision (fix vs. leave as-is) needs a baseline number for the current hot path — these benchmarks supply that.
How
No code changes outside tests/benchmarks/.
Testing
poetry run pytest tests/benchmarks/test_cache_mark_expire.py -v — 3 passed.
Quality Report
Changes: 1 file changed, 123 insertions(+)
Code scan: clean
Tests: passed (4 PASSED)
Branch hygiene: clean
Generated by Kōan post-mission quality pipeline