src: port coverage serialization to C++
This patch moves the serialization of coverage profiles into C++. With this we no longer need to patch `process.reallyExit` and hook into the exit events, but instead hook into relevant places in C++ which are safe from user manipulation. This also makes the code easier to reuse for other types of profiles. PR-URL: https://github.com/nodejs/node/pull/26874 Reviewed-By: Ben Coe <bencoe@gmail.com>
This commit is contained in:
parent
baa54a5ae7
commit
864860e9f3
@ -110,20 +110,10 @@ function setupWarningHandler() {
|
||||
// Setup User-facing NODE_V8_COVERAGE environment variable that writes
|
||||
// ScriptCoverage to a specified file.
|
||||
function setupCoverageHooks(dir) {
|
||||
const originalReallyExit = process.reallyExit;
|
||||
const cwd = require('internal/process/execution').tryGetCwd();
|
||||
const { resolve } = require('path');
|
||||
const coverageDirectory = resolve(cwd, dir);
|
||||
const {
|
||||
writeCoverage,
|
||||
setCoverageDirectory
|
||||
} = require('internal/profiler');
|
||||
setCoverageDirectory(coverageDirectory);
|
||||
process.on('exit', writeCoverage);
|
||||
process.reallyExit = (code) => {
|
||||
writeCoverage();
|
||||
originalReallyExit(code);
|
||||
};
|
||||
internalBinding('profiler').setCoverageDirectory(coverageDirectory);
|
||||
return coverageDirectory;
|
||||
}
|
||||
|
||||
|
@ -157,10 +157,6 @@ function wrapProcessMethods(binding) {
|
||||
|
||||
function kill(pid, sig) {
|
||||
var err;
|
||||
if (process.env.NODE_V8_COVERAGE) {
|
||||
const { writeCoverage } = require('internal/profiler');
|
||||
writeCoverage();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if (pid != (pid | 0)) {
|
||||
|
@ -1,45 +0,0 @@
|
||||
'use strict';
|
||||
|
||||
// Implements coverage collection exposed by the `NODE_V8_COVERAGE`
|
||||
// environment variable which can also be used in the user land.
|
||||
|
||||
const { JSON } = primordials;
|
||||
|
||||
let coverageDirectory;
|
||||
|
||||
function writeCoverage() {
|
||||
const { join } = require('path');
|
||||
const { mkdirSync, writeFileSync } = require('fs');
|
||||
const { threadId } = require('internal/worker');
|
||||
|
||||
const filename = `coverage-${process.pid}-${Date.now()}-${threadId}.json`;
|
||||
try {
|
||||
mkdirSync(coverageDirectory, { recursive: true });
|
||||
} catch (err) {
|
||||
if (err.code !== 'EEXIST') {
|
||||
console.error(err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const target = join(coverageDirectory, filename);
|
||||
internalBinding('profiler').endCoverage((msg) => {
|
||||
try {
|
||||
const coverageInfo = JSON.parse(msg).result;
|
||||
if (coverageInfo) {
|
||||
writeFileSync(target, JSON.stringify(coverageInfo));
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function setCoverageDirectory(dir) {
|
||||
coverageDirectory = dir;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
writeCoverage,
|
||||
setCoverageDirectory
|
||||
};
|
1
node.gyp
1
node.gyp
@ -169,7 +169,6 @@
|
||||
'lib/internal/process/worker_thread_only.js',
|
||||
'lib/internal/process/report.js',
|
||||
'lib/internal/process/task_queues.js',
|
||||
'lib/internal/profiler.js',
|
||||
'lib/internal/querystring.js',
|
||||
'lib/internal/readline.js',
|
||||
'lib/internal/repl.js',
|
||||
|
@ -638,6 +638,26 @@ inline const std::vector<std::string>& Environment::exec_argv() {
|
||||
return exec_argv_;
|
||||
}
|
||||
|
||||
#if HAVE_INSPECTOR
|
||||
inline void Environment::set_coverage_directory(const char* dir) {
|
||||
coverage_directory_ = std::string(dir);
|
||||
}
|
||||
|
||||
inline void Environment::set_coverage_connection(
|
||||
std::unique_ptr<profiler::V8CoverageConnection> connection) {
|
||||
CHECK_NULL(coverage_connection_);
|
||||
std::swap(coverage_connection_, connection);
|
||||
}
|
||||
|
||||
inline profiler::V8CoverageConnection* Environment::coverage_connection() {
|
||||
return coverage_connection_.get();
|
||||
}
|
||||
|
||||
inline const std::string& Environment::coverage_directory() const {
|
||||
return coverage_directory_;
|
||||
}
|
||||
#endif // HAVE_INSPECTOR
|
||||
|
||||
inline std::shared_ptr<HostPort> Environment::inspector_host_port() {
|
||||
return inspector_host_port_;
|
||||
}
|
||||
|
@ -715,7 +715,6 @@ Local<Value> Environment::GetNow() {
|
||||
return Number::New(isolate(), static_cast<double>(now));
|
||||
}
|
||||
|
||||
|
||||
void Environment::set_debug_categories(const std::string& cats, bool enabled) {
|
||||
std::string debug_categories = cats;
|
||||
while (!debug_categories.empty()) {
|
||||
|
23
src/env.h
23
src/env.h
@ -27,6 +27,7 @@
|
||||
#include "aliased_buffer.h"
|
||||
#if HAVE_INSPECTOR
|
||||
#include "inspector_agent.h"
|
||||
#include "inspector_profiler.h"
|
||||
#endif
|
||||
#include "handle_wrap.h"
|
||||
#include "node.h"
|
||||
@ -67,6 +68,12 @@ namespace tracing {
|
||||
class AgentWriterHandle;
|
||||
}
|
||||
|
||||
#if HAVE_INSPECTOR
|
||||
namespace profiler {
|
||||
class V8CoverageConnection;
|
||||
} // namespace profiler
|
||||
#endif // HAVE_INSPECTOR
|
||||
|
||||
namespace worker {
|
||||
class Worker;
|
||||
}
|
||||
@ -366,7 +373,6 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
|
||||
V(async_hooks_init_function, v8::Function) \
|
||||
V(async_hooks_promise_resolve_function, v8::Function) \
|
||||
V(buffer_prototype_object, v8::Object) \
|
||||
V(coverage_connection, v8::Object) \
|
||||
V(crypto_key_object_constructor, v8::Function) \
|
||||
V(domain_callback, v8::Function) \
|
||||
V(domexception_function, v8::Function) \
|
||||
@ -390,7 +396,6 @@ constexpr size_t kFsStatsBufferLength = kFsStatsFieldsNumber * 2;
|
||||
V(inspector_console_extension_installer, v8::Function) \
|
||||
V(message_port, v8::Object) \
|
||||
V(native_module_require, v8::Function) \
|
||||
V(on_coverage_message_function, v8::Function) \
|
||||
V(performance_entry_callback, v8::Function) \
|
||||
V(performance_entry_template, v8::Function) \
|
||||
V(process_object, v8::Object) \
|
||||
@ -1116,6 +1121,15 @@ class Environment : public MemoryRetainer {
|
||||
|
||||
inline AsyncRequest* thread_stopper() { return &thread_stopper_; }
|
||||
|
||||
#if HAVE_INSPECTOR
|
||||
void set_coverage_connection(
|
||||
std::unique_ptr<profiler::V8CoverageConnection> connection);
|
||||
profiler::V8CoverageConnection* coverage_connection();
|
||||
|
||||
inline void set_coverage_directory(const char* directory);
|
||||
inline const std::string& coverage_directory() const;
|
||||
#endif // HAVE_INSPECTOR
|
||||
|
||||
private:
|
||||
inline void CreateImmediate(native_immediate_callback cb,
|
||||
void* data,
|
||||
@ -1146,6 +1160,11 @@ class Environment : public MemoryRetainer {
|
||||
size_t async_callback_scope_depth_ = 0;
|
||||
std::vector<double> destroy_async_id_list_;
|
||||
|
||||
#if HAVE_INSPECTOR
|
||||
std::unique_ptr<profiler::V8CoverageConnection> coverage_connection_;
|
||||
std::string coverage_directory_;
|
||||
#endif // HAVE_INSPECTOR
|
||||
|
||||
std::shared_ptr<EnvironmentOptions> options_;
|
||||
// options_ contains debug options parsed from CLI arguments,
|
||||
// while inspector_host_port_ stores the actual inspector host
|
||||
|
@ -44,6 +44,7 @@
|
||||
'../../src/inspector_io.cc',
|
||||
'../../src/inspector_agent.h',
|
||||
'../../src/inspector_io.h',
|
||||
'../../src/inspector_profiler.h',
|
||||
'../../src/inspector_profiler.cc',
|
||||
'../../src/inspector_js_api.cc',
|
||||
'../../src/inspector_socket.cc',
|
||||
|
@ -1,6 +1,7 @@
|
||||
#include "inspector_profiler.h"
|
||||
#include "base_object-inl.h"
|
||||
#include "debug_utils.h"
|
||||
#include "inspector_agent.h"
|
||||
#include "node_file.h"
|
||||
#include "node_internals.h"
|
||||
#include "v8-inspector.h"
|
||||
|
||||
@ -8,7 +9,6 @@ namespace node {
|
||||
namespace profiler {
|
||||
|
||||
using v8::Context;
|
||||
using v8::EscapableHandleScope;
|
||||
using v8::Function;
|
||||
using v8::FunctionCallbackInfo;
|
||||
using v8::HandleScope;
|
||||
@ -17,188 +17,192 @@ using v8::Local;
|
||||
using v8::MaybeLocal;
|
||||
using v8::NewStringType;
|
||||
using v8::Object;
|
||||
using v8::ObjectTemplate;
|
||||
using v8::String;
|
||||
using v8::Value;
|
||||
|
||||
using v8_inspector::StringBuffer;
|
||||
using v8_inspector::StringView;
|
||||
|
||||
#ifdef __POSIX__
|
||||
const char* const kPathSeparator = "/";
|
||||
#else
|
||||
const char* const kPathSeparator = "\\/";
|
||||
#endif
|
||||
|
||||
std::unique_ptr<StringBuffer> ToProtocolString(Isolate* isolate,
|
||||
Local<Value> value) {
|
||||
TwoByteValue buffer(isolate, value);
|
||||
return StringBuffer::create(StringView(*buffer, buffer.length()));
|
||||
}
|
||||
|
||||
class V8ProfilerConnection : public BaseObject {
|
||||
public:
|
||||
class V8ProfilerSessionDelegate
|
||||
: public inspector::InspectorSessionDelegate {
|
||||
public:
|
||||
explicit V8ProfilerSessionDelegate(V8ProfilerConnection* connection)
|
||||
: connection_(connection) {}
|
||||
V8ProfilerConnection::V8ProfilerConnection(Environment* env)
|
||||
: session_(env->inspector_agent()->Connect(
|
||||
std::make_unique<V8ProfilerConnection::V8ProfilerSessionDelegate>(
|
||||
this),
|
||||
false)),
|
||||
env_(env) {}
|
||||
|
||||
void SendMessageToFrontend(
|
||||
const v8_inspector::StringView& message) override {
|
||||
Environment* env = connection_->env();
|
||||
|
||||
Local<Function> fn = connection_->GetMessageCallback();
|
||||
bool ending = !fn.IsEmpty();
|
||||
Debug(env,
|
||||
DebugCategory::INSPECTOR_PROFILER,
|
||||
"Sending message to frontend, ending = %s\n",
|
||||
ending ? "true" : "false");
|
||||
if (!ending) {
|
||||
return;
|
||||
}
|
||||
Isolate* isolate = env->isolate();
|
||||
|
||||
HandleScope handle_scope(isolate);
|
||||
Context::Scope context_scope(env->context());
|
||||
MaybeLocal<String> v8string =
|
||||
String::NewFromTwoByte(isolate,
|
||||
message.characters16(),
|
||||
NewStringType::kNormal,
|
||||
message.length());
|
||||
Local<Value> args[] = {v8string.ToLocalChecked().As<Value>()};
|
||||
USE(fn->Call(
|
||||
env->context(), connection_->object(), arraysize(args), args));
|
||||
}
|
||||
|
||||
private:
|
||||
V8ProfilerConnection* connection_;
|
||||
};
|
||||
|
||||
SET_MEMORY_INFO_NAME(V8ProfilerConnection)
|
||||
SET_SELF_SIZE(V8ProfilerConnection)
|
||||
|
||||
void MemoryInfo(MemoryTracker* tracker) const override {
|
||||
tracker->TrackFieldWithSize(
|
||||
"session", sizeof(*session_), "InspectorSession");
|
||||
}
|
||||
|
||||
explicit V8ProfilerConnection(Environment* env, Local<Object> obj)
|
||||
: BaseObject(env, obj), session_(nullptr) {
|
||||
inspector::Agent* inspector = env->inspector_agent();
|
||||
std::unique_ptr<inspector::InspectorSession> session = inspector->Connect(
|
||||
std::make_unique<V8ProfilerSessionDelegate>(this), false);
|
||||
session_ = std::move(session);
|
||||
MakeWeak();
|
||||
}
|
||||
|
||||
void DispatchMessage(Isolate* isolate, Local<String> message) {
|
||||
session_->Dispatch(ToProtocolString(isolate, message)->string());
|
||||
}
|
||||
|
||||
static MaybeLocal<Object> CreateConnectionObject(Environment* env) {
|
||||
Isolate* isolate = env->isolate();
|
||||
Local<Context> context = env->context();
|
||||
EscapableHandleScope scope(isolate);
|
||||
|
||||
Local<ObjectTemplate> t = ObjectTemplate::New(isolate);
|
||||
t->SetInternalFieldCount(1);
|
||||
Local<Object> obj;
|
||||
if (!t->NewInstance(context).ToLocal(&obj)) {
|
||||
return MaybeLocal<Object>();
|
||||
}
|
||||
|
||||
obj->SetAlignedPointerInInternalField(0, nullptr);
|
||||
return scope.Escape(obj);
|
||||
}
|
||||
|
||||
void Start() {
|
||||
SetConnection(object());
|
||||
StartProfiling();
|
||||
}
|
||||
|
||||
void End(Local<Function> callback) {
|
||||
SetMessageCallback(callback);
|
||||
EndProfiling();
|
||||
}
|
||||
|
||||
// Override this to return a JS function that gets called with the message
|
||||
// sent from the session.
|
||||
virtual Local<Function> GetMessageCallback() = 0;
|
||||
virtual void SetMessageCallback(Local<Function> callback) = 0;
|
||||
// Use DispatchMessage() to dispatch necessary inspector messages
|
||||
virtual void StartProfiling() = 0;
|
||||
virtual void EndProfiling() = 0;
|
||||
virtual void SetConnection(Local<Object> connection) = 0;
|
||||
|
||||
private:
|
||||
std::unique_ptr<inspector::InspectorSession> session_;
|
||||
};
|
||||
|
||||
class V8CoverageConnection : public V8ProfilerConnection {
|
||||
public:
|
||||
explicit V8CoverageConnection(Environment* env)
|
||||
: V8ProfilerConnection(env,
|
||||
CreateConnectionObject(env).ToLocalChecked()) {}
|
||||
|
||||
Local<Function> GetMessageCallback() override {
|
||||
return env()->on_coverage_message_function();
|
||||
}
|
||||
|
||||
void SetMessageCallback(Local<Function> callback) override {
|
||||
return env()->set_on_coverage_message_function(callback);
|
||||
}
|
||||
|
||||
static V8ProfilerConnection* GetConnection(Environment* env) {
|
||||
return Unwrap<V8CoverageConnection>(env->coverage_connection());
|
||||
}
|
||||
|
||||
void SetConnection(Local<Object> obj) override {
|
||||
env()->set_coverage_connection(obj);
|
||||
}
|
||||
|
||||
void StartProfiling() override {
|
||||
Debug(env(),
|
||||
DebugCategory::INSPECTOR_PROFILER,
|
||||
"Sending Profiler.startPreciseCoverage\n");
|
||||
Isolate* isolate = env()->isolate();
|
||||
Local<String> enable = FIXED_ONE_BYTE_STRING(
|
||||
isolate, "{\"id\": 1, \"method\": \"Profiler.enable\"}");
|
||||
Local<String> start = FIXED_ONE_BYTE_STRING(
|
||||
isolate,
|
||||
"{"
|
||||
"\"id\": 2,"
|
||||
"\"method\": \"Profiler.startPreciseCoverage\","
|
||||
"\"params\": {\"callCount\": true, \"detailed\": true}"
|
||||
"}");
|
||||
DispatchMessage(isolate, enable);
|
||||
DispatchMessage(isolate, start);
|
||||
}
|
||||
|
||||
void EndProfiling() override {
|
||||
Debug(env(),
|
||||
DebugCategory::INSPECTOR_PROFILER,
|
||||
"Sending Profiler.takePreciseCoverage\n");
|
||||
Isolate* isolate = env()->isolate();
|
||||
Local<String> end =
|
||||
FIXED_ONE_BYTE_STRING(isolate,
|
||||
"{"
|
||||
"\"id\": 3,"
|
||||
"\"method\": \"Profiler.takePreciseCoverage\""
|
||||
"}");
|
||||
DispatchMessage(isolate, end);
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<inspector::InspectorSession> session_;
|
||||
};
|
||||
|
||||
void StartCoverageCollection(Environment* env) {
|
||||
V8CoverageConnection* connection = new V8CoverageConnection(env);
|
||||
connection->Start();
|
||||
void V8ProfilerConnection::DispatchMessage(Local<String> message) {
|
||||
session_->Dispatch(ToProtocolString(env()->isolate(), message)->string());
|
||||
}
|
||||
|
||||
static void EndCoverageCollection(const FunctionCallbackInfo<Value>& args) {
|
||||
bool V8ProfilerConnection::WriteResult(const char* path, Local<String> result) {
|
||||
int ret = WriteFileSync(env()->isolate(), path, result);
|
||||
if (ret != 0) {
|
||||
char err_buf[128];
|
||||
uv_err_name_r(ret, err_buf, sizeof(err_buf));
|
||||
fprintf(stderr, "%s: Failed to write file %s\n", err_buf, path);
|
||||
return false;
|
||||
}
|
||||
Debug(
|
||||
env(), DebugCategory::INSPECTOR_PROFILER, "Written result to %s\n", path);
|
||||
return true;
|
||||
}
|
||||
|
||||
void V8CoverageConnection::OnMessage(const v8_inspector::StringView& message) {
|
||||
Debug(env(),
|
||||
DebugCategory::INSPECTOR_PROFILER,
|
||||
"Receive coverage message, ending = %s\n",
|
||||
ending_ ? "true" : "false");
|
||||
if (!ending_) {
|
||||
return;
|
||||
}
|
||||
Isolate* isolate = env()->isolate();
|
||||
Local<Context> context = env()->context();
|
||||
HandleScope handle_scope(isolate);
|
||||
Context::Scope context_scope(context);
|
||||
Local<String> result;
|
||||
if (!String::NewFromTwoByte(isolate,
|
||||
message.characters16(),
|
||||
NewStringType::kNormal,
|
||||
message.length())
|
||||
.ToLocal(&result)) {
|
||||
fprintf(stderr, "Failed to covert coverage message\n");
|
||||
}
|
||||
WriteCoverage(result);
|
||||
}
|
||||
|
||||
bool V8CoverageConnection::WriteCoverage(Local<String> message) {
|
||||
const std::string& directory = env()->coverage_directory();
|
||||
CHECK(!directory.empty());
|
||||
uv_fs_t req;
|
||||
int ret = fs::MKDirpSync(nullptr, &req, directory, 0777, nullptr);
|
||||
uv_fs_req_cleanup(&req);
|
||||
if (ret < 0 && ret != UV_EEXIST) {
|
||||
char err_buf[128];
|
||||
uv_err_name_r(ret, err_buf, sizeof(err_buf));
|
||||
fprintf(stderr,
|
||||
"%s: Failed to create coverage directory %s\n",
|
||||
err_buf,
|
||||
directory.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string thread_id = std::to_string(env()->thread_id());
|
||||
std::string pid = std::to_string(uv_os_getpid());
|
||||
std::string timestamp = std::to_string(
|
||||
static_cast<uint64_t>(GetCurrentTimeInMicroseconds() / 1000));
|
||||
char filename[1024];
|
||||
snprintf(filename,
|
||||
sizeof(filename),
|
||||
"coverage-%s-%s-%s.json",
|
||||
pid.c_str(),
|
||||
timestamp.c_str(),
|
||||
thread_id.c_str());
|
||||
std::string target = directory + kPathSeparator + filename;
|
||||
MaybeLocal<String> result = GetResult(message);
|
||||
if (result.IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
return WriteResult(target.c_str(), result.ToLocalChecked());
|
||||
}
|
||||
|
||||
MaybeLocal<String> V8CoverageConnection::GetResult(Local<String> message) {
|
||||
Local<Context> context = env()->context();
|
||||
Isolate* isolate = env()->isolate();
|
||||
Local<Value> parsed;
|
||||
if (!v8::JSON::Parse(context, message).ToLocal(&parsed) ||
|
||||
!parsed->IsObject()) {
|
||||
fprintf(stderr, "Failed to parse coverage result as JSON object\n");
|
||||
return MaybeLocal<String>();
|
||||
}
|
||||
|
||||
Local<Value> result_v;
|
||||
if (!parsed.As<Object>()
|
||||
->Get(context, FIXED_ONE_BYTE_STRING(isolate, "result"))
|
||||
.ToLocal(&result_v)) {
|
||||
fprintf(stderr, "Failed to get result from coverage message\n");
|
||||
return MaybeLocal<String>();
|
||||
}
|
||||
|
||||
if (result_v->IsUndefined()) {
|
||||
fprintf(stderr, "'result' from coverage message is undefined\n");
|
||||
return MaybeLocal<String>();
|
||||
}
|
||||
|
||||
Local<String> result_s;
|
||||
if (!v8::JSON::Stringify(context, result_v).ToLocal(&result_s)) {
|
||||
fprintf(stderr, "Failed to stringify coverage result\n");
|
||||
return MaybeLocal<String>();
|
||||
}
|
||||
|
||||
return result_s;
|
||||
}
|
||||
|
||||
void V8CoverageConnection::Start() {
|
||||
Debug(env(),
|
||||
DebugCategory::INSPECTOR_PROFILER,
|
||||
"Sending Profiler.startPreciseCoverage\n");
|
||||
Isolate* isolate = env()->isolate();
|
||||
Local<String> enable = FIXED_ONE_BYTE_STRING(
|
||||
isolate, R"({"id": 1, "method": "Profiler.enable"})");
|
||||
Local<String> start = FIXED_ONE_BYTE_STRING(isolate, R"({
|
||||
"id": 2,
|
||||
"method": "Profiler.startPreciseCoverage",
|
||||
"params": { "callCount": true, "detailed": true }
|
||||
})");
|
||||
DispatchMessage(enable);
|
||||
DispatchMessage(start);
|
||||
}
|
||||
|
||||
void V8CoverageConnection::End() {
|
||||
CHECK_EQ(ending_, false);
|
||||
ending_ = true;
|
||||
Debug(env(),
|
||||
DebugCategory::INSPECTOR_PROFILER,
|
||||
"Sending Profiler.takePreciseCoverage\n");
|
||||
Isolate* isolate = env()->isolate();
|
||||
HandleScope scope(isolate);
|
||||
Local<String> end = FIXED_ONE_BYTE_STRING(isolate, R"({
|
||||
"id": 3,
|
||||
"method": "Profiler.takePreciseCoverage"
|
||||
})");
|
||||
DispatchMessage(end);
|
||||
}
|
||||
|
||||
// For now, we only support coverage profiling, but we may add more
|
||||
// in the future.
|
||||
void EndStartedProfilers(Environment* env) {
|
||||
Debug(env, DebugCategory::INSPECTOR_PROFILER, "EndStartedProfilers\n");
|
||||
V8ProfilerConnection* connection = env->coverage_connection();
|
||||
if (connection != nullptr && !connection->ending()) {
|
||||
Debug(
|
||||
env, DebugCategory::INSPECTOR_PROFILER, "Ending coverage collection\n");
|
||||
connection->End();
|
||||
}
|
||||
}
|
||||
|
||||
void StartCoverageCollection(Environment* env) {
|
||||
CHECK_NULL(env->coverage_connection());
|
||||
env->set_coverage_connection(std::make_unique<V8CoverageConnection>(env));
|
||||
env->coverage_connection()->Start();
|
||||
}
|
||||
|
||||
static void SetCoverageDirectory(const FunctionCallbackInfo<Value>& args) {
|
||||
CHECK(args[0]->IsString());
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
CHECK(args[0]->IsFunction());
|
||||
Debug(env, DebugCategory::INSPECTOR_PROFILER, "Ending coverage collection\n");
|
||||
V8ProfilerConnection* connection = V8CoverageConnection::GetConnection(env);
|
||||
CHECK_NOT_NULL(connection);
|
||||
connection->End(args[0].As<Function>());
|
||||
node::Utf8Value directory(env->isolate(), args[0].As<String>());
|
||||
env->set_coverage_directory(*directory);
|
||||
}
|
||||
|
||||
static void Initialize(Local<Object> target,
|
||||
@ -206,8 +210,9 @@ static void Initialize(Local<Object> target,
|
||||
Local<Context> context,
|
||||
void* priv) {
|
||||
Environment* env = Environment::GetCurrent(context);
|
||||
env->SetMethod(target, "endCoverage", EndCoverageCollection);
|
||||
env->SetMethod(target, "setCoverageDirectory", SetCoverageDirectory);
|
||||
}
|
||||
|
||||
} // namespace profiler
|
||||
} // namespace node
|
||||
|
||||
|
75
src/inspector_profiler.h
Normal file
75
src/inspector_profiler.h
Normal file
@ -0,0 +1,75 @@
|
||||
#ifndef SRC_INSPECTOR_PROFILER_H_
|
||||
#define SRC_INSPECTOR_PROFILER_H_
|
||||
|
||||
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
|
||||
#if !HAVE_INSPECTOR
|
||||
#error("This header can only be used when inspector is enabled")
|
||||
#endif
|
||||
|
||||
#include "env.h"
|
||||
#include "inspector_agent.h"
|
||||
|
||||
namespace node {
|
||||
// Forward declaration to break recursive dependency chain with src/env.h.
|
||||
class Environment;
|
||||
|
||||
namespace profiler {
|
||||
|
||||
class V8ProfilerConnection {
|
||||
public:
|
||||
class V8ProfilerSessionDelegate : public inspector::InspectorSessionDelegate {
|
||||
public:
|
||||
explicit V8ProfilerSessionDelegate(V8ProfilerConnection* connection)
|
||||
: connection_(connection) {}
|
||||
|
||||
void SendMessageToFrontend(
|
||||
const v8_inspector::StringView& message) override {
|
||||
connection_->OnMessage(message);
|
||||
}
|
||||
|
||||
private:
|
||||
V8ProfilerConnection* connection_;
|
||||
};
|
||||
|
||||
explicit V8ProfilerConnection(Environment* env);
|
||||
virtual ~V8ProfilerConnection() = default;
|
||||
Environment* env() { return env_; }
|
||||
|
||||
// Use DispatchMessage() to dispatch necessary inspector messages
|
||||
virtual void Start() = 0;
|
||||
virtual void End() = 0;
|
||||
// Override this to respond to the messages sent from the session.
|
||||
virtual void OnMessage(const v8_inspector::StringView& message) = 0;
|
||||
virtual bool ending() const = 0;
|
||||
|
||||
void DispatchMessage(v8::Local<v8::String> message);
|
||||
// Write the result to a path
|
||||
bool WriteResult(const char* path, v8::Local<v8::String> result);
|
||||
|
||||
private:
|
||||
std::unique_ptr<inspector::InspectorSession> session_;
|
||||
Environment* env_ = nullptr;
|
||||
};
|
||||
|
||||
class V8CoverageConnection : public V8ProfilerConnection {
|
||||
public:
|
||||
explicit V8CoverageConnection(Environment* env) : V8ProfilerConnection(env) {}
|
||||
|
||||
void Start() override;
|
||||
void End() override;
|
||||
void OnMessage(const v8_inspector::StringView& message) override;
|
||||
bool ending() const override { return ending_; }
|
||||
|
||||
private:
|
||||
bool WriteCoverage(v8::Local<v8::String> message);
|
||||
v8::MaybeLocal<v8::String> GetResult(v8::Local<v8::String> message);
|
||||
std::unique_ptr<inspector::InspectorSession> session_;
|
||||
bool ending_ = false;
|
||||
};
|
||||
|
||||
} // namespace profiler
|
||||
} // namespace node
|
||||
|
||||
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
|
||||
#endif // SRC_INSPECTOR_PROFILER_H_
|
10
src/node.cc
10
src/node.cc
@ -170,6 +170,8 @@ static const unsigned kMaxSignal = 32;
|
||||
|
||||
void WaitForInspectorDisconnect(Environment* env) {
|
||||
#if HAVE_INSPECTOR
|
||||
profiler::EndStartedProfilers(env);
|
||||
|
||||
if (env->inspector_agent()->IsActive()) {
|
||||
// Restore signal dispositions, the app is done and is no longer
|
||||
// capable of handling signals.
|
||||
@ -240,13 +242,13 @@ MaybeLocal<Value> RunBootstrapping(Environment* env) {
|
||||
Isolate* isolate = env->isolate();
|
||||
Local<Context> context = env->context();
|
||||
|
||||
std::string coverage;
|
||||
bool rc = credentials::SafeGetenv("NODE_V8_COVERAGE", &coverage);
|
||||
if (rc && !coverage.empty()) {
|
||||
Local<String> coverage_str = env->env_vars()->Get(
|
||||
isolate, FIXED_ONE_BYTE_STRING(isolate, "NODE_V8_COVERAGE"));
|
||||
if (!coverage_str.IsEmpty() && coverage_str->Length() > 0) {
|
||||
#if HAVE_INSPECTOR
|
||||
profiler::StartCoverageCollection(env);
|
||||
#else
|
||||
fprintf(stderr, "NODE_V8_COVERAGE cannot be used without inspector");
|
||||
fprintf(stderr, "NODE_V8_COVERAGE cannot be used without inspector\n");
|
||||
#endif // HAVE_INSPECTOR
|
||||
}
|
||||
|
||||
|
@ -1235,8 +1235,11 @@ static void RMDir(const FunctionCallbackInfo<Value>& args) {
|
||||
}
|
||||
}
|
||||
|
||||
int MKDirpSync(uv_loop_t* loop, uv_fs_t* req, const std::string& path, int mode,
|
||||
uv_fs_cb cb = nullptr) {
|
||||
int MKDirpSync(uv_loop_t* loop,
|
||||
uv_fs_t* req,
|
||||
const std::string& path,
|
||||
int mode,
|
||||
uv_fs_cb cb) {
|
||||
FSContinuationData continuation_data(req, mode, cb);
|
||||
continuation_data.PushPath(std::move(path));
|
||||
|
||||
|
@ -467,6 +467,11 @@ class FileHandle : public AsyncWrap, public StreamBase {
|
||||
std::unique_ptr<FileHandleReadWrap> current_read_ = nullptr;
|
||||
};
|
||||
|
||||
int MKDirpSync(uv_loop_t* loop,
|
||||
uv_fs_t* req,
|
||||
const std::string& path,
|
||||
int mode,
|
||||
uv_fs_cb cb = nullptr);
|
||||
} // namespace fs
|
||||
|
||||
} // namespace node
|
||||
|
@ -308,15 +308,26 @@ v8::MaybeLocal<v8::Value> ExecuteBootstrapper(
|
||||
std::vector<v8::Local<v8::String>>* parameters,
|
||||
std::vector<v8::Local<v8::Value>>* arguments);
|
||||
void MarkBootstrapComplete(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
#if HAVE_INSPECTOR
|
||||
namespace profiler {
|
||||
void StartCoverageCollection(Environment* env);
|
||||
void EndStartedProfilers(Environment* env);
|
||||
}
|
||||
#endif // HAVE_INSPECTOR
|
||||
|
||||
#ifdef _WIN32
|
||||
typedef SYSTEMTIME TIME_TYPE;
|
||||
#else // UNIX, OSX
|
||||
typedef struct tm TIME_TYPE;
|
||||
#endif
|
||||
|
||||
double GetCurrentTimeInMicroseconds();
|
||||
int WriteFileSync(const char* path, uv_buf_t buf);
|
||||
int WriteFileSync(v8::Isolate* isolate,
|
||||
const char* path,
|
||||
v8::Local<v8::String> string);
|
||||
|
||||
class DiagnosticFilename {
|
||||
public:
|
||||
static void LocalTime(TIME_TYPE* tm_struct);
|
||||
|
@ -6,10 +6,6 @@
|
||||
|
||||
#include <cinttypes>
|
||||
|
||||
#ifdef __POSIX__
|
||||
#include <sys/time.h> // gettimeofday
|
||||
#endif
|
||||
|
||||
namespace node {
|
||||
namespace performance {
|
||||
|
||||
@ -37,8 +33,6 @@ using v8::String;
|
||||
using v8::Uint32Array;
|
||||
using v8::Value;
|
||||
|
||||
// Microseconds in a second, as a float.
|
||||
#define MICROS_PER_SEC 1e6
|
||||
// Microseconds in a millisecond, as a float.
|
||||
#define MICROS_PER_MILLIS 1e3
|
||||
|
||||
@ -57,23 +51,6 @@ void performance_state::Mark(enum PerformanceMilestone milestone,
|
||||
TRACE_EVENT_SCOPE_THREAD, ts / 1000);
|
||||
}
|
||||
|
||||
double GetCurrentTimeInMicroseconds() {
|
||||
#ifdef _WIN32
|
||||
// The difference between the Unix Epoch and the Windows Epoch in 100-ns ticks.
|
||||
#define TICKS_TO_UNIX_EPOCH 116444736000000000LL
|
||||
FILETIME ft;
|
||||
GetSystemTimeAsFileTime(&ft);
|
||||
uint64_t filetime_int = static_cast<uint64_t>(ft.dwHighDateTime) << 32 |
|
||||
ft.dwLowDateTime;
|
||||
// FILETIME is measured in terms of 100 ns. Convert that to 1 us (1000 ns).
|
||||
return (filetime_int - TICKS_TO_UNIX_EPOCH) / 10.;
|
||||
#else
|
||||
struct timeval tp;
|
||||
gettimeofday(&tp, nullptr);
|
||||
return MICROS_PER_SEC * tp.tv_sec + tp.tv_usec;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Initialize the performance entry object properties
|
||||
inline void InitObject(const PerformanceEntry& entry, Local<Object> obj) {
|
||||
Environment* env = entry.env();
|
||||
|
@ -25,8 +25,6 @@ using v8::Value;
|
||||
|
||||
extern const uint64_t timeOrigin;
|
||||
|
||||
double GetCurrentTimeInMicroseconds();
|
||||
|
||||
static inline const char* GetPerformanceMilestoneName(
|
||||
enum PerformanceMilestone milestone) {
|
||||
switch (milestone) {
|
||||
|
@ -171,6 +171,12 @@ static void Kill(const FunctionCallbackInfo<Value>& args) {
|
||||
if (!args[0]->Int32Value(context).To(&pid)) return;
|
||||
int sig;
|
||||
if (!args[1]->Int32Value(context).To(&sig)) return;
|
||||
// TODO(joyeecheung): white list the signals?
|
||||
|
||||
#if HAVE_INSPECTOR
|
||||
profiler::EndStartedProfilers(env);
|
||||
#endif
|
||||
|
||||
int err = uv_kill(pid, sig);
|
||||
args.GetReturnValue().Set(err);
|
||||
}
|
||||
|
@ -329,6 +329,9 @@ void Worker::Run() {
|
||||
if (exit_code_ == 0 && !stopped)
|
||||
exit_code_ = exit_code;
|
||||
|
||||
#if HAVE_INSPECTOR
|
||||
profiler::EndStartedProfilers(env_.get());
|
||||
#endif
|
||||
Debug(this, "Exiting thread for worker %llu with exit code %d",
|
||||
thread_id_, exit_code_);
|
||||
}
|
||||
|
59
src/util.cc
59
src/util.cc
@ -28,7 +28,14 @@
|
||||
#include "uv.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <io.h> // _S_IREAD _S_IWRITE
|
||||
#include <time.h>
|
||||
#ifndef S_IRUSR
|
||||
#define S_IRUSR _S_IREAD
|
||||
#endif // S_IRUSR
|
||||
#ifndef S_IWUSR
|
||||
#define S_IWUSR _S_IWRITE
|
||||
#endif // S_IWUSR
|
||||
#else
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
@ -40,6 +47,9 @@
|
||||
|
||||
namespace node {
|
||||
|
||||
// Microseconds in a second, as a float.
|
||||
#define MICROS_PER_SEC 1e6
|
||||
|
||||
using v8::ArrayBufferView;
|
||||
using v8::Isolate;
|
||||
using v8::Local;
|
||||
@ -152,6 +162,55 @@ void ThrowErrStringTooLong(Isolate* isolate) {
|
||||
isolate->ThrowException(ERR_STRING_TOO_LONG(isolate));
|
||||
}
|
||||
|
||||
double GetCurrentTimeInMicroseconds() {
|
||||
#ifdef _WIN32
|
||||
// The difference between the Unix Epoch and the Windows Epoch in 100-ns ticks.
|
||||
#define TICKS_TO_UNIX_EPOCH 116444736000000000LL
|
||||
FILETIME ft;
|
||||
GetSystemTimeAsFileTime(&ft);
|
||||
uint64_t filetime_int =
|
||||
static_cast<uint64_t>(ft.dwHighDateTime) << 32 | ft.dwLowDateTime;
|
||||
// FILETIME is measured in terms of 100 ns. Convert that to 1 us (1000 ns).
|
||||
return (filetime_int - TICKS_TO_UNIX_EPOCH) / 10.;
|
||||
#else
|
||||
struct timeval tp;
|
||||
gettimeofday(&tp, nullptr);
|
||||
return MICROS_PER_SEC * tp.tv_sec + tp.tv_usec;
|
||||
#endif
|
||||
}
|
||||
|
||||
int WriteFileSync(const char* path, uv_buf_t buf) {
|
||||
uv_fs_t req;
|
||||
int fd = uv_fs_open(nullptr,
|
||||
&req,
|
||||
path,
|
||||
O_WRONLY | O_CREAT | O_TRUNC,
|
||||
S_IWUSR | S_IRUSR,
|
||||
nullptr);
|
||||
uv_fs_req_cleanup(&req);
|
||||
if (fd < 0) {
|
||||
return fd;
|
||||
}
|
||||
|
||||
int err = uv_fs_write(nullptr, &req, fd, &buf, 1, 0, nullptr);
|
||||
uv_fs_req_cleanup(&req);
|
||||
if (err < 0) {
|
||||
return err;
|
||||
}
|
||||
|
||||
err = uv_fs_close(nullptr, &req, fd, nullptr);
|
||||
uv_fs_req_cleanup(&req);
|
||||
return err;
|
||||
}
|
||||
|
||||
int WriteFileSync(v8::Isolate* isolate,
|
||||
const char* path,
|
||||
v8::Local<v8::String> string) {
|
||||
node::Utf8Value utf8(isolate, string);
|
||||
uv_buf_t buf = uv_buf_init(utf8.out(), utf8.length());
|
||||
return WriteFileSync(path, buf);
|
||||
}
|
||||
|
||||
void DiagnosticFilename::LocalTime(TIME_TYPE* tm_struct) {
|
||||
#ifdef _WIN32
|
||||
GetLocalTime(tm_struct);
|
||||
|
@ -106,7 +106,7 @@ if (process.features.inspector) {
|
||||
}
|
||||
|
||||
if (process.env.NODE_V8_COVERAGE) {
|
||||
expectedModules.add('NativeModule internal/profiler');
|
||||
expectedModules.add('Internal Binding profiler');
|
||||
}
|
||||
|
||||
const difference = (setA, setB) => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user