-
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 2 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,11 @@ 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) used for event-loop-utilization accounting. */ | ||
| 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() | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| #pragma once | ||
|
|
||
| #include "root.h" | ||
|
|
||
| #if !OS(WINDOWS) | ||
| #include <termios.h> | ||
| #endif | ||
|
|
||
| // Per-handle raw-mode state, mirroring libuv's `uv_tty_t`: every tty handle | ||
| // keeps its own mode plus the termios snapshot captured when it left normal | ||
| // mode, so one handle going back to cooked never disturbs another. | ||
| struct BunTTYState { | ||
| int mode = 0; | ||
| #if !OS(WINDOWS) | ||
| struct termios orig_termios {}; | ||
| #endif | ||
| }; | ||
|
|
||
| // `state` points at `Bun__ttyStateSize()` zero-initialized bytes, owned by the | ||
| // caller for as long as the handle lives. The bytes are copied in and out, so | ||
| // the buffer carries no alignment requirement. | ||
| extern "C" int Bun__ttySetMode(int fd, int mode, void* state); | ||
| extern "C" size_t Bun__ttyStateSize(); | ||
|
Check warning on line 23 in src/jsc/bindings/BunTTYState.h
|
||
|
robobun marked this conversation as resolved.
Outdated
|
||
Uh oh!
There was an error while loading. Please reload this page.