mirror of
https://github.com/nodejs/node.git
synced 2025-08-15 13:48:44 +02:00
inspector: support for worker inspection in chrome devtools
Fixes: https://github.com/nodejs/node/issues/56343 PR-URL: https://github.com/nodejs/node/pull/56759 Reviewed-By: Chengzhong Wu <legendecas@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
parent
cfd2021c35
commit
2281a04e5e
13 changed files with 443 additions and 23 deletions
|
@ -1205,6 +1205,17 @@ added: v22.4.0
|
|||
|
||||
Enable experimental [`Web Storage`][] support.
|
||||
|
||||
### `--experimental-worker-inspection`
|
||||
|
||||
<!-- YAML
|
||||
added:
|
||||
- REPLACEME
|
||||
-->
|
||||
|
||||
> Stability: 1.1 - Active Development
|
||||
|
||||
Enable experimental support for the worker inspection with Chrome DevTools.
|
||||
|
||||
### `--expose-gc`
|
||||
|
||||
<!-- YAML
|
||||
|
|
|
@ -57,6 +57,10 @@ class MainThreadHandle : public std::enable_shared_from_this<MainThreadHandle> {
|
|||
std::unique_ptr<InspectorSessionDelegate> MakeDelegateThreadSafe(
|
||||
std::unique_ptr<InspectorSessionDelegate> delegate);
|
||||
bool Expired();
|
||||
void SetTargetSessionId(int target_session_id) {
|
||||
target_session_id_ = target_session_id;
|
||||
}
|
||||
std::optional<int> GetTargetSessionId() { return target_session_id_; }
|
||||
|
||||
private:
|
||||
void Reset();
|
||||
|
@ -65,6 +69,7 @@ class MainThreadHandle : public std::enable_shared_from_this<MainThreadHandle> {
|
|||
Mutex block_lock_;
|
||||
int next_session_id_ = 0;
|
||||
std::atomic_int next_object_id_ = {1};
|
||||
std::optional<int> target_session_id_ = std::nullopt;
|
||||
|
||||
friend class MainThreadInterface;
|
||||
};
|
||||
|
|
|
@ -32,6 +32,8 @@
|
|||
'src/inspector/network_inspector.h',
|
||||
'src/inspector/network_agent.cc',
|
||||
'src/inspector/network_agent.h',
|
||||
'src/inspector/target_agent.cc',
|
||||
'src/inspector/target_agent.h',
|
||||
'src/inspector/worker_inspector.cc',
|
||||
'src/inspector/worker_inspector.h',
|
||||
],
|
||||
|
@ -47,6 +49,8 @@
|
|||
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/NodeRuntime.h',
|
||||
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/Network.cpp',
|
||||
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/Network.h',
|
||||
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/Target.cpp',
|
||||
'<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/Target.h',
|
||||
],
|
||||
'node_protocol_files': [
|
||||
'<(protocol_tool_path)/lib/Forward_h.template',
|
||||
|
|
|
@ -249,3 +249,28 @@ experimental domain NodeRuntime
|
|||
# This event is fired when the runtime is waiting for the debugger. For
|
||||
# example, when inspector.waitingForDebugger is called
|
||||
event waitingForDebugger
|
||||
|
||||
# https://chromedevtools.github.io/devtools-protocol/1-3/Target/
|
||||
experimental domain Target
|
||||
type SessionID extends string
|
||||
type TargetID extends string
|
||||
type TargetInfo extends object
|
||||
properties
|
||||
TargetID targetId
|
||||
string type
|
||||
string title
|
||||
string url
|
||||
boolean attached
|
||||
boolean canAccessOpener
|
||||
event targetCreated
|
||||
parameters
|
||||
TargetInfo targetInfo
|
||||
event attachedToTarget
|
||||
parameters
|
||||
SessionID sessionId
|
||||
TargetInfo targetInfo
|
||||
boolean waitingForDebugger
|
||||
command setAutoAttach
|
||||
parameters
|
||||
boolean autoAttach
|
||||
boolean waitForDebuggerOnStart
|
||||
|
|
136
src/inspector/target_agent.cc
Normal file
136
src/inspector/target_agent.cc
Normal file
|
@ -0,0 +1,136 @@
|
|||
#include "target_agent.h"
|
||||
#include <string_view>
|
||||
#include "crdtp/dispatch.h"
|
||||
#include "inspector/worker_inspector.h"
|
||||
#include "main_thread_interface.h"
|
||||
|
||||
namespace node {
|
||||
namespace inspector {
|
||||
namespace protocol {
|
||||
|
||||
std::unordered_map<int, std::shared_ptr<MainThreadHandle>>
|
||||
TargetAgent::target_session_id_worker_map_ =
|
||||
std::unordered_map<int, std::shared_ptr<MainThreadHandle>>();
|
||||
int TargetAgent::next_session_id_ = 1;
|
||||
class WorkerTargetDelegate : public WorkerDelegate {
|
||||
public:
|
||||
explicit WorkerTargetDelegate(std::shared_ptr<TargetAgent> target_agent)
|
||||
: target_agent_(target_agent) {}
|
||||
|
||||
void WorkerCreated(const std::string& title,
|
||||
const std::string& url,
|
||||
bool waiting,
|
||||
std::shared_ptr<MainThreadHandle> worker) override {
|
||||
target_agent_->createAndAttachIfNecessary(worker, title, url);
|
||||
}
|
||||
|
||||
private:
|
||||
const std::shared_ptr<TargetAgent> target_agent_;
|
||||
};
|
||||
|
||||
std::unique_ptr<Target::TargetInfo> createTargetInfo(
|
||||
const std::string_view target_id,
|
||||
const std::string_view type,
|
||||
const std::string_view title,
|
||||
const std::string_view url) {
|
||||
return Target::TargetInfo::create()
|
||||
.setTargetId(std::string(target_id))
|
||||
.setType(std::string(type))
|
||||
.setTitle(std::string(title))
|
||||
.setUrl(std::string(url))
|
||||
.setAttached(false)
|
||||
.setCanAccessOpener(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
void TargetAgent::Wire(UberDispatcher* dispatcher) {
|
||||
frontend_ = std::make_unique<Target::Frontend>(dispatcher->channel());
|
||||
Target::Dispatcher::wire(dispatcher, this);
|
||||
}
|
||||
|
||||
void TargetAgent::createAndAttachIfNecessary(
|
||||
std::shared_ptr<MainThreadHandle> worker,
|
||||
const std::string& title,
|
||||
const std::string& url) {
|
||||
std::string target_id = std::to_string(getNextTargetId());
|
||||
std::string type = "node_worker";
|
||||
|
||||
targetCreated(target_id, type, title, url);
|
||||
bool attached = false;
|
||||
if (auto_attach_) {
|
||||
attached = true;
|
||||
attachedToTarget(worker, target_id, type, title, url);
|
||||
}
|
||||
targets_.push_back({target_id, type, title, url, worker, attached});
|
||||
}
|
||||
|
||||
void TargetAgent::listenWorker(std::weak_ptr<WorkerManager> worker_manager) {
|
||||
auto manager = worker_manager.lock();
|
||||
if (!manager) {
|
||||
return;
|
||||
}
|
||||
std::unique_ptr<WorkerDelegate> delegate(
|
||||
new WorkerTargetDelegate(shared_from_this()));
|
||||
worker_event_handle_ = manager->SetAutoAttach(std::move(delegate));
|
||||
}
|
||||
|
||||
void TargetAgent::reset() {
|
||||
if (worker_event_handle_) {
|
||||
worker_event_handle_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void TargetAgent::targetCreated(const std::string_view target_id,
|
||||
const std::string_view type,
|
||||
const std::string_view title,
|
||||
const std::string_view url) {
|
||||
frontend_->targetCreated(createTargetInfo(target_id, type, title, url));
|
||||
}
|
||||
|
||||
int TargetAgent::getNextSessionId() {
|
||||
return next_session_id_++;
|
||||
}
|
||||
|
||||
int TargetAgent::getNextTargetId() {
|
||||
return next_target_id_++;
|
||||
}
|
||||
|
||||
void TargetAgent::attachedToTarget(std::shared_ptr<MainThreadHandle> worker,
|
||||
const std::string& target_id,
|
||||
const std::string& type,
|
||||
const std::string& title,
|
||||
const std::string& url) {
|
||||
int session_id = getNextSessionId();
|
||||
target_session_id_worker_map_[session_id] = worker;
|
||||
worker->SetTargetSessionId(session_id);
|
||||
frontend_->attachedToTarget(std::to_string(session_id),
|
||||
createTargetInfo(target_id, type, title, url),
|
||||
true);
|
||||
}
|
||||
|
||||
// TODO(islandryu): Currently, setAutoAttach applies the main thread's value to
|
||||
// all threads. Modify it to be managed per worker thread.
|
||||
crdtp::DispatchResponse TargetAgent::setAutoAttach(
|
||||
bool auto_attach, bool wait_for_debugger_on_start) {
|
||||
auto_attach_ = auto_attach;
|
||||
wait_for_debugger_on_start_ = wait_for_debugger_on_start;
|
||||
|
||||
if (auto_attach) {
|
||||
for (auto& target : targets_) {
|
||||
if (!target.attached) {
|
||||
target.attached = true;
|
||||
attachedToTarget(target.worker,
|
||||
target.target_id,
|
||||
target.type,
|
||||
target.title,
|
||||
target.url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return DispatchResponse::Success();
|
||||
}
|
||||
|
||||
} // namespace protocol
|
||||
} // namespace inspector
|
||||
} // namespace node
|
75
src/inspector/target_agent.h
Normal file
75
src/inspector/target_agent.h
Normal file
|
@ -0,0 +1,75 @@
|
|||
#ifndef SRC_INSPECTOR_TARGET_AGENT_H_
|
||||
#define SRC_INSPECTOR_TARGET_AGENT_H_
|
||||
|
||||
#include <string_view>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include "inspector/worker_inspector.h"
|
||||
#include "node/inspector/protocol/Target.h"
|
||||
|
||||
namespace node {
|
||||
|
||||
namespace inspector {
|
||||
class TargetInspector;
|
||||
|
||||
namespace protocol {
|
||||
|
||||
struct TargetInfo {
|
||||
std::string target_id;
|
||||
std::string type;
|
||||
std::string title;
|
||||
std::string url;
|
||||
std::shared_ptr<MainThreadHandle> worker;
|
||||
bool attached;
|
||||
};
|
||||
|
||||
class TargetAgent : public Target::Backend,
|
||||
public std::enable_shared_from_this<TargetAgent> {
|
||||
public:
|
||||
void Wire(UberDispatcher* dispatcher);
|
||||
|
||||
void createAndAttachIfNecessary(std::shared_ptr<MainThreadHandle> worker,
|
||||
const std::string& title,
|
||||
const std::string& url);
|
||||
|
||||
DispatchResponse setAutoAttach(bool auto_attach,
|
||||
bool wait_for_debugger_on_start) override;
|
||||
|
||||
void listenWorker(std::weak_ptr<WorkerManager> worker_manager);
|
||||
void reset();
|
||||
static std::unordered_map<int, std::shared_ptr<MainThreadHandle>>
|
||||
target_session_id_worker_map_;
|
||||
|
||||
bool isThisThread(MainThreadHandle* worker) { return worker == main_thread_; }
|
||||
|
||||
private:
|
||||
int getNextTargetId();
|
||||
int getNextSessionId();
|
||||
void targetCreated(const std::string_view target_id,
|
||||
const std::string_view type,
|
||||
const std::string_view title,
|
||||
const std::string_view url);
|
||||
void attachedToTarget(std::shared_ptr<MainThreadHandle> worker,
|
||||
const std::string& target_id,
|
||||
const std::string& type,
|
||||
const std::string& title,
|
||||
const std::string& url);
|
||||
|
||||
std::shared_ptr<Target::Frontend> frontend_;
|
||||
std::weak_ptr<WorkerManager> worker_manager_;
|
||||
static int next_session_id_;
|
||||
int next_target_id_ = 1;
|
||||
std::unique_ptr<WorkerManagerEventHandle> worker_event_handle_ = nullptr;
|
||||
bool auto_attach_ = false;
|
||||
// TODO(islandryu): If false, implement it so that each thread does not wait
|
||||
// for the worker to execute.
|
||||
bool wait_for_debugger_on_start_ = true;
|
||||
std::vector<TargetInfo> targets_;
|
||||
MainThreadHandle* main_thread_;
|
||||
};
|
||||
|
||||
} // namespace protocol
|
||||
} // namespace inspector
|
||||
} // namespace node
|
||||
|
||||
#endif // SRC_INSPECTOR_TARGET_AGENT_H_
|
|
@ -8,6 +8,7 @@
|
|||
#include "inspector/node_string.h"
|
||||
#include "inspector/protocol_helper.h"
|
||||
#include "inspector/runtime_agent.h"
|
||||
#include "inspector/target_agent.h"
|
||||
#include "inspector/tracing_agent.h"
|
||||
#include "inspector/worker_agent.h"
|
||||
#include "inspector/worker_inspector.h"
|
||||
|
@ -218,9 +219,11 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
|
|||
const std::unique_ptr<V8Inspector>& inspector,
|
||||
std::shared_ptr<WorkerManager> worker_manager,
|
||||
std::unique_ptr<InspectorSessionDelegate> delegate,
|
||||
std::shared_ptr<MainThreadHandle> main_thread_,
|
||||
std::shared_ptr<MainThreadHandle> main_thread,
|
||||
bool prevent_shutdown)
|
||||
: delegate_(std::move(delegate)), prevent_shutdown_(prevent_shutdown),
|
||||
: delegate_(std::move(delegate)),
|
||||
main_thread_(main_thread),
|
||||
prevent_shutdown_(prevent_shutdown),
|
||||
retaining_context_(false) {
|
||||
session_ = inspector->connect(CONTEXT_GROUP_ID,
|
||||
this,
|
||||
|
@ -239,6 +242,11 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
|
|||
network_inspector_ =
|
||||
std::make_unique<NetworkInspector>(env, inspector.get());
|
||||
network_inspector_->Wire(node_dispatcher_.get());
|
||||
if (env->options()->experimental_worker_inspection) {
|
||||
target_agent_ = std::make_shared<protocol::TargetAgent>();
|
||||
target_agent_->Wire(node_dispatcher_.get());
|
||||
target_agent_->listenWorker(worker_manager);
|
||||
}
|
||||
}
|
||||
|
||||
~ChannelImpl() override {
|
||||
|
@ -252,6 +260,9 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
|
|||
runtime_agent_.reset(); // Dispose before the dispatchers
|
||||
network_inspector_->Disable();
|
||||
network_inspector_.reset(); // Dispose before the dispatchers
|
||||
if (target_agent_) {
|
||||
target_agent_->reset();
|
||||
}
|
||||
}
|
||||
|
||||
void emitNotificationFromBackend(v8::Local<v8::Context> context,
|
||||
|
@ -334,6 +345,15 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
|
|||
// crdtp::FrontendChannel
|
||||
void FlushProtocolNotifications() override {}
|
||||
|
||||
std::string serializeToJSON(std::unique_ptr<Serializable> message) {
|
||||
std::vector<uint8_t> cbor = message->Serialize();
|
||||
std::string json;
|
||||
crdtp::Status status = ConvertCBORToJSON(crdtp::SpanFrom(cbor), &json);
|
||||
CHECK(status.ok());
|
||||
USE(status);
|
||||
return json;
|
||||
}
|
||||
|
||||
void sendMessageToFrontend(const StringView& message) {
|
||||
if (per_process::enabled_debug_list.enabled(
|
||||
DebugCategory::INSPECTOR_SERVER)) {
|
||||
|
@ -342,7 +362,18 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
|
|||
"[inspector send] %s\n",
|
||||
raw_message);
|
||||
}
|
||||
delegate_->SendMessageToFrontend(message);
|
||||
std::optional<int> target_session_id = main_thread_->GetTargetSessionId();
|
||||
if (target_session_id.has_value()) {
|
||||
std::string raw_message = protocol::StringUtil::StringViewToUtf8(message);
|
||||
std::unique_ptr<protocol::DictionaryValue> value =
|
||||
protocol::DictionaryValue::cast(JsonUtil::parseJSON(raw_message));
|
||||
std::string target_session_id_str = std::to_string(*target_session_id);
|
||||
value->setString("sessionId", target_session_id_str);
|
||||
std::string json = serializeToJSON(std::move(value));
|
||||
delegate_->SendMessageToFrontend(Utf8ToStringView(json)->string());
|
||||
} else {
|
||||
delegate_->SendMessageToFrontend(message);
|
||||
}
|
||||
}
|
||||
|
||||
void sendMessageToFrontend(const std::string& message) {
|
||||
|
@ -352,24 +383,14 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
|
|||
// crdtp::FrontendChannel
|
||||
void SendProtocolResponse(int callId,
|
||||
std::unique_ptr<Serializable> message) override {
|
||||
std::vector<uint8_t> cbor = message->Serialize();
|
||||
std::string json;
|
||||
crdtp::Status status = ConvertCBORToJSON(crdtp::SpanFrom(cbor), &json);
|
||||
DCHECK(status.ok());
|
||||
USE(status);
|
||||
|
||||
std::string json = serializeToJSON(std::move(message));
|
||||
sendMessageToFrontend(json);
|
||||
}
|
||||
|
||||
// crdtp::FrontendChannel
|
||||
void SendProtocolNotification(
|
||||
std::unique_ptr<Serializable> message) override {
|
||||
std::vector<uint8_t> cbor = message->Serialize();
|
||||
std::string json;
|
||||
crdtp::Status status = ConvertCBORToJSON(crdtp::SpanFrom(cbor), &json);
|
||||
DCHECK(status.ok());
|
||||
USE(status);
|
||||
|
||||
std::string json = serializeToJSON(std::move(message));
|
||||
sendMessageToFrontend(json);
|
||||
}
|
||||
|
||||
|
@ -383,10 +404,12 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
|
|||
std::unique_ptr<protocol::RuntimeAgent> runtime_agent_;
|
||||
std::unique_ptr<protocol::TracingAgent> tracing_agent_;
|
||||
std::unique_ptr<protocol::WorkerAgent> worker_agent_;
|
||||
std::shared_ptr<protocol::TargetAgent> target_agent_;
|
||||
std::unique_ptr<NetworkInspector> network_inspector_;
|
||||
std::unique_ptr<InspectorSessionDelegate> delegate_;
|
||||
std::unique_ptr<v8_inspector::V8InspectorSession> session_;
|
||||
std::unique_ptr<UberDispatcher> node_dispatcher_;
|
||||
std::shared_ptr<MainThreadHandle> main_thread_;
|
||||
bool prevent_shutdown_;
|
||||
bool retaining_context_;
|
||||
};
|
||||
|
|
|
@ -4,7 +4,9 @@
|
|||
#include "crypto/crypto_util.h"
|
||||
#include "debug_utils-inl.h"
|
||||
#include "inspector/main_thread_interface.h"
|
||||
#include "inspector/node_json.h"
|
||||
#include "inspector/node_string.h"
|
||||
#include "inspector/target_agent.h"
|
||||
#include "inspector_socket_server.h"
|
||||
#include "ncrypto.h"
|
||||
#include "node.h"
|
||||
|
@ -218,6 +220,7 @@ class InspectorIoDelegate: public node::inspector::SocketServerDelegate {
|
|||
void StartSession(int session_id, const std::string& target_id) override;
|
||||
void MessageReceived(int session_id, const std::string& message) override;
|
||||
void EndSession(int session_id) override;
|
||||
std::optional<std::string> GetTargetSessionId(const std::string& message);
|
||||
|
||||
std::vector<std::string> GetTargetIds() override;
|
||||
std::string GetTargetTitle(const std::string& id) override;
|
||||
|
@ -342,20 +345,72 @@ InspectorIoDelegate::InspectorIoDelegate(
|
|||
|
||||
void InspectorIoDelegate::StartSession(int session_id,
|
||||
const std::string& target_id) {
|
||||
auto session = main_thread_->Connect(
|
||||
std::unique_ptr<InspectorSessionDelegate>(
|
||||
new IoSessionDelegate(request_queue_->handle(), session_id)), true);
|
||||
if (session) {
|
||||
sessions_[session_id] = std::move(session);
|
||||
fprintf(stderr, "Debugger attached.\n");
|
||||
fprintf(stderr, "Debugger attached.\n");
|
||||
}
|
||||
|
||||
std::optional<std::string> InspectorIoDelegate::GetTargetSessionId(
|
||||
const std::string& message) {
|
||||
std::string_view view(message.data(), message.size());
|
||||
std::unique_ptr<protocol::DictionaryValue> value =
|
||||
protocol::DictionaryValue::cast(JsonUtil::parseJSON(view));
|
||||
protocol::String target_session_id;
|
||||
protocol::Value* target_session_id_value = value->get("sessionId");
|
||||
if (target_session_id_value) {
|
||||
target_session_id_value->asString(&target_session_id);
|
||||
}
|
||||
|
||||
if (!target_session_id.empty()) {
|
||||
return target_session_id;
|
||||
}
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void InspectorIoDelegate::MessageReceived(int session_id,
|
||||
const std::string& message) {
|
||||
auto session = sessions_.find(session_id);
|
||||
if (session != sessions_.end())
|
||||
std::optional<std::string> target_session_id_str =
|
||||
GetTargetSessionId(message);
|
||||
std::shared_ptr<MainThreadHandle> worker = nullptr;
|
||||
int merged_session_id = session_id;
|
||||
if (target_session_id_str) {
|
||||
bool is_number = std::all_of(target_session_id_str->begin(),
|
||||
target_session_id_str->end(),
|
||||
::isdigit);
|
||||
if (is_number) {
|
||||
int target_session_id = std::stoi(*target_session_id_str);
|
||||
worker = protocol::TargetAgent::target_session_id_worker_map_
|
||||
[target_session_id];
|
||||
if (worker) {
|
||||
merged_session_id += target_session_id << 16;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto session = sessions_.find(merged_session_id);
|
||||
|
||||
if (session == sessions_.end()) {
|
||||
std::unique_ptr<InspectorSession> session;
|
||||
if (worker) {
|
||||
session = worker->Connect(
|
||||
std::unique_ptr<InspectorSessionDelegate>(
|
||||
new IoSessionDelegate(request_queue_->handle(), session_id)),
|
||||
true);
|
||||
} else {
|
||||
session = main_thread_->Connect(
|
||||
std::unique_ptr<InspectorSessionDelegate>(
|
||||
new IoSessionDelegate(request_queue_->handle(), session_id)),
|
||||
true);
|
||||
}
|
||||
|
||||
if (session) {
|
||||
sessions_[merged_session_id] = std::move(session);
|
||||
sessions_[merged_session_id]->Dispatch(
|
||||
Utf8ToStringView(message)->string());
|
||||
} else {
|
||||
fprintf(stderr, "Failed to connect to inspector session.\n");
|
||||
}
|
||||
} else {
|
||||
session->second->Dispatch(Utf8ToStringView(message)->string());
|
||||
}
|
||||
}
|
||||
|
||||
void InspectorIoDelegate::EndSession(int session_id) {
|
||||
|
|
|
@ -642,6 +642,9 @@ EnvironmentOptionsParser::EnvironmentOptionsParser() {
|
|||
AddOption("--experimental-network-inspection",
|
||||
"experimental network inspection support",
|
||||
&EnvironmentOptions::experimental_network_inspection);
|
||||
AddOption("--experimental-worker-inspection",
|
||||
"experimental worker inspection support",
|
||||
&EnvironmentOptions::experimental_worker_inspection);
|
||||
AddOption(
|
||||
"--heap-prof",
|
||||
"Start the V8 heap profiler on start up, and write the heap profile "
|
||||
|
|
|
@ -173,6 +173,7 @@ class EnvironmentOptions : public Options {
|
|||
std::string cpu_prof_name;
|
||||
bool cpu_prof = false;
|
||||
bool experimental_network_inspection = false;
|
||||
bool experimental_worker_inspection = false;
|
||||
std::string heap_prof_dir;
|
||||
std::string heap_prof_name;
|
||||
static const uint64_t kDefaultHeapProfInterval = 512 * 1024;
|
||||
|
|
3
test/fixtures/inspect-worker/index.js
vendored
Normal file
3
test/fixtures/inspect-worker/index.js
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
const { Worker } = require('worker_threads');
|
||||
|
||||
new Worker(__dirname + '/worker.js', { type: 'module' });
|
4
test/fixtures/inspect-worker/worker.js
vendored
Normal file
4
test/fixtures/inspect-worker/worker.js
vendored
Normal file
|
@ -0,0 +1,4 @@
|
|||
console.log("worker thread");
|
||||
process.on('exit', () => {
|
||||
console.log('Worker1: Exiting...');
|
||||
});
|
75
test/parallel/test-inspector-worker-target.js
Normal file
75
test/parallel/test-inspector-worker-target.js
Normal file
|
@ -0,0 +1,75 @@
|
|||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const fixtures = require('../common/fixtures');
|
||||
|
||||
common.skipIfInspectorDisabled();
|
||||
|
||||
const { NodeInstance } = require('../common/inspector-helper.js');
|
||||
|
||||
async function setupInspector(session, sessionId = undefined) {
|
||||
await session.send({ method: 'NodeRuntime.enable', sessionId });
|
||||
await session.waitForNotification('NodeRuntime.waitingForDebugger');
|
||||
await session.send({ method: 'Runtime.enable', sessionId });
|
||||
await session.send({ method: 'Debugger.enable', sessionId });
|
||||
await session.send({ method: 'Runtime.runIfWaitingForDebugger', sessionId });
|
||||
await session.send({ method: 'NodeRuntime.disable', sessionId });
|
||||
await session.waitForNotification((notification) => {
|
||||
return notification.method === 'Debugger.scriptParsed' &&
|
||||
notification.params.url === 'node:internal/bootstrap/realm' &&
|
||||
notification.sessionId === sessionId;
|
||||
});
|
||||
}
|
||||
|
||||
async function test(isSetAutoAttachBeforeExecution) {
|
||||
const child = new NodeInstance(['--inspect-brk=0', '--experimental-worker-inspection'],
|
||||
'',
|
||||
fixtures.path('inspect-worker/index.js')
|
||||
);
|
||||
|
||||
|
||||
const session = await child.connectInspectorSession();
|
||||
await setupInspector(session);
|
||||
|
||||
if (isSetAutoAttachBeforeExecution) {
|
||||
await session.send({ method: 'Target.setAutoAttach', params: { autoAttach: true, waitForDebuggerOnStart: true } });
|
||||
}
|
||||
await session.waitForNotification('Debugger.paused');
|
||||
await session.send({ method: 'Debugger.resume' });
|
||||
|
||||
const sessionId = '1';
|
||||
await session.waitForNotification('Target.targetCreated');
|
||||
|
||||
if (!isSetAutoAttachBeforeExecution) {
|
||||
await session.send({ method: 'Target.setAutoAttach', params: { autoAttach: true, waitForDebuggerOnStart: true } });
|
||||
}
|
||||
await session.waitForNotification((notification) => {
|
||||
return notification.method === 'Target.attachedToTarget' &&
|
||||
notification.params.sessionId === sessionId;
|
||||
});
|
||||
await setupInspector(session, sessionId);
|
||||
await session.waitForNotification('Debugger.paused');
|
||||
await session.send({ method: 'Debugger.resume', sessionId });
|
||||
await session.waitForDisconnect();
|
||||
}
|
||||
|
||||
test(true).then(common.mustCall());
|
||||
test(false).then(common.mustCall());
|
||||
|
||||
function withPermissionOptionTest() {
|
||||
const permissionErrorThrow = common.mustCall();
|
||||
const child = new NodeInstance(['--inspect-brk=0', '--experimental-worker-inspection', '--permission'],
|
||||
'',
|
||||
fixtures.path('inspect-worker/index.js'),
|
||||
{
|
||||
log: (_, msg) => {
|
||||
if (msg.includes('Access to this API has been restricted')) {
|
||||
permissionErrorThrow();
|
||||
}
|
||||
},
|
||||
error: () => {},
|
||||
}
|
||||
);
|
||||
child.connectInspectorSession();
|
||||
}
|
||||
withPermissionOptionTest();
|
Loading…
Add table
Add a link
Reference in a new issue