mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
Merge pull request #2473 from PR0M3TH3AN/agents/daily/perf-agent/2026-02-13-run-5262819670352031129
perf-agent: visibility gate VideoEventBuffer
This commit is contained in:
11 files changed
+139
-2
No files matched your search
@@ -3,7 +3,11 @@
|
||||
## Performance Bottlenecks
|
||||
- **Unbounded Concurrency (P0):** `js/nostr/commentEvents.js` uses `Promise.all` for relay requests in `publishComment` and `listVideoComments`. This can saturate network connections if a user has many relays (20+), causing UI freeze or timeouts.
|
||||
- **Remediation:** Apply `pMap` with `RELAY_BACKGROUND_CONCURRENCY` (3).
|
||||
- **VideoEventBuffer (P1):** Flushes every 75ms on event ingest regardless of visibility.
|
||||
- **Remediation:** Implemented visibility gating (2026-02-13).
|
||||
- **WatchHistory (P2):** Republish loop has backoff but no visibility check.
|
||||
|
||||
## Documentation Gaps
|
||||
- **CORS Configuration:** `content/docs/guides/upload-content.md` suggests `http://localhost:5500` for CORS, but the default dev port is `3000`.
|
||||
- **Remediation:** Update port to `3000`.
|
||||
- **Upload Limit:** 2GB (documented and code-verified).
|
||||
@@ -0,0 +1,18 @@
|
||||
# Context
|
||||
|
||||
## Goal
|
||||
Improve application responsiveness by optimizing background tasks and ensuring documentation accuracy.
|
||||
|
||||
## Scope
|
||||
- Analyze codebase for performance bottlenecks (timers, loops, concurrency).
|
||||
- Fix: Visibility-gate `VideoEventBuffer` to prevent UI thrashing when hidden.
|
||||
- Audit: Verify upload documentation matches implementation.
|
||||
|
||||
## Assumptions
|
||||
- `document.hidden` is reliable.
|
||||
- `VideoEventBuffer` is the primary feed ingestion path.
|
||||
|
||||
## Definition of Done
|
||||
- `VideoEventBuffer` pauses UI updates when hidden.
|
||||
- `daily-perf-report` generated.
|
||||
- Lint passes.
|
||||
@@ -0,0 +1,24 @@
|
||||
# Daily Perf Report - 2026-02-13
|
||||
|
||||
## Summary
|
||||
Implemented visibility gating for `VideoEventBuffer` to prevent UI thrashing when the tab is hidden. Audited upload documentation against implementation.
|
||||
|
||||
## P0/P1 Findings
|
||||
- **P1 - VideoEventBuffer Thrashing**: The `VideoEventBuffer` was flushing events and triggering UI callbacks every 75ms regardless of visibility. This wasted CPU on layout/paint recalculations for hidden tabs.
|
||||
- **Fix**: Modified `flush()` to buffer updates when `document.hidden` is true, and flush them in a batch upon `visibilitychange`.
|
||||
- **P2 - Upload Limits**: Documentation states a 2GB limit due to client-side hashing. Verification confirmed `js/utils/torrentHash.js` reads the file for hashing, which is memory-bound in browsers. Docs are accurate.
|
||||
|
||||
## Metrics
|
||||
- **Login Time**: (Not measured this run)
|
||||
- **Queue Sizes**: (Not measured this run)
|
||||
|
||||
## PRs / Changes
|
||||
- **VideoEventBuffer**: Added visibility gating.
|
||||
|
||||
## Blockers & Decisions
|
||||
- **Decision**: Prioritized `VideoEventBuffer` fix over `WatchHistory` backoff as it affects the main feed loop directly.
|
||||
- **Decision**: Accepted the 2GB upload limit documentation as accurate for the current architecture.
|
||||
|
||||
## Next Steps
|
||||
- Investigate `WatchHistory` republish loop for similar visibility gating.
|
||||
- Consider using streaming hashing (e.g., `crypto.subtle`) to lift the 2GB limit in the future.
|
||||
@@ -0,0 +1,6 @@
|
||||
# Decisions
|
||||
|
||||
## Visibility Gating for VideoEventBuffer
|
||||
- **Problem**: `VideoEventBuffer` flushes every 75ms if events are arriving, triggering UI updates via `onVideo` callback even if the tab is hidden.
|
||||
- **Solution**: Check `document.hidden` in `flush()`. If hidden, process events into internal state (`activeMap`) but skip `onVideo` and buffer the updated video objects. On `visibilitychange` (visible), flush the buffered video objects to UI.
|
||||
- **Tradeoff**: Memory usage might increase slightly to hold pending video references, but CPU usage on main thread (layout/paint) will decrease significantly.
|
||||
@@ -0,0 +1 @@
|
||||
Started perf-agent run for 2026-02-13.
|
||||
@@ -0,0 +1,6 @@
|
||||
# Completed: perf-agent
|
||||
|
||||
**Timestamp:** 2026-02-13 20:45:00
|
||||
**Agent:** perf-agent
|
||||
**Summary:**
|
||||
Implemented visibility gating for `VideoEventBuffer`. Now pauses UI updates (flushes) when the tab is hidden, accumulating changes to be applied in a batch when visibility is restored. This reduces background CPU usage and UI thrashing. Verified upload documentation against code (2GB limit is accurate). Generated daily perf report.
|
||||
@@ -21,6 +21,13 @@ export class VideoEventBuffer {
|
||||
this.invalidEvents = [];
|
||||
this.flushTimerId = null;
|
||||
this.FLUSH_DEBOUNCE_MS = 75;
|
||||
|
||||
// Buffer updates while hidden to avoid UI thrashing
|
||||
this.pendingVideos = [];
|
||||
this.handleVisibilityChange = this.handleVisibilityChange.bind(this);
|
||||
if (typeof document !== "undefined") {
|
||||
document.addEventListener("visibilitychange", this.handleVisibilityChange);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,8 +147,12 @@ export class VideoEventBuffer {
|
||||
}
|
||||
|
||||
if (updatedVideos.length > 0) {
|
||||
// Trigger the callback once per batch to avoid UI thrashing
|
||||
this.onVideo(updatedVideos);
|
||||
if (typeof document !== "undefined" && document.hidden) {
|
||||
this.pendingVideos.push(...updatedVideos);
|
||||
} else {
|
||||
// Trigger the callback once per batch to avoid UI thrashing
|
||||
this.onVideo(updatedVideos);
|
||||
}
|
||||
|
||||
// Fetch NIP-71 metadata (categorization tags) in the background for the whole batch
|
||||
this.client.populateNip71MetadataForVideos(updatedVideos)
|
||||
@@ -162,6 +173,16 @@ export class VideoEventBuffer {
|
||||
this.client.saveLocalData("subscribeVideos:flush");
|
||||
}
|
||||
|
||||
handleVisibilityChange() {
|
||||
if (typeof document !== "undefined" && !document.hidden) {
|
||||
if (this.pendingVideos.length > 0) {
|
||||
const batch = this.pendingVideos;
|
||||
this.pendingVideos = [];
|
||||
this.onVideo(batch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the subscription reaches End-of-Stored-Events (EOSE).
|
||||
* Triggers an immediate flush to ensure the UI is fully up-to-date.
|
||||
@@ -188,6 +209,9 @@ export class VideoEventBuffer {
|
||||
clearTimeout(this.flushTimerId);
|
||||
this.flushTimerId = null;
|
||||
}
|
||||
if (typeof document !== "undefined") {
|
||||
document.removeEventListener("visibilitychange", this.handleVisibilityChange);
|
||||
}
|
||||
// Ensure any straggling events are flushed before tearing down.
|
||||
this.flush();
|
||||
}
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"hits": ["VideoEventBuffer flush loop", "WatchHistory republish loop", "ViewCounter debounce"]}
|
||||
@@ -0,0 +1,8 @@
|
||||
# Test Log
|
||||
|
||||
## Environment
|
||||
- Agent: perf-agent
|
||||
- Date: 2026-02-13
|
||||
|
||||
## Verification
|
||||
- Lint: Passed
|
||||
@@ -151,4 +151,43 @@ test('VideoEventBuffer', async (t) => {
|
||||
assert.equal(buffer.buffer.length, 0); // Should have flushed
|
||||
assert.equal(client.allEvents.size, 1);
|
||||
});
|
||||
|
||||
await t.test('Visibility Gating', () => {
|
||||
// Mock document
|
||||
const originalDocument = global.document;
|
||||
let visibilityHandler = null;
|
||||
global.document = {
|
||||
hidden: true,
|
||||
addEventListener: (type, handler) => {
|
||||
if (type === 'visibilitychange') visibilityHandler = handler;
|
||||
},
|
||||
removeEventListener: () => {}
|
||||
};
|
||||
|
||||
const client = new MockClient();
|
||||
let onVideoCalls = [];
|
||||
const onVideo = (videos) => onVideoCalls.push(videos);
|
||||
const buffer = new VideoEventBuffer(client, onVideo);
|
||||
|
||||
// Push event while hidden
|
||||
buffer.push(validEvent);
|
||||
buffer.scheduleFlush(true);
|
||||
|
||||
// Should NOT have called onVideo yet
|
||||
assert.equal(onVideoCalls.length, 0);
|
||||
// Should have buffered pending videos
|
||||
assert.equal(buffer.pendingVideos.length, 1);
|
||||
|
||||
// Simulate visibility change
|
||||
global.document.hidden = false;
|
||||
assert.ok(visibilityHandler, "Listener attached");
|
||||
visibilityHandler();
|
||||
|
||||
// Should flush now
|
||||
assert.equal(onVideoCalls.length, 1);
|
||||
assert.equal(buffer.pendingVideos.length, 0);
|
||||
|
||||
// Cleanup mock
|
||||
global.document = originalDocument;
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,6 @@
|
||||
- [ ] Create context/logs
|
||||
- [ ] Create INITIAL_BASELINE.md
|
||||
- [ ] Save search hits
|
||||
- [ ] Implement visibility gating in `VideoEventBuffer.js`
|
||||
- [ ] Generate report
|
||||
- [ ] Lint
|
||||
Reference in new issue
Block a user