-
Notifications
You must be signed in to change notification settings - Fork 482
Support importing Instruments profiles #2138
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
23cf52a
9afacb7
5a0fd89
e9ac49b
944eb96
7e7edc6
ccbeff5
a1d046a
13bbc15
6967c39
bcac0ec
aefe23a
c0fbb06
6ff3c49
a87e5ef
bf542ea
fdae901
95df0b0
0c78726
7d83fa6
30e9c3c
2e1f996
d0b102d
e533bc9
5fbce59
1f89292
d2068b7
a1ba6f5
680b409
f723314
976dfdb
f15612a
326531e
2c8bb4c
459a4cb
154eaf6
167e097
997d734
b3a6bb2
d188674
09fa898
9524b69
5e3d9ca
538ce58
f75dbd4
87f4cb6
dbbcbc3
f22b786
654da4d
b317023
d206b0e
f22341a
795e9e9
6159032
9094fa3
f818b08
6b02380
448ee92
ba962ea
8e5c217
a144941
065af02
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||
| * License, v. 2.0. If a copy of the MPL was not distributed with this | ||
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
| // @flow | ||
|
|
||
| // This class is inspired from here: https://github.com/jlfwong/speedscope/blob/master/src/import/instruments.ts#L200 | ||
| export class BinReader { | ||
| view: DataView; | ||
| bytePos: number; | ||
|
|
||
| constructor(buffer: ArrayBuffer) { | ||
| this.view = new DataView(buffer); | ||
| this.bytePos = 0; | ||
| } | ||
| seek(pos: number) { | ||
| this.bytePos = pos; | ||
| } | ||
| skip(byteCount: number) { | ||
| this.bytePos += byteCount; | ||
| } | ||
| hasMore() { | ||
| return this.bytePos < this.view.byteLength; | ||
| } | ||
| bytesLeft() { | ||
| return this.view.byteLength - this.bytePos; | ||
| } | ||
| readUint8() { | ||
| this.bytePos++; | ||
| if (this.bytePos > this.view.byteLength) { | ||
| return 0; | ||
| } | ||
| return this.view.getUint8(this.bytePos - 1); | ||
| } | ||
|
|
||
| readUint32() { | ||
| this.bytePos += 4; | ||
| if (this.bytePos > this.view.byteLength) { | ||
| return 0; | ||
| } | ||
| return this.view.getUint32(this.bytePos - 4, true); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so, we're reading little endian values everywhere in this file. Is that on purpose? Is that because that's how the instruments file is structured? |
||
| } | ||
| readUint48() { | ||
| this.bytePos += 6; | ||
| if (this.bytePos > this.view.byteLength) { | ||
| return 0; | ||
| } | ||
|
|
||
| // Note: we intentionally use Math.pow here rather than bit shifts | ||
| // because JavaScript doesn't have true 64 bit integers. | ||
| return ( | ||
| this.view.getUint32(this.bytePos - 6, true) + | ||
| this.view.getUint16(this.bytePos - 2, true) * Math.pow(2, 32) | ||
| ); | ||
| } | ||
| readUint64() { | ||
| this.bytePos += 8; | ||
| if (this.bytePos > this.view.byteLength) { | ||
| return 0; | ||
| } | ||
| return ( | ||
| this.view.getUint32(this.bytePos - 8, true) + | ||
| this.view.getUint32(this.bytePos - 4, true) * Math.pow(2, 32) | ||
| ); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,220 @@ | ||
| /* This Source Code Form is subject to the terms of the Mozilla Public | ||
|
rajmeghpara marked this conversation as resolved.
|
||
| * License, v. 2.0. If a copy of the MPL was not distributed with this | ||
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ | ||
| // @flow | ||
|
|
||
| export class UID { | ||
| index: number; | ||
|
|
||
| constructor(index: number) { | ||
| this.index = index; | ||
| } | ||
| } | ||
|
|
||
| // This class is inspired from here:https://github.com/jlfwong/speedscope/blob/master/src/import/instruments.ts#L828 | ||
| export class BinaryPlistParser { | ||
| view: DataView; | ||
| referenceSize: number; | ||
| offsetTable: Array<number>; | ||
|
|
||
| constructor(view: DataView) { | ||
| this.view = view; | ||
| this.referenceSize = 0; | ||
| this.offsetTable = []; | ||
| } | ||
|
|
||
| parseRoot(): any { | ||
| const trailer = this.view.byteLength - 32; | ||
| const offsetSize = this.view.getUint8(trailer + 6); | ||
| this.referenceSize = this.view.getUint8(trailer + 7); | ||
|
|
||
| // Just use the last 32-bits of these 64-bit big-endian values | ||
| const objectCount = this.view.getUint32(trailer + 12); | ||
| const rootIndex = this.view.getUint32(trailer + 20); | ||
| let tableOffset = this.view.getUint32(trailer + 28); | ||
|
|
||
| // Parse all offsets before starting to parse objects | ||
| for (let i = 0; i < objectCount; i++) { | ||
| this.offsetTable.push(this.parseInteger(tableOffset, offsetSize)); | ||
| tableOffset += offsetSize; | ||
| } | ||
|
|
||
| return this.parseObject(this.offsetTable[rootIndex]); | ||
| } | ||
|
|
||
| parseLengthAndOffset(offset: number, extra: number) { | ||
| if (extra !== 0x0f) return { length: extra, offset: 0 }; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please use brackets
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. also please explain this condition, it's super unclear to me ! Maybe with a comment for this method too. |
||
| const marker = this.view.getUint8(offset++); | ||
| if ((marker & 0xf0) !== 0x10) { | ||
| throw new Error('Unexpected non-integer length at offset ' + offset); | ||
| } | ||
| const size = 1 << (marker & 0x0f); | ||
| return { length: this.parseInteger(offset, size), offset: size + 1 }; | ||
| } | ||
|
|
||
| parseSingleton(offset: number, extra: number): any { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why this |
||
| if (extra === 0) { | ||
| return null; | ||
| } | ||
| if (extra === 8) { | ||
| return false; | ||
| } | ||
| if (extra === 9) { | ||
| return true; | ||
| } | ||
| throw new Error('Unexpected extra value ' + extra + ' at offset ' + offset); | ||
| } | ||
|
|
||
| parseInteger(offset: number, size: number): number { | ||
| if (size === 1) { | ||
| return this.view.getUint8(offset); | ||
| } | ||
| if (size === 2) { | ||
| return this.view.getUint16(offset, false); | ||
| } | ||
| if (size === 4) { | ||
| return this.view.getUint32(offset, false); | ||
| } | ||
|
|
||
| if (size === 8) { | ||
| return ( | ||
| Math.pow(2, 32 * 1) * this.view.getUint32(offset + 0, false) + | ||
| Math.pow(2, 32 * 0) * this.view.getUint32(offset + 4, false) | ||
| ); | ||
| } | ||
|
|
||
| if (size === 16) { | ||
| return ( | ||
| Math.pow(2, 32 * 3) * this.view.getUint32(offset + 0, false) + | ||
| Math.pow(2, 32 * 2) * this.view.getUint32(offset + 4, false) + | ||
| Math.pow(2, 32 * 1) * this.view.getUint32(offset + 8, false) + | ||
| Math.pow(2, 32 * 0) * this.view.getUint32(offset + 12, false) | ||
| ); | ||
| } | ||
|
|
||
| throw new Error( | ||
| 'Unexpected integer of size ' + size + ' at offset ' + offset | ||
| ); | ||
| } | ||
|
|
||
| parseFloat(offset: number, size: number): number { | ||
| if (size === 4) { | ||
| return this.view.getFloat32(offset, false); | ||
| } | ||
| if (size === 8) { | ||
| return this.view.getFloat64(offset, false); | ||
| } | ||
| throw new Error( | ||
| 'Unexpected float of size ' + size + ' at offset ' + offset | ||
| ); | ||
| } | ||
|
|
||
| parseDate(offset: number, size: number): Date { | ||
| if (size !== 8) { | ||
| throw new Error( | ||
| 'Unexpected date of size ' + size + ' at offset ' + offset | ||
| ); | ||
| } | ||
| const seconds = this.view.getFloat64(offset, false); | ||
| return new Date(978307200000 + seconds * 1000); // Starts from January 1st, 2001 | ||
| } | ||
|
|
||
| parseData(offset: number, extra: number): Uint8Array { | ||
| const both = this.parseLengthAndOffset(offset, extra); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what does that mean, |
||
|
|
||
| return new Uint8Array(this.view.buffer, offset + both.offset, both.length); | ||
| } | ||
|
|
||
| parseStringASCII(offset: number, extra: number): string { | ||
| const both = this.parseLengthAndOffset(offset, extra); | ||
| let text = ''; | ||
| offset += both.offset; | ||
| for (let i = 0; i < both.length; i++) { | ||
| text += String.fromCharCode(this.view.getUint8(offset++)); | ||
| } | ||
| return text; | ||
| } | ||
|
|
||
| parseStringUTF16(offset: number, extra: number): string { | ||
| const both = this.parseLengthAndOffset(offset, extra); | ||
| let text = ''; | ||
| offset += both.offset; | ||
| for (let i = 0; i < both.length; i++) { | ||
| text += String.fromCharCode(this.view.getUint16(offset, false)); | ||
| offset += 2; | ||
| } | ||
| return text; | ||
| } | ||
|
|
||
| parseUID(offset: number, size: number): UID { | ||
| return new UID(this.parseInteger(offset, size)); | ||
| } | ||
|
|
||
| parseArray(offset: number, extra: number): any[] { | ||
| const both = this.parseLengthAndOffset(offset, extra); | ||
| const array: any[] = []; | ||
| const size = this.referenceSize; | ||
| offset += both.offset; | ||
| for (let i = 0; i < both.length; i++) { | ||
| array.push( | ||
| this.parseObject(this.offsetTable[this.parseInteger(offset, size)]) | ||
| ); | ||
| offset += size; | ||
| } | ||
| return array; | ||
| } | ||
|
|
||
| parseDictionary(offset: number, extra: number): Object { | ||
| const both = this.parseLengthAndOffset(offset, extra); | ||
| const dictionary = Object.create(null); | ||
| const size = this.referenceSize; | ||
| let nextKey = offset + both.offset; | ||
| let nextValue = nextKey + both.length * size; | ||
| for (let i = 0; i < both.length; i++) { | ||
| const key = this.parseObject( | ||
| this.offsetTable[this.parseInteger(nextKey, size)] | ||
| ); | ||
| const value = this.parseObject( | ||
| this.offsetTable[this.parseInteger(nextValue, size)] | ||
| ); | ||
| if (typeof key !== 'string') { | ||
| throw new Error('Unexpected non-string key at offset ' + nextKey); | ||
| } | ||
| dictionary[key] = value; | ||
| nextKey += size; | ||
| nextValue += size; | ||
| } | ||
| return dictionary; | ||
| } | ||
|
|
||
| parseObject(offset: number): any { | ||
| const marker = this.view.getUint8(offset++); | ||
| const extra = marker & 0x0f; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. we need more comments explaining what these variables hold. |
||
| switch (marker >> 4) { | ||
| case 0x0: | ||
| return this.parseSingleton(offset, extra); | ||
| case 0x1: | ||
| return this.parseInteger(offset, 1 << extra); | ||
| case 0x2: | ||
| return this.parseFloat(offset, 1 << extra); | ||
| case 0x3: | ||
| return this.parseDate(offset, 1 << extra); | ||
| case 0x4: | ||
| return this.parseData(offset, extra); | ||
| case 0x5: | ||
| return this.parseStringASCII(offset, extra); | ||
| case 0x6: | ||
| return this.parseStringUTF16(offset, extra); | ||
| case 0x8: | ||
| return this.parseUID(offset, extra + 1); | ||
| case 0xa: | ||
| return this.parseArray(offset, extra); | ||
| case 0xd: | ||
| return this.parseDictionary(offset, extra); | ||
| default: | ||
| throw new Error( | ||
| 'Unexpected marker ' + marker + ' at offset ' + --offset | ||
| ); | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.