mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 18:38:55 +00:00
## Summary This PR implements `perf_hooks.monitorEventLoopDelay()` for Node.js compatibility, enabling monitoring of event loop delays and collection of performance metrics via histograms. Fixes #17650 ## Implementation Details ### JavaScript Layer (`perf_hooks.ts`) - Added `IntervalHistogram` class with: - `enable()` / `disable()` methods with proper state tracking - `reset()` method to clear histogram data - Properties: `min`, `max`, `mean`, `stddev`, `exceeds`, `percentiles` - `percentile(p)` method with validation - Full input validation matching Node.js behavior (TypeError vs RangeError) ### C++ Bindings (`JSNodePerformanceHooksHistogramPrototype.cpp`) - `jsFunction_monitorEventLoopDelay` - Creates histogram for event loop monitoring - `jsFunction_enableEventLoopDelay` - Enables monitoring and starts timer - `jsFunction_disableEventLoopDelay` - Disables monitoring and stops timer - `JSNodePerformanceHooksHistogram_recordDelay` - Records delay measurements ### Zig Implementation (`EventLoopDelayMonitor.zig`) - Embedded `EventLoopTimer` that fires periodically based on resolution - Tracks last fire time and calculates delay between expected vs actual - Records delays > 0 to the histogram - Integrates seamlessly with existing Timer system ## Testing ✅ All tests pass: - Custom test suite with 8 comprehensive tests - Adapted Node.js core test for full compatibility - Tests cover enable/disable behavior, percentiles, error handling, and delay recording ## Test plan - [x] Run `bun test test/js/node/perf_hooks/test-monitorEventLoopDelay.test.js` - [x] Run adapted Node.js test `test/js/node/test/sequential/test-performance-eventloopdelay-adapted.test.js` - [x] Verify proper error handling for invalid arguments - [x] Confirm delay measurements are recorded correctly 🤖 Generated with [Claude Code](https://claude.ai/code) --------- Co-authored-by: Claude Bot <claude-bot@bun.sh> Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Jarred Sumner <jarred@jarredsumner.com>
84 lines
2.6 KiB
Zig
84 lines
2.6 KiB
Zig
const EventLoopDelayMonitor = @This();
|
|
|
|
/// We currently only globally share the same instance, which is kept alive by
|
|
/// the existence of the src/js/internal/perf_hooks/monitorEventLoopDelay.ts
|
|
/// function's scope.
|
|
///
|
|
/// I don't think having a single event loop delay monitor histogram instance
|
|
/// /will cause any issues? Let's find out.
|
|
js_histogram: jsc.JSValue = jsc.JSValue.zero,
|
|
|
|
event_loop_timer: jsc.API.Timer.EventLoopTimer = .{
|
|
.next = .epoch,
|
|
.tag = .EventLoopDelayMonitor,
|
|
},
|
|
resolution_ms: i32 = 10,
|
|
last_fire_ns: u64 = 0,
|
|
enabled: bool = false,
|
|
|
|
pub fn enable(this: *EventLoopDelayMonitor, vm: *VirtualMachine, histogram: jsc.JSValue, resolution_ms: i32) void {
|
|
if (this.enabled) return;
|
|
this.js_histogram = histogram;
|
|
this.resolution_ms = resolution_ms;
|
|
|
|
this.enabled = true;
|
|
|
|
// Schedule timer
|
|
const now = bun.timespec.now();
|
|
this.event_loop_timer.next = now.addMs(@intCast(resolution_ms));
|
|
vm.timer.insert(&this.event_loop_timer);
|
|
}
|
|
|
|
pub fn disable(this: *EventLoopDelayMonitor, vm: *VirtualMachine) void {
|
|
if (!this.enabled) return;
|
|
|
|
this.enabled = false;
|
|
this.js_histogram = jsc.JSValue.zero;
|
|
this.last_fire_ns = 0;
|
|
vm.timer.remove(&this.event_loop_timer);
|
|
}
|
|
|
|
pub fn isEnabled(this: *const EventLoopDelayMonitor) bool {
|
|
return this.enabled and this.js_histogram != jsc.JSValue.zero;
|
|
}
|
|
|
|
pub fn onFire(this: *EventLoopDelayMonitor, vm: *VirtualMachine, now: *const bun.timespec) void {
|
|
if (!this.enabled or this.js_histogram == jsc.JSValue.zero) {
|
|
return;
|
|
}
|
|
|
|
const now_ns = now.ns();
|
|
if (this.last_fire_ns > 0) {
|
|
const expected_ns = @as(u64, @intCast(this.resolution_ms)) *| 1_000_000;
|
|
const actual_ns = now_ns - this.last_fire_ns;
|
|
|
|
if (actual_ns > expected_ns) {
|
|
const delay_ns = @as(i64, @intCast(actual_ns -| expected_ns));
|
|
JSNodePerformanceHooksHistogram_recordDelay(this.js_histogram, delay_ns);
|
|
}
|
|
}
|
|
|
|
this.last_fire_ns = now_ns;
|
|
|
|
// Reschedule
|
|
this.event_loop_timer.next = now.addMs(@intCast(this.resolution_ms));
|
|
vm.timer.insert(&this.event_loop_timer);
|
|
}
|
|
|
|
// Record delay to histogram
|
|
extern fn JSNodePerformanceHooksHistogram_recordDelay(histogram: jsc.JSValue, delay_ns: i64) void;
|
|
|
|
// Export functions for C++
|
|
export fn Timer_enableEventLoopDelayMonitoring(vm: *VirtualMachine, histogram: jsc.JSValue, resolution_ms: i32) void {
|
|
vm.timer.event_loop_delay.enable(vm, histogram, resolution_ms);
|
|
}
|
|
|
|
export fn Timer_disableEventLoopDelayMonitoring(vm: *VirtualMachine) void {
|
|
vm.timer.event_loop_delay.disable(vm);
|
|
}
|
|
|
|
const bun = @import("bun");
|
|
|
|
const jsc = bun.jsc;
|
|
const VirtualMachine = jsc.VirtualMachine;
|