Skip to content

Commit 723ef00

Browse files
authored
Make binary profile format parsing use Uint8Array instead of ArrayBuffer (#5678)
Accepting an ArrayBuffer is a bit restrictive because it means the buffer has to be sized to exactly contain the profile bytes and nothing else. Accept a Uint8Array is more flexible because the Uint8Array can refer to just the sub-part of the buffer that actually contains the profile. This lets us avoid a copy in symbolicator-cli.
2 parents 611c19f + 722c3f3 commit 723ef00

File tree

6 files changed

+53
-48
lines changed

6 files changed

+53
-48
lines changed

src/profile-logic/import/art-trace.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -194,12 +194,12 @@ type ArtTrace = {
194194
};
195195

196196
function detectArtTraceFormat(
197-
traceBuffer: ArrayBufferLike
197+
traceBuffer: Uint8Array
198198
): 'regular' | 'streaming' | 'unrecognized' {
199199
try {
200200
const lengthOfExpectedFirstTwoLinesOfSummarySection = '*version\nX\n'
201201
.length;
202-
const firstTwoLinesBuffer = traceBuffer.slice(
202+
const firstTwoLinesBuffer = traceBuffer.subarray(
203203
0,
204204
lengthOfExpectedFirstTwoLinesOfSummarySection
205205
);
@@ -213,7 +213,11 @@ function detectArtTraceFormat(
213213
}
214214

215215
try {
216-
const dataView = new DataView(traceBuffer);
216+
const dataView = new DataView(
217+
traceBuffer.buffer,
218+
traceBuffer.byteOffset,
219+
traceBuffer.byteLength
220+
);
217221
const magic = dataView.getUint32(0, true);
218222
if (magic === TRACE_MAGIC) {
219223
return 'streaming';
@@ -523,9 +527,9 @@ function parseStreamingFormat(reader: ByteReader) {
523527
};
524528
}
525529

526-
function parseArtTrace(buffer: ArrayBufferLike): ArtTrace {
530+
function parseArtTrace(buffer: Uint8Array): ArtTrace {
527531
try {
528-
const reader = new ByteReader(new Uint8Array(buffer));
532+
const reader = new ByteReader(buffer);
529533
switch (detectArtTraceFormat(buffer)) {
530534
case 'regular':
531535
return parseRegularFormat(reader);
@@ -915,13 +919,13 @@ class ThreadBuilder {
915919
}
916920
}
917921

918-
export function isArtTraceFormat(traceBuffer: ArrayBufferLike) {
922+
export function isArtTraceFormat(traceBuffer: Uint8Array) {
919923
return detectArtTraceFormat(traceBuffer) !== 'unrecognized';
920924
}
921925

922926
// Convert an ART trace to the Gecko profile format.
923927
export function convertArtTraceProfile(
924-
traceBuffer: ArrayBufferLike
928+
traceBuffer: Uint8Array
925929
): GeckoProfileVersion11 {
926930
const trace = parseArtTrace(traceBuffer);
927931
const originalIntervalInUsec = procureSamplingInterval(trace);

src/profile-logic/import/simpleperf.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -474,13 +474,17 @@ class FirefoxProfile {
474474
}
475475

476476
export class SimpleperfReportConverter {
477-
buffer: ArrayBufferLike;
477+
buffer: Uint8Array;
478478
bufferView: DataView;
479479
bufferOffset: number = 0;
480480

481-
constructor(buffer: ArrayBufferLike) {
481+
constructor(buffer: Uint8Array) {
482482
this.buffer = buffer;
483-
this.bufferView = new DataView(buffer);
483+
this.bufferView = new DataView(
484+
buffer.buffer,
485+
buffer.byteOffset,
486+
buffer.byteLength
487+
);
484488
}
485489

486490
readUint16LE() {
@@ -509,11 +513,10 @@ export class SimpleperfReportConverter {
509513
}
510514

511515
readRecord(recordSize: number): report.Record {
512-
const recordBuffer = this.buffer.slice(
516+
const recordArray = this.buffer.subarray(
513517
this.bufferOffset,
514518
this.bufferOffset + recordSize
515519
);
516-
const recordArray = new Uint8Array(recordBuffer);
517520
this.bufferOffset += recordSize;
518521

519522
return report.Record.decode(recordArray);
@@ -577,7 +580,7 @@ export class SimpleperfReportConverter {
577580
}
578581

579582
export function convertSimpleperfTraceProfile(
580-
traceBuffer: ArrayBufferLike
583+
traceBuffer: Uint8Array
581584
): Profile {
582585
return new SimpleperfReportConverter(traceBuffer).process();
583586
}

src/profile-logic/process-profile.ts

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1965,27 +1965,33 @@ export async function unserializeProfileOfArbitraryFormat(
19651965
// object is constructed from an ArrayBuffer in a different context... which
19661966
// happens in our tests.
19671967
if (String(arbitraryFormat) === '[object ArrayBuffer]') {
1968-
let arrayBuffer = arbitraryFormat as ArrayBuffer;
1968+
const arrayBuffer = arbitraryFormat as ArrayBuffer;
1969+
arbitraryFormat = new Uint8Array(arrayBuffer);
1970+
}
19691971

1972+
// Handle binary formats.
1973+
if (
1974+
arbitraryFormat instanceof Uint8Array ||
1975+
(globalThis.Buffer && arbitraryFormat instanceof globalThis.Buffer)
1976+
) {
19701977
// Check for the gzip magic number in the header. If we find it, decompress
19711978
// the data first.
1972-
const profileBytes = new Uint8Array(arrayBuffer);
1979+
let profileBytes = arbitraryFormat as Uint8Array<ArrayBuffer>;
19731980
if (isGzip(profileBytes)) {
1974-
const decompressedProfile = await decompress(profileBytes);
1975-
arrayBuffer = decompressedProfile.buffer;
1981+
profileBytes = await decompress(profileBytes);
19761982
}
19771983

1978-
if (isArtTraceFormat(arrayBuffer)) {
1979-
arbitraryFormat = convertArtTraceProfile(arrayBuffer);
1980-
} else if (verifyMagic(SIMPLEPERF_MAGIC, arrayBuffer)) {
1984+
if (isArtTraceFormat(profileBytes)) {
1985+
arbitraryFormat = convertArtTraceProfile(profileBytes);
1986+
} else if (verifyMagic(SIMPLEPERF_MAGIC, profileBytes)) {
19811987
const { convertSimpleperfTraceProfile } = await import(
19821988
'./import/simpleperf'
19831989
);
1984-
arbitraryFormat = convertSimpleperfTraceProfile(arrayBuffer);
1990+
arbitraryFormat = convertSimpleperfTraceProfile(profileBytes);
19851991
} else {
19861992
try {
19871993
const textDecoder = new TextDecoder(undefined, { fatal: true });
1988-
arbitraryFormat = await textDecoder.decode(arrayBuffer);
1994+
arbitraryFormat = await textDecoder.decode(profileBytes);
19891995
} catch (e) {
19901996
console.error('Source exception:', e);
19911997
throw new Error(

src/symbolicator-cli/index.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,8 @@ export async function run(options: CliOptions) {
8585
// by our importers.
8686
const bytes = fs.readFileSync(options.input, null);
8787

88-
// bytes is a Uint8Array whose underlying ArrayBuffer can be longer than bytes.length.
89-
// Copy the contents into a new ArrayBuffer which is sized correctly, so that we
90-
// don't include uninitialized data from the extra parts of the underlying buffer.
91-
// Alternatively, we could make unserializeProfileOfArbitraryFormat support
92-
// Uint8Array or Buffer in addition to ArrayBuffer.
93-
const byteBufferCopy = Uint8Array.prototype.slice.call(bytes).buffer;
94-
9588
// Load the profile.
96-
const profile = await unserializeProfileOfArbitraryFormat(byteBufferCopy);
89+
const profile = await unserializeProfileOfArbitraryFormat(bytes);
9790
if (profile === undefined) {
9891
throw new Error('Unable to parse the profile.');
9992
}

src/test/unit/profile-conversion.test.ts

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -245,9 +245,8 @@ describe('converting Google Chrome profile', function () {
245245
'src/test/fixtures/upgrades/chrome-tracing.json.gz'
246246
);
247247
const decompressedBuffer = zlib.gunzipSync(compressedBuffer);
248-
const profile = await unserializeProfileOfArbitraryFormat(
249-
decompressedBuffer.buffer
250-
);
248+
const profile =
249+
await unserializeProfileOfArbitraryFormat(decompressedBuffer);
251250
if (profile === undefined) {
252251
throw new Error('Unable to parse the profile.');
253252
}
@@ -263,9 +262,8 @@ describe('converting Google Chrome profile', function () {
263262
'src/test/fixtures/upgrades/chrome-trace-issue-5429.json.gz'
264263
);
265264
const decompressedBuffer = zlib.gunzipSync(compressedBuffer);
266-
const profile = await unserializeProfileOfArbitraryFormat(
267-
decompressedBuffer.buffer
268-
);
265+
const profile =
266+
await unserializeProfileOfArbitraryFormat(decompressedBuffer);
269267
if (profile === undefined) {
270268
throw new Error('Unable to parse the profile.');
271269
}
@@ -425,8 +423,9 @@ describe('converting ART trace', function () {
425423
const buffer = fs.readFileSync(
426424
'src/test/fixtures/upgrades/art-trace-regular.trace.gz'
427425
);
428-
const arrayBuffer = zlib.gunzipSync(buffer).buffer;
429-
const profile = await unserializeProfileOfArbitraryFormat(arrayBuffer);
426+
const uncompressedBytes = zlib.gunzipSync(buffer);
427+
const profile =
428+
await unserializeProfileOfArbitraryFormat(uncompressedBytes);
430429
if (profile === undefined) {
431430
throw new Error('Unable to parse the profile.');
432431
}
@@ -440,8 +439,9 @@ describe('converting ART trace', function () {
440439
const buffer = fs.readFileSync(
441440
'src/test/fixtures/upgrades/art-trace-streaming.trace.gz'
442441
);
443-
const arrayBuffer = zlib.gunzipSync(buffer).buffer;
444-
const profile = await unserializeProfileOfArbitraryFormat(arrayBuffer);
442+
const uncompressedBytes = zlib.gunzipSync(buffer);
443+
const profile =
444+
await unserializeProfileOfArbitraryFormat(uncompressedBytes);
445445
if (profile === undefined) {
446446
throw new Error('Unable to parse the profile.');
447447
}
@@ -457,8 +457,9 @@ describe('converting Simpleperf trace', function () {
457457
const buffer = fs.readFileSync(
458458
'src/test/fixtures/upgrades/simpleperf-task-clock.trace.gz'
459459
);
460-
const arrayBuffer = zlib.gunzipSync(buffer).buffer;
461-
const profile = await unserializeProfileOfArbitraryFormat(arrayBuffer);
460+
const uncompressedBytes = zlib.gunzipSync(buffer);
461+
const profile =
462+
await unserializeProfileOfArbitraryFormat(uncompressedBytes);
462463
if (profile === undefined) {
463464
throw new Error('Unable to parse the profile.');
464465
}
@@ -472,8 +473,9 @@ describe('converting Simpleperf trace', function () {
472473
const buffer = fs.readFileSync(
473474
'src/test/fixtures/upgrades/simpleperf-cpu-clock.trace.gz'
474475
);
475-
const arrayBuffer = zlib.gunzipSync(buffer).buffer;
476-
const profile = await unserializeProfileOfArbitraryFormat(arrayBuffer);
476+
const uncompressedBytes = zlib.gunzipSync(buffer);
477+
const profile =
478+
await unserializeProfileOfArbitraryFormat(uncompressedBytes);
477479
if (profile === undefined) {
478480
throw new Error('Unable to parse the profile.');
479481
}

src/utils/magic.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
export const SIMPLEPERF = 'SIMPLEPERF';
22

3-
export function verifyMagic(
4-
magic: string,
5-
traceBuffer: ArrayBufferLike
6-
): boolean {
3+
export function verifyMagic(magic: string, traceBuffer: Uint8Array): boolean {
74
return (
85
new TextDecoder('utf8').decode(traceBuffer.slice(0, magic.length)) === magic
96
);

0 commit comments

Comments
 (0)