Skip to content

Commit bf36ef9

Browse files
committed
src: parse inspector profiles with simdjson
This allows us to start the profilers before context creation so that more samples can be collected.
1 parent 53aed88 commit bf36ef9

File tree

3 files changed

+168
-118
lines changed

3 files changed

+168
-118
lines changed

node.gyp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,6 +1248,8 @@
12481248
'deps/histogram/histogram.gyp:histogram',
12491249
'deps/uvwasi/uvwasi.gyp:uvwasi',
12501250
'deps/ada/ada.gyp:ada',
1251+
'deps/simdjson/simdjson.gyp:simdjson',
1252+
'deps/simdutf/simdutf.gyp:simdutf',
12511253
],
12521254

12531255
'includes': [

src/inspector_profiler.cc

Lines changed: 151 additions & 105 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
#include <cinttypes>
1414
#include <sstream>
15+
#include "simdutf.h"
1516

1617
namespace node {
1718
namespace profiler {
@@ -38,11 +39,11 @@ V8ProfilerConnection::V8ProfilerConnection(Environment* env)
3839
false)),
3940
env_(env) {}
4041

41-
uint32_t V8ProfilerConnection::DispatchMessage(const char* method,
42+
uint64_t V8ProfilerConnection::DispatchMessage(const char* method,
4243
const char* params,
4344
bool is_profile_request) {
4445
std::stringstream ss;
45-
uint32_t id = next_id();
46+
uint64_t id = next_id();
4647
ss << R"({ "id": )" << id;
4748
DCHECK(method != nullptr);
4849
ss << R"(, "method": ")" << method << '"';
@@ -67,8 +68,10 @@ uint32_t V8ProfilerConnection::DispatchMessage(const char* method,
6768

6869
static void WriteResult(Environment* env,
6970
const char* path,
70-
Local<String> result) {
71-
int ret = WriteFileSync(env->isolate(), path, result);
71+
std::string_view profile) {
72+
uv_buf_t buf =
73+
uv_buf_init(const_cast<char*>(profile.data()), profile.length());
74+
int ret = WriteFileSync(path, buf);
7275
if (ret != 0) {
7376
char err_buf[128];
7477
uv_err_name_r(ret, err_buf, sizeof(err_buf));
@@ -78,77 +81,103 @@ static void WriteResult(Environment* env,
7881
Debug(env, DebugCategory::INSPECTOR_PROFILER, "Written result to %s\n", path);
7982
}
8083

84+
bool StringViewToUTF8(const v8_inspector::StringView& source,
85+
std::vector<char>* utf8_out,
86+
size_t* utf8_length) {
87+
size_t source_len = source.length();
88+
if (source.is8Bit()) {
89+
const char* latin1 = reinterpret_cast<const char*>(source.characters8());
90+
*utf8_length = simdutf::utf8_length_from_latin1(latin1, source_len);
91+
utf8_out->resize(*utf8_length);
92+
size_t result_len =
93+
simdutf::convert_latin1_to_utf8(latin1, source_len, utf8_out->data());
94+
return *utf8_length == result_len;
95+
}
96+
97+
const char16_t* utf16 =
98+
reinterpret_cast<const char16_t*>(source.characters16());
99+
*utf8_length = simdutf::utf8_length_from_utf16(utf16, source_len);
100+
utf8_out->resize(*utf8_length);
101+
size_t result_len =
102+
simdutf::convert_utf16_to_utf8(utf16, source_len, utf8_out->data());
103+
return *utf8_length == result_len;
104+
}
105+
81106
void V8ProfilerConnection::V8ProfilerSessionDelegate::SendMessageToFrontend(
82107
const v8_inspector::StringView& message) {
83108
Environment* env = connection_->env();
84109
Isolate* isolate = env->isolate();
85110
HandleScope handle_scope(isolate);
86111
Local<Context> context = env->context();
87112
Context::Scope context_scope(context);
88-
89113
const char* type = connection_->type();
90-
// Convert StringView to a Local<String>.
91-
Local<String> message_str;
92-
if (!String::NewFromTwoByte(isolate,
93-
message.characters16(),
94-
NewStringType::kNormal,
95-
message.length())
96-
.ToLocal(&message_str)) {
97-
fprintf(
98-
stderr, "Failed to convert %s profile message to V8 string\n", type);
99-
return;
100-
}
101114

102115
Debug(env,
103116
DebugCategory::INSPECTOR_PROFILER,
104-
"Receive %s profile message\n",
117+
"Received %s profile message\n",
105118
type);
106119

107-
Local<Value> parsed;
108-
if (!v8::JSON::Parse(context, message_str).ToLocal(&parsed) ||
109-
!parsed->IsObject()) {
110-
fprintf(stderr, "Failed to parse %s profile result as JSON object\n", type);
120+
std::vector<char> message_utf8;
121+
size_t message_utf8_length;
122+
if (!StringViewToUTF8(message, &message_utf8, &message_utf8_length)) {
123+
fprintf(
124+
stderr, "Failed to convert %s profile message to UTF8 string\n", type);
111125
return;
112126
}
127+
// Allocate extra padding for JSON parsing.
128+
message_utf8.resize(message_utf8_length + simdjson::SIMDJSON_PADDING);
113129

114-
Local<Object> response = parsed.As<Object>();
115-
Local<Value> id_v;
116-
if (!response->Get(context, FIXED_ONE_BYTE_STRING(isolate, "id"))
117-
.ToLocal(&id_v) ||
118-
!id_v->IsUint32()) {
119-
Utf8Value str(isolate, message_str);
130+
simdjson::ondemand::document parsed;
131+
simdjson::ondemand::object response;
132+
if (connection_->json_parser_
133+
.iterate(
134+
message_utf8.data(), message_utf8_length, message_utf8.size())
135+
.get(parsed) ||
136+
parsed.get_object().get(response)) {
120137
fprintf(
121-
stderr, "Cannot retrieve id from the response message:\n%s\n", *str);
138+
stderr, "Failed to parse %s profile result as JSON object:\n", type);
139+
fprintf(stderr,
140+
"%.*s\n",
141+
static_cast<int>(message_utf8_length),
142+
message_utf8.data());
143+
return;
144+
}
145+
146+
uint64_t id;
147+
if (response["id"].get_uint64().get(id)) {
148+
fprintf(stderr, "Cannot retrieve id from %s profile response:\n", type);
149+
fprintf(stderr,
150+
"%.*s\n",
151+
static_cast<int>(message_utf8_length),
152+
message_utf8.data());
122153
return;
123154
}
124-
uint32_t id = id_v.As<v8::Uint32>()->Value();
125155

126156
if (!connection_->HasProfileId(id)) {
127-
Utf8Value str(isolate, message_str);
128-
Debug(env, DebugCategory::INSPECTOR_PROFILER, "%s\n", *str);
157+
Debug(env,
158+
DebugCategory::INSPECTOR_PROFILER,
159+
"%s\n",
160+
std::string_view(message_utf8.data(), message_utf8_length));
129161
return;
130162
} else {
131163
Debug(env,
132164
DebugCategory::INSPECTOR_PROFILER,
133165
"Writing profile response (id = %" PRIu64 ")\n",
134-
static_cast<uint64_t>(id));
166+
id);
135167
}
136168

169+
simdjson::ondemand::object result;
137170
// Get message.result from the response.
138-
Local<Value> result_v;
139-
if (!response->Get(context, FIXED_ONE_BYTE_STRING(isolate, "result"))
140-
.ToLocal(&result_v)) {
141-
fprintf(stderr, "Failed to get 'result' from %s profile response\n", type);
142-
return;
143-
}
144-
145-
if (!result_v->IsObject()) {
146-
fprintf(
147-
stderr, "'result' from %s profile response is not an object\n", type);
171+
if (response["result"].get_object().get(result)) {
172+
fprintf(stderr, "Failed to get 'result' from %s profile response:\n", type);
173+
fprintf(stderr,
174+
"%.*s\n",
175+
static_cast<int>(message_utf8_length),
176+
message_utf8.data());
148177
return;
149178
}
150179

151-
connection_->WriteProfile(result_v.As<Object>());
180+
connection_->WriteProfile(result);
152181
connection_->RemoveProfileId(id);
153182
}
154183

@@ -178,20 +207,31 @@ std::string V8CoverageConnection::GetFilename() const {
178207
env()->thread_id());
179208
}
180209

181-
void V8ProfilerConnection::WriteProfile(Local<Object> result) {
182-
Local<Context> context = env_->context();
183-
184-
// Generate the profile output from the subclass.
185-
Local<Object> profile;
186-
if (!GetProfile(result).ToLocal(&profile)) {
187-
return;
210+
std::optional<std::string_view> V8ProfilerConnection::GetProfile(
211+
simdjson::ondemand::object& result) {
212+
simdjson::ondemand::object profile_object;
213+
if (result["profile"].get_object().get(profile_object)) {
214+
fprintf(
215+
stderr, "'profile' from %s profile result is not an Object\n", type());
216+
return std::nullopt;
188217
}
218+
std::string_view profile_raw;
219+
if (profile_object.raw_json().get(profile_raw)) {
220+
fprintf(stderr,
221+
"Cannot get raw string of the 'profile' field from %s profile\n",
222+
type());
223+
return std::nullopt;
224+
}
225+
return profile_raw;
226+
}
189227

190-
Local<String> result_s;
191-
if (!v8::JSON::Stringify(context, profile).ToLocal(&result_s)) {
192-
fprintf(stderr, "Failed to stringify %s profile result\n", type());
228+
void V8ProfilerConnection::WriteProfile(simdjson::ondemand::object& result) {
229+
// Generate the profile output from the subclass.
230+
auto profile_opt = GetProfile(result);
231+
if (!profile_opt.has_value()) {
193232
return;
194233
}
234+
std::string_view profile = profile_opt.value();
195235

196236
// Create the directory if necessary.
197237
std::string directory = GetDirectory();
@@ -204,14 +244,12 @@ void V8ProfilerConnection::WriteProfile(Local<Object> result) {
204244
DCHECK(!filename.empty());
205245
std::string path = directory + kPathSeparator + filename;
206246

207-
WriteResult(env_, path.c_str(), result_s);
247+
WriteResult(env_, path.c_str(), profile);
208248
}
209249

210-
void V8CoverageConnection::WriteProfile(Local<Object> result) {
250+
void V8CoverageConnection::WriteProfile(simdjson::ondemand::object& result) {
211251
Isolate* isolate = env_->isolate();
212-
Local<Context> context = env_->context();
213252
HandleScope handle_scope(isolate);
214-
Context::Scope context_scope(context);
215253

216254
// This is only set up during pre-execution (when the environment variables
217255
// becomes available in the JS land). If it's empty, we don't have coverage
@@ -223,11 +261,15 @@ void V8CoverageConnection::WriteProfile(Local<Object> result) {
223261
return;
224262
}
225263

264+
Local<Context> context = env_->context();
265+
Context::Scope context_scope(context);
266+
226267
// Generate the profile output from the subclass.
227-
Local<Object> profile;
228-
if (!GetProfile(result).ToLocal(&profile)) {
268+
auto profile_opt = GetProfile(result);
269+
if (!profile_opt.has_value()) {
229270
return;
230271
}
272+
std::string_view profile = profile_opt.value();
231273

232274
// append source-map cache information to coverage object:
233275
Local<Value> source_map_cache_v;
@@ -246,17 +288,6 @@ void V8CoverageConnection::WriteProfile(Local<Object> result) {
246288
PrintCaughtException(isolate, context, try_catch);
247289
}
248290
}
249-
// Avoid writing to disk if no source-map data:
250-
if (!source_map_cache_v->IsUndefined()) {
251-
profile->Set(context, FIXED_ONE_BYTE_STRING(isolate, "source-map-cache"),
252-
source_map_cache_v).ToChecked();
253-
}
254-
255-
Local<String> result_s;
256-
if (!v8::JSON::Stringify(context, profile).ToLocal(&result_s)) {
257-
fprintf(stderr, "Failed to stringify %s profile result\n", type());
258-
return;
259-
}
260291

261292
// Create the directory if necessary.
262293
std::string directory = GetDirectory();
@@ -269,11 +300,58 @@ void V8CoverageConnection::WriteProfile(Local<Object> result) {
269300
DCHECK(!filename.empty());
270301
std::string path = directory + kPathSeparator + filename;
271302

272-
WriteResult(env_, path.c_str(), result_s);
303+
// Only insert source map cache when there's source map data at all.
304+
if (!source_map_cache_v->IsUndefined()) {
305+
// It would be more performant to just find the last } and insert the source
306+
// map cache in front of it, but source map cache is still experimental
307+
// anyway so just re-parse it with V8 for now.
308+
Local<String> profile_str;
309+
if (!v8::String::NewFromUtf8(isolate,
310+
profile.data(),
311+
v8::NewStringType::kNormal,
312+
profile.length())
313+
.ToLocal(&profile_str)) {
314+
fprintf(stderr, "Failed to re-parse %s profile as UTF8\n", type());
315+
return;
316+
}
317+
Local<Value> profile_value;
318+
if (!v8::JSON::Parse(context, profile_str).ToLocal(&profile_value) ||
319+
!profile_value->IsObject()) {
320+
fprintf(stderr, "Failed to re-parse %s profile from JSON\n", type());
321+
return;
322+
}
323+
if (profile_value.As<Object>()
324+
->Set(context,
325+
FIXED_ONE_BYTE_STRING(isolate, "source-map-cache"),
326+
source_map_cache_v)
327+
.IsNothing()) {
328+
fprintf(stderr,
329+
"Failed to insert source map cache into %s profile\n",
330+
type());
331+
return;
332+
}
333+
Local<String> result_s;
334+
if (!v8::JSON::Stringify(context, profile_value).ToLocal(&result_s)) {
335+
fprintf(stderr, "Failed to stringify %s profile result\n", type());
336+
return;
337+
}
338+
Utf8Value result_utf8(isolate, result_s);
339+
WriteResult(env_, path.c_str(), result_utf8.ToStringView());
340+
} else {
341+
WriteResult(env_, path.c_str(), profile);
342+
}
273343
}
274344

275-
MaybeLocal<Object> V8CoverageConnection::GetProfile(Local<Object> result) {
276-
return result;
345+
std::optional<std::string_view> V8CoverageConnection::GetProfile(
346+
simdjson::ondemand::object& result) {
347+
std::string_view profile_raw;
348+
if (result.raw_json().get(profile_raw)) {
349+
fprintf(stderr,
350+
"Cannot get raw string of the 'profile' field from %s profile\n",
351+
type());
352+
return std::nullopt;
353+
}
354+
return profile_raw;
277355
}
278356

279357
std::string V8CoverageConnection::GetDirectory() const {
@@ -313,22 +391,6 @@ std::string V8CpuProfilerConnection::GetFilename() const {
313391
return env()->cpu_prof_name();
314392
}
315393

316-
MaybeLocal<Object> V8CpuProfilerConnection::GetProfile(Local<Object> result) {
317-
Local<Value> profile_v;
318-
if (!result
319-
->Get(env()->context(),
320-
FIXED_ONE_BYTE_STRING(env()->isolate(), "profile"))
321-
.ToLocal(&profile_v)) {
322-
fprintf(stderr, "'profile' from CPU profile result is undefined\n");
323-
return MaybeLocal<Object>();
324-
}
325-
if (!profile_v->IsObject()) {
326-
fprintf(stderr, "'profile' from CPU profile result is not an Object\n");
327-
return MaybeLocal<Object>();
328-
}
329-
return profile_v.As<Object>();
330-
}
331-
332394
void V8CpuProfilerConnection::Start() {
333395
DispatchMessage("Profiler.enable");
334396
std::string params = R"({ "interval": )";
@@ -357,22 +419,6 @@ std::string V8HeapProfilerConnection::GetFilename() const {
357419
return env()->heap_prof_name();
358420
}
359421

360-
MaybeLocal<Object> V8HeapProfilerConnection::GetProfile(Local<Object> result) {
361-
Local<Value> profile_v;
362-
if (!result
363-
->Get(env()->context(),
364-
FIXED_ONE_BYTE_STRING(env()->isolate(), "profile"))
365-
.ToLocal(&profile_v)) {
366-
fprintf(stderr, "'profile' from heap profile result is undefined\n");
367-
return MaybeLocal<Object>();
368-
}
369-
if (!profile_v->IsObject()) {
370-
fprintf(stderr, "'profile' from heap profile result is not an Object\n");
371-
return MaybeLocal<Object>();
372-
}
373-
return profile_v.As<Object>();
374-
}
375-
376422
void V8HeapProfilerConnection::Start() {
377423
DispatchMessage("HeapProfiler.enable");
378424
std::string params = R"({ "samplingInterval": )";

0 commit comments

Comments
 (0)