Feat 83 recording (socket profiler recording & replay)#200
Draft
theultimatewasteofspace wants to merge 54 commits into
Draft
Feat 83 recording (socket profiler recording & replay)#200theultimatewasteofspace wants to merge 54 commits into
theultimatewasteofspace wants to merge 54 commits into
Conversation
- Add userId column to trace table (nullable FK to user) - Refactor recorder to use server-wide activeRecordingId flag - Attach/detach trace listeners on every connected user's socket - New mid-recording connections auto-attach listeners on init - Each trace tagged with the userId that emitted it - Add recordingGetTraces endpoint for edit modal
…cator - Add userId to trace table for per-user event tagging - Refactor recorder to server-wide capture across all connected sockets - Add participantUserIds filter on recording (null = everyone) - Start Recording modal with user picker and online status - Edit Recording modal with trace pruning - Discard Recording button in save modal - New endpoints: recordingGetTraces, recordingGetOnlineUsers
# Please enter a commit message to explain why this merge is necessary, # especially if it merges an updated upstream into a topic branch. # # Lines starting with '#' will be ignored, and an empty message aborts # the commit.
- Server.recoverInterruptedRecordings() runs on startup to mark stale 'recording' rows as 'interrupted' after a server restart, freeing the Start Recording button - RecordingModal confirm() now waits for rename + trace deletions before closing modal and reporting success
Each socket connection now writes a distinct socketId on every trace, so admin's two tabs are recorded as two separate streams instead of being collapsed into one merged userId stream. Foundation for per-session replay. - Migration: collapsed three trace migrations into one create-trace with userId + socketId from the start - Model: added socketId field - Recorder: writes socket.id on every trace
- Pre-select admin inside the getOnlineUsers callback so userTable is stable when we resolve the row, fixing 'Record 2 User(s)' when only admin was checked - getOnlineUsers now returns per-user session count, exposed as Sessions column in the start-recording table
groupTracesBySocket replaces groupTracesByUser. Each session (socket connection) becomes one replay client, so admin's two recorded tabs replay as two parallel clients instead of one merged stream. Falls back to per-user grouping for legacy recordings without socketId.
added 5 commits
May 3, 2026 16:24
Recording participants are now socketIds (one per active socket connection), not userIds. Admin's two tabs are two selectable sessions. New connections during a recording are silently uncaptured - a warning toast goes to the recording owner so they know. - Migration: add participantSocketIds JSONB to recording table (participantUserIds kept for now in case selection model changes, but no longer written to) - Recorder: isUserIncluded -> isSessionIncluded, getOnlineUsers -> getOnlineSessions returning per-socket entries - Server: warning toast to recording owner on uncaptured connection - Start modal: per-session table, pre-selects current tab's session, shows '(this tab)' marker on owner's row
- TopBar.vue: red pulsing icon visible app-wide during recordings (admins always see; non-admin participants see when their socketId is in participantSocketIds; click is informational only) - Dashboard.vue: remove RecordingBar mount/import - SocketProfiler.vue: stop button calls recorderStop directly instead of going through eventBus -> RecordingBar - StartRecordingModal.vue: drop name input (renamed at save time in RecordingModal instead) - Recording model: publicTable=true so recording state reaches participants reactively (admin and non-admin) - recorder.js: drop debugging console.logs RecordingBar.vue file kept untouched in the repo for git history.
Click Play on a recording opens a configuration modal instead of firing the replay immediately. The modal lets the user configure the run before starting: - Mode selector (Scaling now; Load mode placeholder for later) - Speed: Fast or Realtime - Max iterations (required positive integer) - Continue past failures toggle - Multi-recording selection (sessions get pooled) - Live load preview: pool size, peak parallel sockets, cumulative socket-runs Scaling math change: sessions from all selected recordings are pooled into one list of N sessions. Iteration K runs K * N parallel sockets, cycling the pool with wraparound. Linear add per iteration (K=1 -> N sockets, K=2 -> 2N, ..., K=M -> M*N). - replayer.js: rewritten around buildSessionPool + runScalingTest on the pooled list. New replayRun signature accepts recordingIds array, maxIterations, continueOnFailure - ReplayResultsModal.vue: flat iteration list (was per-recording). Each session row labels its source recording so multi-recording runs are still readable - SocketProfiler.vue: Play button opens StartReplayModal instead of running directly - worker.js: emitWithTimeout now resolves success: false on timeout (was silently passing as success: true) - StartRecordingModal.vue: drop stray 's' character (lint fix) Cross-session timing offsets are intentionally not preserved during scaling — that's a separate behavioral-replay mode for future work.
The original extend-nav-socket-profiler migration inserted Socket Profiler into the legacy Admin group (id 2). Dev's later restructure-nav_group migration replaced the Admin layout with category-based groups but didn't relocate Socket Profiler since it lived on a separate feature branch at the time. After merging dev into feat-83-recording, Socket Profiler ends up stranded in the legacy Admin group and disappears from the sidebar. This migration relocates it into Manage, alongside other admin operational tools like Users.
added 15 commits
May 11, 2026 13:44
…igation - Topbar.vue: show ticking HH:MM:SS timer next to the recording dot, updates every second via setInterval - Restore click-to-navigate: admins jump to /dashboard/Socket_Profiler when clicking the icon (route path was wrong in the previous attempt; used the actual path from the router this time) - Tooltip text simplified: elapsed time is now visible inline, no need to duplicate it in the tooltip
macOS auto-generates .DS_Store in every folder, which polluted git status output. Adding the pattern to .gitignore and removing the two tracked instances so they stop showing up as modified.
The mounted() method's closing brace was missing, causing a Vite compile error. Added the missing }, between setInterval and beforeUnmount.
The trace table in RecordingModal now shows when each trace fired, both as absolute wall-clock time (HH:MM:SS) and as offset from the recording's start time (+HH:MM:SS). - Reads the recording's startTime reactively from the Vuex store so the elapsed calculation works in both the save-after-stop flow and the edit flow - Falls back to '-' gracefully when timestamps are missing
- SocketProfiler.vue: move :max-table-height before @action - StartRecordingModal.vue: reorder input attributes to match Vue style guide (directives -> bindings -> plain attrs)
The log was added during development to verify DB-change tracking was working. The same info is now captured properly in each trace's dbChanges result field, so the server-console echo is redundant.
Export: download button on each recording row produces a JSON file containing the recording metadata and all of its traces. DB-managed and derived fields are stripped on export so the file only carries real recording columns. Import: new modal accessible from the Socket Profiler header, opens a three-step picker (file -> preview -> confirm). On submit the recording is re-created under the importing user's account with '(imported)' appended to the name; traces are re-created one at a time via appDataUpdate. Original userId, participantUserIds, and participantSocketIds are dropped since they're meaningless on the importing machine. Schema versioned (currently v1) so future format changes can be rejected cleanly on import. Pattern modeled on the workflow export/import for consistency with existing CARE conventions.
Previously the per-trace ack timeout was hardcoded to 2000ms in worker.js. This adds a numeric input in StartReplayModal (default 2000, allowed range 100-30000ms) that flows through replayer.js into worker.js's emitWithTimeout call. Useful because different recordings target different actions some fire quickly, others legitimately take longer. A fixed 2s threshold caused spurious timeouts on slow operations and gave too much slack to fast ones.
- start() method: fixed over-indented body (4 leading lines at 12 spaces collapsed to the method's normal 8-space indent) - stop() method: realigned closing brace of the inner if and of the method itself; both were a level too deep - recoverInterruptedRecordings(): the JSDoc block was flush-left; re-indented to match the method - Same JSDoc: missing punctuation between two clauses (added em-dash) - Removed trailing-whitespace line at the end of the class
After dev added nodemon to package.json, the lock file kept regenerating locally whenever npm install ran. Committing the synced lock so this stops showing as a recurring modification.
When a replay completes successfully, a JSON file is downloaded automatically alongside opening the results modal. Users who close the modal without saving now still have the full run preserved on disk. The file captures both the iteration results and the run config (recording IDs and names, timing mode, max iterations, ack timeout, continue-on-failure) so saved runs can be reviewed later without needing to remember which settings produced them. Filename uses an ISO timestamp plus either the recording name (single recording) or 'N-recordings' (multi).
The first trace of every replayed session was timing out while subsequent traces acked in 1-3ms. Root cause: the client's 'connect' event fires before the server finishes its asynchronous per-socket handler init (the await init() loop in Server.js's connection handler), so the first trace raced handler setup before e.g. StatisticSocket was registered. createAuthenticatedClient now waits a short warm-up delay after connect before handing back the client, giving the server time to finish wiring up the socket.
Replaces hand-rolled inputs in StartReplayModal with CARE's BasicForm component driven by a field schema (mode, speed, max iterations, ack timeout, continue-past-failures). Numeric range validation stays in the component's canStart check since BasicForm only enforces required. The recordings selection table and load preview remain separate as they aren't form fields.
The per-trace dbChanges data bloated the exported JSON without being useful for offline review. It's now removed from the download payload. The in-app results modal still receives the full data including dbChanges for live inspection.
added 4 commits
June 2, 2026 02:05
Both the Topbar recording icon and the RecordingBar now key off whether this tab's socket is in the active recording's participantSocketIds, rather than showing whenever any recording is active. Removes the admin bypass in Topbar that made every admin tab show the icon regardless of participation, which is inconsistent with the session-based recording model.
Adds a '#' column numbering traces 1..N in chronological order by startTime. The number is assigned from a time-sorted copy and stays attached to each trace, so it reflects true recording order even when the table is sorted or filtered by another column.
getOnlineSessions was reading connectedAt off the UserSocket handler instance, where it doesn't exist, so the Connected column always showed '—'. It now reads connectedAt from the raw socket (set in Server.js's connection handler). The session picker also drops the orphaned blank-header column and instead appends '(this tab)' to the current session's username, which is clearer and fixes the apparent header/value misalignment.
Adds help text to the ack timeout and continue-past-failures fields via BasicForm's help property, shown as hover tooltips consistent with the rest of CARE.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Implements Issue #83 — Socket Profiler recording & replay tool for stress-testing CARE.
What's included
Recording side
recordingandtracetables with full lifecycle (start → recording → finished/interrupted → soft-deleted)interruptedon next bootReplay side
success: truewithin 2s (will adjust later)UI
Architectural decisions
publicTable: trueon Recording model: needed so non-admin participants get the recording row in their store and the topbar icon can render.Known issues or observations
Roadmap (out of scope for this PR)
Migration & merge notes
dev(53 commits behind at last sync). The merge was clean at the file level except for.gitignoreandpackage-lock.json, both auto-resolved.restructure-nav_groupmigration relocated nav elements but didn't touch Socket Profiler since it lived on this branch. A small follow-up migration (20260506220447-move-socket-profiler-to-manage) moves Socket Profiler into the Manage group.Testing
End-to-end manual testing covered:
Closes #83