Skip to content

Commit 96728f4

Browse files
authored
feat: expose timing data to userland code (#49)
1 parent 9916e5a commit 96728f4

File tree

4 files changed

+103
-3
lines changed

4 files changed

+103
-3
lines changed

resources/entry-point-trampoline.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,8 @@ module.exports = (src, codeCacheMode, codeCache) => {
6060
const __dirname = path.dirname(process.execPath);
6161
let innerRequire;
6262
const exports = {};
63-
const usesSnapshot = !!v8?.startupSnapshot?.isBuildingSnapshot();
63+
const isBuildingSnapshot = () => !!v8?.startupSnapshot?.isBuildingSnapshot();
64+
const usesSnapshot = isBuildingSnapshot();
6465

6566
if (usesSnapshot) {
6667
innerRequire = outerRequire; // Node.js snapshots currently do not support userland require()
@@ -119,6 +120,27 @@ module.exports = (src, codeCacheMode, codeCache) => {
119120
// https://github.com/nodejs/node/pull/46320
120121
process.boxednode.rejectedCodeCache = mainFunction.cachedDataRejected;
121122

123+
let jsTimingEntries = [];
124+
if (usesSnapshot) {
125+
v8.startupSnapshot.addDeserializeCallback(() => {
126+
jsTimingEntries = [];
127+
});
128+
}
129+
process.boxednode.markTime = (label) => {
130+
jsTimingEntries.push([label, process.hrtime.bigint()]);
131+
};
132+
process.boxednode.getTimingData = () => {
133+
if (isBuildingSnapshot()) {
134+
throw new Error('getTimingData() is not available during snapshot building');
135+
}
136+
const data = [
137+
...jsTimingEntries,
138+
...process._linkedBinding('boxednode_linked_bindings').getTimingData()
139+
].sort((a, b) => Number(a[1] - b[1]));
140+
// Adjust times so that process initialization happens at time 0
141+
return data.map(([label, time]) => [label, Number(time - data[0][1])]);
142+
};
143+
122144
mainFunction(__filename, __dirname, require, exports, module);
123145
return module.exports;
124146
};

resources/main-template.cc

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include "node_api.h"
88
#include "uv.h"
99
#include "brotli/decode.h"
10+
#include <atomic>
1011
#if HAVE_OPENSSL
1112
#include <openssl/err.h>
1213
#include <openssl/ssl.h>
@@ -50,9 +51,54 @@ void TearDownOncePerProcess();
5051
}
5152
#endif
5253
namespace boxednode {
54+
namespace {
55+
struct TimingEntry {
56+
const char* const label;
57+
uint64_t const time;
58+
TimingEntry* next = nullptr;
59+
~TimingEntry() {
60+
delete next;
61+
}
62+
};
63+
TimingEntry start_time_entry { "Process initialization", uv_hrtime() };
64+
std::atomic<TimingEntry*> current_time_entry { &start_time_entry };
65+
66+
void MarkTime(const char* label) {
67+
TimingEntry* new_entry = new TimingEntry { label, uv_hrtime() };
68+
do {
69+
new_entry->next = current_time_entry.load();
70+
} while(!current_time_entry.compare_exchange_strong(new_entry->next, new_entry));
71+
}
72+
} // anonymous namespace
73+
5374
Local<String> GetBoxednodeMainScriptSource(Isolate* isolate);
5475
Local<Uint8Array> GetBoxednodeCodeCacheBuffer(Isolate* isolate);
5576
std::vector<char> GetBoxednodeSnapshotBlobVector();
77+
78+
void GetTimingData(const FunctionCallbackInfo<Value>& info) {
79+
Isolate* isolate = info.GetIsolate();
80+
TimingEntry* head = current_time_entry.load();
81+
std::vector<Local<Value>> entries;
82+
while (head != nullptr) {
83+
Local<Value> elements[] = {
84+
String::NewFromUtf8(isolate, head->label).ToLocalChecked(),
85+
BigInt::NewFromUnsigned(isolate, head->time)
86+
};
87+
entries.push_back(Array::New(isolate, elements, sizeof(elements)/sizeof(elements[0])));
88+
head = head->next;
89+
}
90+
Local<Array> retval = Array::New(isolate, entries.data(), entries.size());
91+
info.GetReturnValue().Set(retval);
92+
}
93+
94+
void boxednode_linked_bindings_register(
95+
Local<Object> exports,
96+
Local<Value> module,
97+
Local<Context> context,
98+
void* priv) {
99+
NODE_SET_METHOD(exports, "getTimingData", GetTimingData);
100+
}
101+
56102
}
57103

58104
extern "C" {
@@ -97,13 +143,15 @@ static MaybeLocal<Value> LoadBoxednodeEnvironment(Local<Context> context) {
97143
String::NewFromUtf8Literal(isolate, BOXEDNODE_CODE_CACHE_MODE),
98144
boxednode::GetBoxednodeCodeCacheBuffer(isolate),
99145
};
146+
boxednode::MarkTime("Calling entrypoint");
100147
if (entrypoint_ret.As<Function>()->Call(
101148
context,
102149
Null(isolate),
103150
sizeof(trampoline_args) / sizeof(trampoline_args[0]),
104151
trampoline_args).IsEmpty()) {
105152
return {}; // JS exception.
106153
}
154+
boxednode::MarkTime("Called entrypoint");
107155
return Null(isolate);
108156
}
109157
#endif
@@ -160,15 +208,18 @@ static int RunNodeInstance(MultiIsolatePlatform* platform,
160208
#else
161209
loop = uv_default_loop();
162210
#endif
211+
boxednode::MarkTime("Initialized Loop");
163212

164213
std::shared_ptr<ArrayBufferAllocator> allocator =
165214
ArrayBufferAllocator::Create();
166215

167216
#ifdef BOXEDNODE_CONSUME_SNAPSHOT
168217
std::vector<char> snapshot_blob_vec = boxednode::GetBoxednodeSnapshotBlobVector();
218+
boxednode::MarkTime("Decoded snapshot");
169219
assert(EmbedderSnapshotData::CanUseCustomSnapshotPerIsolate());
170220
node::EmbedderSnapshotData::Pointer snapshot_blob =
171221
EmbedderSnapshotData::FromBlob(snapshot_blob_vec);
222+
boxednode::MarkTime("Read snapshot");
172223
Isolate* isolate = NewIsolate(allocator, loop, platform, snapshot_blob.get());
173224
#elif NODE_VERSION_AT_LEAST(14, 0, 0)
174225
Isolate* isolate = NewIsolate(allocator, loop, platform);
@@ -179,6 +230,7 @@ static int RunNodeInstance(MultiIsolatePlatform* platform,
179230
fprintf(stderr, "%s: Failed to initialize V8 Isolate\n", args[0].c_str());
180231
return 1;
181232
}
233+
boxednode::MarkTime("Created Isolate");
182234

183235
{
184236
Locker locker(isolate);
@@ -194,6 +246,7 @@ static int RunNodeInstance(MultiIsolatePlatform* platform,
194246
),
195247
node::FreeIsolateData);
196248

249+
boxednode::MarkTime("Created IsolateData");
197250
HandleScope handle_scope(isolate);
198251
Local<Context> context;
199252
#ifndef BOXEDNODE_CONSUME_SNAPSHOT
@@ -209,6 +262,7 @@ static int RunNodeInstance(MultiIsolatePlatform* platform,
209262
// node::LoadEnvironment() are being called.
210263
Context::Scope context_scope(context);
211264
#endif
265+
boxednode::MarkTime("Created Context");
212266

213267
// Create a node::Environment instance that will later be released using
214268
// node::FreeEnvironment().
@@ -222,6 +276,7 @@ static int RunNodeInstance(MultiIsolatePlatform* platform,
222276
Context::Scope context_scope(context);
223277
#endif
224278
assert(isolate->InContext());
279+
boxednode::MarkTime("Created Environment");
225280

226281
const void* node_mod;
227282
const void* napi_mod;
@@ -238,6 +293,11 @@ static int RunNodeInstance(MultiIsolatePlatform* platform,
238293
AddLinkedBinding(env.get(), *static_cast<const napi_module*>(napi_mod));
239294
#endif
240295
}
296+
AddLinkedBinding(
297+
env.get(),
298+
"boxednode_linked_bindings",
299+
boxednode::boxednode_linked_bindings_register, nullptr);
300+
boxednode::MarkTime("Added bindings");
241301

242302
// Set up the Node.js instance for execution, and run code inside of it.
243303
// There is also a variant that takes a callback and provides it with
@@ -251,6 +311,7 @@ static int RunNodeInstance(MultiIsolatePlatform* platform,
251311
if (LoadBoxednodeEnvironment(context).IsEmpty()) {
252312
return 1; // There has been a JS exception.
253313
}
314+
boxednode::MarkTime("Loaded Environment, entering loop");
254315

255316
{
256317
// SealHandleScope protects against handle leaks from callbacks.
@@ -335,11 +396,13 @@ static int BoxednodeMain(std::vector<std::string> args) {
335396
if (args.size() > 1)
336397
args.insert(args.begin() + 1, "--openssl-shared-config");
337398
#endif
399+
boxednode::MarkTime("Start InitializeOncePerProcess");
338400
auto result = node::InitializeOncePerProcess(args, {
339401
node::ProcessInitializationFlags::kNoInitializeV8,
340402
node::ProcessInitializationFlags::kNoInitializeNodeV8Platform,
341403
node::ProcessInitializationFlags::kNoPrintHelpOrVersionOutput
342404
});
405+
boxednode::MarkTime("Finished InitializeOncePerProcess");
343406
for (const std::string& error : result->errors())
344407
fprintf(stderr, "%s: %s\n", args[0].c_str(), error.c_str());
345408
if (result->exit_code() != 0) {
@@ -364,6 +427,7 @@ static int BoxednodeMain(std::vector<std::string> args) {
364427
V8::InitializePlatform(platform.get());
365428
V8::Initialize();
366429

430+
boxednode::MarkTime("Initialized V8");
367431
// See below for the contents of this function.
368432
int ret = RunNodeInstance(platform.get(), args, exec_args);
369433

@@ -412,6 +476,7 @@ int wmain(int argc, wchar_t* wargv[]) {
412476
int main(int argc, char** argv) {
413477
argv = uv_setup_args(argc, argv);
414478
std::vector<std::string> args(argv, argv + argc);
479+
boxednode::MarkTime("Enter BoxednodeMain");
415480
return BoxednodeMain(std::move(args));
416481
}
417482
#endif

test/index.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,17 @@ describe('basic functionality', () => {
9696
assert(stdout.includes('bananananana'), `Missed process.title change in ${stdout}`);
9797
proc.kill();
9898
}
99+
100+
{
101+
const { stdout } = await execFile(
102+
path.resolve(__dirname, `resources/example${exeSuffix}`), [
103+
'process.boxednode.markTime("running js");JSON.stringify(process.boxednode.getTimingData())'
104+
],
105+
{ encoding: 'utf8' });
106+
const timingData = JSON.parse(stdout);
107+
assert.strictEqual(timingData[0][0], 'Process initialization');
108+
assert.strictEqual(timingData[timingData.length - 1][0], 'running js');
109+
}
99110
});
100111

101112
it('works with a Nan addon', async function () {
@@ -216,11 +227,12 @@ describe('basic functionality', () => {
216227
const { stdout } = await execFile(
217228
path.resolve(__dirname, `resources/snapshot-echo-args${exeSuffix}`), ['a', 'b', 'c'],
218229
{ encoding: 'utf8' });
219-
const { currentArgv, originalArgv } = JSON.parse(stdout);
230+
const { currentArgv, originalArgv, timingData } = JSON.parse(stdout);
220231
assert(currentArgv[0].includes('snapshot-echo-args'));
221232
assert(currentArgv[1].includes('snapshot-echo-args'));
222233
assert.deepStrictEqual(currentArgv.slice(2), ['a', 'b', 'c']);
223234
assert.strictEqual(originalArgv.length, 2); // [execPath, execPath]
235+
assert.strictEqual(timingData[0][0], 'Process initialization');
224236
}
225237
});
226238
});

test/resources/snapshot-echo-args.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ const originalArgv = [...process.argv];
77
setDeserializeMainFunction(() => {
88
console.log(JSON.stringify({
99
currentArgv: process.argv,
10-
originalArgv
10+
originalArgv,
11+
timingData: process.boxednode.getTimingData()
1112
}));
1213
});

0 commit comments

Comments
 (0)