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:
parent
5248401174
commit
47bdc716f8
213
node.gyp
213
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' ]
|
||||
@ -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
|
||||
}
|
||||
|
39
src/inspector/node_protocol.pdl
Normal file
39
src/inspector/node_protocol.pdl
Normal 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
|
27
src/inspector/node_protocol_config.json
Normal file
27
src/inspector/node_protocol_config.json
Normal 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"
|
||||
}
|
||||
}
|
92
src/inspector/node_string.cc
Normal file
92
src/inspector/node_string.cc
Normal 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
|
||||
|
79
src/inspector/node_string.h
Normal file
79
src/inspector/node_string.h
Normal 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_
|
106
src/inspector/tracing_agent.cc
Normal file
106
src/inspector/tracing_agent.cc
Normal 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
|
45
src/inspector/tracing_agent.h
Normal file
45
src/inspector/tracing_agent.h
Normal 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_
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -7,7 +7,7 @@
|
||||
#include "uv.h"
|
||||
|
||||
#include <deque>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <memory>
|
||||
#include <stddef.h>
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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_;
|
||||
|
@ -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_) {
|
||||
|
@ -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;
|
||||
|
||||
|
@ -56,4 +56,8 @@ async function test() {
|
||||
|
||||
common.crashOnUnhandledRejection();
|
||||
|
||||
test();
|
||||
const interval = setInterval(() => {}, 1000);
|
||||
test().then(() => {
|
||||
clearInterval(interval);
|
||||
console.log('Done!');
|
||||
});
|
||||
|
70
test/parallel/test-inspector-tracing-domain.js
Normal file
70
test/parallel/test-inspector-tracing-domain.js
Normal 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();
|
Loading…
x
Reference in New Issue
Block a user