Skip to content

Commit bfe877e

Browse files
committed
fix(telemetry): make flight recorder big-endian on all platforms
1 parent fbe7edf commit bfe877e

File tree

3 files changed

+76
-63
lines changed

3 files changed

+76
-63
lines changed

packages/telemetry/src/flight-recorder.js

+54-49
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,13 @@ const { details: X } = assert;
2222

2323
export const DEFAULT_CIRCULAR_BUFFER_SIZE = 100 * 1024 * 1024;
2424
export const DEFAULT_CIRCULAR_BUFFER_FILE = 'flight-recorder.bin';
25-
export const SLOG_MAGIC = 0x21474f4c532d4741n; // 'AG-SLOG!'
25+
export const SLOG_MAGIC = 0x41472d534c4f4721n; // 'AG-SLOG!'
2626

27-
const I_MAGIC = 0;
28-
const I_ARENA_SIZE = 1;
29-
const I_CIRC_START = 2;
30-
const I_CIRC_END = 3;
31-
const HEADER_LENGTH = 4;
27+
const I_MAGIC = 0 * BigUint64Array.BYTES_PER_ELEMENT;
28+
const I_ARENA_SIZE = 1 * BigUint64Array.BYTES_PER_ELEMENT;
29+
const I_CIRC_START = 2 * BigUint64Array.BYTES_PER_ELEMENT;
30+
const I_CIRC_END = 3 * BigUint64Array.BYTES_PER_ELEMENT;
31+
const I_ARENA_START = 4 * BigUint64Array.BYTES_PER_ELEMENT;
3232

3333
const initializeCircularBuffer = async (bufferFile, circularBufferSize) => {
3434
if (!circularBufferSize) {
@@ -41,25 +41,24 @@ const initializeCircularBuffer = async (bufferFile, circularBufferSize) => {
4141
}
4242
throw e;
4343
});
44-
const arenaSize = BigInt(
45-
circularBufferSize - HEADER_LENGTH * BigUint64Array.BYTES_PER_ELEMENT,
46-
);
44+
const arenaSize = BigInt(circularBufferSize - I_ARENA_START);
4745

4846
const writeHeader = async () => {
49-
if (
50-
stbuf &&
51-
stbuf.size >= HEADER_LENGTH * BigUint64Array.BYTES_PER_ELEMENT
52-
) {
47+
if (stbuf && stbuf.size >= I_ARENA_START) {
5348
// Header already exists.
5449
return;
5550
}
5651

5752
// Write the header.
58-
const header = new Array(HEADER_LENGTH).fill(0n);
59-
header[I_MAGIC] = SLOG_MAGIC;
60-
header[I_ARENA_SIZE] = arenaSize;
53+
const headerBuf = new Uint8Array(I_ARENA_START);
54+
const header = new DataView(headerBuf.buffer);
55+
header.setBigUint64(I_MAGIC, SLOG_MAGIC);
56+
header.setBigUint64(I_ARENA_SIZE, arenaSize);
57+
header.setBigUint64(I_CIRC_START, 0n);
58+
header.setBigUint64(I_CIRC_END, 0n);
59+
6160
await fsPromises.mkdir(path.dirname(bufferFile), { recursive: true });
62-
await fsPromises.writeFile(bufferFile, BigUint64Array.from(header));
61+
await fsPromises.writeFile(bufferFile, headerBuf);
6362
};
6463
await writeHeader();
6564

@@ -92,20 +91,22 @@ export const makeMemoryMappedCircularBuffer = async ({
9291

9392
/** @type {Uint8Array} */
9493
const fileBuf = BufferFromFile(bufferFile).Uint8Array();
95-
const header = new BigUint64Array(fileBuf.buffer, 0, HEADER_LENGTH);
94+
const header = new DataView(fileBuf.buffer, 0, I_ARENA_START);
9695

9796
// Detect the arena size from the header, if not initialized.
98-
const arenaSize = newArenaSize || header[I_ARENA_SIZE];
97+
const hdrArenaSize = header.getBigUint64(I_ARENA_SIZE);
98+
const arenaSize = newArenaSize || hdrArenaSize;
9999

100+
const hdrMagic = header.getBigUint64(I_MAGIC);
100101
assert.equal(
101102
SLOG_MAGIC,
102-
header[I_MAGIC],
103-
X`${bufferFile} is not a slog buffer; wanted magic ${SLOG_MAGIC}, got ${header[I_MAGIC]}`,
103+
hdrMagic,
104+
X`${bufferFile} is not a slog buffer; wanted magic ${SLOG_MAGIC}, got ${hdrMagic}`,
104105
);
105106
assert.equal(
106107
arenaSize,
107-
header[I_ARENA_SIZE],
108-
X`${bufferFile} arena size mismatch; wanted ${arenaSize}, got ${header[I_ARENA_SIZE]}`,
108+
hdrArenaSize,
109+
X`${bufferFile} arena size mismatch; wanted ${arenaSize}, got ${hdrArenaSize}`,
109110
);
110111
const arena = new Uint8Array(
111112
fileBuf.buffer,
@@ -126,21 +127,19 @@ export const makeMemoryMappedCircularBuffer = async ({
126127

127128
// Read the data to the end of the arena.
128129
let firstReadLength = data.byteLength;
129-
const circStart = Number(header[I_CIRC_START]);
130-
const readStart = (circStart + offset) % Number(arenaSize);
131-
if (header[I_CIRC_START] > header[I_CIRC_END]) {
130+
const circStart = header.getBigUint64(I_CIRC_START);
131+
const circEnd = header.getBigUint64(I_CIRC_END);
132+
const readStart = (Number(circStart) + offset) % Number(arenaSize);
133+
if (circStart > circEnd) {
132134
// The data is wrapped around the end of the arena, like BBB---AAA
133135
firstReadLength = Math.min(
134136
firstReadLength,
135137
Number(arenaSize) - readStart,
136138
);
137-
if (readStart >= header[I_CIRC_END] && readStart < header[I_CIRC_START]) {
139+
if (readStart >= circEnd && readStart < circStart) {
138140
return { done: true, value: undefined };
139141
}
140-
} else if (
141-
readStart < header[I_CIRC_START] ||
142-
readStart >= header[I_CIRC_END]
143-
) {
142+
} else if (readStart < circStart || readStart >= circEnd) {
144143
// The data is contiguous, like ---AAABBB---
145144
return { done: true, value: undefined };
146145
}
@@ -168,58 +167,64 @@ export const makeMemoryMappedCircularBuffer = async ({
168167
const record = new Uint8Array(
169168
BigUint64Array.BYTES_PER_ELEMENT + data.byteLength,
170169
);
171-
const lengthPrefix = new BigUint64Array(record.buffer, 0, 1);
172-
lengthPrefix[0] = BigInt(data.byteLength);
173170
record.set(data, BigUint64Array.BYTES_PER_ELEMENT);
174171

172+
const lengthPrefix = new DataView(record.buffer);
173+
lengthPrefix.setBigUint64(0, BigInt(data.byteLength));
174+
175175
// Check if we need to wrap around.
176176
/** @type {bigint} */
177177
let capacity;
178-
if (header[I_CIRC_START] <= header[I_CIRC_END]) {
178+
let circStart = header.getBigUint64(I_CIRC_START);
179+
const circEnd = header.getBigUint64(I_CIRC_END);
180+
if (circStart <= circEnd) {
179181
// ---AAAABBBB----
180-
capacity =
181-
header[I_ARENA_SIZE] - header[I_CIRC_END] + header[I_CIRC_START];
182+
capacity = arenaSize - circEnd + circStart;
182183
} else {
183184
// BBB---AAAA
184-
capacity = header[I_CIRC_START] - header[I_CIRC_END];
185+
capacity = circStart - circEnd;
185186
}
186187

187188
// Advance the start pointer until we have space to write the record.
188189
let overlap = BigInt(record.byteLength) - capacity;
189190
while (overlap > 0n) {
190-
const startRecordLength = new BigUint64Array(1);
191-
const { done } = readCircBuf(new Uint8Array(startRecordLength.buffer));
191+
const startRecordLength = new Uint8Array(
192+
BigUint64Array.BYTES_PER_ELEMENT,
193+
);
194+
const { done } = readCircBuf(startRecordLength);
192195
if (done) {
193196
break;
194197
}
195198

199+
const dv = new DataView(startRecordLength.buffer);
196200
const totalRecordLength =
197201
BigInt(startRecordLength.byteLength) + // size of the length field
198-
startRecordLength[0]; // size of the record
202+
dv.getBigUint64(0); // size of the record
199203

200-
header[I_CIRC_START] =
201-
(header[I_CIRC_START] + totalRecordLength) % header[I_ARENA_SIZE];
204+
circStart = (circStart + totalRecordLength) % arenaSize;
205+
header.setBigUint64(I_CIRC_START, circStart);
202206
overlap -= totalRecordLength;
203207
}
204208

205209
// Append the record.
206210
let firstWriteLength = record.byteLength;
207-
if (header[I_CIRC_START] < header[I_CIRC_END]) {
211+
if (circStart < circEnd) {
208212
// May need to wrap, it's ---AAAABBBB---
209213
firstWriteLength = Math.min(
210214
firstWriteLength,
211-
Number(header[I_ARENA_SIZE] - header[I_CIRC_END]),
215+
Number(arenaSize - circEnd),
212216
);
213217
}
214218

215-
const circEnd = Number(header[I_CIRC_END]);
216-
arena.set(record.subarray(0, firstWriteLength), circEnd);
219+
arena.set(record.subarray(0, firstWriteLength), Number(circEnd));
217220
if (firstWriteLength < record.byteLength) {
218221
// Write to the beginning of the arena.
219222
arena.set(record.subarray(firstWriteLength, record.byteLength), 0);
220223
}
221-
header[I_CIRC_END] =
222-
(header[I_CIRC_END] + BigInt(record.byteLength)) % header[I_ARENA_SIZE];
224+
header.setBigUint64(
225+
I_CIRC_END,
226+
(circEnd + BigInt(record.byteLength)) % arenaSize,
227+
);
223228
};
224229

225230
const writeJSON = (obj, jsonObj) => {
@@ -231,7 +236,7 @@ export const makeMemoryMappedCircularBuffer = async ({
231236
}
232237
// Prepend a newline so that the file can be more easily manipulated.
233238
const data = new TextEncoder().encode(`\n${jsonObj}`);
234-
// console.log('have obj', obj);
239+
// console.log('have obj', obj, data);
235240
writeCircBuf(data);
236241
};
237242

packages/telemetry/src/frcat-entrypoint.js

+5-4
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,21 @@ const main = async () => {
1313
}
1414

1515
for await (const file of files) {
16-
const { readCircBuf } = makeMemoryMappedCircularBuffer({
16+
const { readCircBuf } = await makeMemoryMappedCircularBuffer({
1717
circularBufferFile: file,
1818
circularBufferSize: null,
1919
});
2020

2121
let offset = 0;
2222
for (;;) {
23-
const lenBuf = new BigUint64Array(1);
24-
const { done } = readCircBuf(new Uint8Array(lenBuf.buffer), offset);
23+
const lenBuf = new Uint8Array(BigUint64Array.BYTES_PER_ELEMENT);
24+
const { done } = readCircBuf(lenBuf, offset);
2525
if (done) {
2626
break;
2727
}
2828
offset += 8;
29-
const len = Number(lenBuf[0]);
29+
const dv = new DataView(lenBuf.buffer);
30+
const len = Number(dv.getBigUint64(0));
3031

3132
const { done: done2, value: buf } = readCircBuf(
3233
new Uint8Array(len),

packages/telemetry/test/test-flight-recorder.js

+17-10
Original file line numberDiff line numberDiff line change
@@ -14,35 +14,42 @@ test('flight-recorder sanity', async t => {
1414
});
1515
slogSender({ type: 'start' });
1616

17-
const len0 = new BigUint64Array(1);
18-
const { done: done0 } = readCircBuf(new Uint8Array(len0.buffer));
17+
const len0 = new Uint8Array(BigUint64Array.BYTES_PER_ELEMENT);
18+
const { done: done0 } = readCircBuf(len0);
1919
t.false(done0, 'readCircBuf should not be done');
20-
const buf0 = new Uint8Array(Number(len0[0]));
20+
const dv0 = new DataView(len0.buffer);
21+
const buf0 = new Uint8Array(Number(dv0.getBigUint64(0)));
2122
const { done: done0b } = readCircBuf(buf0, len0.byteLength);
2223
t.false(done0b, 'readCircBuf should not be done');
2324
const buf0Str = new TextDecoder().decode(buf0);
24-
t.is(buf0Str, `\n{"type":"start"}`);
25+
t.is(buf0Str, `\n{"type":"start"}`, `start compare failed`);
2526

2627
const last = 500;
2728
for (let i = 0; i < last; i += 1) {
2829
slogSender({ type: 'iteration', iteration: i });
2930
}
3031

3132
let offset = 0;
32-
const len1 = new BigUint64Array(1);
33+
const len1 = new Uint8Array(BigUint64Array.BYTES_PER_ELEMENT);
3334
for (let i = 490; i < last; i += 1) {
34-
const { done: done1 } = readCircBuf(new Uint8Array(len1.buffer), offset);
35+
const { done: done1 } = readCircBuf(len1, offset);
3536
offset += len1.byteLength;
3637
t.false(done1, `readCircBuf ${i} should not be done`);
37-
const buf1 = new Uint8Array(Number(len1[0]));
38+
const dv1 = new DataView(len1.buffer);
39+
const buf1 = new Uint8Array(Number(dv1.getBigUint64(0)));
3840
const { done: done1b } = readCircBuf(buf1, offset);
3941
offset += buf1.byteLength;
4042
t.false(done1b, `readCircBuf ${i} should not be done`);
4143
const buf1Str = new TextDecoder().decode(buf1);
42-
t.is(buf1Str, `\n{"type":"iteration","iteration":${i}}`);
44+
t.is(
45+
buf1Str,
46+
`\n{"type":"iteration","iteration":${i}}`,
47+
`iteration ${i} compare failed`,
48+
);
4349
}
4450

45-
const { done: done2 } = readCircBuf(new Uint8Array(len1.buffer), offset);
51+
const { done: done2 } = readCircBuf(len1, offset);
4652
t.assert(done2, `readCircBuf ${last} should be done`);
47-
removeCallback();
53+
console.log({ tmpFile });
54+
// removeCallback();
4855
});

0 commit comments

Comments
 (0)