Skip to content

Commit 1e0195f

Browse files
joyeecheungeliphazbouye
authored andcommitted
src: add utilities to help debugging reproducibility of snapshots
- Print offsets in blob serializer - Add a special node:generate_default_snapshot ID to generate the built-in snapshot. - Improve logging - Add a test to check the reproducibilty of the snapshot PR-URL: nodejs#50983 Refs: nodejs/build#3043 Reviewed-By: Daniel Lemire <daniel@lemire.me> Reviewed-By: James M Snell <jasnell@gmail.com>
1 parent 3848e04 commit 1e0195f

File tree

4 files changed

+110
-19
lines changed

4 files changed

+110
-19
lines changed

src/blob_serializer_deserializer-inl.h

+19-5
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,8 @@ size_t BlobSerializer<Impl>::WriteVector(const std::vector<T>& data) {
238238
if (is_debug) {
239239
std::string str = std::is_arithmetic_v<T> ? "" : ToStr(data);
240240
std::string name = GetName<T>();
241-
Debug("\nWriteVector<%s>() (%d-byte), count=%d: %s\n",
241+
Debug("\nAt 0x%x: WriteVector<%s>() (%d-byte), count=%d: %s\n",
242+
sink.size(),
242243
name.c_str(),
243244
sizeof(T),
244245
data.size(),
@@ -270,7 +271,10 @@ size_t BlobSerializer<Impl>::WriteVector(const std::vector<T>& data) {
270271
template <typename Impl>
271272
size_t BlobSerializer<Impl>::WriteStringView(std::string_view data,
272273
StringLogMode mode) {
273-
Debug("WriteStringView(), length=%zu: %p\n", data.size(), data.data());
274+
Debug("At 0x%x: WriteStringView(), length=%zu: %p\n",
275+
sink.size(),
276+
data.size(),
277+
data.data());
274278
size_t written_total = WriteArithmetic<size_t>(data.size());
275279

276280
size_t length = data.size();
@@ -294,17 +298,27 @@ size_t BlobSerializer<Impl>::WriteString(const std::string& data) {
294298
return WriteStringView(data, StringLogMode::kAddressAndContent);
295299
}
296300

301+
static size_t kPreviewCount = 16;
302+
297303
// Helper for writing an array of numeric types.
298304
template <typename Impl>
299305
template <typename T>
300306
size_t BlobSerializer<Impl>::WriteArithmetic(const T* data, size_t count) {
301307
static_assert(std::is_arithmetic_v<T>, "Arithmetic type");
302308
DCHECK_GT(count, 0); // Should not write contents for vectors of size 0.
303309
if (is_debug) {
304-
std::string str =
305-
"{ " + std::to_string(data[0]) + (count > 1 ? ", ... }" : " }");
310+
size_t preview_count = count < kPreviewCount ? count : kPreviewCount;
311+
std::string str = "{ ";
312+
for (size_t i = 0; i < preview_count; ++i) {
313+
str += (std::to_string(data[i]) + ",");
314+
}
315+
if (count > preview_count) {
316+
str += "...";
317+
}
318+
str += "}";
306319
std::string name = GetName<T>();
307-
Debug("Write<%s>() (%zu-byte), count=%zu: %s",
320+
Debug("At 0x%x: Write<%s>() (%zu-byte), count=%zu: %s",
321+
sink.size(),
308322
name.c_str(),
309323
sizeof(T),
310324
count,

src/node.cc

+15-9
Original file line numberDiff line numberDiff line change
@@ -1288,18 +1288,24 @@ ExitCode GenerateAndWriteSnapshotData(const SnapshotData** snapshot_data_ptr,
12881288
return exit_code;
12891289
}
12901290
} else {
1291+
std::optional<std::string> builder_script_content;
12911292
// Otherwise, load and run the specified builder script.
12921293
std::unique_ptr<SnapshotData> generated_data =
12931294
std::make_unique<SnapshotData>();
1294-
std::string builder_script_content;
1295-
int r = ReadFileSync(&builder_script_content, builder_script.c_str());
1296-
if (r != 0) {
1297-
FPrintF(stderr,
1298-
"Cannot read builder script %s for building snapshot. %s: %s",
1299-
builder_script,
1300-
uv_err_name(r),
1301-
uv_strerror(r));
1302-
return ExitCode::kGenericUserError;
1295+
if (builder_script != "node:generate_default_snapshot") {
1296+
builder_script_content = std::string();
1297+
int r = ReadFileSync(&(builder_script_content.value()),
1298+
builder_script.c_str());
1299+
if (r != 0) {
1300+
FPrintF(stderr,
1301+
"Cannot read builder script %s for building snapshot. %s: %s\n",
1302+
builder_script,
1303+
uv_err_name(r),
1304+
uv_strerror(r));
1305+
return ExitCode::kGenericUserError;
1306+
}
1307+
} else {
1308+
snapshot_config.builder_script_path = std::nullopt;
13031309
}
13041310

13051311
exit_code = node::SnapshotBuilder::Generate(generated_data.get(),

src/node_snapshotable.cc

+6-5
Original file line numberDiff line numberDiff line change
@@ -599,16 +599,17 @@ std::vector<char> SnapshotData::ToBlob() const {
599599
size_t written_total = 0;
600600

601601
// Metadata
602-
w.Debug("Write magic %" PRIx32 "\n", kMagic);
602+
w.Debug("0x%x: Write magic %" PRIx32 "\n", w.sink.size(), kMagic);
603603
written_total += w.WriteArithmetic<uint32_t>(kMagic);
604-
w.Debug("Write metadata\n");
604+
w.Debug("0x%x: Write metadata\n", w.sink.size());
605605
written_total += w.Write<SnapshotMetadata>(metadata);
606-
606+
w.Debug("0x%x: Write snapshot blob\n", w.sink.size());
607607
written_total += w.Write<v8::StartupData>(v8_snapshot_blob_data);
608-
w.Debug("Write isolate_data_indices\n");
608+
w.Debug("0x%x: Write IsolateDataSerializeInfo\n", w.sink.size());
609609
written_total += w.Write<IsolateDataSerializeInfo>(isolate_data_info);
610+
w.Debug("0x%x: Write EnvSerializeInfo\n", w.sink.size());
610611
written_total += w.Write<EnvSerializeInfo>(env_info);
611-
w.Debug("Write code_cache\n");
612+
w.Debug("0x%x: Write CodeCacheInfo\n", w.sink.size());
612613
written_total += w.WriteVector<builtins::CodeCacheInfo>(code_cache);
613614
w.Debug("SnapshotData::ToBlob() Wrote %d bytes\n", written_total);
614615

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
'use strict';
2+
3+
require('../common');
4+
const { spawnSyncAndAssert } = require('../common/child_process');
5+
const tmpdir = require('../common/tmpdir');
6+
const fs = require('fs');
7+
const assert = require('assert');
8+
9+
// When the test fails this helper can be modified to write outputs
10+
// differently and aid debugging.
11+
function log(line) {
12+
console.log(line);
13+
}
14+
15+
function generateSnapshot() {
16+
tmpdir.refresh();
17+
18+
spawnSyncAndAssert(
19+
process.execPath,
20+
[
21+
'--random_seed=42',
22+
'--predictable',
23+
'--build-snapshot',
24+
'node:generate_default_snapshot',
25+
],
26+
{
27+
env: { ...process.env, NODE_DEBUG_NATIVE: 'SNAPSHOT_SERDES' },
28+
cwd: tmpdir.path
29+
},
30+
{
31+
stderr(output) {
32+
const lines = output.split('\n');
33+
for (const line of lines) {
34+
if (line.startsWith('0x')) {
35+
log(line);
36+
}
37+
}
38+
},
39+
}
40+
);
41+
const blobPath = tmpdir.resolve('snapshot.blob');
42+
return fs.readFileSync(blobPath);
43+
}
44+
45+
const buf1 = generateSnapshot();
46+
const buf2 = generateSnapshot();
47+
48+
const diff = [];
49+
let offset = 0;
50+
const step = 16;
51+
do {
52+
const length = Math.min(buf1.length - offset, step);
53+
const slice1 = buf1.slice(offset, offset + length).toString('hex');
54+
const slice2 = buf2.slice(offset, offset + length).toString('hex');
55+
if (slice1 !== slice2) {
56+
diff.push({ offset: '0x' + (offset).toString(16), slice1, slice2 });
57+
}
58+
offset += length;
59+
} while (offset < buf1.length);
60+
61+
assert.strictEqual(offset, buf1.length);
62+
if (offset < buf2.length) {
63+
const length = Math.min(buf2.length - offset, step);
64+
const slice2 = buf2.slice(offset, offset + length).toString('hex');
65+
diff.push({ offset, slice1: '', slice2 });
66+
offset += length;
67+
} while (offset < buf2.length);
68+
69+
assert.deepStrictEqual(diff, []);
70+
assert.strictEqual(buf1.length, buf2.length);

0 commit comments

Comments
 (0)