Skip to content

perf(vscode_parser): use MappingProxyType for immutable summary dicts (#848)#854

Merged
microsasa merged 3 commits intomainfrom
fix/848-immutable-vscode-summary-dicts-a922ab1b88f8e98c
Apr 8, 2026
Merged

perf(vscode_parser): use MappingProxyType for immutable summary dicts (#848)#854
microsasa merged 3 commits intomainfrom
fix/848-immutable-vscode-summary-dicts-a922ab1b88f8e98c

Conversation

@microsasa
Copy link
Copy Markdown
Owner

Closes #848

Problem

get_vscode_summary() called _copy_summary() on every cache hit, constructing 4 new dict objects (copying ~1,600 key-value pairs for a user with 400 date-bucketed entries). This generated unnecessary GC pressure in the interactive refresh loop.

Solution

Replace the four mutable dict[str, int] fields in VSCodeLogSummary with Mapping[str, int] backed by types.MappingProxyType:

  • VSCodeLogSummary: Field types changed from dict[str, int] to Mapping[str, int]
  • _finalize_summary: Wraps accumulator dicts in MappingProxyType at construction time
  • _copy_summary: Removed entirely — no longer needed
  • get_vscode_summary: Cache hit returns the summary object directly (O(1), zero allocation); cache store no longer copies

Performance

Eliminates 4 dict allocations + deep copies per get_vscode_summary() call in steady state. Each cache-hit call is now O(1) with zero allocation.

Tests

  • Cache-hit identity: two consecutive get_vscode_summary() calls return the same object (is identity)
  • Immutability: attempting summary.requests_by_date["x"] = 1 raises TypeError (all 4 dict fields)
  • build_vscode_summary also produces immutable MappingProxyType fields
  • Updated existing test_cached_return_is_mutation_safe and test_finalized_summary_uses_mapping_proxy
  • All 1,224 tests pass, pyright strict clean

Generated by Issue Implementer · ● 13.5M ·

…#848)

Replace the four mutable dict[str, int] fields in VSCodeLogSummary with
Mapping[str, int] backed by types.MappingProxyType. This eliminates the
_copy_summary() function and its 4 dict copies on every
get_vscode_summary() cache hit.

Changes:
- VSCodeLogSummary fields typed as Mapping[str, int]
- _finalize_summary wraps dicts in MappingProxyType
- _copy_summary removed; cache hit returns summary directly
- Cache store no longer copies; summary is inherently immutable
- Tests updated: cache-hit identity (is), mutation raises TypeError,
  build_vscode_summary also produces immutable fields

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@microsasa microsasa added the aw Created by agentic workflow label Apr 8, 2026
Copilot AI review requested due to automatic review settings April 8, 2026 07:41
@microsasa microsasa enabled auto-merge April 8, 2026 07:41
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR improves the steady-state performance of VS Code Copilot Chat log summarization by eliminating repeated deep copies on get_vscode_summary() cache hits, making cached summaries safe to share by ensuring the per-dimension counters are immutable.

Changes:

  • Change VSCodeLogSummary counter fields from dict[str, int] to Mapping[str, int], and back them with types.MappingProxyType.
  • Remove _copy_summary() and return cached summaries directly on cache hits (O(1), zero allocation).
  • Update and add tests to assert cache-hit identity and immutability of summary mapping fields.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
src/copilot_usage/vscode_parser.py Wrap summary dict fields in MappingProxyType and remove defensive copying on cache hits.
tests/copilot_usage/test_vscode_parser.py Update tests to validate mapping immutability and cache-hit identity semantics.

Enforce immutability at the VSCodeLogSummary dataclass boundary:
- Add __post_init__ that wraps any plain dict in MappingProxyType
- Change default_factory to produce MappingProxyType({}) via _EMPTY_MAPPING
- Simplify _finalize_summary to pass plain dicts (wrapping handled by __post_init__)
- Add tests for constructor-level wrapping and default immutability

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

Commit pushed: bfdf1da

Generated by Review Responder

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

- Annotate _EMPTY_MAPPING as Final[Mapping[str, int]] to avoid runtime
  TypeError from subscripting types.MappingProxyType
- Always snapshot mapping values in __post_init__ via
  MappingProxyType(dict(val)) to prevent callers from mutating frozen
  summaries through retained dict references
- Special-case _EMPTY_MAPPING singleton to skip unnecessary allocation
- Update docstring to reflect the always-snapshot behavior

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 8, 2026

Commit pushed: a9d63f3

Generated by Review Responder

@microsasa microsasa requested a review from Copilot April 8, 2026 08:05
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Comments suppressed due to low confidence (1)

src/copilot_usage/vscode_parser.py:509

  • _finalize_summary() currently does dict(acc.<field>) for each counter, but VSCodeLogSummary.__post_init__ then snapshots again via MappingProxyType(dict(val)). When val is already a plain dict, this results in an extra full copy/allocation of each mapping (4 redundant dict copies) on every summary construction (cache miss / initial build). Consider passing the accumulator mappings directly (e.g., the defaultdicts) into VSCodeLogSummary(...) and letting __post_init__ perform the single snapshot/copy, which preserves the immutability/aliasing guarantees while reducing allocations.
    return VSCodeLogSummary(
        total_requests=acc.total_requests,
        total_duration_ms=acc.total_duration_ms,
        requests_by_model=dict(acc.requests_by_model),
        duration_by_model=dict(acc.duration_by_model),
        requests_by_category=dict(acc.requests_by_category),
        requests_by_date=dict(acc.requests_by_date),
        first_timestamp=acc.first_timestamp,

@microsasa microsasa added the aw-quality-gate-approved Quality gate approved the PR label Apr 8, 2026
Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Quality Gate: APPROVE — Medium-impact performance refactoring with comprehensive test coverage. Auto-approving for merge.

What was evaluated:

  • VSCodeLogSummary dict fields changed from dict[str, int] to Mapping[str, int] backed by MappingProxyType — enforces immutability at construction time via __post_init__
  • _copy_summary removed; cache hits now return the same object (O(1), zero allocation)
  • All downstream consumers (vscode_report.py) use read-only Mapping operations — no breakage
  • 8 new/updated tests cover: cache-hit identity, immutability for all 4 fields, constructor wrapping, defaults
  • Fully compliant with CODING_GUIDELINES.md (strict typing, Final, frozen dataclass with slots, default_factory=lambda:)

@microsasa microsasa merged commit 038bef8 into main Apr 8, 2026
10 checks passed
@microsasa microsasa deleted the fix/848-immutable-vscode-summary-dicts-a922ab1b88f8e98c branch April 8, 2026 08:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

aw Created by agentic workflow aw-quality-gate-approved Quality gate approved the PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[aw][perf] vscode_parser: O(n) dict copies on every get_vscode_summary cache hit via _copy_summary

2 participants