Skip to content

Commit 1786bac

Browse files
authored
Add Isolate::SetIdle and CpuProfiler bindings (denoland#2001)
Expose v8::Isolate::SetIdle, along with the CpuProfiler static helpers CollectSample and UseDetailedSourcePositionsForProfiling. SetIdle lets embedders inform V8 when they are idle so the CPU profiler attributes idle samples to the (idle) node instead of counting them as running code. Without it, tools like Chrome DevTools report ~100% CPU for a program that is merely parked waiting for I/O.
1 parent 5d0e31e commit 1786bac

3 files changed

Lines changed: 105 additions & 0 deletions

File tree

src/binding.cc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,10 @@ void v8__Isolate__LowMemoryNotification(v8::Isolate* isolate) {
189189
isolate->LowMemoryNotification();
190190
}
191191

192+
void v8__Isolate__SetIdle(v8::Isolate* isolate, bool is_idle) {
193+
isolate->SetIdle(is_idle);
194+
}
195+
192196
void v8__Isolate__GetHeapStatistics(v8::Isolate* isolate,
193197
v8::HeapStatistics* s) {
194198
isolate->GetHeapStatistics(s);
@@ -3876,6 +3880,20 @@ void v8__HeapProfiler__TakeHeapSnapshot(v8::Isolate* isolate,
38763880
const_cast<v8::HeapSnapshot*>(snapshot)->Delete();
38773881
}
38783882

3883+
void v8__CpuProfiler__CollectSample(v8::Isolate* isolate,
3884+
const uint64_t* trace_id) {
3885+
if (trace_id == nullptr) {
3886+
v8::CpuProfiler::CollectSample(isolate);
3887+
} else {
3888+
v8::CpuProfiler::CollectSample(isolate, *trace_id);
3889+
}
3890+
}
3891+
3892+
void v8__CpuProfiler__UseDetailedSourcePositionsForProfiling(
3893+
v8::Isolate* isolate) {
3894+
v8::CpuProfiler::UseDetailedSourcePositionsForProfiling(isolate);
3895+
}
3896+
38793897
} // extern "C"
38803898

38813899
// v8::ValueSerializer::Delegate

src/isolate.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -623,6 +623,14 @@ unsafe extern "C" {
623623
fn v8__Isolate__MemoryPressureNotification(this: *mut RealIsolate, level: u8);
624624
fn v8__Isolate__ClearKeptObjects(isolate: *mut RealIsolate);
625625
fn v8__Isolate__LowMemoryNotification(isolate: *mut RealIsolate);
626+
fn v8__Isolate__SetIdle(isolate: *mut RealIsolate, is_idle: bool);
627+
fn v8__CpuProfiler__CollectSample(
628+
isolate: *mut RealIsolate,
629+
trace_id: *const u64,
630+
);
631+
fn v8__CpuProfiler__UseDetailedSourcePositionsForProfiling(
632+
isolate: *mut RealIsolate,
633+
);
626634
fn v8__Isolate__GetHeapStatistics(
627635
this: *mut RealIsolate,
628636
s: *mut v8__HeapStatistics,
@@ -1333,6 +1341,49 @@ impl Isolate {
13331341
unsafe { v8__Isolate__LowMemoryNotification(self.as_real_ptr()) }
13341342
}
13351343

1344+
/// Tells the VM whether the embedder is currently idle or not.
1345+
///
1346+
/// This is consulted by V8's CPU profiler: samples taken while the embedder
1347+
/// is idle (for instance, blocked waiting for I/O in the event loop) are
1348+
/// attributed to the "(idle)" node instead of being counted as running code.
1349+
/// Embedders that don't call this end up reporting ~100% CPU usage in tools
1350+
/// like Chrome DevTools even when the program is doing nothing.
1351+
///
1352+
/// Must be called on the isolate's own thread while no JavaScript is
1353+
/// executing (e.g. right before parking the event loop, and again with
1354+
/// `false` once it resumes).
1355+
#[inline(always)]
1356+
pub fn set_idle(&mut self, is_idle: bool) {
1357+
unsafe { v8__Isolate__SetIdle(self.as_real_ptr(), is_idle) }
1358+
}
1359+
1360+
/// Synchronously collect a CPU profiling sample in all CPU profilers
1361+
/// attached to this isolate. This does not affect the number of ticks
1362+
/// recorded for the current top node.
1363+
///
1364+
/// When `trace_id` is `Some`, the sample is tagged with that identifier,
1365+
/// which is useful to associate the sample with a trace event.
1366+
#[inline(always)]
1367+
pub fn collect_cpu_profiler_sample(&mut self, trace_id: Option<u64>) {
1368+
let trace_id_ptr = match &trace_id {
1369+
Some(id) => id as *const u64,
1370+
None => std::ptr::null(),
1371+
};
1372+
unsafe { v8__CpuProfiler__CollectSample(self.as_real_ptr(), trace_id_ptr) }
1373+
}
1374+
1375+
/// Generate more detailed source positions for code objects. This results in
1376+
/// better accuracy when mapping CPU profiling samples back to script source,
1377+
/// at the cost of some additional memory and CPU overhead.
1378+
#[inline(always)]
1379+
pub fn use_detailed_source_positions_for_profiling(&mut self) {
1380+
unsafe {
1381+
v8__CpuProfiler__UseDetailedSourcePositionsForProfiling(
1382+
self.as_real_ptr(),
1383+
)
1384+
}
1385+
}
1386+
13361387
/// Get statistics about the heap memory usage.
13371388
#[inline(always)]
13381389
pub fn get_heap_statistics(&mut self) -> HeapStatistics {

tests/test_api.rs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8097,6 +8097,42 @@ fn low_memory_notification() {
80978097
isolate.low_memory_notification();
80988098
}
80998099

8100+
#[test]
8101+
fn set_idle() {
8102+
let _setup_guard = setup::parallel_test();
8103+
8104+
let mut isolate = v8::Isolate::new(Default::default());
8105+
// Toggling the idle state must not affect the ability to run code.
8106+
isolate.set_idle(true);
8107+
isolate.set_idle(false);
8108+
8109+
v8::scope!(let scope, &mut isolate);
8110+
let context = v8::Context::new(scope, Default::default());
8111+
let scope = &mut v8::ContextScope::new(scope, context);
8112+
let value = eval(scope, "1 + 1").unwrap();
8113+
assert_eq!(value.uint32_value(scope).unwrap(), 2);
8114+
}
8115+
8116+
#[test]
8117+
fn cpu_profiler_bindings() {
8118+
let _setup_guard = setup::parallel_test();
8119+
8120+
let mut isolate = v8::Isolate::new(Default::default());
8121+
isolate.use_detailed_source_positions_for_profiling();
8122+
8123+
v8::scope!(let scope, &mut isolate);
8124+
let context = v8::Context::new(scope, Default::default());
8125+
let scope = &mut v8::ContextScope::new(scope, context);
8126+
8127+
// Collecting a sample without any profiler attached is a no-op but must be
8128+
// safe to call, both with and without a trace id.
8129+
scope.collect_cpu_profiler_sample(None);
8130+
scope.collect_cpu_profiler_sample(Some(42));
8131+
8132+
let value = eval(scope, "1 + 1").unwrap();
8133+
assert_eq!(value.uint32_value(scope).unwrap(), 2);
8134+
}
8135+
81008136
// Clippy thinks the return value doesn't need to be an Option, it's unaware
81018137
// of the mapping that MapFnFrom<F> does for ResolveModuleCallback.
81028138
#[allow(clippy::unnecessary_wraps)]

0 commit comments

Comments
 (0)