Skip to content

Commit 15ffed5

Browse files
joyeecheungtargos
authored andcommitted
v8: implement v8.takeCoverage()
Add an v8.takeCoverage() API that allows the user to write the coverage started by NODE_V8_COVERAGE to disk on demand. The coverage can be written multiple times during the lifetime of the process, each time the execution counter will be reset. When the process is about to exit, one last coverage will still be written to disk. Also refactors the internal profiler connection code so that we use the inspector response id to identify the profile response instead of using an ad-hoc flag in C++. PR-URL: #33807 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Ben Noordhuis <[email protected]> Reviewed-By: Jiawen Geng <[email protected]> Reviewed-By: Ben Coe <[email protected]>
1 parent 11eca36 commit 15ffed5

File tree

8 files changed

+292
-80
lines changed

8 files changed

+292
-80
lines changed

doc/api/v8.md

+16
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,21 @@ v8.setFlagsFromString('--trace_gc');
214214
setTimeout(() => { v8.setFlagsFromString('--notrace_gc'); }, 60e3);
215215
```
216216

217+
## `v8.takeCoverage()`
218+
219+
<!-- YAML
220+
added: REPLACEME
221+
-->
222+
223+
The `v8.takeCoverage()` method allows the user to write the coverage started by
224+
[`NODE_V8_COVERAGE`][] to disk on demand. This method can be invoked multiple
225+
times during the lifetime of the process, each time the execution counter will
226+
be reset and a new coverage report will be written to the directory specified
227+
by [`NODE_V8_COVERAGE`][].
228+
229+
When the process is about to exit, one last coverage will still be written to
230+
disk.
231+
217232
## `v8.writeHeapSnapshot([filename])`
218233
<!-- YAML
219234
added: v11.13.0
@@ -511,6 +526,7 @@ A subclass of [`Deserializer`][] corresponding to the format written by
511526
[`Deserializer`]: #v8_class_v8_deserializer
512527
[`Error`]: errors.md#errors_class_error
513528
[`GetHeapSpaceStatistics`]: https://v8docs.nodesource.com/node-13.2/d5/dda/classv8_1_1_isolate.html#ac673576f24fdc7a33378f8f57e1d13a4
529+
[`NODE_V8_COVERAGE`]: cli.html#cli_node_v8_coverage_dir
514530
[`Serializer`]: #v8_class_v8_serializer
515531
[`deserializer._readHostObject()`]: #v8_deserializer_readhostobject
516532
[`deserializer.transferArrayBuffer()`]: #v8_deserializer_transferarraybuffer_id_arraybuffer

lib/v8.js

+7
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ const {
3737
Serializer: _Serializer,
3838
Deserializer: _Deserializer
3939
} = internalBinding('serdes');
40+
41+
let profiler = {};
42+
if (internalBinding('config').hasInspector) {
43+
profiler = internalBinding('profiler');
44+
}
45+
4046
const assert = require('internal/assert');
4147
const { copy } = internalBinding('buffer');
4248
const { inspect } = require('internal/util/inspect');
@@ -275,6 +281,7 @@ module.exports = {
275281
DefaultSerializer,
276282
DefaultDeserializer,
277283
deserialize,
284+
takeCoverage: profiler.takeCoverage,
278285
serialize,
279286
writeHeapSnapshot,
280287
};

src/inspector_profiler.cc

+112-75
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "util-inl.h"
1010
#include "v8-inspector.h"
1111

12+
#include <cinttypes>
1213
#include <sstream>
1314

1415
namespace node {
@@ -36,10 +37,11 @@ V8ProfilerConnection::V8ProfilerConnection(Environment* env)
3637
false)),
3738
env_(env) {}
3839

39-
size_t V8ProfilerConnection::DispatchMessage(const char* method,
40-
const char* params) {
40+
uint32_t V8ProfilerConnection::DispatchMessage(const char* method,
41+
const char* params,
42+
bool is_profile_request) {
4143
std::stringstream ss;
42-
size_t id = next_id();
44+
uint32_t id = next_id();
4345
ss << R"({ "id": )" << id;
4446
DCHECK(method != nullptr);
4547
ss << R"(, "method": ")" << method << '"';
@@ -50,12 +52,15 @@ size_t V8ProfilerConnection::DispatchMessage(const char* method,
5052
std::string message = ss.str();
5153
const uint8_t* message_data =
5254
reinterpret_cast<const uint8_t*>(message.c_str());
55+
// Save the id of the profile request to identify its response.
56+
if (is_profile_request) {
57+
profile_ids_.insert(id);
58+
}
5359
Debug(env(),
5460
DebugCategory::INSPECTOR_PROFILER,
5561
"Dispatching message %s\n",
5662
message.c_str());
5763
session_->Dispatch(StringView(message_data, message.length()));
58-
// TODO(joyeecheung): use this to identify the ending message.
5964
return id;
6065
}
6166

@@ -77,33 +82,73 @@ void V8ProfilerConnection::V8ProfilerSessionDelegate::SendMessageToFrontend(
7782
Environment* env = connection_->env();
7883
Isolate* isolate = env->isolate();
7984
HandleScope handle_scope(isolate);
80-
Context::Scope context_scope(env->context());
85+
Local<Context> context = env->context();
86+
Context::Scope context_scope(context);
8187

82-
// TODO(joyeecheung): always parse the message so that we can use the id to
83-
// identify ending messages as well as printing the message in the debug
84-
// output when there is an error.
8588
const char* type = connection_->type();
86-
Debug(env,
87-
DebugCategory::INSPECTOR_PROFILER,
88-
"Receive %s profile message, ending = %s\n",
89-
type,
90-
connection_->ending() ? "true" : "false");
91-
if (!connection_->ending()) {
92-
return;
93-
}
94-
9589
// Convert StringView to a Local<String>.
9690
Local<String> message_str;
9791
if (!String::NewFromTwoByte(isolate,
9892
message.characters16(),
9993
NewStringType::kNormal,
10094
message.length())
10195
.ToLocal(&message_str)) {
102-
fprintf(stderr, "Failed to convert %s profile message\n", type);
96+
fprintf(
97+
stderr, "Failed to convert %s profile message to V8 string\n", type);
98+
return;
99+
}
100+
101+
Debug(env,
102+
DebugCategory::INSPECTOR_PROFILER,
103+
"Receive %s profile message\n",
104+
type);
105+
106+
Local<Value> parsed;
107+
if (!v8::JSON::Parse(context, message_str).ToLocal(&parsed) ||
108+
!parsed->IsObject()) {
109+
fprintf(stderr, "Failed to parse %s profile result as JSON object\n", type);
103110
return;
104111
}
105112

106-
connection_->WriteProfile(message_str);
113+
Local<Object> response = parsed.As<Object>();
114+
Local<Value> id_v;
115+
if (!response->Get(context, FIXED_ONE_BYTE_STRING(isolate, "id"))
116+
.ToLocal(&id_v) ||
117+
!id_v->IsUint32()) {
118+
Utf8Value str(isolate, message_str);
119+
fprintf(
120+
stderr, "Cannot retrieve id from the response message:\n%s\n", *str);
121+
return;
122+
}
123+
uint32_t id = id_v.As<v8::Uint32>()->Value();
124+
125+
if (!connection_->HasProfileId(id)) {
126+
Utf8Value str(isolate, message_str);
127+
Debug(env, DebugCategory::INSPECTOR_PROFILER, "%s\n", *str);
128+
return;
129+
} else {
130+
Debug(env,
131+
DebugCategory::INSPECTOR_PROFILER,
132+
"Writing profile response (id = %" PRIu64 ")\n",
133+
static_cast<uint64_t>(id));
134+
}
135+
136+
// Get message.result from the response.
137+
Local<Value> result_v;
138+
if (!response->Get(context, FIXED_ONE_BYTE_STRING(isolate, "result"))
139+
.ToLocal(&result_v)) {
140+
fprintf(stderr, "Failed to get 'result' from %s profile response\n", type);
141+
return;
142+
}
143+
144+
if (!result_v->IsObject()) {
145+
fprintf(
146+
stderr, "'result' from %s profile response is not an object\n", type);
147+
return;
148+
}
149+
150+
connection_->WriteProfile(result_v.As<Object>());
151+
connection_->RemoveProfileId(id);
107152
}
108153

109154
static bool EnsureDirectory(const std::string& directory, const char* type) {
@@ -138,45 +183,9 @@ std::string V8CoverageConnection::GetFilename() const {
138183
return filename;
139184
}
140185

141-
static MaybeLocal<Object> ParseProfile(Environment* env,
142-
Local<String> message,
143-
const char* type) {
144-
Local<Context> context = env->context();
145-
Isolate* isolate = env->isolate();
146-
147-
// Get message.result from the response
148-
Local<Value> parsed;
149-
if (!v8::JSON::Parse(context, message).ToLocal(&parsed) ||
150-
!parsed->IsObject()) {
151-
fprintf(stderr, "Failed to parse %s profile result as JSON object\n", type);
152-
return MaybeLocal<Object>();
153-
}
154-
155-
Local<Value> result_v;
156-
if (!parsed.As<Object>()
157-
->Get(context, FIXED_ONE_BYTE_STRING(isolate, "result"))
158-
.ToLocal(&result_v)) {
159-
fprintf(stderr, "Failed to get 'result' from %s profile message\n", type);
160-
return MaybeLocal<Object>();
161-
}
162-
163-
if (!result_v->IsObject()) {
164-
fprintf(
165-
stderr, "'result' from %s profile message is not an object\n", type);
166-
return MaybeLocal<Object>();
167-
}
168-
169-
return result_v.As<Object>();
170-
}
171-
172-
void V8ProfilerConnection::WriteProfile(Local<String> message) {
186+
void V8ProfilerConnection::WriteProfile(Local<Object> result) {
173187
Local<Context> context = env_->context();
174188

175-
// Get message.result from the response.
176-
Local<Object> result;
177-
if (!ParseProfile(env_, message, type()).ToLocal(&result)) {
178-
return;
179-
}
180189
// Generate the profile output from the subclass.
181190
Local<Object> profile;
182191
if (!GetProfile(result).ToLocal(&profile)) {
@@ -203,7 +212,7 @@ void V8ProfilerConnection::WriteProfile(Local<String> message) {
203212
WriteResult(env_, path.c_str(), result_s);
204213
}
205214

206-
void V8CoverageConnection::WriteProfile(Local<String> message) {
215+
void V8CoverageConnection::WriteProfile(Local<Object> result) {
207216
Isolate* isolate = env_->isolate();
208217
Local<Context> context = env_->context();
209218
HandleScope handle_scope(isolate);
@@ -219,11 +228,6 @@ void V8CoverageConnection::WriteProfile(Local<String> message) {
219228
return;
220229
}
221230

222-
// Get message.result from the response.
223-
Local<Object> result;
224-
if (!ParseProfile(env_, message, type()).ToLocal(&result)) {
225-
return;
226-
}
227231
// Generate the profile output from the subclass.
228232
Local<Object> profile;
229233
if (!GetProfile(result).ToLocal(&profile)) {
@@ -287,10 +291,19 @@ void V8CoverageConnection::Start() {
287291
R"({ "callCount": true, "detailed": true })");
288292
}
289293

294+
void V8CoverageConnection::TakeCoverage() {
295+
DispatchMessage("Profiler.takePreciseCoverage", nullptr, true);
296+
}
297+
290298
void V8CoverageConnection::End() {
291-
CHECK_EQ(ending_, false);
299+
Debug(env_,
300+
DebugCategory::INSPECTOR_PROFILER,
301+
"V8CoverageConnection::End(), ending = %d\n", ending_);
302+
if (ending_) {
303+
return;
304+
}
292305
ending_ = true;
293-
DispatchMessage("Profiler.takePreciseCoverage");
306+
TakeCoverage();
294307
}
295308

296309
std::string V8CpuProfilerConnection::GetDirectory() const {
@@ -327,9 +340,14 @@ void V8CpuProfilerConnection::Start() {
327340
}
328341

329342
void V8CpuProfilerConnection::End() {
330-
CHECK_EQ(ending_, false);
343+
Debug(env_,
344+
DebugCategory::INSPECTOR_PROFILER,
345+
"V8CpuProfilerConnection::End(), ending = %d\n", ending_);
346+
if (ending_) {
347+
return;
348+
}
331349
ending_ = true;
332-
DispatchMessage("Profiler.stop");
350+
DispatchMessage("Profiler.stop", nullptr, true);
333351
}
334352

335353
std::string V8HeapProfilerConnection::GetDirectory() const {
@@ -365,31 +383,33 @@ void V8HeapProfilerConnection::Start() {
365383
}
366384

367385
void V8HeapProfilerConnection::End() {
368-
CHECK_EQ(ending_, false);
386+
Debug(env_,
387+
DebugCategory::INSPECTOR_PROFILER,
388+
"V8HeapProfilerConnection::End(), ending = %d\n", ending_);
389+
if (ending_) {
390+
return;
391+
}
369392
ending_ = true;
370-
DispatchMessage("HeapProfiler.stopSampling");
393+
DispatchMessage("HeapProfiler.stopSampling", nullptr, true);
371394
}
372395

373396
// For now, we only support coverage profiling, but we may add more
374397
// in the future.
375398
static void EndStartedProfilers(Environment* env) {
399+
// TODO(joyeechueng): merge these connections and use one session per env.
376400
Debug(env, DebugCategory::INSPECTOR_PROFILER, "EndStartedProfilers\n");
377401
V8ProfilerConnection* connection = env->cpu_profiler_connection();
378-
if (connection != nullptr && !connection->ending()) {
379-
Debug(env, DebugCategory::INSPECTOR_PROFILER, "Ending cpu profiling\n");
402+
if (connection != nullptr) {
380403
connection->End();
381404
}
382405

383406
connection = env->heap_profiler_connection();
384-
if (connection != nullptr && !connection->ending()) {
385-
Debug(env, DebugCategory::INSPECTOR_PROFILER, "Ending heap profiling\n");
407+
if (connection != nullptr) {
386408
connection->End();
387409
}
388410

389411
connection = env->coverage_connection();
390-
if (connection != nullptr && !connection->ending()) {
391-
Debug(
392-
env, DebugCategory::INSPECTOR_PROFILER, "Ending coverage collection\n");
412+
if (connection != nullptr) {
393413
connection->End();
394414
}
395415
}
@@ -453,13 +473,30 @@ static void SetSourceMapCacheGetter(const FunctionCallbackInfo<Value>& args) {
453473
env->set_source_map_cache_getter(args[0].As<Function>());
454474
}
455475

476+
static void TakeCoverage(const FunctionCallbackInfo<Value>& args) {
477+
Environment* env = Environment::GetCurrent(args);
478+
V8CoverageConnection* connection = env->coverage_connection();
479+
480+
Debug(
481+
env,
482+
DebugCategory::INSPECTOR_PROFILER,
483+
"TakeCoverage, connection %s nullptr\n",
484+
connection == nullptr ? "==" : "!=");
485+
486+
if (connection != nullptr) {
487+
Debug(env, DebugCategory::INSPECTOR_PROFILER, "taking coverage\n");
488+
connection->TakeCoverage();
489+
}
490+
}
491+
456492
static void Initialize(Local<Object> target,
457493
Local<Value> unused,
458494
Local<Context> context,
459495
void* priv) {
460496
Environment* env = Environment::GetCurrent(context);
461497
env->SetMethod(target, "setCoverageDirectory", SetCoverageDirectory);
462498
env->SetMethod(target, "setSourceMapCacheGetter", SetSourceMapCacheGetter);
499+
env->SetMethod(target, "takeCoverage", TakeCoverage);
463500
}
464501

465502
} // namespace profiler

0 commit comments

Comments
 (0)