-
Notifications
You must be signed in to change notification settings - Fork 5k
perf_hooks: implement performance.eventLoopUtilization() #32618
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
7fa9cb8
c2c63ba
1319771
9287f5c
09fbeb6
1fb33b3
e3de1e3
76a1c96
1b4d69b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -75,6 +75,13 @@ extern void __attribute__((__noreturn__)) Bun__panic(const char *message, size_t | |
| * allocations this library has no way to fail gracefully from. */ | ||
| extern void __attribute__((__noreturn__)) Bun__outOfMemory(void); | ||
|
|
||
| /* Monotonic clock (ns): uv_hrtime under libuv, else CLOCK_MONOTONIC. Used for | ||
| * event-loop-utilization accounting and (POSIX) the sweep-timer deadlines, so | ||
| * anything comparing against a loop deadline must read it and not another. */ | ||
| uint64_t us_loop_monotonic_ns(void); | ||
| /* Sample the loop's accumulated idle time and active time (both ns). */ | ||
| void us_loop_event_loop_utilization(struct us_loop_t *loop, uint64_t *idle_ns_out, uint64_t *active_ns_out); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems wasteful and doesn't actually fix the issue at hand. Find another approach
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Want to make sure I rework this in the direction you have in mind, so a bit of context on both points: On cost: the two clock reads happen only when the wait can actually block (NULL or nonzero timeout). Zero-timeout poll-throughs, i.e. every tick while the loop is busy, skip the instrumentation entirely, so the steady-state overhead is two vDSO clock_gettime calls per sleep/wake cycle, paid next to a syscall that already context-switches. This is the same measurement libuv performs under UV_METRICS_IDLE_TIME, which Node enables unconditionally for every loop including workers; the Windows path here just uses that. On the issue: the reporter's repro from #32609 now produces Node's output and exits the same way. This branch prints { idle: 0, active: 168.4, utilization: 1 } and climbing, where Node v26 prints { idle: 0, active: 167.6, utilization: 1 }; the released Bun prints the NotImplementedError warning and zeros forever. So I may be missing what you mean by not fixing the issue at hand; if you saw a case where the numbers are wrong, I would like to chase it. If the objection is that the accounting is always on, I can gate it behind a per-loop flag that flips on the first eventLoopUtilization() call, making the cost exactly zero until someone uses the API. The tradeoff is that idle time before the first call gets reported as active (Node reports absolute idle since loop start), though the common delta usage elu(prev) is unaffected. If you have a different mechanism in mind instead, point me at it and I will rework the PR.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you show me in libuv?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure. In libuv the measurement is the UV_METRICS_IDLE_TIME machinery:
Node turns this on unconditionally for every loop it creates:
and One difference: libuv takes a mutex around both the entry and exit bookkeeping on every wait. This PR uses lock-free atomics for the same two stores, so the per-wait cost here is strictly lower than what every Node process (and Bun on Windows, where we run libuv) already pays. On Windows this PR does not add any measurement of its own, it just flips the same UV_METRICS_IDLE_TIME flag and reads |
||
|
|
||
| #ifdef _WIN32 | ||
| #define IS_EINTR(rc) (rc == SOCKET_ERROR && WSAGetLastError() == WSAEINTR) | ||
| #define LIBUS_ERR WSAGetLastError() | ||
|
|
@@ -150,9 +157,6 @@ void us_internal_timer_sweep(us_loop_r loop); | |
| void us_internal_enable_sweep_timer(struct us_loop_t *loop); | ||
| void us_internal_disable_sweep_timer(struct us_loop_t *loop); | ||
| #ifndef LIBUS_USE_LIBUV | ||
| /* CLOCK_MONOTONIC in ns. The clock every deadline on the loop is measured | ||
| * against, so anything comparing against one must read it and not another. */ | ||
| uint64_t us_internal_monotonic_ns(void); | ||
| long long us_internal_sweep_timeout_ns(struct us_loop_t *loop); | ||
| void us_internal_sweep_if_due(struct us_loop_t *loop); | ||
| #endif | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.