Skip to content

Add usage statistics tracking and display#198

Merged
PureWeen merged 7 commits intomainfrom
fix/create-stats-ofapp-usage-and-details-of-20260223-1545
Feb 24, 2026
Merged

Add usage statistics tracking and display#198
PureWeen merged 7 commits intomainfrom
fix/create-stats-ofapp-usage-and-details-of-20260223-1545

Conversation

@rmarinho
Copy link
Collaborator

Summary

Implements app usage statistics tracking to monitor session activity, code suggestions, and user engagement.

Changes

  • UsageStatistics Model: Tracks sessions, duration, lines suggested, messages
  • UsageStatsService: Manages tracking with debounced persistence (2s)
  • CopilotService Integration: Tracks session lifecycle and message events
  • Settings UI: New Statistics section displaying metrics with formatted durations
  • Comprehensive Tests: 14 unit tests covering all tracking scenarios

Features

  • Session Tracking: Creation, closing, duration (including longest session)
  • Code Suggestions: Counts lines in code blocks from Copilot responses
  • Message Count: Tracks total messages received
  • Persistent Storage: Saves to ~/.polypilot/usage-stats.json
  • Thread-Safe: Lock-based synchronization
  • Resilient: Gracefully handles corrupt stats files

Testing

  • All 14 new tests pass
  • Existing tests remain passing (1205 passed, 2 pre-existing failures unrelated)
  • Mac Catalyst build successful

Screenshots

Statistics section shows:

  • Total sessions created/closed
  • Total/longest session time
  • Lines of code suggested
  • Messages received
  • First used and last updated dates

rmarinho and others added 2 commits February 23, 2026 16:58
- Created UsageStatistics model to track app usage metrics
- Implemented UsageStatsService with debounced persistence
- Added tracking for:
  - Session creation/closing with duration tracking
  - Messages received from Copilot
  - Lines of code suggested in code blocks
  - Longest session duration
- Integrated tracking into CopilotService lifecycle
- Added Statistics section to Settings page with:
  - Stats grid showing key metrics
  - Formatted durations for readability
  - First used and last updated timestamps
- Added comprehensive unit tests (14 tests, all passing)
- Persists to ~/.polypilot/usage-stats.json
- Uses timer-based debounce (2s) for efficient disk I/O
- Thread-safe with lock-based synchronization
- Gracefully handles corrupt stats files

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…session key, ActiveSessions serialization

- Remove TrackMessage/TrackCodeSuggestion from AssistantMessageEvent handler (kept only in FlushCurrentResponse) to fix double-counting on non-streaming responses
- Fix DisposeAsync to call FlushSave() BEFORE setting _disposed=true so final stats flush actually runs
- Add _timerLock around timer dispose+create in DebounceSave/FlushSave to prevent concurrent timer leak
- Add [JsonIgnore] to ActiveSessions property so runtime state is never persisted to usage-stats.json
- Use session GUID (SessionId) as the dictionary key in TrackSessionStart/End instead of display name to avoid collisions when multiple sessions share the same name
- Simplify SaveStats() to rely on [JsonIgnore] instead of manually copying all fields

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PureWeen PureWeen force-pushed the fix/create-stats-ofapp-usage-and-details-of-20260223-1545 branch from 7243f7e to 4b2a3b4 Compare February 23, 2026 23:01
PureWeen and others added 5 commits February 23, 2026 20:25
- Make _disposed volatile to ensure timer callback threads see up-to-date value
- Move File.WriteAllText inside _statsLock to prevent concurrent stale-snapshot writes
- Use pre-compiled static readonly Regex for CodeBlockRegex (not recompiled per message)
- Add TrackSessionResume() for restored sessions: records start time for duration
  tracking without inflating TotalSessionsCreated (sessions counted in prior run)
- Call TrackSessionResume() at end of ResumeSessionAsync so closing a restored
  session correctly records duration and increments TotalSessionsClosed

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
copilotSession.SessionId may be empty at CreateSessionAsync time
(see null-check at line 1344). Using it as the TrackSessionStart key
stored '' while TrackSessionEnd looked up by GUID, causing every
TryGetValue to miss — TotalSessionsClosed never incremented and
no session durations ever recorded.

Fix: use 'name' (display name) consistently as the ActiveSessions key
for all three paths: TrackSessionStart, TrackSessionResume, TrackSessionEnd.
The _sessions dictionary is already keyed by name everywhere in CopilotService.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nters

- Move TrackMessage() from FlushCurrentResponse to CompleteResponse so it
  counts once per completed turn instead of once per tool-round flush segment.
  A single user prompt with N tool calls was inflating TotalMessagesReceived
  by N+1x.
- Keep TrackCodeSuggestion in both FlushCurrentResponse (intermediate segments)
  AND CompleteResponse (final segment) so all code blocks are counted.
- Broaden CodeBlockRegex from [\w]* to [^\n`]* to match language tags like
  c++, c#, objective-c that contain non-word characters.
- TrackSessionResume now increments TotalSessionsCreated to maintain the
  invariant Created >= Closed. Without this, restoring 5 sessions from a
  prior run and closing them produced Closed=5, Created=0.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…turn guard

- Add RenameActiveSession() to re-key ActiveSessions entry when a session
  is renamed, preventing orphaned entries and lost duration/close counts
- Revert TrackSessionResume to NOT increment TotalSessionsCreated — the
  Created >= Closed invariant holds because TrackSessionEnd only increments
  Closed when the key is found. Previous behavior inflated Created by K×N
  (K sessions × N restarts)
- Promote JsonSerializerOptions to static readonly SaveOptions field to
  avoid allocating on every save under _statsLock
- Remove duplicate _usageStats resolution from public constructor (internal
  ctor already resolves it via the :this() chain)
  avoid counting empty-response turns
- Add Math.Max(0, seconds) clamp in FormatDuration for NTP clock adjustments

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Add _disposed guard to DebounceSave() to prevent orphaned Timer creation
  after DisposeAsync (late CopilotService callbacks during shutdown)
- Clamp duration to Math.Max(0L, ...) in TrackSessionEnd to prevent NTP
  clock adjustments from producing negative values that corrupt totals
  heavy turns flush CurrentResponse before the last tool, leaving response
  empty, so gating on non-empty text under-counted all tool-using turns

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@PureWeen PureWeen merged commit ebf37a4 into main Feb 24, 2026
@PureWeen PureWeen deleted the fix/create-stats-ofapp-usage-and-details-of-20260223-1545 branch February 24, 2026 05:24
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