Skip to content

Commit aaf9806

Browse files
author
Stephen Belanger
committed
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 0ee2cea commit aaf9806

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
@@ -60,6 +60,8 @@ InternalCallbackScope::InternalCallbackScope(Environment* env,
6060
// If you hit this assertion, you forgot to enter the v8::Context first.
6161
CHECK_EQ(Environment::GetCurrent(env->isolate()), env);
6262

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

@@ -81,6 +83,8 @@ void InternalCallbackScope::Close() {
8183
if (closed_) return;
8284
closed_ = true;
8385

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

src/env.cc

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

548548
uv_check_start(immediate_check_handle(), CheckImmediate);
549549

550+
// Inform V8's CPU profiler when we're idle. The profiler is sampling-based
551+
// but not all samples are created equal; mark the wall clock time spent in
552+
// epoll_wait() and friends so profiling tools can filter it out. The samples
553+
// still end up in v8.log but with state=IDLE rather than state=EXTERNAL.
554+
uv_prepare_init(event_loop(), &idle_prepare_handle_);
555+
uv_check_init(event_loop(), &idle_check_handle_);
556+
550557
uv_async_init(
551558
event_loop(),
552559
&task_queues_async_,
@@ -557,6 +564,8 @@ void Environment::InitializeLibuv() {
557564
Context::Scope context_scope(env->context());
558565
env->RunAndClearNativeImmediates();
559566
});
567+
uv_unref(reinterpret_cast<uv_handle_t*>(&idle_prepare_handle_));
568+
uv_unref(reinterpret_cast<uv_handle_t*>(&idle_check_handle_));
560569
uv_unref(reinterpret_cast<uv_handle_t*>(&task_queues_async_));
561570

562571
{
@@ -573,6 +582,8 @@ void Environment::InitializeLibuv() {
573582
// the one environment per process setup, but will be called in
574583
// FreeEnvironment.
575584
RegisterHandleCleanups();
585+
586+
StartProfilerIdleNotifier();
576587
}
577588

578589
void Environment::ExitEnv() {
@@ -600,6 +611,8 @@ void Environment::RegisterHandleCleanups() {
600611
register_handle(reinterpret_cast<uv_handle_t*>(timer_handle()));
601612
register_handle(reinterpret_cast<uv_handle_t*>(immediate_check_handle()));
602613
register_handle(reinterpret_cast<uv_handle_t*>(immediate_idle_handle()));
614+
register_handle(reinterpret_cast<uv_handle_t*>(&idle_prepare_handle_));
615+
register_handle(reinterpret_cast<uv_handle_t*>(&idle_check_handle_));
603616
register_handle(reinterpret_cast<uv_handle_t*>(&task_queues_async_));
604617
}
605618

@@ -631,6 +644,17 @@ void Environment::CleanupHandles() {
631644
}
632645
}
633646

647+
void Environment::StartProfilerIdleNotifier() {
648+
uv_prepare_start(&idle_prepare_handle_, [](uv_prepare_t* handle) {
649+
Environment* env = ContainerOf(&Environment::idle_prepare_handle_, handle);
650+
env->isolate()->SetIdle(true);
651+
});
652+
uv_check_start(&idle_check_handle_, [](uv_check_t* handle) {
653+
Environment* env = ContainerOf(&Environment::idle_check_handle_, handle);
654+
env->isolate()->SetIdle(false);
655+
});
656+
}
657+
634658
void Environment::PrintSyncTrace() const {
635659
if (!trace_sync_io_) return;
636660

src/env.h

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

1067+
void StartProfilerIdleNotifier();
1068+
10671069
inline v8::Isolate* isolate() const;
10681070
inline uv_loop_t* event_loop() const;
10691071
inline void TryLoadAddon(
@@ -1414,6 +1416,8 @@ class Environment : public MemoryRetainer {
14141416
uv_timer_t timer_handle_;
14151417
uv_check_t immediate_check_handle_;
14161418
uv_idle_t immediate_idle_handle_;
1419+
uv_prepare_t idle_prepare_handle_;
1420+
uv_check_t idle_check_handle_;
14171421
uv_async_t task_queues_async_;
14181422
int64_t task_queues_async_refs_ = 0;
14191423

+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)