mirror of
https://github.com/nodejs/node.git
synced 2026-10-14 10:26:26 -04:00
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: https://github.com/nodejs/node/pull/50983 Refs: https://github.com/nodejs/build/issues/3043 Reviewed-By: Daniel Lemire <daniel@lemire.me> Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
1 parent
4e58cde589
commit
1872167bb7
4 files changed
+110
-19
No files matched your search
@@ -238,7 +238,8 @@ size_t BlobSerializer<Impl>::WriteVector(const std::vector<T>& data) {
|
||||
if (is_debug) {
|
||||
std::string str = std::is_arithmetic_v<T> ? "" : ToStr(data);
|
||||
std::string name = GetName<T>();
|
||||
Debug("\nWriteVector<%s>() (%d-byte), count=%d: %s\n",
|
||||
Debug("\nAt 0x%x: WriteVector<%s>() (%d-byte), count=%d: %s\n",
|
||||
sink.size(),
|
||||
name.c_str(),
|
||||
sizeof(T),
|
||||
data.size(),
|
||||
@@ -270,7 +271,10 @@ size_t BlobSerializer<Impl>::WriteVector(const std::vector<T>& data) {
|
||||
template <typename Impl>
|
||||
size_t BlobSerializer<Impl>::WriteStringView(std::string_view data,
|
||||
StringLogMode mode) {
|
||||
Debug("WriteStringView(), length=%zu: %p\n", data.size(), data.data());
|
||||
Debug("At 0x%x: WriteStringView(), length=%zu: %p\n",
|
||||
sink.size(),
|
||||
data.size(),
|
||||
data.data());
|
||||
size_t written_total = WriteArithmetic<size_t>(data.size());
|
||||
|
||||
size_t length = data.size();
|
||||
@@ -294,6 +298,8 @@ size_t BlobSerializer<Impl>::WriteString(const std::string& data) {
|
||||
return WriteStringView(data, StringLogMode::kAddressAndContent);
|
||||
}
|
||||
|
||||
static size_t kPreviewCount = 16;
|
||||
|
||||
// Helper for writing an array of numeric types.
|
||||
template <typename Impl>
|
||||
template <typename T>
|
||||
@@ -301,10 +307,18 @@ size_t BlobSerializer<Impl>::WriteArithmetic(const T* data, size_t count) {
|
||||
static_assert(std::is_arithmetic_v<T>, "Arithmetic type");
|
||||
DCHECK_GT(count, 0); // Should not write contents for vectors of size 0.
|
||||
if (is_debug) {
|
||||
std::string str =
|
||||
"{ " + std::to_string(data[0]) + (count > 1 ? ", ... }" : " }");
|
||||
size_t preview_count = count < kPreviewCount ? count : kPreviewCount;
|
||||
std::string str = "{ ";
|
||||
for (size_t i = 0; i < preview_count; ++i) {
|
||||
str += (std::to_string(data[i]) + ",");
|
||||
}
|
||||
if (count > preview_count) {
|
||||
str += "...";
|
||||
}
|
||||
str += "}";
|
||||
std::string name = GetName<T>();
|
||||
Debug("Write<%s>() (%zu-byte), count=%zu: %s",
|
||||
Debug("At 0x%x: Write<%s>() (%zu-byte), count=%zu: %s",
|
||||
sink.size(),
|
||||
name.c_str(),
|
||||
sizeof(T),
|
||||
count,
|
||||
|
||||
+15
-9
@@ -1288,18 +1288,24 @@ ExitCode GenerateAndWriteSnapshotData(const SnapshotData** snapshot_data_ptr,
|
||||
return exit_code;
|
||||
}
|
||||
} else {
|
||||
std::optional<std::string> builder_script_content;
|
||||
// Otherwise, load and run the specified builder script.
|
||||
std::unique_ptr<SnapshotData> generated_data =
|
||||
std::make_unique<SnapshotData>();
|
||||
std::string builder_script_content;
|
||||
int r = ReadFileSync(&builder_script_content, builder_script.c_str());
|
||||
if (r != 0) {
|
||||
FPrintF(stderr,
|
||||
"Cannot read builder script %s for building snapshot. %s: %s",
|
||||
builder_script,
|
||||
uv_err_name(r),
|
||||
uv_strerror(r));
|
||||
return ExitCode::kGenericUserError;
|
||||
if (builder_script != "node:generate_default_snapshot") {
|
||||
builder_script_content = std::string();
|
||||
int r = ReadFileSync(&(builder_script_content.value()),
|
||||
builder_script.c_str());
|
||||
if (r != 0) {
|
||||
FPrintF(stderr,
|
||||
"Cannot read builder script %s for building snapshot. %s: %s\n",
|
||||
builder_script,
|
||||
uv_err_name(r),
|
||||
uv_strerror(r));
|
||||
return ExitCode::kGenericUserError;
|
||||
}
|
||||
} else {
|
||||
snapshot_config.builder_script_path = std::nullopt;
|
||||
}
|
||||
|
||||
exit_code = node::SnapshotBuilder::Generate(generated_data.get(),
|
||||
|
||||
@@ -599,16 +599,17 @@ std::vector<char> SnapshotData::ToBlob() const {
|
||||
size_t written_total = 0;
|
||||
|
||||
// Metadata
|
||||
w.Debug("Write magic %" PRIx32 "\n", kMagic);
|
||||
w.Debug("0x%x: Write magic %" PRIx32 "\n", w.sink.size(), kMagic);
|
||||
written_total += w.WriteArithmetic<uint32_t>(kMagic);
|
||||
w.Debug("Write metadata\n");
|
||||
w.Debug("0x%x: Write metadata\n", w.sink.size());
|
||||
written_total += w.Write<SnapshotMetadata>(metadata);
|
||||
|
||||
w.Debug("0x%x: Write snapshot blob\n", w.sink.size());
|
||||
written_total += w.Write<v8::StartupData>(v8_snapshot_blob_data);
|
||||
w.Debug("Write isolate_data_indices\n");
|
||||
w.Debug("0x%x: Write IsolateDataSerializeInfo\n", w.sink.size());
|
||||
written_total += w.Write<IsolateDataSerializeInfo>(isolate_data_info);
|
||||
w.Debug("0x%x: Write EnvSerializeInfo\n", w.sink.size());
|
||||
written_total += w.Write<EnvSerializeInfo>(env_info);
|
||||
w.Debug("Write code_cache\n");
|
||||
w.Debug("0x%x: Write CodeCacheInfo\n", w.sink.size());
|
||||
written_total += w.WriteVector<builtins::CodeCacheInfo>(code_cache);
|
||||
w.Debug("SnapshotData::ToBlob() Wrote %d bytes\n", written_total);
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
'use strict';
|
||||
|
||||
require('../common');
|
||||
const { spawnSyncAndAssert } = require('../common/child_process');
|
||||
const tmpdir = require('../common/tmpdir');
|
||||
const fs = require('fs');
|
||||
const assert = require('assert');
|
||||
|
||||
// When the test fails this helper can be modified to write outputs
|
||||
// differently and aid debugging.
|
||||
function log(line) {
|
||||
console.log(line);
|
||||
}
|
||||
|
||||
function generateSnapshot() {
|
||||
tmpdir.refresh();
|
||||
|
||||
spawnSyncAndAssert(
|
||||
process.execPath,
|
||||
[
|
||||
'--random_seed=42',
|
||||
'--predictable',
|
||||
'--build-snapshot',
|
||||
'node:generate_default_snapshot',
|
||||
],
|
||||
{
|
||||
env: { ...process.env, NODE_DEBUG_NATIVE: 'SNAPSHOT_SERDES' },
|
||||
cwd: tmpdir.path
|
||||
},
|
||||
{
|
||||
stderr(output) {
|
||||
const lines = output.split('\n');
|
||||
for (const line of lines) {
|
||||
if (line.startsWith('0x')) {
|
||||
log(line);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
);
|
||||
const blobPath = tmpdir.resolve('snapshot.blob');
|
||||
return fs.readFileSync(blobPath);
|
||||
}
|
||||
|
||||
const buf1 = generateSnapshot();
|
||||
const buf2 = generateSnapshot();
|
||||
|
||||
const diff = [];
|
||||
let offset = 0;
|
||||
const step = 16;
|
||||
do {
|
||||
const length = Math.min(buf1.length - offset, step);
|
||||
const slice1 = buf1.slice(offset, offset + length).toString('hex');
|
||||
const slice2 = buf2.slice(offset, offset + length).toString('hex');
|
||||
if (slice1 !== slice2) {
|
||||
diff.push({ offset: '0x' + (offset).toString(16), slice1, slice2 });
|
||||
}
|
||||
offset += length;
|
||||
} while (offset < buf1.length);
|
||||
|
||||
assert.strictEqual(offset, buf1.length);
|
||||
if (offset < buf2.length) {
|
||||
const length = Math.min(buf2.length - offset, step);
|
||||
const slice2 = buf2.slice(offset, offset + length).toString('hex');
|
||||
diff.push({ offset, slice1: '', slice2 });
|
||||
offset += length;
|
||||
} while (offset < buf2.length);
|
||||
|
||||
assert.deepStrictEqual(diff, []);
|
||||
assert.strictEqual(buf1.length, buf2.length);
|
||||
Reference in new issue
Block a user