Skip to content

Commit 6aa1ed2

Browse files
Stephen Belangertargos
Stephen Belanger
authored andcommitted
src: report idle time correctly
With this change, the V8 profiler will attribute any time between prepare and check cycles, except any entrances to InternalCallbackScope, to be "idle" time. All callbacks, microtasks, and timers will be marked as not idle. The one exception is native modules which don't use the MakeCallback helper, but those are already broken anyway for async context tracking so we should just encourage broken modules to be fixed. PR-URL: #37868 Reviewed-By: Gerhard Stöbich <[email protected]> Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Michael Dawson <[email protected]> Reviewed-By: Gireesh Punathil <[email protected]>
1 parent eb6ea85 commit 6aa1ed2

File tree

4 files changed

+75
-0
lines changed

4 files changed

+75
-0
lines changed

src/api/callback.cc

+4
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ InternalCallbackScope::InternalCallbackScope(Environment* env,
6161
// If you hit this assertion, you forgot to enter the v8::Context first.
6262
CHECK_EQ(Environment::GetCurrent(env->isolate()), env);
6363

64+
env->isolate()->SetIdle(false);
65+
6466
env->async_hooks()->push_async_context(
6567
async_context_.async_id, async_context_.trigger_async_id, object);
6668

@@ -82,6 +84,8 @@ void InternalCallbackScope::Close() {
8284
if (closed_) return;
8385
closed_ = true;
8486

87+
auto idle = OnScopeLeave([&]() { env_->isolate()->SetIdle(true); });
88+
8589
if (!env_->can_call_into_js()) return;
8690
auto perform_stopping_check = [&]() {
8791
if (env_->is_stopping()) {

src/env.cc

+24
Original file line numberDiff line numberDiff line change
@@ -495,6 +495,13 @@ void Environment::InitializeLibuv() {
495495

496496
uv_check_start(immediate_check_handle(), CheckImmediate);
497497

498+
// Inform V8's CPU profiler when we're idle. The profiler is sampling-based
499+
// but not all samples are created equal; mark the wall clock time spent in
500+
// epoll_wait() and friends so profiling tools can filter it out. The samples
501+
// still end up in v8.log but with state=IDLE rather than state=EXTERNAL.
502+
uv_prepare_init(event_loop(), &idle_prepare_handle_);
503+
uv_check_init(event_loop(), &idle_check_handle_);
504+
498505
uv_async_init(
499506
event_loop(),
500507
&task_queues_async_,
@@ -505,6 +512,8 @@ void Environment::InitializeLibuv() {
505512
Context::Scope context_scope(env->context());
506513
env->RunAndClearNativeImmediates();
507514
});
515+
uv_unref(reinterpret_cast<uv_handle_t*>(&idle_prepare_handle_));
516+
uv_unref(reinterpret_cast<uv_handle_t*>(&idle_check_handle_));
508517
uv_unref(reinterpret_cast<uv_handle_t*>(&task_queues_async_));
509518

510519
{
@@ -521,6 +530,8 @@ void Environment::InitializeLibuv() {
521530
// the one environment per process setup, but will be called in
522531
// FreeEnvironment.
523532
RegisterHandleCleanups();
533+
534+
StartProfilerIdleNotifier();
524535
}
525536

526537
void Environment::ExitEnv() {
@@ -548,6 +559,8 @@ void Environment::RegisterHandleCleanups() {
548559
register_handle(reinterpret_cast<uv_handle_t*>(timer_handle()));
549560
register_handle(reinterpret_cast<uv_handle_t*>(immediate_check_handle()));
550561
register_handle(reinterpret_cast<uv_handle_t*>(immediate_idle_handle()));
562+
register_handle(reinterpret_cast<uv_handle_t*>(&idle_prepare_handle_));
563+
register_handle(reinterpret_cast<uv_handle_t*>(&idle_check_handle_));
551564
register_handle(reinterpret_cast<uv_handle_t*>(&task_queues_async_));
552565
}
553566

@@ -579,6 +592,17 @@ void Environment::CleanupHandles() {
579592
}
580593
}
581594

595+
void Environment::StartProfilerIdleNotifier() {
596+
uv_prepare_start(&idle_prepare_handle_, [](uv_prepare_t* handle) {
597+
Environment* env = ContainerOf(&Environment::idle_prepare_handle_, handle);
598+
env->isolate()->SetIdle(true);
599+
});
600+
uv_check_start(&idle_check_handle_, [](uv_check_t* handle) {
601+
Environment* env = ContainerOf(&Environment::idle_check_handle_, handle);
602+
env->isolate()->SetIdle(false);
603+
});
604+
}
605+
582606
void Environment::PrintSyncTrace() const {
583607
if (!trace_sync_io_) return;
584608

src/env.h

+4
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,8 @@ class Environment : public MemoryRetainer {
919919
inline void AssignToContext(v8::Local<v8::Context> context,
920920
const ContextInfo& info);
921921

922+
void StartProfilerIdleNotifier();
923+
922924
inline v8::Isolate* isolate() const;
923925
inline uv_loop_t* event_loop() const;
924926
inline void TryLoadAddon(
@@ -1252,6 +1254,8 @@ class Environment : public MemoryRetainer {
12521254
uv_timer_t timer_handle_;
12531255
uv_check_t immediate_check_handle_;
12541256
uv_idle_t immediate_idle_handle_;
1257+
uv_prepare_t idle_prepare_handle_;
1258+
uv_check_t idle_check_handle_;
12551259
uv_async_t task_queues_async_;
12561260
int64_t task_queues_async_refs_ = 0;
12571261

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
'use strict';
2+
const common = require('../common');
3+
4+
common.skipIfInspectorDisabled();
5+
6+
const assert = require('assert');
7+
const { Session } = require('inspector');
8+
const { promisify } = require('util');
9+
10+
const sleep = promisify(setTimeout);
11+
12+
async function test() {
13+
const inspector = new Session();
14+
inspector.connect();
15+
16+
inspector.post('Profiler.enable');
17+
inspector.post('Profiler.start');
18+
19+
await sleep(1000);
20+
21+
const { profile } = await new Promise((resolve, reject) => {
22+
inspector.post('Profiler.stop', (err, params) => {
23+
if (err) return reject(err);
24+
resolve(params);
25+
});
26+
});
27+
28+
let hasIdle = false;
29+
for (const node of profile.nodes) {
30+
if (node.callFrame.functionName === '(idle)') {
31+
hasIdle = true;
32+
break;
33+
}
34+
}
35+
assert(hasIdle);
36+
37+
inspector.post('Profiler.disable');
38+
inspector.disconnect();
39+
}
40+
41+
test().then(common.mustCall(() => {
42+
console.log('Done!');
43+
}));

0 commit comments

Comments
 (0)