summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--node.gyp213
-rw-r--r--src/inspector/node_protocol.pdl39
-rw-r--r--src/inspector/node_protocol_config.json27
-rw-r--r--src/inspector/node_string.cc92
-rw-r--r--src/inspector/node_string.h79
-rw-r--r--src/inspector/tracing_agent.cc106
-rw-r--r--src/inspector/tracing_agent.h45
-rw-r--r--src/inspector_agent.cc51
-rw-r--r--src/inspector_agent.h9
-rw-r--r--src/inspector_io.cc29
-rw-r--r--src/inspector_io.h2
-rw-r--r--src/tracing/agent.cc153
-rw-r--r--src/tracing/agent.h42
-rw-r--r--src/tracing/node_trace_buffer.cc16
-rw-r--r--src/tracing/node_trace_buffer.h12
-rw-r--r--src/tracing/node_trace_writer.cc6
-rw-r--r--src/tracing/node_trace_writer.h6
-rw-r--r--test/parallel/test-inspector-multisession-js.js6
-rw-r--r--test/parallel/test-inspector-tracing-domain.js70
19 files changed, 843 insertions, 160 deletions
diff --git a/node.gyp b/node.gyp
index 54f22a33da..ecdb147956 100644
--- a/node.gyp
+++ b/node.gyp
@@ -455,17 +455,24 @@
'src/inspector_js_api.cc',
'src/inspector_socket.cc',
'src/inspector_socket_server.cc',
+ 'src/inspector/tracing_agent.cc',
+ 'src/inspector/node_string.cc',
'src/inspector_agent.h',
'src/inspector_io.h',
'src/inspector_socket.h',
'src/inspector_socket_server.h',
+ 'src/inspector/node_string.h',
+ 'src/inspector/tracing_agent.h',
+ '<@(node_inspector_generated_sources)'
],
'dependencies': [
+ 'node_protocol_generated_sources#host',
'v8_inspector_compress_protocol_json#host',
],
'include_dirs': [
'<(SHARED_INTERMEDIATE_DIR)/include', # for inspector
'<(SHARED_INTERMEDIATE_DIR)',
+ '<(SHARED_INTERMEDIATE_DIR)/src', # for inspector
],
}, {
'defines': [ 'HAVE_INSPECTOR=0' ]
@@ -678,54 +685,6 @@
]
},
{
- 'target_name': 'v8_inspector_compress_protocol_json',
- 'type': 'none',
- 'toolsets': ['host'],
- 'conditions': [
- [ 'v8_enable_inspector==1', {
- 'copies': [
- {
- 'destination': '<(SHARED_INTERMEDIATE_DIR)',
- 'files': ['deps/v8/src/inspector/js_protocol.pdl']
- }
- ],
- 'actions': [
- {
- 'action_name': 'v8_inspector_convert_protocol_to_json',
- 'inputs': [
- '<(SHARED_INTERMEDIATE_DIR)/js_protocol.pdl',
- ],
- 'outputs': [
- '<(SHARED_INTERMEDIATE_DIR)/js_protocol.json',
- ],
- 'action': [
- 'python',
- 'deps/v8/third_party/inspector_protocol/ConvertProtocolToJSON.py',
- '<@(_inputs)',
- '<@(_outputs)',
- ],
- },
- {
- 'action_name': 'v8_inspector_compress_protocol_json',
- 'process_outputs_as_sources': 1,
- 'inputs': [
- '<(SHARED_INTERMEDIATE_DIR)/js_protocol.json',
- ],
- 'outputs': [
- '<(SHARED_INTERMEDIATE_DIR)/v8_inspector_protocol_json.h',
- ],
- 'action': [
- 'python',
- 'tools/compress_json.py',
- '<@(_inputs)',
- '<@(_outputs)',
- ],
- },
- ],
- }],
- ],
- },
- {
'target_name': 'node_js2c',
'type': 'none',
'toolsets': ['host'],
@@ -1044,5 +1003,163 @@
},
]
}], # end aix section
+ [ 'v8_enable_inspector==1', {
+ 'variables': {
+ 'protocol_path': 'deps/v8/third_party/inspector_protocol',
+ 'node_inspector_path': 'src/inspector',
+ 'node_inspector_generated_sources': [
+ '<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/Forward.h',
+ '<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/Protocol.cpp',
+ '<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/Protocol.h',
+ '<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/NodeTracing.cpp',
+ '<(SHARED_INTERMEDIATE_DIR)/src/node/inspector/protocol/NodeTracing.h',
+ ],
+ 'node_protocol_files': [
+ '<(protocol_path)/lib/Allocator_h.template',
+ '<(protocol_path)/lib/Array_h.template',
+ '<(protocol_path)/lib/Collections_h.template',
+ '<(protocol_path)/lib/DispatcherBase_cpp.template',
+ '<(protocol_path)/lib/DispatcherBase_h.template',
+ '<(protocol_path)/lib/ErrorSupport_cpp.template',
+ '<(protocol_path)/lib/ErrorSupport_h.template',
+ '<(protocol_path)/lib/Forward_h.template',
+ '<(protocol_path)/lib/FrontendChannel_h.template',
+ '<(protocol_path)/lib/Maybe_h.template',
+ '<(protocol_path)/lib/Object_cpp.template',
+ '<(protocol_path)/lib/Object_h.template',
+ '<(protocol_path)/lib/Parser_cpp.template',
+ '<(protocol_path)/lib/Parser_h.template',
+ '<(protocol_path)/lib/Protocol_cpp.template',
+ '<(protocol_path)/lib/ValueConversions_h.template',
+ '<(protocol_path)/lib/Values_cpp.template',
+ '<(protocol_path)/lib/Values_h.template',
+ '<(protocol_path)/templates/Exported_h.template',
+ '<(protocol_path)/templates/Imported_h.template',
+ '<(protocol_path)/templates/TypeBuilder_cpp.template',
+ '<(protocol_path)/templates/TypeBuilder_h.template',
+ '<(protocol_path)/CodeGenerator.py',
+ ]
+ },
+ 'targets': [
+ {
+ 'target_name': 'prepare_protocol_json',
+ 'type': 'none',
+ 'toolsets': ['host'],
+ 'copies': [
+ {
+ 'files': [
+ '<(node_inspector_path)/node_protocol_config.json',
+ '<(node_inspector_path)/node_protocol.pdl'
+ ],
+ 'destination': '<(SHARED_INTERMEDIATE_DIR)',
+ }
+ ],
+ 'actions': [
+ {
+ 'action_name': 'convert_node_protocol_to_json',
+ 'inputs': [
+ '<(SHARED_INTERMEDIATE_DIR)/node_protocol.pdl',
+ ],
+ 'outputs': [
+ '<(SHARED_INTERMEDIATE_DIR)/node_protocol.json',
+ ],
+ 'action': [
+ 'python',
+ 'deps/v8/third_party/inspector_protocol/ConvertProtocolToJSON.py',
+ '<@(_inputs)',
+ '<@(_outputs)',
+ ],
+ },
+ ]
+ },
+ {
+ 'target_name': 'node_protocol_generated_sources',
+ 'type': 'none',
+ 'toolsets': ['host'],
+ 'dependencies': ['prepare_protocol_json'],
+ 'actions': [
+ {
+ 'action_name': 'node_protocol_generated_sources',
+ 'inputs': [
+ '<(SHARED_INTERMEDIATE_DIR)/node_protocol_config.json',
+ '<(SHARED_INTERMEDIATE_DIR)/node_protocol.json',
+ '<@(node_protocol_files)',
+ ],
+ 'outputs': [
+ '<@(node_inspector_generated_sources)',
+ ],
+ 'action': [
+ 'python',
+ '<(protocol_path)/CodeGenerator.py',
+ '--jinja_dir', '<@(protocol_path)/..',
+ '--output_base', '<(SHARED_INTERMEDIATE_DIR)/src/',
+ '--config', '<(SHARED_INTERMEDIATE_DIR)/node_protocol_config.json',
+ ],
+ 'message': 'Generating node protocol sources from protocol json',
+ },
+ ]
+ },
+ {
+ 'target_name': 'v8_inspector_compress_protocol_json',
+ 'type': 'none',
+ 'toolsets': ['host'],
+ 'copies': [
+ {
+ 'destination': '<(SHARED_INTERMEDIATE_DIR)',
+ 'files': ['deps/v8/src/inspector/js_protocol.pdl']
+ }
+ ],
+ 'actions': [
+ {
+ 'action_name': 'v8_inspector_convert_protocol_to_json',
+ 'inputs': [
+ '<(SHARED_INTERMEDIATE_DIR)/js_protocol.pdl',
+ ],
+ 'outputs': [
+ '<(SHARED_INTERMEDIATE_DIR)/js_protocol.json',
+ ],
+ 'action': [
+ 'python',
+ 'deps/v8/third_party/inspector_protocol/ConvertProtocolToJSON.py',
+ '<@(_inputs)',
+ '<@(_outputs)',
+ ],
+ },
+ {
+ 'action_name': 'concatenate_protocols',
+ 'inputs': [
+ '<(SHARED_INTERMEDIATE_DIR)/js_protocol.json',
+ '<(SHARED_INTERMEDIATE_DIR)/node_protocol.json',
+ ],
+ 'outputs': [
+ '<(SHARED_INTERMEDIATE_DIR)/concatenated_protocol.json',
+ ],
+ 'action': [
+ 'python',
+ 'deps/v8/third_party/inspector_protocol/ConcatenateProtocols.py',
+ '<@(_inputs)',
+ '<@(_outputs)',
+ ],
+ },
+ {
+ 'action_name': 'v8_inspector_compress_protocol_json',
+ 'process_outputs_as_sources': 1,
+ 'inputs': [
+ '<(SHARED_INTERMEDIATE_DIR)/concatenated_protocol.json',
+ ],
+ 'outputs': [
+ '<(SHARED_INTERMEDIATE_DIR)/v8_inspector_protocol_json.h',
+ ],
+ 'action': [
+ 'python',
+ 'tools/compress_json.py',
+ '<@(_inputs)',
+ '<@(_outputs)',
+ ],
+ },
+ ],
+ },
+ ]
+ }]
], # end conditions block
}
diff --git a/src/inspector/node_protocol.pdl b/src/inspector/node_protocol.pdl
new file mode 100644
index 0000000000..27b3d814c8
--- /dev/null
+++ b/src/inspector/node_protocol.pdl
@@ -0,0 +1,39 @@
+# Please notify @nodejs/v8-inspector and @nodejs/trace-events before modifying this file
+version
+ major 1
+ minor 0
+
+experimental domain NodeTracing
+ type TraceConfig extends object
+ properties
+ # Controls how the trace buffer stores data.
+ optional enum recordMode
+ recordUntilFull
+ recordContinuously
+ recordAsMuchAsPossible
+ # Included category filters.
+ array of string includedCategories
+
+ # Gets supported tracing categories.
+ command getCategories
+ returns
+ # A list of supported tracing categories.
+ array of string categories
+
+ # Start trace events collection.
+ command start
+ parameters
+ TraceConfig traceConfig
+
+ # Stop trace events collection. Remaining collected events will be sent as a sequence of
+ # dataCollected events followed by tracingComplete event.
+ command stop
+
+ # Contains an bucket of collected trace events.
+ event dataCollected
+ parameters
+ array of object value
+
+ # Signals that tracing is stopped and there is no trace buffers pending flush, all data were
+ # delivered via dataCollected events.
+ event tracingComplete
diff --git a/src/inspector/node_protocol_config.json b/src/inspector/node_protocol_config.json
new file mode 100644
index 0000000000..7cea20ae93
--- /dev/null
+++ b/src/inspector/node_protocol_config.json
@@ -0,0 +1,27 @@
+{
+ "protocol": {
+ "path": "node_protocol.json",
+ "package": "src/node/inspector/protocol",
+ "output": "node/inspector/protocol",
+ "namespace": ["node", "inspector", "protocol"],
+ "options": [
+ {
+ "domain": "NodeTracing"
+ }
+ ]
+ },
+ "exported": {
+ "package": "include/inspector",
+ "output": "../../include/inspector",
+ "string_header": "v8-inspector.h",
+ "string_in": "StringView",
+ "string_out": "std::unique_ptr<StringBuffer>",
+ "to_string_out": "StringBufferImpl::adopt(%s)",
+ "export_macro": "V8_EXPORT"
+ },
+ "lib": {
+ "package": "src/node/inspector/protocol",
+ "output": "node/inspector/protocol",
+ "string_header": "inspector/node_string.h"
+ }
+}
diff --git a/src/inspector/node_string.cc b/src/inspector/node_string.cc
new file mode 100644
index 0000000000..cb9e90c20e
--- /dev/null
+++ b/src/inspector/node_string.cc
@@ -0,0 +1,92 @@
+#include "node_string.h"
+#include "node/inspector/protocol/Protocol.h"
+
+#include <unicode/unistr.h>
+
+namespace node {
+namespace inspector {
+namespace protocol {
+namespace StringUtil {
+
+size_t kNotFound = std::string::npos;
+
+// NOLINTNEXTLINE(runtime/references) V8 API requirement
+void builderAppendQuotedString(StringBuilder& builder, const String& string) {
+ builder.put('"');
+ if (!string.empty()) {
+ icu::UnicodeString utf16 = icu::UnicodeString::fromUTF8(
+ icu::StringPiece(string.data(), string.length()));
+ escapeWideStringForJSON(
+ reinterpret_cast<const uint16_t*>(utf16.getBuffer()), utf16.length(),
+ &builder);
+ }
+ builder.put('"');
+}
+
+std::unique_ptr<Value> parseJSON(const String& string) {
+ if (string.empty())
+ return nullptr;
+
+ icu::UnicodeString utf16 =
+ icu::UnicodeString::fromUTF8(icu::StringPiece(string.data(),
+ string.length()));
+ return parseJSONCharacters(
+ reinterpret_cast<const uint16_t*>(utf16.getBuffer()), utf16.length());
+}
+
+std::unique_ptr<Value> parseJSON(v8_inspector::StringView string) {
+ if (string.length() == 0)
+ return nullptr;
+ if (string.is8Bit())
+ return parseJSONCharacters(string.characters8(), string.length());
+ return parseJSONCharacters(string.characters16(), string.length());
+}
+
+String StringViewToUtf8(v8_inspector::StringView view) {
+ if (view.length() == 0)
+ return "";
+ if (view.is8Bit()) {
+ return std::string(reinterpret_cast<const char*>(view.characters8()),
+ view.length());
+ }
+ const uint16_t* source = view.characters16();
+ const UChar* unicodeSource = reinterpret_cast<const UChar*>(source);
+ static_assert(sizeof(*source) == sizeof(*unicodeSource),
+ "sizeof(*source) == sizeof(*unicodeSource)");
+
+ size_t result_length = view.length() * sizeof(*source);
+ std::string result(result_length, '\0');
+ icu::UnicodeString utf16(unicodeSource, view.length());
+ // ICU components for std::string compatibility are not enabled in build...
+ bool done = false;
+ while (!done) {
+ icu::CheckedArrayByteSink sink(&result[0], result_length);
+ utf16.toUTF8(sink);
+ result_length = sink.NumberOfBytesAppended();
+ result.resize(result_length);
+ done = !sink.Overflowed();
+ }
+ return result;
+}
+
+String fromDouble(double d) {
+ std::ostringstream stream;
+ stream.imbue(std::locale("C")); // Ignore locale
+ stream << d;
+ return stream.str();
+}
+
+double toDouble(const char* buffer, size_t length, bool* ok) {
+ std::istringstream stream(std::string(buffer, length));
+ stream.imbue(std::locale("C")); // Ignore locale
+ double d;
+ stream >> d;
+ *ok = !stream.fail();
+ return d;
+}
+
+} // namespace StringUtil
+} // namespace protocol
+} // namespace inspector
+} // namespace node
+
diff --git a/src/inspector/node_string.h b/src/inspector/node_string.h
new file mode 100644
index 0000000000..468aec96b5
--- /dev/null
+++ b/src/inspector/node_string.h
@@ -0,0 +1,79 @@
+// Bridges V8 Inspector generated code with the std::string used by the Node
+// Compare to V8 counterpart - deps/v8/src/inspector/string-util.h
+#ifndef SRC_INSPECTOR_NODE_STRING_H_
+#define SRC_INSPECTOR_NODE_STRING_H_
+
+#include "util.h"
+#include "v8-inspector.h"
+
+#include <cstring>
+#include <sstream>
+#include <string>
+
+namespace node {
+namespace inspector {
+namespace protocol {
+
+class Value;
+
+using String = std::string;
+using StringBuilder = std::ostringstream;
+
+namespace StringUtil {
+// NOLINTNEXTLINE(runtime/references) This is V8 API...
+inline void builderAppend(StringBuilder& builder, char c) {
+ builder.put(c);
+}
+
+// NOLINTNEXTLINE(runtime/references)
+inline void builderAppend(StringBuilder& builder, const char* value,
+ size_t length) {
+ builder.write(value, length);
+}
+
+// NOLINTNEXTLINE(runtime/references)
+inline void builderAppend(StringBuilder& builder, const char* value) {
+ builderAppend(builder, value, std::strlen(value));
+}
+
+// NOLINTNEXTLINE(runtime/references)
+inline void builderAppend(StringBuilder& builder, const String& string) {
+ builder << string;
+}
+
+// NOLINTNEXTLINE(runtime/references)
+inline void builderReserve(StringBuilder& builder, size_t) {
+ // ostringstream does not have a counterpart
+}
+inline String substring(const String& string, size_t start, size_t count) {
+ return string.substr(start, count);
+}
+inline String fromInteger(int n) {
+ return std::to_string(n);
+}
+inline String builderToString(const StringBuilder& builder) {
+ return builder.str();
+}
+inline size_t find(const String& string, const char* substring) {
+ return string.find(substring);
+}
+String fromDouble(double d);
+double toDouble(const char* buffer, size_t length, bool* ok);
+
+String StringViewToUtf8(v8_inspector::StringView view);
+
+// NOLINTNEXTLINE(runtime/references)
+void builderAppendQuotedString(StringBuilder& builder, const String&);
+std::unique_ptr<Value> parseJSON(const String&);
+std::unique_ptr<Value> parseJSON(v8_inspector::StringView view);
+
+extern size_t kNotFound;
+} // namespace StringUtil
+} // namespace protocol
+} // namespace inspector
+} // namespace node
+
+#define DCHECK CHECK
+#define DCHECK_LT CHECK_LT
+
+#endif // SRC_INSPECTOR_NODE_STRING_H_
diff --git a/src/inspector/tracing_agent.cc b/src/inspector/tracing_agent.cc
new file mode 100644
index 0000000000..2d98fa1ee5
--- /dev/null
+++ b/src/inspector/tracing_agent.cc
@@ -0,0 +1,106 @@
+#include "tracing_agent.h"
+
+#include "env-inl.h"
+#include "v8.h"
+
+#include <set>
+#include <sstream>
+
+namespace node {
+namespace inspector {
+namespace protocol {
+
+namespace {
+using v8::platform::tracing::TraceWriter;
+
+class InspectorTraceWriter : public node::tracing::AsyncTraceWriter {
+ public:
+ explicit InspectorTraceWriter(NodeTracing::Frontend* frontend)
+ : frontend_(frontend) {}
+
+ void AppendTraceEvent(
+ v8::platform::tracing::TraceObject* trace_event) override {
+ if (!json_writer_)
+ json_writer_.reset(TraceWriter::CreateJSONTraceWriter(stream_, "value"));
+ json_writer_->AppendTraceEvent(trace_event);
+ }
+
+ void Flush(bool) override {
+ if (!json_writer_)
+ return;
+ json_writer_.reset();
+ std::ostringstream result(
+ "{\"method\":\"NodeTracing.dataCollected\",\"data\":",
+ std::ostringstream::ate);
+ result << stream_.str();
+ result << "}";
+ frontend_->sendRawNotification(result.str());
+ stream_.str("");
+ }
+
+ private:
+ std::unique_ptr<TraceWriter> json_writer_;
+ std::ostringstream stream_;
+ NodeTracing::Frontend* frontend_;
+};
+} // namespace
+
+TracingAgent::TracingAgent(Environment* env)
+ : env_(env),
+ trace_writer_(
+ tracing::Agent::EmptyClientHandle()) {
+}
+
+TracingAgent::~TracingAgent() {
+ trace_writer_.reset();
+}
+
+void TracingAgent::Wire(UberDispatcher* dispatcher) {
+ frontend_ = std::make_unique<NodeTracing::Frontend>(dispatcher->channel());
+ NodeTracing::Dispatcher::wire(dispatcher, this);
+}
+
+DispatchResponse TracingAgent::start(
+ std::unique_ptr<protocol::NodeTracing::TraceConfig> traceConfig) {
+ if (trace_writer_ != nullptr) {
+ return DispatchResponse::Error(
+ "Call NodeTracing::end to stop tracing before updating the config");
+ }
+
+ std::set<std::string> categories_set;
+ protocol::Array<std::string>* categories =
+ traceConfig->getIncludedCategories();
+ for (size_t i = 0; i < categories->length(); i++)
+ categories_set.insert(categories->get(i));
+
+ if (categories_set.empty())
+ return DispatchResponse::Error("At least one category should be enabled");
+
+ trace_writer_ = env_->tracing_agent()->AddClient(
+ categories_set, std::make_unique<InspectorTraceWriter>(frontend_.get()));
+ return DispatchResponse::OK();
+}
+
+DispatchResponse TracingAgent::stop() {
+ trace_writer_.reset();
+ frontend_->tracingComplete();
+ return DispatchResponse::OK();
+}
+
+DispatchResponse TracingAgent::getCategories(
+ std::unique_ptr<protocol::Array<String>>* categories) {
+ *categories = Array<String>::create();
+ categories->get()->addItem("node");
+ categories->get()->addItem("node.async");
+ categories->get()->addItem("node.bootstrap");
+ categories->get()->addItem("node.fs.sync");
+ categories->get()->addItem("node.perf");
+ categories->get()->addItem("node.perf.usertiming");
+ categories->get()->addItem("node.perf.timerify");
+ categories->get()->addItem("v8");
+ return DispatchResponse::OK();
+}
+
+} // namespace protocol
+} // namespace inspector
+} // namespace node
diff --git a/src/inspector/tracing_agent.h b/src/inspector/tracing_agent.h
new file mode 100644
index 0000000000..478107c5ac
--- /dev/null
+++ b/src/inspector/tracing_agent.h
@@ -0,0 +1,45 @@
+#ifndef SRC_INSPECTOR_TRACING_AGENT_H_
+#define SRC_INSPECTOR_TRACING_AGENT_H_
+
+#include "node/inspector/protocol/NodeTracing.h"
+#include "v8.h"
+
+
+namespace node {
+class Environment;
+
+namespace tracing {
+class Agent;
+} // namespace tracing
+
+namespace inspector {
+namespace protocol {
+
+class TracingAgent : public NodeTracing::Backend {
+ public:
+ explicit TracingAgent(Environment*);
+ ~TracingAgent() override;
+
+ void Wire(UberDispatcher* dispatcher);
+
+ DispatchResponse start(
+ std::unique_ptr<protocol::NodeTracing::TraceConfig> traceConfig) override;
+ DispatchResponse stop() override;
+ DispatchResponse getCategories(
+ std::unique_ptr<protocol::Array<String>>* categories) override;
+
+ private:
+ void DisconnectTraceClient();
+
+ Environment* env_;
+ std::unique_ptr<std::pair<tracing::Agent*, int>,
+ void (*)(std::pair<tracing::Agent*, int>*)> trace_writer_;
+ std::unique_ptr<NodeTracing::Frontend> frontend_;
+};
+
+
+} // namespace protocol
+} // namespace inspector
+} // namespace node
+
+#endif // SRC_INSPECTOR_TRACING_AGENT_H_
diff --git a/src/inspector_agent.cc b/src/inspector_agent.cc
index c8e62f6f0a..50aa23b63c 100644
--- a/src/inspector_agent.cc
+++ b/src/inspector_agent.cc
@@ -1,6 +1,9 @@
#include "inspector_agent.h"
#include "inspector_io.h"
+#include "inspector/node_string.h"
+#include "inspector/tracing_agent.h"
+#include "node/inspector/protocol/Protocol.h"
#include "node_internals.h"
#include "v8-inspector.h"
#include "v8-platform.h"
@@ -187,18 +190,35 @@ static int StartDebugSignalHandler() {
const int NANOS_PER_MSEC = 1000000;
const int CONTEXT_GROUP_ID = 1;
-class ChannelImpl final : public v8_inspector::V8Inspector::Channel {
+class ChannelImpl final : public v8_inspector::V8Inspector::Channel,
+ public protocol::FrontendChannel {
public:
- explicit ChannelImpl(const std::unique_ptr<V8Inspector>& inspector,
+ explicit ChannelImpl(Environment* env,
+ const std::unique_ptr<V8Inspector>& inspector,
std::unique_ptr<InspectorSessionDelegate> delegate)
: delegate_(std::move(delegate)) {
session_ = inspector->connect(1, this, StringView());
+ node_dispatcher_ = std::make_unique<protocol::UberDispatcher>(this);
+ tracing_agent_ = std::make_unique<protocol::TracingAgent>(env);
+ tracing_agent_->Wire(node_dispatcher_.get());
}
- virtual ~ChannelImpl() {}
+ virtual ~ChannelImpl() {
+ tracing_agent_->disable();
+ tracing_agent_.reset(); // Dispose before the dispatchers
+ }
void dispatchProtocolMessage(const StringView& message) {
- session_->dispatchProtocolMessage(message);
+ std::unique_ptr<protocol::DictionaryValue> parsed;
+ std::string method;
+ node_dispatcher_->getCommandName(
+ protocol::StringUtil::StringViewToUtf8(message), &method, &parsed);
+ if (v8_inspector::V8InspectorSession::canDispatchMethod(
+ Utf8ToStringView(method)->string())) {
+ session_->dispatchProtocolMessage(message);
+ } else {
+ node_dispatcher_->dispatch(std::move(parsed));
+ }
}
void schedulePauseOnNextStatement(const std::string& reason) {
@@ -224,8 +244,25 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel {
delegate_->SendMessageToFrontend(message);
}
+ void sendMessageToFrontend(const std::string& message) {
+ sendMessageToFrontend(Utf8ToStringView(message)->string());
+ }
+
+ using Serializable = protocol::Serializable;
+
+ void sendProtocolResponse(int callId,
+ std::unique_ptr<Serializable> message) override {
+ sendMessageToFrontend(message->serialize());
+ }
+ void sendProtocolNotification(
+ std::unique_ptr<Serializable> message) override {
+ sendMessageToFrontend(message->serialize());
+ }
+
+ std::unique_ptr<protocol::TracingAgent> tracing_agent_;
std::unique_ptr<InspectorSessionDelegate> delegate_;
std::unique_ptr<v8_inspector::V8InspectorSession> session_;
+ std::unique_ptr<protocol::UberDispatcher> node_dispatcher_;
};
class InspectorTimer {
@@ -369,7 +406,8 @@ class NodeInspectorClient : public V8InspectorClient {
int session_id = next_session_id_++;
// TODO(addaleax): Revert back to using make_unique once we get issues
// with CI resolved (i.e. revert the patch that added this comment).
- channels_[session_id].reset(new ChannelImpl(client_, std::move(delegate)));
+ channels_[session_id].reset(
+ new ChannelImpl(env_, client_, std::move(delegate)));
return session_id;
}
@@ -638,7 +676,8 @@ void Agent::DisableAsyncHook() {
}
}
-void Agent::ToggleAsyncHook(Isolate* isolate, const Persistent<Function>& fn) {
+void Agent::ToggleAsyncHook(Isolate* isolate,
+ const node::Persistent<Function>& fn) {
HandleScope handle_scope(isolate);
CHECK(!fn.IsEmpty());
auto context = parent_env_->context();
diff --git a/src/inspector_agent.h b/src/inspector_agent.h
index 64e4202ee8..21cf4c9f12 100644
--- a/src/inspector_agent.h
+++ b/src/inspector_agent.h
@@ -10,7 +10,7 @@
#endif
#include "node_debug_options.h"
-#include "node_platform.h"
+#include "node_persistent.h"
#include "v8.h"
namespace v8_inspector {
@@ -20,6 +20,7 @@ class StringView;
namespace node {
// Forward declaration to break recursive dependency chain with src/env.h.
class Environment;
+class NodePlatform;
struct ContextInfo;
namespace inspector {
@@ -102,7 +103,7 @@ class Agent {
private:
void ToggleAsyncHook(v8::Isolate* isolate,
- const Persistent<v8::Function>& fn);
+ const node::Persistent<v8::Function>& fn);
node::Environment* parent_env_;
std::shared_ptr<NodeInspectorClient> client_;
@@ -113,8 +114,8 @@ class Agent {
bool pending_enable_async_hook_;
bool pending_disable_async_hook_;
- Persistent<v8::Function> enable_async_hook_function_;
- Persistent<v8::Function> disable_async_hook_function_;
+ node::Persistent<v8::Function> enable_async_hook_function_;
+ node::Persistent<v8::Function> disable_async_hook_function_;
};
} // namespace inspector
diff --git a/src/inspector_io.cc b/src/inspector_io.cc
index e6ceaf5b73..ce18e98973 100644
--- a/src/inspector_io.cc
+++ b/src/inspector_io.cc
@@ -1,6 +1,7 @@
#include "inspector_io.h"
#include "inspector_socket_server.h"
+#include "inspector/node_string.h"
#include "env-inl.h"
#include "node.h"
#include "node_crypto.h"
@@ -62,31 +63,6 @@ std::string GenerateID() {
return uuid;
}
-std::string StringViewToUtf8(const StringView& view) {
- if (view.is8Bit()) {
- return std::string(reinterpret_cast<const char*>(view.characters8()),
- view.length());
- }
- const uint16_t* source = view.characters16();
- const UChar* unicodeSource = reinterpret_cast<const UChar*>(source);
- static_assert(sizeof(*source) == sizeof(*unicodeSource),
- "sizeof(*source) == sizeof(*unicodeSource)");
-
- size_t result_length = view.length() * sizeof(*source);
- std::string result(result_length, '\0');
- icu::UnicodeString utf16(unicodeSource, view.length());
- // ICU components for std::string compatibility are not enabled in build...
- bool done = false;
- while (!done) {
- icu::CheckedArrayByteSink sink(&result[0], result_length);
- utf16.toUTF8(sink);
- result_length = sink.NumberOfBytesAppended();
- result.resize(result_length);
- done = !sink.Overflowed();
- }
- return result;
-}
-
void HandleSyncCloseCb(uv_handle_t* handle) {
*static_cast<bool*>(handle->data) = true;
}
@@ -272,7 +248,8 @@ void InspectorIo::IoThreadAsyncCb(uv_async_t* async) {
break;
case TransportAction::kSendMessage:
transport->Send(session_id,
- StringViewToUtf8(std::get<2>(outgoing)->string()));
+ protocol::StringUtil::StringViewToUtf8(
+ std::get<2>(outgoing)->string()));
break;
case TransportAction::kAcceptSession:
transport->AcceptSession(session_id);
diff --git a/src/inspector_io.h b/src/inspector_io.h
index 276c78056c..05de2d17cd 100644
--- a/src/inspector_io.h
+++ b/src/inspector_io.h
@@ -7,7 +7,7 @@
#include "uv.h"
#include <deque>
-#include <map>
+#include <unordered_map>
#include <memory>
#include <stddef.h>
diff --git a/src/tracing/agent.cc b/src/tracing/agent.cc
index cd4c3c0df9..a3ddfb61a9 100644
--- a/src/tracing/agent.cc
+++ b/src/tracing/agent.cc
@@ -8,11 +8,43 @@
namespace node {
namespace tracing {
+namespace {
+
+class ScopedSuspendTracing {
+ public:
+ ScopedSuspendTracing(TracingController* controller, Agent* agent)
+ : controller_(controller), agent_(agent) {
+ controller->StopTracing();
+ }
+
+ ~ScopedSuspendTracing() {
+ TraceConfig* config = agent_->CreateTraceConfig();
+ if (config != nullptr) {
+ controller_->StartTracing(config);
+ }
+ }
+
+ private:
+ TracingController* controller_;
+ Agent* agent_;
+};
+
+std::set<std::string> flatten(
+ const std::unordered_map<int, std::set<std::string>>& map) {
+ std::set<std::string> result;
+ for (const auto& id_value : map)
+ result.insert(id_value.second.begin(), id_value.second.end());
+ return result;
+}
+
+} // namespace
+
using v8::platform::tracing::TraceConfig;
+using v8::platform::tracing::TraceWriter;
using std::string;
Agent::Agent(const std::string& log_file_pattern)
- : log_file_pattern_(log_file_pattern) {
+ : log_file_pattern_(log_file_pattern), file_writer_(EmptyClientHandle()) {
tracing_controller_ = new TracingController();
tracing_controller_->Initialize(nullptr);
}
@@ -23,11 +55,9 @@ void Agent::Start() {
CHECK_EQ(uv_loop_init(&tracing_loop_), 0);
- NodeTraceWriter* trace_writer =
- new NodeTraceWriter(log_file_pattern_, &tracing_loop_);
- TraceBuffer* trace_buffer = new NodeTraceBuffer(
- NodeTraceBuffer::kBufferChunks, trace_writer, &tracing_loop_);
- tracing_controller_->Initialize(trace_buffer);
+ NodeTraceBuffer* trace_buffer_ = new NodeTraceBuffer(
+ NodeTraceBuffer::kBufferChunks, this, &tracing_loop_);
+ tracing_controller_->Initialize(trace_buffer_);
// This thread should be created *after* async handles are created
// (within NodeTraceWriter and NodeTraceBuffer constructors).
@@ -36,7 +66,23 @@ void Agent::Start() {
started_ = true;
}
+Agent::ClientHandle Agent::AddClient(const std::set<std::string>& categories,
+ std::unique_ptr<AsyncTraceWriter> writer) {
+ Start();
+ ScopedSuspendTracing suspend(tracing_controller_, this);
+ int id = next_writer_id_++;
+ writers_[id] = std::move(writer);
+ categories_[id] = categories;
+
+ auto client_id = new std::pair<Agent*, int>(this, id);
+ return ClientHandle(client_id, &DisconnectClient);
+}
+
void Agent::Stop() {
+ file_writer_.reset();
+}
+
+void Agent::StopTracing() {
if (!started_)
return;
// Perform final Flush on TraceBuffer. We don't want the tracing controller
@@ -49,6 +95,12 @@ void Agent::Stop() {
uv_thread_join(&thread_);
}
+void Agent::Disconnect(int client) {
+ ScopedSuspendTracing suspend(tracing_controller_, this);
+ writers_.erase(client);
+ categories_.erase(client);
+}
+
// static
void Agent::ThreadCb(void* arg) {
Agent* agent = static_cast<Agent*>(arg);
@@ -56,72 +108,81 @@ void Agent::ThreadCb(void* arg) {
}
void Agent::Enable(const std::string& categories) {
- if (!categories.empty()) {
- std::stringstream category_list(categories);
- while (category_list.good()) {
- std::string category;
- getline(category_list, category, ',');
- categories_.insert(category.c_str());
- }
- RestartTracing();
+ if (categories.empty())
+ return;
+ std::set<std::string> categories_set;
+ std::stringstream category_list(categories);
+ while (category_list.good()) {
+ std::string category;
+ getline(category_list, category, ',');
+ categories_set.insert(category);
}
+ Enable(categories_set);
}
void Agent::Enable(const std::set<std::string>& categories) {
- if (!categories.empty()) {
- categories_.insert(categories.begin(), categories.end());
- RestartTracing();
- }
-}
+ std::string cats;
+ for (const std::string cat : categories)
+ cats += cat + ", ";
+ if (categories.empty())
+ return;
-void Agent::Disable(const std::set<std::string>& categories) {
- if (!categories.empty()) {
- for (auto category : categories) {
- auto pos = categories_.lower_bound(category);
- if (pos != categories_.end())
- categories_.erase(pos);
- }
- RestartTracing();
+ file_writer_categories_.insert(categories.begin(), categories.end());
+ std::set<std::string> full_list(file_writer_categories_.begin(),
+ file_writer_categories_.end());
+ if (!file_writer_) {
+ // Ensure background thread is running
+ Start();
+ std::unique_ptr<NodeTraceWriter> writer(
+ new NodeTraceWriter(log_file_pattern_, &tracing_loop_));
+ file_writer_ = AddClient(full_list, std::move(writer));
+ } else {
+ ScopedSuspendTracing suspend(tracing_controller_, this);
+ categories_[file_writer_->second] = full_list;
}
}
-void Agent::RestartTracing() {
- static bool warned;
- if (!warned) {
- warned = true;
- fprintf(stderr, "Warning: Trace event is an experimental feature "
- "and could change at any time.\n");
+void Agent::Disable(const std::set<std::string>& categories) {
+ for (auto category : categories) {
+ auto it = file_writer_categories_.find(category);
+ if (it != file_writer_categories_.end())
+ file_writer_categories_.erase(it);
}
- Start(); // Start the agent if it hasn't already been started
- tracing_controller_->StopTracing();
- auto config = CreateTraceConfig();
- if (config != nullptr)
- tracing_controller_->StartTracing(config);
+ if (!file_writer_)
+ return;
+ ScopedSuspendTracing suspend(tracing_controller_, this);
+ categories_[file_writer_->second] = { file_writer_categories_.begin(),
+ file_writer_categories_.end() };
}
TraceConfig* Agent::CreateTraceConfig() {
if (categories_.empty())
return nullptr;
TraceConfig* trace_config = new TraceConfig();
- for (auto category = categories_.begin();
- category != categories_.end();
- category = categories_.upper_bound(*category)) {
- trace_config->AddIncludedCategory(category->c_str());
+ for (const auto& category : flatten(categories_)) {
+ trace_config->AddIncludedCategory(category.c_str());
}
return trace_config;
}
std::string Agent::GetEnabledCategories() {
std::string categories;
- for (auto category = categories_.begin();
- category != categories_.end();
- category = categories_.upper_bound(*category)) {
+ for (const auto& category : flatten(categories_)) {
if (!categories.empty())
categories += ',';
- categories += *category;
+ categories += category;
}
return categories;
}
+void Agent::AppendTraceEvent(TraceObject* trace_event) {
+ for (const auto& id_writer : writers_)
+ id_writer.second->AppendTraceEvent(trace_event);
+}
+
+void Agent::Flush(bool blocking) {
+ for (const auto& id_writer : writers_)
+ id_writer.second->Flush(blocking);
+}
} // namespace tracing
} // namespace node
diff --git a/src/tracing/agent.h b/src/tracing/agent.h
index ca5fd35417..fd79842759 100644
--- a/src/tracing/agent.h
+++ b/src/tracing/agent.h
@@ -7,11 +7,20 @@
#include <set>
#include <string>
+#include <unordered_map>
namespace node {
namespace tracing {
using v8::platform::tracing::TraceConfig;
+using v8::platform::tracing::TraceObject;
+
+class AsyncTraceWriter {
+ public:
+ virtual ~AsyncTraceWriter() {}
+ virtual void AppendTraceEvent(TraceObject* trace_event) = 0;
+ virtual void Flush(bool blocking) = 0;
+};
class TracingController : public v8::platform::tracing::TracingController {
public:
@@ -22,33 +31,58 @@ class TracingController : public v8::platform::tracing::TracingController {
}
};
+
class Agent {
public:
+ // Resetting the pointer disconnects client
+ using ClientHandle = std::unique_ptr<std::pair<Agent*, int>,
+ void (*)(std::pair<Agent*, int>*)>;
+
+ static ClientHandle EmptyClientHandle() {
+ return ClientHandle(nullptr, DisconnectClient);
+ }
explicit Agent(const std::string& log_file_pattern);
void Stop();
TracingController* GetTracingController() { return tracing_controller_; }
+ // Destroying the handle disconnects the client
+ ClientHandle AddClient(const std::set<std::string>& categories,
+ std::unique_ptr<AsyncTraceWriter> writer);
+
+ // These 3 methods operate on a "default" client, e.g. the file writer
void Enable(const std::string& categories);
void Enable(const std::set<std::string>& categories);
void Disable(const std::set<std::string>& categories);
std::string GetEnabledCategories();
+ void AppendTraceEvent(TraceObject* trace_event);
+ void Flush(bool blocking);
+
+ TraceConfig* CreateTraceConfig();
+
private:
static void ThreadCb(void* arg);
+ static void DisconnectClient(std::pair<Agent*, int>* id_agent) {
+ id_agent->first->Disconnect(id_agent->second);
+ delete id_agent;
+ }
void Start();
- void RestartTracing();
-
- TraceConfig* CreateTraceConfig();
+ void StopTracing();
+ void Disconnect(int client);
const std::string& log_file_pattern_;
uv_thread_t thread_;
uv_loop_t tracing_loop_;
bool started_ = false;
- std::multiset<std::string> categories_;
+ std::unordered_map<int, std::set<std::string>> categories_;
TracingController* tracing_controller_ = nullptr;
+ ClientHandle file_writer_;
+ int next_writer_id_ = 1;
+ std::unordered_map<int, std::unique_ptr<AsyncTraceWriter>> writers_;
+ std::multiset<std::string> file_writer_categories_;
};
} // namespace tracing
diff --git a/src/tracing/node_trace_buffer.cc b/src/tracing/node_trace_buffer.cc
index 4c9f7c658f..1765b88df1 100644
--- a/src/tracing/node_trace_buffer.cc
+++ b/src/tracing/node_trace_buffer.cc
@@ -4,9 +4,9 @@ namespace node {
namespace tracing {
InternalTraceBuffer::InternalTraceBuffer(size_t max_chunks, uint32_t id,
- NodeTraceWriter* trace_writer)
+ Agent* agent)
: flushing_(false), max_chunks_(max_chunks),
- trace_writer_(trace_writer), id_(id) {
+ agent_(agent), id_(id) {
chunks_.resize(max_chunks);
}
@@ -59,14 +59,14 @@ void InternalTraceBuffer::Flush(bool blocking) {
for (size_t i = 0; i < total_chunks_; ++i) {
auto& chunk = chunks_[i];
for (size_t j = 0; j < chunk->size(); ++j) {
- trace_writer_->AppendTraceEvent(chunk->GetEventAt(j));
+ agent_->AppendTraceEvent(chunk->GetEventAt(j));
}
}
total_chunks_ = 0;
flushing_ = false;
}
}
- trace_writer_->Flush(blocking);
+ agent_->Flush(blocking);
}
uint64_t InternalTraceBuffer::MakeHandle(
@@ -87,10 +87,10 @@ void InternalTraceBuffer::ExtractHandle(
}
NodeTraceBuffer::NodeTraceBuffer(size_t max_chunks,
- NodeTraceWriter* trace_writer, uv_loop_t* tracing_loop)
- : tracing_loop_(tracing_loop), trace_writer_(trace_writer),
- buffer1_(max_chunks, 0, trace_writer),
- buffer2_(max_chunks, 1, trace_writer) {
+ Agent* agent, uv_loop_t* tracing_loop)
+ : tracing_loop_(tracing_loop), agent_(agent),
+ buffer1_(max_chunks, 0, agent),
+ buffer2_(max_chunks, 1, agent) {
current_buf_.store(&buffer1_);
flush_signal_.data = this;
diff --git a/src/tracing/node_trace_buffer.h b/src/tracing/node_trace_buffer.h
index 4d725ce31f..f08e6a69da 100644
--- a/src/tracing/node_trace_buffer.h
+++ b/src/tracing/node_trace_buffer.h
@@ -1,8 +1,8 @@
#ifndef SRC_TRACING_NODE_TRACE_BUFFER_H_
#define SRC_TRACING_NODE_TRACE_BUFFER_H_
+#include "tracing/agent.h"
#include "node_mutex.h"
-#include "tracing/node_trace_writer.h"
#include "libplatform/v8-tracing.h"
#include <atomic>
@@ -19,8 +19,7 @@ class NodeTraceBuffer;
class InternalTraceBuffer {
public:
- InternalTraceBuffer(size_t max_chunks, uint32_t id,
- NodeTraceWriter* trace_writer);
+ InternalTraceBuffer(size_t max_chunks, uint32_t id, Agent* agent);
TraceObject* AddTraceEvent(uint64_t* handle);
TraceObject* GetEventByHandle(uint64_t handle);
@@ -42,7 +41,7 @@ class InternalTraceBuffer {
Mutex mutex_;
bool flushing_;
size_t max_chunks_;
- NodeTraceWriter* trace_writer_;
+ Agent* agent_;
std::vector<std::unique_ptr<TraceBufferChunk>> chunks_;
size_t total_chunks_ = 0;
uint32_t current_chunk_seq_ = 1;
@@ -51,8 +50,7 @@ class InternalTraceBuffer {
class NodeTraceBuffer : public TraceBuffer {
public:
- NodeTraceBuffer(size_t max_chunks, NodeTraceWriter* trace_writer,
- uv_loop_t* tracing_loop);
+ NodeTraceBuffer(size_t max_chunks, Agent* agent, uv_loop_t* tracing_loop);
~NodeTraceBuffer();
TraceObject* AddTraceEvent(uint64_t* handle) override;
@@ -74,7 +72,7 @@ class NodeTraceBuffer : public TraceBuffer {
Mutex exit_mutex_;
// Used to wait until async handles have been closed.
ConditionVariable exit_cond_;
- std::unique_ptr<NodeTraceWriter> trace_writer_;
+ Agent* agent_;
std::atomic<InternalTraceBuffer*> current_buf_;
InternalTraceBuffer buffer1_;
InternalTraceBuffer buffer2_;
diff --git a/src/tracing/node_trace_writer.cc b/src/tracing/node_trace_writer.cc
index 15c59fc98a..88ff3e6e7c 100644
--- a/src/tracing/node_trace_writer.cc
+++ b/src/tracing/node_trace_writer.cc
@@ -126,12 +126,6 @@ void NodeTraceWriter::FlushSignalCb(uv_async_t* signal) {
trace_writer->FlushPrivate();
}
-// TODO(matthewloring): Remove (is it necessary to change the API?
-// Since because of WriteSuffix it no longer matters whether it's true or false)
-void NodeTraceWriter::Flush() {
- Flush(true);
-}
-
void NodeTraceWriter::Flush(bool blocking) {
Mutex::ScopedLock scoped_lock(request_mutex_);
if (!json_trace_writer_) {
diff --git a/src/tracing/node_trace_writer.h b/src/tracing/node_trace_writer.h
index 9211790777..b2d5e7912f 100644
--- a/src/tracing/node_trace_writer.h
+++ b/src/tracing/node_trace_writer.h
@@ -6,6 +6,7 @@
#include "node_mutex.h"
#include "libplatform/v8-tracing.h"
+#include "tracing/agent.h"
#include "uv.h"
namespace node {
@@ -14,15 +15,14 @@ namespace tracing {
using v8::platform::tracing::TraceObject;
using v8::platform::tracing::TraceWriter;
-class NodeTraceWriter : public TraceWriter {
+class NodeTraceWriter : public AsyncTraceWriter {
public:
explicit NodeTraceWriter(const std::string& log_file_pattern,
uv_loop_t* tracing_loop);
~NodeTraceWriter();
void AppendTraceEvent(TraceObject* trace_event) override;
- void Flush() override;
- void Flush(bool blocking);
+ void Flush(bool blocking) override;
static const int kTracesPerFile = 1 << 19;
diff --git a/test/parallel/test-inspector-multisession-js.js b/test/parallel/test-inspector-multisession-js.js
index 58533f4cd6..c899eeae71 100644
--- a/test/parallel/test-inspector-multisession-js.js
+++ b/test/parallel/test-inspector-multisession-js.js
@@ -56,4 +56,8 @@ async function test() {
common.crashOnUnhandledRejection();
-test();
+const interval = setInterval(() => {}, 1000);
+test().then(() => {
+ clearInterval(interval);
+ console.log('Done!');
+});
diff --git a/test/parallel/test-inspector-tracing-domain.js b/test/parallel/test-inspector-tracing-domain.js
new file mode 100644
index 0000000000..61a853a265
--- /dev/null
+++ b/test/parallel/test-inspector-tracing-domain.js
@@ -0,0 +1,70 @@
+'use strict';
+
+const common = require('../common');
+
+common.skipIfInspectorDisabled();
+
+const assert = require('assert');
+const { Session } = require('inspector');
+
+const session = new Session();
+
+function compareIgnoringOrder(array1, array2) {
+ const set = new Set(array1);
+ const test = set.size === array2.length && array2.every((el) => set.has(el));
+ assert.ok(test, `[${array1}] differs from [${array2}]`);
+}
+
+function post(message, data) {
+ return new Promise((resolve, reject) => {
+ session.post(message, data, (err, result) => {
+ if (err)
+ reject(new Error(JSON.stringify(err)));
+ else
+ resolve(result);
+ });
+ });
+}
+
+function generateTrace() {
+ return new Promise((resolve) => setTimeout(() => {
+ for (let i = 0; i << 1000000; i++) {
+ 'test' + i;
+ }
+ resolve();
+ }, 1));
+}
+
+async function test() {
+ // This interval ensures Node does not terminate till the test is finished.
+ // Inspector session does not keep the node process running (e.g. it does not
+ // have async handles on the main event loop). It is debatable whether this
+ // should be considered a bug, and there are no plans to fix it atm.
+ const interval = setInterval(() => {}, 5000);
+ session.connect();
+ let traceNotification = null;
+ let tracingComplete = false;
+ session.on('NodeTracing.dataCollected', (n) => traceNotification = n);
+ session.on('NodeTracing.tracingComplete', () => tracingComplete = true);
+ const { categories } = await post('NodeTracing.getCategories');
+ compareIgnoringOrder(['node', 'node.async', 'node.bootstrap', 'node.fs.sync',
+ 'node.perf', 'node.perf.usertiming',
+ 'node.perf.timerify', 'v8'],
+ categories);
+
+ const traceConfig = { includedCategories: ['node'] };
+ await post('NodeTracing.start', { traceConfig });
+
+ for (let i = 0; i < 5; i++)
+ await generateTrace();
+ JSON.stringify(await post('NodeTracing.stop', { traceConfig }));
+ session.disconnect();
+ assert(traceNotification.data.value.length > 0);
+ assert(tracingComplete);
+ clearInterval(interval);
+ console.log('Success');
+}
+
+common.crashOnUnhandledRejection();
+
+test();