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_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' ]
@ -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',
'type': 'none',
@ -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
}

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

View File

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

View File

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

View File

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

View File

@ -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;
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) {
if (!categories.empty()) {
for (auto category : categories) {
auto pos = categories_.lower_bound(category);
if (pos != categories_.end())
categories_.erase(pos);
}
RestartTracing();
for (auto category : categories) {
auto it = file_writer_categories_.find(category);
if (it != file_writer_categories_.end())
file_writer_categories_.erase(it);
}
}
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");
}
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

View File

@ -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();
private:
static void ThreadCb(void* arg);
void Start();
void RestartTracing();
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 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

View File

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

View File

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

View File

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

View File

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

View File

@ -56,4 +56,8 @@ async function test() {
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();