Skip to content

Add peak KPS and CPS tracking#97

Merged
debugtheworldbot merged 18 commits intomainfrom
copilot/add-cps-kps-recording
Apr 6, 2026
Merged

Add peak KPS and CPS tracking#97
debugtheworldbot merged 18 commits intomainfrom
copilot/add-cps-kps-recording

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 5, 2026

Tracks the highest keys-per-second (KPS) and clicks-per-second (CPS) recorded within any 1-second sliding window during the current day, and surfaces these peaks in the stats UI on both macOS and Windows.

Data model

  • Added peakKPS: Double / peakCPS: Double to DailyStats on both platforms, backward-compatible (decoded via decodeIfPresent / JSON optional)
  • On import merge, takes the max of both sides rather than summing

Tracking logic (1-second sliding window)

Each key press / click enqueues a timestamp, purges entries older than 1 s, and updates the stored peak if the current window count exceeds it. Queues are cleared atomically with CurrentStats on day rollover.

macOS – dedicated peakRateLock protects the timestamp queues; currentStats.peakKPS/CPS is updated outside the lock, consistent with the rest of the codebase.

Windows – timestamp queues and peak update share _lock alongside CurrentStats, so the queue clear in SynchronizeCurrentDay is inherently atomic with the stats reset:

lock (_lock)
{
    _recentKeyTimestamps.Enqueue(now);
    while (_recentKeyTimestamps.Peek() <= cutoff) _recentKeyTimestamps.Dequeue();
    var kps = (double)_recentKeyTimestamps.Count;
    if (kps > CurrentStats.PeakKPS) CurrentStats.PeakKPS = kps;
}

UI

  • macOS: new side-by-side peakKPSView / peakCPSView row appended to the stats stack in StatsPopoverViewController
  • Windows: new card row (峰值 KPS / 峰值 CPS) added below the distance row in StatsPopupWindow.xaml, bound to PeakKPS / PeakCPS on StatsPopupViewModel
  • Localization strings added for both en and zh-Hans

Copilot AI and others added 3 commits April 5, 2026 03:01
Agent-Logs-Url: https://github.com/debugtheworldbot/keyStats/sessions/2462d95c-e8b9-423e-8931-2248c29db376

Co-authored-by: debugtheworldbot <62830430+debugtheworldbot@users.noreply.github.com>
…peak tracking

Agent-Logs-Url: https://github.com/debugtheworldbot/keyStats/sessions/2462d95c-e8b9-423e-8931-2248c29db376

Co-authored-by: debugtheworldbot <62830430+debugtheworldbot@users.noreply.github.com>
Copilot AI changed the title [WIP] Add functionality for CPS and KPS recording Add peak KPS and CPS tracking Apr 5, 2026
Copilot AI requested a review from debugtheworldbot April 5, 2026 03:07
@debugtheworldbot debugtheworldbot requested a review from Copilot April 5, 2026 03:07
Copy link
Copy Markdown
Contributor

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

Adds per-day peak input-rate metrics (peak KPS/CPS over a 1-second sliding window) and displays them in the stats UI on macOS and Windows.

Changes:

  • Extended the daily stats model with peakKPS / peakCPS (backward-compatible decoding).
  • Implemented 1-second sliding-window peak tracking for keys and clicks.
  • Surfaced peak KPS/CPS in the macOS popover and Windows stats popup UI.

Reviewed changes

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

Show a summary per file
File Description
KeyStats/StatsManager.swift Adds timestamp-window tracking and stores peak KPS/CPS in current daily stats.
KeyStats/StatsModels.swift Extends DailyStats Codable model with peakKPS/peakCPS and defaults.
KeyStats/StatsPopoverViewController.swift Displays peak KPS/CPS in the macOS stats popover.
KeyStats/en.lproj/Localizable.strings Adds English localized labels for peak KPS/CPS.
KeyStats/zh-Hans.lproj/Localizable.strings Adds Simplified Chinese localized labels for peak KPS/CPS.
KeyStats.Windows/KeyStats/Services/StatsManager.cs Adds Windows-side peak tracking and clears windows on day rollover.
KeyStats.Windows/KeyStats/Models/DailyStats.cs Adds JSON fields for peak KPS/CPS on Windows daily stats.
KeyStats.Windows/KeyStats/ViewModels/StatsPopupViewModel.cs Exposes formatted PeakKPS/PeakCPS bindings for the popup UI.
KeyStats.Windows/KeyStats/Views/StatsPopupWindow.xaml Adds two stat cards for Peak KPS/CPS.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +46 to +49
// KPS/CPS peak tracking (1-second sliding window)
private readonly Queue<DateTime> _recentKeyTimestamps = new();
private readonly Queue<DateTime> _recentClickTimestamps = new();

Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

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

PeakKPS/PeakCPS are now tracked and serialized on DailyStats, but the Windows import/normalize pipeline still drops them: NormalizeDailyStats(...) and MergeDailyStats(...) don’t copy/merge the new fields, so peaks will reset to 0 after load/import and won’t merge as described. Update normalization to carry forward sanitized PeakKPS/PeakCPS and update merge to use max(existing, incoming) for both fields.

Copilot uses AI. Check for mistakes.
Comment thread KeyStats/StatsManager.swift Outdated
Comment on lines +556 to +562
let now = Date()
let cutoff = now.addingTimeInterval(-1.0)
peakRateLock.lock()
recentKeyTimestamps.append(now)
recentKeyTimestamps = recentKeyTimestamps.filter { $0 > cutoff }
let currentKPS = Double(recentKeyTimestamps.count)
peakRateLock.unlock()
Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

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

This peak-rate path allocates a new array on every keypress (filter + reassignment). Since it runs per input event, consider using a queue/deque (or an array with a moving start index + occasional compaction) to purge old timestamps in-place to reduce allocations/CPU; apply the same approach to the CPS tracker too.

Copilot uses AI. Check for mistakes.
var peakKPS: Double
/// 今日峰值 CPS(每秒点击数)
var peakCPS: Double

Copy link

Copilot AI Apr 5, 2026

Choose a reason for hiding this comment

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

DailyStats gained new Codable fields (peakKPS/peakCPS) but the StatsModels test suite doesn’t currently cover their defaults/backward-compatible decoding. Add unit tests that (1) decode legacy JSON without these keys and assert both are 0, and (2) round-trip encode/decode preserves non-zero peaks.

Suggested change
enum CodingKeys: String, CodingKey {
case date
case keyPresses
case keyPressCounts
case leftClicks
case rightClicks
case sideBackClicks
case sideForwardClicks
case mouseDistance
case scrollDistance
case appStats
case peakKPS
case peakCPS
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.date = try container.decode(Date.self, forKey: .date)
self.keyPresses = try container.decode(Int.self, forKey: .keyPresses)
self.keyPressCounts = try container.decode([String: Int].self, forKey: .keyPressCounts)
self.leftClicks = try container.decode(Int.self, forKey: .leftClicks)
self.rightClicks = try container.decode(Int.self, forKey: .rightClicks)
self.sideBackClicks = try container.decode(Int.self, forKey: .sideBackClicks)
self.sideForwardClicks = try container.decode(Int.self, forKey: .sideForwardClicks)
self.mouseDistance = try container.decode(Double.self, forKey: .mouseDistance)
self.scrollDistance = try container.decode(Double.self, forKey: .scrollDistance)
self.appStats = try container.decode([String: AppStats].self, forKey: .appStats)
self.peakKPS = try container.decodeIfPresent(Double.self, forKey: .peakKPS) ?? 0
self.peakCPS = try container.decodeIfPresent(Double.self, forKey: .peakCPS) ?? 0
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(date, forKey: .date)
try container.encode(keyPresses, forKey: .keyPresses)
try container.encode(keyPressCounts, forKey: .keyPressCounts)
try container.encode(leftClicks, forKey: .leftClicks)
try container.encode(rightClicks, forKey: .rightClicks)
try container.encode(sideBackClicks, forKey: .sideBackClicks)
try container.encode(sideForwardClicks, forKey: .sideForwardClicks)
try container.encode(mouseDistance, forKey: .mouseDistance)
try container.encode(scrollDistance, forKey: .scrollDistance)
try container.encode(appStats, forKey: .appStats)
try container.encode(peakKPS, forKey: .peakKPS)
try container.encode(peakCPS, forKey: .peakCPS)
}

Copilot uses AI. Check for mistakes.
debugtheworldbot and others added 14 commits April 6, 2026 20:42
- Add getCurrentKPS()/getCurrentCPS() methods to StatsManager
- Add ⚡ KPS badge button next to key press count in stats popover
- Badge auto-refreshes every 0.3s while popover is visible
- Click badge to show KPS/CPS detail popover with current & peak values
- New KPSDetailView (SwiftUI) showing real-time KPS, CPS and peaks
- All values displayed as integers
- Add localization strings (en/zh-Hans)
Peak KPS/CPS data is now accessible via the ⚡ badge detail popover,
so the dedicated row in the main popover is redundant.
- Replace bold centered title + separator with semibold left-aligned 'KeyStats' title (matching section title style)
- Move ⚡ KPS badge from key press row to title row (right of 'KeyStats')
- Remove keyPressRow wrapper, use keyPressView directly in stats stack
Badge now displays two lines:
- Line 1: ⚡{peakKPS} (keyboard peak keys per second)
- Line 2: ⚡{peakCPS} (mouse peak clicks per second)
… on right

Layout:
⚡️ {peakKPS}
   {peakCPS}

Single large lightning icon vertically centered on the left,
two lines of numbers (KPS/CPS) stacked on the right.
Replaced NSButton with custom KPSBadgeView using NSStackView.
- Rounded corners (6pt) with subtle controlBackgroundColor
- Inner padding for better visual appearance
Anchor popover to the bottom edge of the badge view.
- Dynamic cornerRadius (height/2) for capsule/pill appearance
- Bold weight for KPS/CPS numbers
- Tighter icon spacing and balanced padding

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Peak data on left (large), current data on right (small)
- Descriptive titles: "今日峰值按键速度(KPS)" / "当前KPS" etc.
- Widened popover to 260pt to fit longer labels

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…iency

- Add unified statsStateLock protecting currentStats + sliding window state
- Make peak update and day reset atomic within same critical section
- Ensure scheduleSave and midnight timer are created on main thread
- Replace Array+filter with head-pointer queue for O(1) prune
- Switch from Date() to ProcessInfo.systemUptime (monotonic clock)
- Unify peakKPS/peakCPS type from Double to Int with legacy decode compat
- Add currentRatesSnapshot() for atomic UI reads

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…snapshots

saveStats() now takes a single atomic snapshot of both currentStats and
history under statsStateLock before encoding. Export and import paths
also snapshot history under the same lock to prevent inconsistent reads.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@debugtheworldbot debugtheworldbot marked this pull request as ready for review April 6, 2026 14:34
@debugtheworldbot debugtheworldbot merged commit 27d1a46 into main Apr 6, 2026
@debugtheworldbot debugtheworldbot deleted the copilot/add-cps-kps-recording branch April 6, 2026 14:34
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.

[Feature Requested]: 建议增加CPS和KPS记录

3 participants