Conversation
When is_cache_hit=True and deferred_sessions is empty, the sorted sessions cache is guaranteed valid. Add an early return before the frozenset construction to avoid O(n) tuple allocations and hash computations on every warm-cache call. Closes #859 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR targets a steady-state performance issue in copilot_usage.parser.get_all_sessions() by avoiding redundant O(n) work when internal discovery/session caches indicate nothing has changed.
Changes:
- Adds an early-return fast path in
get_all_sessions()to skip building the(Path, file_id)frozensetfingerprint on warm discovery-cache hits. - Adds new tests intended to verify the fast path behavior and that the fingerprint path still runs when the session set changes.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
src/copilot_usage/parser.py |
Adds a new early-return fast path before fingerprint construction. |
tests/copilot_usage/test_parser.py |
Adds tests for the warm-cache fast path and cache-miss behavior. |
…hits The fast-path early return in get_all_sessions checked is_cache_hit and _sorted_sessions_cache but did not verify the cache belonged to the current base_path. When different paths were used in succession (e.g. e2e test_empty_directory_graceful followed by FIXTURES-based tests), the fast path returned stale empty results from the wrong root. Add a root field to _SortedSessionsCache and guard the fast path with a root == resolved_root check. Also hoist resolved_root computation before the stale-prune block to reuse it and eliminate the duplicate resolve() call. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CI Fix AppliedRoot cause: The fast-path early return in Fix: Added a All checks pass: ruff ✅ | pyright ✅ | bandit ✅ | unit tests (99% cov) ✅ | e2e tests (86 passed) ✅ |
|
Commit pushed:
|
- Replace fast path that skipped fingerprint construction with a fingerprint-validated fast path: always build the O(n) frozenset fingerprint but skip the sort when is_cache_hit, no deferred sessions, and the fingerprint matches the cached value. This prevents cross-root false hits when get_all_sessions() is called with different base_path values in the same process. - Update tests to spy on both session_sort_key and _SortedSessionsCache construction, ensuring the test actually validates the optimization (sort skipped) rather than just cache construction (which was already skipped pre-PR). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Extract _build_fingerprint() helper for testability - Add cheap root-based early return BEFORE O(n) frozenset construction - Restore original fingerprint-only fallback (drop is_cache_hit gate) - Remove duplicate TestSortedSessionsCacheFastPath test class - Add test asserting _build_fingerprint is skipped on warm cache hit Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Commit pushed:
|
- Add len(summaries)==len(discovered) guard to fast-path early return to prevent stale cache when a session re-parse fails/yields nothing - Add test_fingerprint_built_when_session_added to verify the fingerprint path is taken when the session set changes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Commit pushed:
|
Have _discover_with_identity return the resolved root so get_all_sessions can reuse it instead of calling .resolve() again. This eliminates the redundant Path.resolve() call on every invocation, including warm-cache hits. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Commit pushed:
|
There was a problem hiding this comment.
Quality Gate: APPROVED (auto-merge eligible)
Impact: MEDIUM — Internal performance refactoring of private cache logic (_SortedSessionsCache, _discover_with_identity) with unchanged public API.
What was evaluated:
- Optimization logic: The 5-condition early-return guard (
is_cache_hit,not deferred_sessions,len(summaries) == len(discovered), cache exists, same root) is sound — when all hold, the sorted order cannot have changed, so the O(n) frozenset construction is correctly skipped. - Return type change:
_discover_with_identitynow returns the resolved root as a 3-tuple element. All 30+ existing call sites updated correctly. _build_fingerprintextraction: Clean refactor — the inline frozenset comprehension is now a named, testable function._SortedSessionsCache.rootfield: Added correctly to thedataclass(slots=True)and threaded through construction.- Tests: Two new tests (
test_fingerprint_skipped_on_warm_cache_hit,test_fingerprint_built_when_session_added) use spy/counter approach consistent with repo conventions — no wall-clock timing. - CI: All 9 checks pass (lint, typecheck, security, tests, CodeQL).
Closes #859
Problem
Every call to
get_all_sessionsunconditionally built afrozensetof all(Path, file_id)pairs — O(n) allocations and O(n) hash operations — before checking_sorted_sessions_cache. When the discovery cache hits (is_cache_hit=True) and no sessions were re-parsed or had name updates (deferred_sessions=[]), this work was entirely wasted since the sorted cache is guaranteed valid.Fix
Added a cheap early-return check using the already-available
is_cache_hitflag before the frozenset construction:The existing frozenset path is preserved for cache-miss cases (root changed or sessions re-parsed), so
_sorted_sessions_cacheis still updated correctly on misses.Tests
Added
TestSortedSessionsCacheFastPathwith two tests:test_frozenset_not_built_on_warm_cache_hit— uses a counting wrapper around_SortedSessionsCacheto verify it is NOT constructed on the second call when the session state is unchanged (spy/counter approach, no wall-clock assertions).test_frozenset_built_when_session_added— verifies the frozenset path IS still taken when the session set changes (cache miss).All 1238 tests pass with 99% coverage.