Skip to content

Commit 230e98b

Browse files
joyeecheungrvagg
authored andcommitted
process: start coverage collection before bootstrap
This patch moves the dispatch of `Profiler.takePreciseCoverage` to a point before the bootstrap scripts are run to ensure that we can collect coverage data for all the scripts run after the inspector agent is ready. Before this patch `lib/internal/bootstrap/primordials.js` was not covered by `make coverage`, after this patch it is. PR-URL: #26006 Reviewed-By: Anna Henningsen <[email protected]> Reviewed-By: Ben Coe <[email protected]> Reviewed-By: James M Snell <[email protected]>
1 parent 76c2f4f commit 230e98b

File tree

8 files changed

+205
-58
lines changed

8 files changed

+205
-58
lines changed

lib/internal/bootstrap/loaders.js

-13
Original file line numberDiff line numberDiff line change
@@ -311,18 +311,5 @@ NativeModule.prototype.compile = function() {
311311
}
312312
};
313313

314-
// Coverage must be turned on early, so that we can collect
315-
// it for Node.js' own internal libraries.
316-
if (process.env.NODE_V8_COVERAGE) {
317-
if (internalBinding('config').hasInspector) {
318-
const coverage =
319-
NativeModule.require('internal/coverage-gen/with_profiler');
320-
// Inform the profiler to start collecting coverage
321-
coverage.startCoverageCollection();
322-
} else {
323-
process._rawDebug('NODE_V8_COVERAGE cannot be used without inspector');
324-
}
325-
}
326-
327314
// This will be passed to internal/bootstrap/node.js.
328315
return loaderExports;

lib/internal/coverage-gen/with_profiler.js

+6-40
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,9 @@
33
// Implements coverage collection exposed by the `NODE_V8_COVERAGE`
44
// environment variable which can also be used in the user land.
55

6-
let coverageConnection = null;
76
let coverageDirectory;
87

98
function writeCoverage() {
10-
if (!coverageConnection && coverageDirectory) {
11-
return;
12-
}
13-
149
const { join } = require('path');
1510
const { mkdirSync, writeFileSync } = require('fs');
1611
const { threadId } = require('internal/worker');
@@ -28,21 +23,14 @@ function writeCoverage() {
2823
const target = join(coverageDirectory, filename);
2924
try {
3025
disableAllAsyncHooks();
31-
let msg;
32-
coverageConnection._coverageCallback = function(_msg) {
33-
msg = _msg;
34-
};
35-
coverageConnection.dispatch(JSON.stringify({
36-
id: 3,
37-
method: 'Profiler.takePreciseCoverage'
38-
}));
39-
const coverageInfo = JSON.parse(msg).result;
40-
writeFileSync(target, JSON.stringify(coverageInfo));
26+
internalBinding('coverage').end((msg) => {
27+
const coverageInfo = JSON.parse(msg).result;
28+
if (coverageInfo) {
29+
writeFileSync(target, JSON.stringify(coverageInfo));
30+
}
31+
});
4132
} catch (err) {
4233
console.error(err);
43-
} finally {
44-
coverageConnection.disconnect();
45-
coverageConnection = null;
4634
}
4735
}
4836

@@ -52,33 +40,11 @@ function disableAllAsyncHooks() {
5240
hooks_array.forEach((hook) => { hook.disable(); });
5341
}
5442

55-
function startCoverageCollection() {
56-
const { Connection } = internalBinding('inspector');
57-
coverageConnection = new Connection((res) => {
58-
if (coverageConnection._coverageCallback) {
59-
coverageConnection._coverageCallback(res);
60-
}
61-
});
62-
coverageConnection.dispatch(JSON.stringify({
63-
id: 1,
64-
method: 'Profiler.enable'
65-
}));
66-
coverageConnection.dispatch(JSON.stringify({
67-
id: 2,
68-
method: 'Profiler.startPreciseCoverage',
69-
params: {
70-
callCount: true,
71-
detailed: true
72-
}
73-
}));
74-
}
75-
7643
function setCoverageDirectory(dir) {
7744
coverageDirectory = dir;
7845
}
7946

8047
module.exports = {
81-
startCoverageCollection,
8248
writeCoverage,
8349
setCoverageDirectory
8450
};

src/env.h

+6-3
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
329329
V(async_wrap_ctor_template, v8::FunctionTemplate) \
330330
V(async_wrap_object_ctor_template, v8::FunctionTemplate) \
331331
V(buffer_prototype_object, v8::Object) \
332+
V(coverage_connection, v8::Object) \
332333
V(context, v8::Context) \
333334
V(crypto_key_object_constructor, v8::Function) \
334335
V(domain_callback, v8::Function) \
@@ -364,6 +365,7 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
364365
V(message_event_object_template, v8::ObjectTemplate) \
365366
V(message_port_constructor_template, v8::FunctionTemplate) \
366367
V(native_module_require, v8::Function) \
368+
V(on_coverage_message_function, v8::Function) \
367369
V(performance_entry_callback, v8::Function) \
368370
V(performance_entry_template, v8::Function) \
369371
V(pipe_constructor_template, v8::FunctionTemplate) \
@@ -448,9 +450,10 @@ struct ContextInfo {
448450

449451
// Listing the AsyncWrap provider types first enables us to cast directly
450452
// from a provider type to a debug category.
451-
#define DEBUG_CATEGORY_NAMES(V) \
452-
NODE_ASYNC_PROVIDER_TYPES(V) \
453-
V(INSPECTOR_SERVER)
453+
#define DEBUG_CATEGORY_NAMES(V) \
454+
NODE_ASYNC_PROVIDER_TYPES(V) \
455+
V(INSPECTOR_SERVER) \
456+
V(COVERAGE)
454457

455458
enum class DebugCategory {
456459
#define V(name) name,

src/inspector/node_inspector.gypi

+1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
'../../src/inspector_io.cc',
4646
'../../src/inspector_agent.h',
4747
'../../src/inspector_io.h',
48+
'../../src/inspector_coverage.cc',
4849
'../../src/inspector_js_api.cc',
4950
'../../src/inspector_socket.cc',
5051
'../../src/inspector_socket.h',

src/inspector_coverage.cc

+168
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
#include "base_object-inl.h"
2+
#include "debug_utils.h"
3+
#include "inspector_agent.h"
4+
#include "node_internals.h"
5+
#include "v8-inspector.h"
6+
7+
namespace node {
8+
namespace coverage {
9+
10+
using v8::Context;
11+
using v8::Function;
12+
using v8::FunctionCallbackInfo;
13+
using v8::HandleScope;
14+
using v8::Isolate;
15+
using v8::Local;
16+
using v8::MaybeLocal;
17+
using v8::NewStringType;
18+
using v8::Object;
19+
using v8::ObjectTemplate;
20+
using v8::String;
21+
using v8::Value;
22+
23+
using v8_inspector::StringBuffer;
24+
using v8_inspector::StringView;
25+
26+
std::unique_ptr<StringBuffer> ToProtocolString(Isolate* isolate,
27+
Local<Value> value) {
28+
TwoByteValue buffer(isolate, value);
29+
return StringBuffer::create(StringView(*buffer, buffer.length()));
30+
}
31+
32+
class V8CoverageConnection : public BaseObject {
33+
public:
34+
class V8CoverageSessionDelegate : public inspector::InspectorSessionDelegate {
35+
public:
36+
explicit V8CoverageSessionDelegate(V8CoverageConnection* connection)
37+
: connection_(connection) {}
38+
39+
void SendMessageToFrontend(
40+
const v8_inspector::StringView& message) override {
41+
Environment* env = connection_->env();
42+
Local<Function> fn = connection_->env()->on_coverage_message_function();
43+
bool ending = !fn.IsEmpty();
44+
Debug(env,
45+
DebugCategory::COVERAGE,
46+
"Sending message to frontend, ending = %s\n",
47+
ending ? "true" : "false");
48+
if (!ending) {
49+
return;
50+
}
51+
Isolate* isolate = env->isolate();
52+
53+
HandleScope handle_scope(isolate);
54+
Context::Scope context_scope(env->context());
55+
MaybeLocal<String> v8string =
56+
String::NewFromTwoByte(isolate,
57+
message.characters16(),
58+
NewStringType::kNormal,
59+
message.length());
60+
Local<Value> args[] = {v8string.ToLocalChecked().As<Value>()};
61+
USE(MakeCallback(isolate,
62+
connection_->object(),
63+
fn,
64+
arraysize(args),
65+
args,
66+
async_context{0, 0}));
67+
}
68+
69+
private:
70+
V8CoverageConnection* connection_;
71+
};
72+
73+
SET_MEMORY_INFO_NAME(V8CoverageConnection)
74+
SET_SELF_SIZE(V8CoverageConnection)
75+
76+
void MemoryInfo(MemoryTracker* tracker) const override {
77+
tracker->TrackFieldWithSize(
78+
"session", sizeof(*session_), "InspectorSession");
79+
}
80+
81+
explicit V8CoverageConnection(Environment* env)
82+
: BaseObject(env, env->coverage_connection()), session_(nullptr) {
83+
inspector::Agent* inspector = env->inspector_agent();
84+
std::unique_ptr<inspector::InspectorSession> session = inspector->Connect(
85+
std::make_unique<V8CoverageSessionDelegate>(this), false);
86+
session_ = std::move(session);
87+
MakeWeak();
88+
}
89+
90+
void Start() {
91+
Debug(this->env(),
92+
DebugCategory::COVERAGE,
93+
"Sending Profiler.startPreciseCoverage\n");
94+
Isolate* isolate = this->env()->isolate();
95+
Local<Value> enable = FIXED_ONE_BYTE_STRING(
96+
isolate, "{\"id\": 1, \"method\": \"Profiler.enable\"}");
97+
Local<Value> start = FIXED_ONE_BYTE_STRING(
98+
isolate,
99+
"{"
100+
"\"id\": 2,"
101+
"\"method\": \"Profiler.startPreciseCoverage\","
102+
"\"params\": {\"callCount\": true, \"detailed\": true}"
103+
"}");
104+
session_->Dispatch(ToProtocolString(isolate, enable)->string());
105+
session_->Dispatch(ToProtocolString(isolate, start)->string());
106+
}
107+
108+
void End() {
109+
Debug(this->env(),
110+
DebugCategory::COVERAGE,
111+
"Sending Profiler.takePreciseCoverage\n");
112+
Isolate* isolate = this->env()->isolate();
113+
Local<Value> end =
114+
FIXED_ONE_BYTE_STRING(isolate,
115+
"{"
116+
"\"id\": 3,"
117+
"\"method\": \"Profiler.takePreciseCoverage\""
118+
"}");
119+
session_->Dispatch(ToProtocolString(isolate, end)->string());
120+
}
121+
122+
friend class V8CoverageSessionDelegate;
123+
124+
private:
125+
std::unique_ptr<inspector::InspectorSession> session_;
126+
};
127+
128+
bool StartCoverageCollection(Environment* env) {
129+
HandleScope scope(env->isolate());
130+
131+
Local<ObjectTemplate> t = ObjectTemplate::New(env->isolate());
132+
t->SetInternalFieldCount(1);
133+
Local<Object> obj;
134+
if (!t->NewInstance(env->context()).ToLocal(&obj)) {
135+
return false;
136+
}
137+
138+
obj->SetAlignedPointerInInternalField(0, nullptr);
139+
140+
CHECK(env->coverage_connection().IsEmpty());
141+
env->set_coverage_connection(obj);
142+
V8CoverageConnection* connection = new V8CoverageConnection(env);
143+
connection->Start();
144+
return true;
145+
}
146+
147+
static void EndCoverageCollection(const FunctionCallbackInfo<Value>& args) {
148+
Environment* env = Environment::GetCurrent(args);
149+
CHECK(args[0]->IsFunction());
150+
Debug(env, DebugCategory::COVERAGE, "Ending coverage collection\n");
151+
env->set_on_coverage_message_function(args[0].As<Function>());
152+
V8CoverageConnection* connection =
153+
Unwrap<V8CoverageConnection>(env->coverage_connection());
154+
CHECK_NOT_NULL(connection);
155+
connection->End();
156+
}
157+
158+
static void Initialize(Local<Object> target,
159+
Local<Value> unused,
160+
Local<Context> context,
161+
void* priv) {
162+
Environment* env = Environment::GetCurrent(context);
163+
env->SetMethod(target, "end", EndCoverageCollection);
164+
}
165+
} // namespace coverage
166+
} // namespace node
167+
168+
NODE_MODULE_CONTEXT_AWARE_INTERNAL(coverage, node::coverage::Initialize)

src/node.cc

+13
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
2020
// USE OR OTHER DEALINGS IN THE SOFTWARE.
2121

22+
#include "debug_utils.h"
2223
#include "node_binding.h"
2324
#include "node_buffer.h"
2425
#include "node_constants.h"
@@ -230,6 +231,18 @@ MaybeLocal<Value> RunBootstrapping(Environment* env) {
230231
Isolate* isolate = env->isolate();
231232
Local<Context> context = env->context();
232233

234+
std::string coverage;
235+
bool rc = credentials::SafeGetenv("NODE_V8_COVERAGE", &coverage);
236+
if (rc && !coverage.empty()) {
237+
#if HAVE_INSPECTOR
238+
if (!coverage::StartCoverageCollection(env)) {
239+
return MaybeLocal<Value>();
240+
}
241+
#else
242+
fprintf(stderr, "NODE_V8_COVERAGE cannot be used without inspector");
243+
#endif // HAVE_INSPECTOR
244+
}
245+
233246
// Add a reference to the global object
234247
Local<Object> global = context->Global();
235248

src/node_binding.cc

+8-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,12 @@
2121
#define NODE_BUILTIN_REPORT_MODULES(V)
2222
#endif
2323

24+
#if HAVE_INSPECTOR
25+
#define NODE_BUILTIN_COVERAGE_MODULES(V) V(coverage)
26+
#else
27+
#define NODE_BUILTIN_COVERAGE_MODULES(V)
28+
#endif
29+
2430
// A list of built-in modules. In order to do module registration
2531
// in node::Init(), need to add built-in modules in the following list.
2632
// Then in binding::RegisterBuiltinModules(), it calls modules' registration
@@ -77,7 +83,8 @@
7783
NODE_BUILTIN_STANDARD_MODULES(V) \
7884
NODE_BUILTIN_OPENSSL_MODULES(V) \
7985
NODE_BUILTIN_ICU_MODULES(V) \
80-
NODE_BUILTIN_REPORT_MODULES(V)
86+
NODE_BUILTIN_REPORT_MODULES(V) \
87+
NODE_BUILTIN_COVERAGE_MODULES(V)
8188

8289
// This is used to load built-in modules. Instead of using
8390
// __attribute__((constructor)), we call the _register_<modname>

src/node_internals.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,9 @@ void DefineZlibConstants(v8::Local<v8::Object> target);
269269
v8::MaybeLocal<v8::Value> RunBootstrapping(Environment* env);
270270
v8::MaybeLocal<v8::Value> StartExecution(Environment* env,
271271
const char* main_script_id);
272-
272+
namespace coverage {
273+
bool StartCoverageCollection(Environment* env);
274+
}
273275
} // namespace node
274276

275277
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

0 commit comments

Comments
 (0)