Skip to content
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 41 additions & 9 deletions packages/utils/src/lib/performance-observer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,16 @@ export type PerformanceObserverOptions<T> = {

/**
* Whether to enable buffered observation mode.
* When true, captures all performance entries that occurred before observation started.
* When false, only captures entries after subscription begins.
*
* When true, captures all performance marks and measures that exist in the Node.js
* performance buffer at the time `subscribe()` is called. This allows you to capture
* performance entries that were created before the observer was created.
*
* When false, only captures entries created after `subscribe()` is called.
*
* **Important:** The implementation uses a manual approach via `performance.getEntriesByType()`
* rather than the native PerformanceObserver `buffered` option. See the `subscribe()` method
* documentation for details on guarantees and limitations.
*
* @default true
*/
Expand Down Expand Up @@ -312,6 +320,34 @@ export class PerformanceObserverSink<T> {
* When buffered mode is enabled, any existing buffered entries are immediately flushed.
* If the sink is closed, items stay in the queue until reopened.
*
* ## Buffered Mode Implementation
*
* When `captureBufferedEntries` is true, this method captures all performance entries
* that exist in the Node.js performance buffer at the time of subscription.
*
* **Why Manual Approach:**
* The standard `buffered: true` option in PerformanceObserver.observe() is not used
* because it has proven unreliable in Node.js environments. Instead, we use
* `performance.getEntriesByType()` to manually retrieve buffered entries.
*
* **Guarantees:**
* - All marks and measures in the performance buffer at subscription time will be captured
* - Entries are processed synchronously before the observer begins watching for new entries
* - No entries created after subscription will be missed (observer handles them)
*
* **Limitations:**
* - Potential for duplicate processing if an entry exists both in the buffer and is
* delivered by the observer callback (though Node.js typically avoids this)
* - Performance buffer has a limited size; very old entries may have been evicted by Node.js
* - The manual approach captures a snapshot at subscription time; there's a small race
* condition window where entries created during getEntriesByType() execution might
* be captured by both the manual call and the observer
*
* **Memory Management:**
* Applications should call `performance.clearMarks()` and `performance.clearMeasures()`
* periodically to prevent the Node.js performance buffer from growing unbounded.
* This is especially important when using buffered mode, as the entire buffer is
* processed on subscription.
*/
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Too muh test. could you reduce it to 1 sentence @copilot

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Reduced the extensive documentation section to one concise sentence that explains the manual approach and why the native option isn't used. Changes in commit 37cc84e.

subscribe(): void {
if (this.#observer) {
Expand All @@ -322,11 +358,8 @@ export class PerformanceObserverSink<T> {
this.processPerformanceEntries(list.getEntries());
});

// When buffered mode is enabled, Node.js PerformanceObserver invokes
// the callback synchronously with all buffered entries before observe() returns.
// However, entries created before any observer existed may not be buffered by Node.js.
// We manually retrieve entries from the performance buffer using getEntriesByType()
// to capture entries that were created before the observer was created.
// Manually capture buffered entries instead of using the native buffered option.
// See method documentation above for rationale and guarantees.
if (this.#buffered) {
const existingMarks = performance.getEntriesByType('mark');
const existingMeasures = performance.getEntriesByType('measure');
Expand All @@ -336,8 +369,7 @@ export class PerformanceObserverSink<T> {

this.#observer.observe({
entryTypes: OBSERVED_TYPES,
// @NOTE: This is for unknown reasons not working, and we manually do it above
// buffered: this.#buffered,
// Note: buffered option intentionally omitted. See method documentation above.
});
}

Expand Down
Loading