inspector: add a "NodeTracing" domain support

This change adds a new inspector domain for receiving Node tracing
data.
  1. Node.js now can extend Inspector protocol with new domains with
     the API defined in the src/inspector/node_protocol.pdl.
  2. Plumbing code will be generated at the build time. /json/protocol
     HTTP endpoint returns both V8 and Node.js inspector protocol.
  3. "NodeTracing" domain was introduced. It is based on the Chrome
     "Tracing" domain.

PR-URL: https://github.com/nodejs/node/pull/20608
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Ali Ijaz Sheikh <ofrobots@google.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
Eugene Ostroukhov 2018-04-27 17:20:37 -07:00
parent 5248401174
commit 47bdc716f8
19 changed files with 845 additions and 162 deletions

213
node.gyp
View File

@ -455,17 +455,24 @@
'src/inspector_js_api.cc', 'src/inspector_js_api.cc',
'src/inspector_socket.cc', 'src/inspector_socket.cc',
'src/inspector_socket_server.cc', 'src/inspector_socket_server.cc',
'src/inspector/tracing_agent.cc',
'src/inspector/node_string.cc',
'src/inspector_agent.h', 'src/inspector_agent.h',
'src/inspector_io.h', 'src/inspector_io.h',
'src/inspector_socket.h', 'src/inspector_socket.h',
'src/inspector_socket_server.h', 'src/inspector_socket_server.h',
'src/inspector/node_string.h',
'src/inspector/tracing_agent.h',
'<@(node_inspector_generated_sources)'
], ],
'dependencies': [ 'dependencies': [
'node_protocol_generated_sources#host',
'v8_inspector_compress_protocol_json#host', 'v8_inspector_compress_protocol_json#host',
], ],
'include_dirs': [ 'include_dirs': [
'<(SHARED_INTERMEDIATE_DIR)/include', # for inspector '<(SHARED_INTERMEDIATE_DIR)/include', # for inspector
'<(SHARED_INTERMEDIATE_DIR)', '<(SHARED_INTERMEDIATE_DIR)',
'<(SHARED_INTERMEDIATE_DIR)/src', # for inspector
], ],
}, { }, {
'defines': [ 'HAVE_INSPECTOR=0' ] 'defines': [ 'HAVE_INSPECTOR=0' ]
@ -677,54 +684,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', 'target_name': 'node_js2c',
'type': 'none', 'type': 'none',
@ -1044,5 +1003,163 @@
}, },
] ]
}], # end aix section }], # 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 ], # end conditions block
} }

View File

@ -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

View File

@ -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"
}
}

View File

@ -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

View File

@ -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_

View File

@ -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

View File

@ -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_

View File

@ -1,6 +1,9 @@
#include "inspector_agent.h" #include "inspector_agent.h"
#include "inspector_io.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 "node_internals.h"
#include "v8-inspector.h" #include "v8-inspector.h"
#include "v8-platform.h" #include "v8-platform.h"
@ -187,18 +190,35 @@ static int StartDebugSignalHandler() {
const int NANOS_PER_MSEC = 1000000; const int NANOS_PER_MSEC = 1000000;
const int CONTEXT_GROUP_ID = 1; 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: public:
explicit ChannelImpl(const std::unique_ptr<V8Inspector>& inspector, explicit ChannelImpl(Environment* env,
const std::unique_ptr<V8Inspector>& inspector,
std::unique_ptr<InspectorSessionDelegate> delegate) std::unique_ptr<InspectorSessionDelegate> delegate)
: delegate_(std::move(delegate)) { : delegate_(std::move(delegate)) {
session_ = inspector->connect(1, this, StringView()); 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) { 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) { void schedulePauseOnNextStatement(const std::string& reason) {
@ -224,8 +244,25 @@ class ChannelImpl final : public v8_inspector::V8Inspector::Channel {
delegate_->SendMessageToFrontend(message); 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<InspectorSessionDelegate> delegate_;
std::unique_ptr<v8_inspector::V8InspectorSession> session_; std::unique_ptr<v8_inspector::V8InspectorSession> session_;
std::unique_ptr<protocol::UberDispatcher> node_dispatcher_;
}; };
class InspectorTimer { class InspectorTimer {
@ -369,7 +406,8 @@ class NodeInspectorClient : public V8InspectorClient {
int session_id = next_session_id_++; int session_id = next_session_id_++;
// TODO(addaleax): Revert back to using make_unique once we get issues // TODO(addaleax): Revert back to using make_unique once we get issues
// with CI resolved (i.e. revert the patch that added this comment). // 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; 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); HandleScope handle_scope(isolate);
CHECK(!fn.IsEmpty()); CHECK(!fn.IsEmpty());
auto context = parent_env_->context(); auto context = parent_env_->context();

View File

@ -10,7 +10,7 @@
#endif #endif
#include "node_debug_options.h" #include "node_debug_options.h"
#include "node_platform.h" #include "node_persistent.h"
#include "v8.h" #include "v8.h"
namespace v8_inspector { namespace v8_inspector {
@ -20,6 +20,7 @@ class StringView;
namespace node { namespace node {
// Forward declaration to break recursive dependency chain with src/env.h. // Forward declaration to break recursive dependency chain with src/env.h.
class Environment; class Environment;
class NodePlatform;
struct ContextInfo; struct ContextInfo;
namespace inspector { namespace inspector {
@ -102,7 +103,7 @@ class Agent {
private: private:
void ToggleAsyncHook(v8::Isolate* isolate, void ToggleAsyncHook(v8::Isolate* isolate,
const Persistent<v8::Function>& fn); const node::Persistent<v8::Function>& fn);
node::Environment* parent_env_; node::Environment* parent_env_;
std::shared_ptr<NodeInspectorClient> client_; std::shared_ptr<NodeInspectorClient> client_;
@ -113,8 +114,8 @@ class Agent {
bool pending_enable_async_hook_; bool pending_enable_async_hook_;
bool pending_disable_async_hook_; bool pending_disable_async_hook_;
Persistent<v8::Function> enable_async_hook_function_; node::Persistent<v8::Function> enable_async_hook_function_;
Persistent<v8::Function> disable_async_hook_function_; node::Persistent<v8::Function> disable_async_hook_function_;
}; };
} // namespace inspector } // namespace inspector

View File

@ -1,6 +1,7 @@
#include "inspector_io.h" #include "inspector_io.h"
#include "inspector_socket_server.h" #include "inspector_socket_server.h"
#include "inspector/node_string.h"
#include "env-inl.h" #include "env-inl.h"
#include "node.h" #include "node.h"
#include "node_crypto.h" #include "node_crypto.h"
@ -62,31 +63,6 @@ std::string GenerateID() {
return uuid; 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) { void HandleSyncCloseCb(uv_handle_t* handle) {
*static_cast<bool*>(handle->data) = true; *static_cast<bool*>(handle->data) = true;
} }
@ -272,7 +248,8 @@ void InspectorIo::IoThreadAsyncCb(uv_async_t* async) {
break; break;
case TransportAction::kSendMessage: case TransportAction::kSendMessage:
transport->Send(session_id, transport->Send(session_id,
StringViewToUtf8(std::get<2>(outgoing)->string())); protocol::StringUtil::StringViewToUtf8(
std::get<2>(outgoing)->string()));
break; break;
case TransportAction::kAcceptSession: case TransportAction::kAcceptSession:
transport->AcceptSession(session_id); transport->AcceptSession(session_id);

View File

@ -7,7 +7,7 @@
#include "uv.h" #include "uv.h"
#include <deque> #include <deque>
#include <map> #include <unordered_map>
#include <memory> #include <memory>
#include <stddef.h> #include <stddef.h>

View File

@ -8,11 +8,43 @@
namespace node { namespace node {
namespace tracing { 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::TraceConfig;
using v8::platform::tracing::TraceWriter;
using std::string; using std::string;
Agent::Agent(const std::string& log_file_pattern) 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_ = new TracingController();
tracing_controller_->Initialize(nullptr); tracing_controller_->Initialize(nullptr);
} }
@ -23,11 +55,9 @@ void Agent::Start() {
CHECK_EQ(uv_loop_init(&tracing_loop_), 0); CHECK_EQ(uv_loop_init(&tracing_loop_), 0);
NodeTraceWriter* trace_writer = NodeTraceBuffer* trace_buffer_ = new NodeTraceBuffer(
new NodeTraceWriter(log_file_pattern_, &tracing_loop_); NodeTraceBuffer::kBufferChunks, this, &tracing_loop_);
TraceBuffer* trace_buffer = new NodeTraceBuffer( tracing_controller_->Initialize(trace_buffer_);
NodeTraceBuffer::kBufferChunks, trace_writer, &tracing_loop_);
tracing_controller_->Initialize(trace_buffer);
// This thread should be created *after* async handles are created // This thread should be created *after* async handles are created
// (within NodeTraceWriter and NodeTraceBuffer constructors). // (within NodeTraceWriter and NodeTraceBuffer constructors).
@ -36,7 +66,23 @@ void Agent::Start() {
started_ = true; 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() { void Agent::Stop() {
file_writer_.reset();
}
void Agent::StopTracing() {
if (!started_) if (!started_)
return; return;
// Perform final Flush on TraceBuffer. We don't want the tracing controller // Perform final Flush on TraceBuffer. We don't want the tracing controller
@ -49,6 +95,12 @@ void Agent::Stop() {
uv_thread_join(&thread_); uv_thread_join(&thread_);
} }
void Agent::Disconnect(int client) {
ScopedSuspendTracing suspend(tracing_controller_, this);
writers_.erase(client);
categories_.erase(client);
}
// static // static
void Agent::ThreadCb(void* arg) { void Agent::ThreadCb(void* arg) {
Agent* agent = static_cast<Agent*>(arg); Agent* agent = static_cast<Agent*>(arg);
@ -56,72 +108,81 @@ void Agent::ThreadCb(void* arg) {
} }
void Agent::Enable(const std::string& categories) { void Agent::Enable(const std::string& categories) {
if (!categories.empty()) { if (categories.empty())
std::stringstream category_list(categories); return;
while (category_list.good()) { std::set<std::string> categories_set;
std::string category; std::stringstream category_list(categories);
getline(category_list, category, ','); while (category_list.good()) {
categories_.insert(category.c_str()); std::string category;
} getline(category_list, category, ',');
RestartTracing(); categories_set.insert(category);
} }
Enable(categories_set);
} }
void Agent::Enable(const std::set<std::string>& categories) { void Agent::Enable(const std::set<std::string>& categories) {
if (!categories.empty()) { std::string cats;
categories_.insert(categories.begin(), categories.end()); for (const std::string cat : categories)
RestartTracing(); cats += cat + ", ";
if (categories.empty())
return;
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::Disable(const std::set<std::string>& categories) { void Agent::Disable(const std::set<std::string>& categories) {
if (!categories.empty()) { for (auto category : categories) {
for (auto category : categories) { auto it = file_writer_categories_.find(category);
auto pos = categories_.lower_bound(category); if (it != file_writer_categories_.end())
if (pos != categories_.end()) file_writer_categories_.erase(it);
categories_.erase(pos);
}
RestartTracing();
} }
} if (!file_writer_)
return;
void Agent::RestartTracing() { ScopedSuspendTracing suspend(tracing_controller_, this);
static bool warned; categories_[file_writer_->second] = { file_writer_categories_.begin(),
if (!warned) { file_writer_categories_.end() };
warned = true;
fprintf(stderr, "Warning: Trace event is an experimental feature "
"and could change at any time.\n");
}
Start(); // Start the agent if it hasn't already been started
tracing_controller_->StopTracing();
auto config = CreateTraceConfig();
if (config != nullptr)
tracing_controller_->StartTracing(config);
} }
TraceConfig* Agent::CreateTraceConfig() { TraceConfig* Agent::CreateTraceConfig() {
if (categories_.empty()) if (categories_.empty())
return nullptr; return nullptr;
TraceConfig* trace_config = new TraceConfig(); TraceConfig* trace_config = new TraceConfig();
for (auto category = categories_.begin(); for (const auto& category : flatten(categories_)) {
category != categories_.end(); trace_config->AddIncludedCategory(category.c_str());
category = categories_.upper_bound(*category)) {
trace_config->AddIncludedCategory(category->c_str());
} }
return trace_config; return trace_config;
} }
std::string Agent::GetEnabledCategories() { std::string Agent::GetEnabledCategories() {
std::string categories; std::string categories;
for (auto category = categories_.begin(); for (const auto& category : flatten(categories_)) {
category != categories_.end();
category = categories_.upper_bound(*category)) {
if (!categories.empty()) if (!categories.empty())
categories += ','; categories += ',';
categories += *category; categories += category;
} }
return categories; 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 tracing
} // namespace node } // namespace node

View File

@ -7,11 +7,20 @@
#include <set> #include <set>
#include <string> #include <string>
#include <unordered_map>
namespace node { namespace node {
namespace tracing { namespace tracing {
using v8::platform::tracing::TraceConfig; 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 { class TracingController : public v8::platform::tracing::TracingController {
public: public:
@ -22,33 +31,58 @@ class TracingController : public v8::platform::tracing::TracingController {
} }
}; };
class Agent { class Agent {
public: 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); explicit Agent(const std::string& log_file_pattern);
void Stop(); void Stop();
TracingController* GetTracingController() { return tracing_controller_; } 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::string& categories);
void Enable(const std::set<std::string>& categories); void Enable(const std::set<std::string>& categories);
void Disable(const std::set<std::string>& categories); void Disable(const std::set<std::string>& categories);
std::string GetEnabledCategories(); std::string GetEnabledCategories();
private: void AppendTraceEvent(TraceObject* trace_event);
static void ThreadCb(void* arg); void Flush(bool blocking);
void Start();
void RestartTracing();
TraceConfig* CreateTraceConfig(); 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 StopTracing();
void Disconnect(int client);
const std::string& log_file_pattern_; const std::string& log_file_pattern_;
uv_thread_t thread_; uv_thread_t thread_;
uv_loop_t tracing_loop_; uv_loop_t tracing_loop_;
bool started_ = false; bool started_ = false;
std::multiset<std::string> categories_; std::unordered_map<int, std::set<std::string>> categories_;
TracingController* tracing_controller_ = nullptr; 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 } // namespace tracing

View File

@ -4,9 +4,9 @@ namespace node {
namespace tracing { namespace tracing {
InternalTraceBuffer::InternalTraceBuffer(size_t max_chunks, uint32_t id, InternalTraceBuffer::InternalTraceBuffer(size_t max_chunks, uint32_t id,
NodeTraceWriter* trace_writer) Agent* agent)
: flushing_(false), max_chunks_(max_chunks), : flushing_(false), max_chunks_(max_chunks),
trace_writer_(trace_writer), id_(id) { agent_(agent), id_(id) {
chunks_.resize(max_chunks); chunks_.resize(max_chunks);
} }
@ -59,14 +59,14 @@ void InternalTraceBuffer::Flush(bool blocking) {
for (size_t i = 0; i < total_chunks_; ++i) { for (size_t i = 0; i < total_chunks_; ++i) {
auto& chunk = chunks_[i]; auto& chunk = chunks_[i];
for (size_t j = 0; j < chunk->size(); ++j) { for (size_t j = 0; j < chunk->size(); ++j) {
trace_writer_->AppendTraceEvent(chunk->GetEventAt(j)); agent_->AppendTraceEvent(chunk->GetEventAt(j));
} }
} }
total_chunks_ = 0; total_chunks_ = 0;
flushing_ = false; flushing_ = false;
} }
} }
trace_writer_->Flush(blocking); agent_->Flush(blocking);
} }
uint64_t InternalTraceBuffer::MakeHandle( uint64_t InternalTraceBuffer::MakeHandle(
@ -87,10 +87,10 @@ void InternalTraceBuffer::ExtractHandle(
} }
NodeTraceBuffer::NodeTraceBuffer(size_t max_chunks, NodeTraceBuffer::NodeTraceBuffer(size_t max_chunks,
NodeTraceWriter* trace_writer, uv_loop_t* tracing_loop) Agent* agent, uv_loop_t* tracing_loop)
: tracing_loop_(tracing_loop), trace_writer_(trace_writer), : tracing_loop_(tracing_loop), agent_(agent),
buffer1_(max_chunks, 0, trace_writer), buffer1_(max_chunks, 0, agent),
buffer2_(max_chunks, 1, trace_writer) { buffer2_(max_chunks, 1, agent) {
current_buf_.store(&buffer1_); current_buf_.store(&buffer1_);
flush_signal_.data = this; flush_signal_.data = this;

View File

@ -1,8 +1,8 @@
#ifndef SRC_TRACING_NODE_TRACE_BUFFER_H_ #ifndef SRC_TRACING_NODE_TRACE_BUFFER_H_
#define SRC_TRACING_NODE_TRACE_BUFFER_H_ #define SRC_TRACING_NODE_TRACE_BUFFER_H_
#include "tracing/agent.h"
#include "node_mutex.h" #include "node_mutex.h"
#include "tracing/node_trace_writer.h"
#include "libplatform/v8-tracing.h" #include "libplatform/v8-tracing.h"
#include <atomic> #include <atomic>
@ -19,8 +19,7 @@ class NodeTraceBuffer;
class InternalTraceBuffer { class InternalTraceBuffer {
public: public:
InternalTraceBuffer(size_t max_chunks, uint32_t id, InternalTraceBuffer(size_t max_chunks, uint32_t id, Agent* agent);
NodeTraceWriter* trace_writer);
TraceObject* AddTraceEvent(uint64_t* handle); TraceObject* AddTraceEvent(uint64_t* handle);
TraceObject* GetEventByHandle(uint64_t handle); TraceObject* GetEventByHandle(uint64_t handle);
@ -42,7 +41,7 @@ class InternalTraceBuffer {
Mutex mutex_; Mutex mutex_;
bool flushing_; bool flushing_;
size_t max_chunks_; size_t max_chunks_;
NodeTraceWriter* trace_writer_; Agent* agent_;
std::vector<std::unique_ptr<TraceBufferChunk>> chunks_; std::vector<std::unique_ptr<TraceBufferChunk>> chunks_;
size_t total_chunks_ = 0; size_t total_chunks_ = 0;
uint32_t current_chunk_seq_ = 1; uint32_t current_chunk_seq_ = 1;
@ -51,8 +50,7 @@ class InternalTraceBuffer {
class NodeTraceBuffer : public TraceBuffer { class NodeTraceBuffer : public TraceBuffer {
public: public:
NodeTraceBuffer(size_t max_chunks, NodeTraceWriter* trace_writer, NodeTraceBuffer(size_t max_chunks, Agent* agent, uv_loop_t* tracing_loop);
uv_loop_t* tracing_loop);
~NodeTraceBuffer(); ~NodeTraceBuffer();
TraceObject* AddTraceEvent(uint64_t* handle) override; TraceObject* AddTraceEvent(uint64_t* handle) override;
@ -74,7 +72,7 @@ class NodeTraceBuffer : public TraceBuffer {
Mutex exit_mutex_; Mutex exit_mutex_;
// Used to wait until async handles have been closed. // Used to wait until async handles have been closed.
ConditionVariable exit_cond_; ConditionVariable exit_cond_;
std::unique_ptr<NodeTraceWriter> trace_writer_; Agent* agent_;
std::atomic<InternalTraceBuffer*> current_buf_; std::atomic<InternalTraceBuffer*> current_buf_;
InternalTraceBuffer buffer1_; InternalTraceBuffer buffer1_;
InternalTraceBuffer buffer2_; InternalTraceBuffer buffer2_;

View File

@ -126,12 +126,6 @@ void NodeTraceWriter::FlushSignalCb(uv_async_t* signal) {
trace_writer->FlushPrivate(); 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) { void NodeTraceWriter::Flush(bool blocking) {
Mutex::ScopedLock scoped_lock(request_mutex_); Mutex::ScopedLock scoped_lock(request_mutex_);
if (!json_trace_writer_) { if (!json_trace_writer_) {

View File

@ -6,6 +6,7 @@
#include "node_mutex.h" #include "node_mutex.h"
#include "libplatform/v8-tracing.h" #include "libplatform/v8-tracing.h"
#include "tracing/agent.h"
#include "uv.h" #include "uv.h"
namespace node { namespace node {
@ -14,15 +15,14 @@ namespace tracing {
using v8::platform::tracing::TraceObject; using v8::platform::tracing::TraceObject;
using v8::platform::tracing::TraceWriter; using v8::platform::tracing::TraceWriter;
class NodeTraceWriter : public TraceWriter { class NodeTraceWriter : public AsyncTraceWriter {
public: public:
explicit NodeTraceWriter(const std::string& log_file_pattern, explicit NodeTraceWriter(const std::string& log_file_pattern,
uv_loop_t* tracing_loop); uv_loop_t* tracing_loop);
~NodeTraceWriter(); ~NodeTraceWriter();
void AppendTraceEvent(TraceObject* trace_event) override; void AppendTraceEvent(TraceObject* trace_event) override;
void Flush() override; void Flush(bool blocking) override;
void Flush(bool blocking);
static const int kTracesPerFile = 1 << 19; static const int kTracesPerFile = 1 << 19;

View File

@ -56,4 +56,8 @@ async function test() {
common.crashOnUnhandledRejection(); common.crashOnUnhandledRejection();
test(); const interval = setInterval(() => {}, 1000);
test().then(() => {
clearInterval(interval);
console.log('Done!');
});

View File

@ -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();