mirror of
https://github.com/nodejs/node.git
synced 2025-12-28 07:50:41 +00:00
src: parse inspector profiles with simdjson
This allows us to start the profilers before context creation so that more samples can be collected. PR-URL: https://github.com/nodejs/node/pull/51783 Reviewed-By: Daniel Lemire <daniel@lemire.me> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
This commit is contained in:
parent
a30ae50860
commit
a6b80c7267
2
node.gyp
2
node.gyp
@ -1257,6 +1257,8 @@
|
||||
'deps/histogram/histogram.gyp:histogram',
|
||||
'deps/uvwasi/uvwasi.gyp:uvwasi',
|
||||
'deps/ada/ada.gyp:ada',
|
||||
'deps/simdjson/simdjson.gyp:simdjson',
|
||||
'deps/simdutf/simdutf.gyp:simdutf',
|
||||
],
|
||||
|
||||
'includes': [
|
||||
|
||||
@ -11,7 +11,9 @@
|
||||
#include "v8-inspector.h"
|
||||
|
||||
#include <cinttypes>
|
||||
#include <limits>
|
||||
#include <sstream>
|
||||
#include "simdutf.h"
|
||||
|
||||
namespace node {
|
||||
namespace profiler {
|
||||
@ -23,7 +25,6 @@ using v8::FunctionCallbackInfo;
|
||||
using v8::HandleScope;
|
||||
using v8::Isolate;
|
||||
using v8::Local;
|
||||
using v8::MaybeLocal;
|
||||
using v8::NewStringType;
|
||||
using v8::Object;
|
||||
using v8::String;
|
||||
@ -38,11 +39,15 @@ V8ProfilerConnection::V8ProfilerConnection(Environment* env)
|
||||
false)),
|
||||
env_(env) {}
|
||||
|
||||
uint32_t V8ProfilerConnection::DispatchMessage(const char* method,
|
||||
uint64_t V8ProfilerConnection::DispatchMessage(const char* method,
|
||||
const char* params,
|
||||
bool is_profile_request) {
|
||||
std::stringstream ss;
|
||||
uint32_t id = next_id();
|
||||
uint64_t id = next_id();
|
||||
// V8's inspector protocol cannot take an integer beyond the int32_t limit.
|
||||
// In practice the id we use is up to 3-5 for the profilers we have
|
||||
// here.
|
||||
CHECK_LT(id, static_cast<uint64_t>(std::numeric_limits<int32_t>::max()));
|
||||
ss << R"({ "id": )" << id;
|
||||
DCHECK(method != nullptr);
|
||||
ss << R"(, "method": ")" << method << '"';
|
||||
@ -67,8 +72,10 @@ uint32_t V8ProfilerConnection::DispatchMessage(const char* method,
|
||||
|
||||
static void WriteResult(Environment* env,
|
||||
const char* path,
|
||||
Local<String> result) {
|
||||
int ret = WriteFileSync(env->isolate(), path, result);
|
||||
std::string_view profile) {
|
||||
uv_buf_t buf =
|
||||
uv_buf_init(const_cast<char*>(profile.data()), profile.length());
|
||||
int ret = WriteFileSync(path, buf);
|
||||
if (ret != 0) {
|
||||
char err_buf[128];
|
||||
uv_err_name_r(ret, err_buf, sizeof(err_buf));
|
||||
@ -78,6 +85,29 @@ static void WriteResult(Environment* env,
|
||||
Debug(env, DebugCategory::INSPECTOR_PROFILER, "Written result to %s\n", path);
|
||||
}
|
||||
|
||||
bool StringViewToUTF8(const v8_inspector::StringView& source,
|
||||
std::vector<char>* utf8_out,
|
||||
size_t* utf8_length,
|
||||
size_t padding) {
|
||||
size_t source_len = source.length();
|
||||
if (source.is8Bit()) {
|
||||
const char* latin1 = reinterpret_cast<const char*>(source.characters8());
|
||||
*utf8_length = simdutf::utf8_length_from_latin1(latin1, source_len);
|
||||
utf8_out->resize(*utf8_length + padding);
|
||||
size_t result_len =
|
||||
simdutf::convert_latin1_to_utf8(latin1, source_len, utf8_out->data());
|
||||
return *utf8_length == result_len;
|
||||
}
|
||||
|
||||
const char16_t* utf16 =
|
||||
reinterpret_cast<const char16_t*>(source.characters16());
|
||||
*utf8_length = simdutf::utf8_length_from_utf16(utf16, source_len);
|
||||
utf8_out->resize(*utf8_length + padding);
|
||||
size_t result_len =
|
||||
simdutf::convert_utf16_to_utf8(utf16, source_len, utf8_out->data());
|
||||
return *utf8_length == result_len;
|
||||
}
|
||||
|
||||
void V8ProfilerConnection::V8ProfilerSessionDelegate::SendMessageToFrontend(
|
||||
const v8_inspector::StringView& message) {
|
||||
Environment* env = connection_->env();
|
||||
@ -85,70 +115,75 @@ void V8ProfilerConnection::V8ProfilerSessionDelegate::SendMessageToFrontend(
|
||||
HandleScope handle_scope(isolate);
|
||||
Local<Context> context = env->context();
|
||||
Context::Scope context_scope(context);
|
||||
|
||||
const char* type = connection_->type();
|
||||
// Convert StringView to a Local<String>.
|
||||
Local<String> message_str;
|
||||
if (!String::NewFromTwoByte(isolate,
|
||||
message.characters16(),
|
||||
NewStringType::kNormal,
|
||||
message.length())
|
||||
.ToLocal(&message_str)) {
|
||||
fprintf(
|
||||
stderr, "Failed to convert %s profile message to V8 string\n", type);
|
||||
return;
|
||||
}
|
||||
|
||||
Debug(env,
|
||||
DebugCategory::INSPECTOR_PROFILER,
|
||||
"Receive %s profile message\n",
|
||||
"Received %s profile message\n",
|
||||
type);
|
||||
|
||||
Local<Value> parsed;
|
||||
if (!v8::JSON::Parse(context, message_str).ToLocal(&parsed) ||
|
||||
!parsed->IsObject()) {
|
||||
fprintf(stderr, "Failed to parse %s profile result as JSON object\n", type);
|
||||
std::vector<char> message_utf8;
|
||||
size_t message_utf8_length;
|
||||
if (!StringViewToUTF8(message,
|
||||
&message_utf8,
|
||||
&message_utf8_length,
|
||||
simdjson::SIMDJSON_PADDING)) {
|
||||
fprintf(
|
||||
stderr, "Failed to convert %s profile message to UTF8 string\n", type);
|
||||
return;
|
||||
}
|
||||
|
||||
Local<Object> response = parsed.As<Object>();
|
||||
Local<Value> id_v;
|
||||
if (!response->Get(context, FIXED_ONE_BYTE_STRING(isolate, "id"))
|
||||
.ToLocal(&id_v) ||
|
||||
!id_v->IsUint32()) {
|
||||
Utf8Value str(isolate, message_str);
|
||||
simdjson::ondemand::document parsed;
|
||||
simdjson::ondemand::object response;
|
||||
if (connection_->json_parser_
|
||||
.iterate(
|
||||
message_utf8.data(), message_utf8_length, message_utf8.size())
|
||||
.get(parsed) ||
|
||||
parsed.get_object().get(response)) {
|
||||
fprintf(
|
||||
stderr, "Cannot retrieve id from the response message:\n%s\n", *str);
|
||||
stderr, "Failed to parse %s profile result as JSON object:\n", type);
|
||||
fprintf(stderr,
|
||||
"%.*s\n",
|
||||
static_cast<int>(message_utf8_length),
|
||||
message_utf8.data());
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t id;
|
||||
if (response["id"].get_uint64().get(id)) {
|
||||
fprintf(stderr, "Cannot retrieve id from %s profile response:\n", type);
|
||||
fprintf(stderr,
|
||||
"%.*s\n",
|
||||
static_cast<int>(message_utf8_length),
|
||||
message_utf8.data());
|
||||
return;
|
||||
}
|
||||
uint32_t id = id_v.As<v8::Uint32>()->Value();
|
||||
|
||||
if (!connection_->HasProfileId(id)) {
|
||||
Utf8Value str(isolate, message_str);
|
||||
Debug(env, DebugCategory::INSPECTOR_PROFILER, "%s\n", *str);
|
||||
Debug(env,
|
||||
DebugCategory::INSPECTOR_PROFILER,
|
||||
"%s\n",
|
||||
std::string_view(message_utf8.data(), message_utf8_length));
|
||||
return;
|
||||
} else {
|
||||
Debug(env,
|
||||
DebugCategory::INSPECTOR_PROFILER,
|
||||
"Writing profile response (id = %" PRIu64 ")\n",
|
||||
static_cast<uint64_t>(id));
|
||||
id);
|
||||
}
|
||||
|
||||
simdjson::ondemand::object result;
|
||||
// Get message.result from the response.
|
||||
Local<Value> result_v;
|
||||
if (!response->Get(context, FIXED_ONE_BYTE_STRING(isolate, "result"))
|
||||
.ToLocal(&result_v)) {
|
||||
fprintf(stderr, "Failed to get 'result' from %s profile response\n", type);
|
||||
if (response["result"].get_object().get(result)) {
|
||||
fprintf(stderr, "Failed to get 'result' from %s profile response:\n", type);
|
||||
fprintf(stderr,
|
||||
"%.*s\n",
|
||||
static_cast<int>(message_utf8_length),
|
||||
message_utf8.data());
|
||||
return;
|
||||
}
|
||||
|
||||
if (!result_v->IsObject()) {
|
||||
fprintf(
|
||||
stderr, "'result' from %s profile response is not an object\n", type);
|
||||
return;
|
||||
}
|
||||
|
||||
connection_->WriteProfile(result_v.As<Object>());
|
||||
connection_->WriteProfile(&result);
|
||||
connection_->RemoveProfileId(id);
|
||||
}
|
||||
|
||||
@ -178,20 +213,31 @@ std::string V8CoverageConnection::GetFilename() const {
|
||||
env()->thread_id());
|
||||
}
|
||||
|
||||
void V8ProfilerConnection::WriteProfile(Local<Object> result) {
|
||||
Local<Context> context = env_->context();
|
||||
std::optional<std::string_view> V8ProfilerConnection::GetProfile(
|
||||
simdjson::ondemand::object* result) {
|
||||
simdjson::ondemand::object profile_object;
|
||||
if ((*result)["profile"].get_object().get(profile_object)) {
|
||||
fprintf(
|
||||
stderr, "'profile' from %s profile result is not an Object\n", type());
|
||||
return std::nullopt;
|
||||
}
|
||||
std::string_view profile_raw;
|
||||
if (profile_object.raw_json().get(profile_raw)) {
|
||||
fprintf(stderr,
|
||||
"Cannot get raw string of the 'profile' field from %s profile\n",
|
||||
type());
|
||||
return std::nullopt;
|
||||
}
|
||||
return profile_raw;
|
||||
}
|
||||
|
||||
void V8ProfilerConnection::WriteProfile(simdjson::ondemand::object* result) {
|
||||
// Generate the profile output from the subclass.
|
||||
Local<Object> profile;
|
||||
if (!GetProfile(result).ToLocal(&profile)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Local<String> result_s;
|
||||
if (!v8::JSON::Stringify(context, profile).ToLocal(&result_s)) {
|
||||
fprintf(stderr, "Failed to stringify %s profile result\n", type());
|
||||
auto profile_opt = GetProfile(result);
|
||||
if (!profile_opt.has_value()) {
|
||||
return;
|
||||
}
|
||||
std::string_view profile = profile_opt.value();
|
||||
|
||||
// Create the directory if necessary.
|
||||
std::string directory = GetDirectory();
|
||||
@ -204,14 +250,12 @@ void V8ProfilerConnection::WriteProfile(Local<Object> result) {
|
||||
DCHECK(!filename.empty());
|
||||
std::string path = directory + kPathSeparator + filename;
|
||||
|
||||
WriteResult(env_, path.c_str(), result_s);
|
||||
WriteResult(env_, path.c_str(), profile);
|
||||
}
|
||||
|
||||
void V8CoverageConnection::WriteProfile(Local<Object> result) {
|
||||
void V8CoverageConnection::WriteProfile(simdjson::ondemand::object* result) {
|
||||
Isolate* isolate = env_->isolate();
|
||||
Local<Context> context = env_->context();
|
||||
HandleScope handle_scope(isolate);
|
||||
Context::Scope context_scope(context);
|
||||
|
||||
// This is only set up during pre-execution (when the environment variables
|
||||
// becomes available in the JS land). If it's empty, we don't have coverage
|
||||
@ -223,11 +267,15 @@ void V8CoverageConnection::WriteProfile(Local<Object> result) {
|
||||
return;
|
||||
}
|
||||
|
||||
Local<Context> context = env_->context();
|
||||
Context::Scope context_scope(context);
|
||||
|
||||
// Generate the profile output from the subclass.
|
||||
Local<Object> profile;
|
||||
if (!GetProfile(result).ToLocal(&profile)) {
|
||||
auto profile_opt = GetProfile(result);
|
||||
if (!profile_opt.has_value()) {
|
||||
return;
|
||||
}
|
||||
std::string_view profile = profile_opt.value();
|
||||
|
||||
// append source-map cache information to coverage object:
|
||||
Local<Value> source_map_cache_v;
|
||||
@ -246,17 +294,6 @@ void V8CoverageConnection::WriteProfile(Local<Object> result) {
|
||||
PrintCaughtException(isolate, context, try_catch);
|
||||
}
|
||||
}
|
||||
// Avoid writing to disk if no source-map data:
|
||||
if (!source_map_cache_v->IsUndefined()) {
|
||||
profile->Set(context, FIXED_ONE_BYTE_STRING(isolate, "source-map-cache"),
|
||||
source_map_cache_v).ToChecked();
|
||||
}
|
||||
|
||||
Local<String> result_s;
|
||||
if (!v8::JSON::Stringify(context, profile).ToLocal(&result_s)) {
|
||||
fprintf(stderr, "Failed to stringify %s profile result\n", type());
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the directory if necessary.
|
||||
std::string directory = GetDirectory();
|
||||
@ -269,11 +306,58 @@ void V8CoverageConnection::WriteProfile(Local<Object> result) {
|
||||
DCHECK(!filename.empty());
|
||||
std::string path = directory + kPathSeparator + filename;
|
||||
|
||||
WriteResult(env_, path.c_str(), result_s);
|
||||
// Only insert source map cache when there's source map data at all.
|
||||
if (!source_map_cache_v->IsUndefined()) {
|
||||
// It would be more performant to just find the last } and insert the source
|
||||
// map cache in front of it, but source map cache is still experimental
|
||||
// anyway so just re-parse it with V8 for now.
|
||||
Local<String> profile_str;
|
||||
if (!v8::String::NewFromUtf8(isolate,
|
||||
profile.data(),
|
||||
v8::NewStringType::kNormal,
|
||||
profile.length())
|
||||
.ToLocal(&profile_str)) {
|
||||
fprintf(stderr, "Failed to re-parse %s profile as UTF8\n", type());
|
||||
return;
|
||||
}
|
||||
Local<Value> profile_value;
|
||||
if (!v8::JSON::Parse(context, profile_str).ToLocal(&profile_value) ||
|
||||
!profile_value->IsObject()) {
|
||||
fprintf(stderr, "Failed to re-parse %s profile from JSON\n", type());
|
||||
return;
|
||||
}
|
||||
if (profile_value.As<Object>()
|
||||
->Set(context,
|
||||
FIXED_ONE_BYTE_STRING(isolate, "source-map-cache"),
|
||||
source_map_cache_v)
|
||||
.IsNothing()) {
|
||||
fprintf(stderr,
|
||||
"Failed to insert source map cache into %s profile\n",
|
||||
type());
|
||||
return;
|
||||
}
|
||||
Local<String> result_s;
|
||||
if (!v8::JSON::Stringify(context, profile_value).ToLocal(&result_s)) {
|
||||
fprintf(stderr, "Failed to stringify %s profile result\n", type());
|
||||
return;
|
||||
}
|
||||
Utf8Value result_utf8(isolate, result_s);
|
||||
WriteResult(env_, path.c_str(), result_utf8.ToStringView());
|
||||
} else {
|
||||
WriteResult(env_, path.c_str(), profile);
|
||||
}
|
||||
}
|
||||
|
||||
MaybeLocal<Object> V8CoverageConnection::GetProfile(Local<Object> result) {
|
||||
return result;
|
||||
std::optional<std::string_view> V8CoverageConnection::GetProfile(
|
||||
simdjson::ondemand::object* result) {
|
||||
std::string_view profile_raw;
|
||||
if (result->raw_json().get(profile_raw)) {
|
||||
fprintf(stderr,
|
||||
"Cannot get raw string of the 'profile' field from %s profile\n",
|
||||
type());
|
||||
return std::nullopt;
|
||||
}
|
||||
return profile_raw;
|
||||
}
|
||||
|
||||
std::string V8CoverageConnection::GetDirectory() const {
|
||||
@ -313,22 +397,6 @@ std::string V8CpuProfilerConnection::GetFilename() const {
|
||||
return env()->cpu_prof_name();
|
||||
}
|
||||
|
||||
MaybeLocal<Object> V8CpuProfilerConnection::GetProfile(Local<Object> result) {
|
||||
Local<Value> profile_v;
|
||||
if (!result
|
||||
->Get(env()->context(),
|
||||
FIXED_ONE_BYTE_STRING(env()->isolate(), "profile"))
|
||||
.ToLocal(&profile_v)) {
|
||||
fprintf(stderr, "'profile' from CPU profile result is undefined\n");
|
||||
return MaybeLocal<Object>();
|
||||
}
|
||||
if (!profile_v->IsObject()) {
|
||||
fprintf(stderr, "'profile' from CPU profile result is not an Object\n");
|
||||
return MaybeLocal<Object>();
|
||||
}
|
||||
return profile_v.As<Object>();
|
||||
}
|
||||
|
||||
void V8CpuProfilerConnection::Start() {
|
||||
DispatchMessage("Profiler.enable");
|
||||
std::string params = R"({ "interval": )";
|
||||
@ -357,22 +425,6 @@ std::string V8HeapProfilerConnection::GetFilename() const {
|
||||
return env()->heap_prof_name();
|
||||
}
|
||||
|
||||
MaybeLocal<Object> V8HeapProfilerConnection::GetProfile(Local<Object> result) {
|
||||
Local<Value> profile_v;
|
||||
if (!result
|
||||
->Get(env()->context(),
|
||||
FIXED_ONE_BYTE_STRING(env()->isolate(), "profile"))
|
||||
.ToLocal(&profile_v)) {
|
||||
fprintf(stderr, "'profile' from heap profile result is undefined\n");
|
||||
return MaybeLocal<Object>();
|
||||
}
|
||||
if (!profile_v->IsObject()) {
|
||||
fprintf(stderr, "'profile' from heap profile result is not an Object\n");
|
||||
return MaybeLocal<Object>();
|
||||
}
|
||||
return profile_v.As<Object>();
|
||||
}
|
||||
|
||||
void V8HeapProfilerConnection::Start() {
|
||||
DispatchMessage("HeapProfiler.enable");
|
||||
std::string params = R"({ "samplingInterval": )";
|
||||
|
||||
@ -7,8 +7,10 @@
|
||||
#error("This header can only be used when inspector is enabled")
|
||||
#endif
|
||||
|
||||
#include <optional>
|
||||
#include <unordered_set>
|
||||
#include "inspector_agent.h"
|
||||
#include "simdjson.h"
|
||||
|
||||
namespace node {
|
||||
// Forward declaration to break recursive dependency chain with src/env.h.
|
||||
@ -40,7 +42,7 @@ class V8ProfilerConnection {
|
||||
// The optional `params` should be formatted in JSON.
|
||||
// The strings should be in one byte characters - which is enough for
|
||||
// the commands we use here.
|
||||
uint32_t DispatchMessage(const char* method,
|
||||
uint64_t DispatchMessage(const char* method,
|
||||
const char* params = nullptr,
|
||||
bool is_profile_request = false);
|
||||
|
||||
@ -59,23 +61,24 @@ class V8ProfilerConnection {
|
||||
virtual std::string GetFilename() const = 0;
|
||||
// Return the profile object parsed from `message.result`,
|
||||
// which will be then written as a JSON.
|
||||
virtual v8::MaybeLocal<v8::Object> GetProfile(
|
||||
v8::Local<v8::Object> result) = 0;
|
||||
virtual void WriteProfile(v8::Local<v8::Object> result);
|
||||
virtual std::optional<std::string_view> GetProfile(
|
||||
simdjson::ondemand::object* result);
|
||||
virtual void WriteProfile(simdjson::ondemand::object* result);
|
||||
|
||||
bool HasProfileId(uint32_t id) const {
|
||||
bool HasProfileId(uint64_t id) const {
|
||||
return profile_ids_.find(id) != profile_ids_.end();
|
||||
}
|
||||
|
||||
void RemoveProfileId(uint32_t id) { profile_ids_.erase(id); }
|
||||
void RemoveProfileId(uint64_t id) { profile_ids_.erase(id); }
|
||||
|
||||
private:
|
||||
uint32_t next_id() { return id_++; }
|
||||
uint64_t next_id() { return id_++; }
|
||||
std::unique_ptr<inspector::InspectorSession> session_;
|
||||
uint32_t id_ = 1;
|
||||
std::unordered_set<uint32_t> profile_ids_;
|
||||
uint64_t id_ = 1;
|
||||
std::unordered_set<uint64_t> profile_ids_;
|
||||
|
||||
protected:
|
||||
simdjson::ondemand::parser json_parser_;
|
||||
Environment* env_ = nullptr;
|
||||
};
|
||||
|
||||
@ -91,8 +94,9 @@ class V8CoverageConnection : public V8ProfilerConnection {
|
||||
|
||||
std::string GetDirectory() const override;
|
||||
std::string GetFilename() const override;
|
||||
v8::MaybeLocal<v8::Object> GetProfile(v8::Local<v8::Object> result) override;
|
||||
void WriteProfile(v8::Local<v8::Object> result) override;
|
||||
std::optional<std::string_view> GetProfile(
|
||||
simdjson::ondemand::object* result) override;
|
||||
void WriteProfile(simdjson::ondemand::object* result) override;
|
||||
void WriteSourceMapCache();
|
||||
void TakeCoverage();
|
||||
void StopCoverage();
|
||||
@ -115,7 +119,6 @@ class V8CpuProfilerConnection : public V8ProfilerConnection {
|
||||
|
||||
std::string GetDirectory() const override;
|
||||
std::string GetFilename() const override;
|
||||
v8::MaybeLocal<v8::Object> GetProfile(v8::Local<v8::Object> result) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<inspector::InspectorSession> session_;
|
||||
@ -135,7 +138,6 @@ class V8HeapProfilerConnection : public V8ProfilerConnection {
|
||||
|
||||
std::string GetDirectory() const override;
|
||||
std::string GetFilename() const override;
|
||||
v8::MaybeLocal<v8::Object> GetProfile(v8::Local<v8::Object> result) override;
|
||||
|
||||
private:
|
||||
std::unique_ptr<inspector::InspectorSession> session_;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user