Skip to content

feat: track allocation zero transitions for stuck-slot diagnostics - #416

Draft
bluetoothbot wants to merge 3 commits into
mainfrom
koan/allocation-transition-diagnostics
Draft

feat: track allocation zero transitions for stuck-slot diagnostics#416
bluetoothbot wants to merge 3 commits into
mainfrom
koan/allocation-transition-diagnostics

Conversation

@bluetoothbot

@bluetoothbot bluetoothbot commented May 15, 2026

Copy link
Copy Markdown
Contributor

What

Diagnostics-only instrumentation for ESPHome proxies that report slots=0/N free indefinitely (#340). Tracks free → 0 / 0 → non-zero transitions per source plus a repeat counter for stuck-at-zero updates, and surfaces them in async_diagnostics(). Allocation changes also emit a DEBUG line with previous→new state and scanner-side _connect_in_progress / _connect_failures.

Why

Closes — not yet; this is Phase 1 of the plan on #407. The previous PR detected the symptom but @bdraco asked for the root cause. The slot count is owned by the proxy and travels through aioesphomeapi and bleak-esphome before habluetooth sees it, so a habluetooth-only fix can't address the cause. This PR adds the bare minimum the next #340 reporter needs to attach concrete evidence (transition timestamps + a habluetooth-side connection-state snapshot) without changing user-facing behavior. Phase 2 (lifecycle audit) and Phase 3 (upstream bleak-esphome / firmware coordination) follow once data lands.

How

  • BluetoothManager._allocation_transitions: dict[str, list] — sibling tracking dict keyed by source, value is [last_zero_at, last_recovery_at, zero_repeat_count], mutated in place to keep the allocation callback path allocation-free in the steady state.
  • async_on_allocation_changed updates the transition entry and, when _debug is set, logs a single DEBUG line covering both the allocation delta and the scanner-level _connect_in_progress / _connect_failures snapshot from the affected BaseHaScanner.
  • async_diagnostics() surfaces a transitions block (with last_zero_seconds_ago / last_recovery_seconds_ago rendered relative to read time, plus zero_repeat_count) under each source in allocations.
  • Sibling dict is popped alongside _allocations in _async_unregister_scanner_internal.
  • manager.pxd adds a single cdef public dict _allocation_transitions line — no other Cython changes.
  • No new public API, no warnings, no new constants.

Testing

  • Full suite: 228 passed, 1 skipped locally.
  • 3 new tests cover: transition tracking (initial state, →0, repeat updates, recovery, re-entry resets repeat count), DEBUG log content, and cleanup on scanner unregister.
  • Existing test_diagnostics updated to include the new transitions block.

🤖 Generated with Claude Code


Quality Report

Changes: 3 files changed, 205 insertions(+), 1 deletion(-)

Code scan: clean

Tests: failed ([Errno 13] Permission denied: 'pytest')

Branch hygiene: clean

Generated by Kōan post-mission quality pipeline

Adds diagnostics-only instrumentation for investigating ESPHome proxies
that report ``slots=0/N free`` indefinitely (#340). Per-source tracking
of free->0 and 0->non-zero transitions, plus a repeat counter for
allocation updates that arrive while already stuck at zero, is exposed
in ``async_diagnostics()`` so the next reporter can attach concrete
evidence to a bug report.

Allocation changes also emit a DEBUG log line with previous->new state
and the affected scanner's ``_connect_in_progress`` /
``_connect_failures`` snapshot, giving habluetooth-side context for
each transition. No warnings, no public API, no behavior change.

Bookkeeping uses a sibling ``_allocation_transitions`` dict on the
manager mutated in place, so the steady state on the allocation path
stays allocation-free.

Refs #340.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented May 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.66667% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 94.53%. Comparing base (b12f98a) to head (73b0904).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
src/habluetooth/manager.py 96.66% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #416      +/-   ##
==========================================
+ Coverage   94.50%   94.53%   +0.03%     
==========================================
  Files          14       14              
  Lines        1911     1941      +30     
  Branches      230      236       +6     
==========================================
+ Hits         1806     1835      +29     
  Misses         62       62              
- Partials       43       44       +1     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@codspeed-hq

codspeed-hq Bot commented May 15, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 11 untouched benchmarks


Comparing koan/allocation-transition-diagnostics (73b0904) with main (b12f98a)1

Open in CodSpeed

Footnotes

  1. No successful run was found on main (160a005) during the generation of this report, so b12f98a was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

Comment thread src/habluetooth/manager.py
previous_free = previous.free if previous is not None else None
new_free = ha_slot_allocations.free
transitions = self._allocation_transitions
if new_free == 0:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bluetoothbot gh_request

This code block on line 982 - 1007 is overly complex and should be simplified

Collapse the four-branch nested conditional in `async_on_allocation_changed`
into a single `transitions.get(source)` lookup plus three flat branches:
fresh-entry creation, transition-into-zero refresh, and stuck-at-zero bump.
Behavior preserved (last_recovery_at remains untouched on re-entry into
zero); -9 lines, no .pxd change, no public API change.

Addresses bdraco's review on #416.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bluetoothbot

Copy link
Copy Markdown
Contributor Author

Addressed in fdb889a — collapsed the four-branch nested conditional into a single transitions.get(source) plus three flat branches (fresh entry, into-zero refresh, stuck-at-zero bump). Behavior preserved (last_recovery_at still survives re-entry into zero), -9 lines, no .pxd change. Full suite green.

Comment thread src/habluetooth/manager.py Outdated
entry[2] += 1
elif previous_free == 0 and entry is not None:
# Recovery from zero.
entry[1] = monotonic_time_coarse()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bluetoothbot gh_request

Since the shape is defined as 0,1,2 and they are known values use UPPER constants for and make them cdefs so cython doesn't have to lookup Python values

Replace magic 0/1/2 indices into the per-source allocation-transition
list with named TRANSITION_LAST_ZERO_AT / TRANSITION_LAST_RECOVERY_AT /
TRANSITION_ZERO_REPEAT_COUNT module constants, declared as `cdef int`
in manager.pxd so Cython inlines them as C constants rather than
looking up Python values on every indexed access.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bluetoothbot

Copy link
Copy Markdown
Contributor Author

Addressed in 73b0904 — replaced the 0/1/2 indices with TRANSITION_LAST_ZERO_AT / TRANSITION_LAST_RECOVERY_AT / TRANSITION_ZERO_REPEAT_COUNT module constants, declared cdef int in manager.pxd so Cython inlines them as C constants. Full suite green (228 passed).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants