Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
23cf52a
Add isInstrumentsProfile function and integration of it inside receiv…
rajmeghpara May 28, 2019
9afacb7
Remove .idea/ from ./gitignore file
rajmeghpara May 28, 2019
5a0fd89
Extract files and directories in form of tree
rajmeghpara Jun 2, 2019
e9ac49b
Add helper functions to get and read the form.template file
rajmeghpara Jun 7, 2019
944eb96
Doubt regarding output of parseBinaryPlist function
rajmeghpara Jun 8, 2019
7e7edc6
Implemented expandKeyedArchive, patternMatchObjectiveC and other help…
rajmeghpara Jun 15, 2019
ccbeff5
Extract runs, instruments type, version and selectedRunNumber
rajmeghpara Jun 18, 2019
a1d046a
Implement importRunFromInstrumentsTrace function
rajmeghpara Jun 23, 2019
13bbc15
Implement backtraceIDtoStack map
rajmeghpara Jul 1, 2019
6967c39
- Populate stackTrace inside each sample
rajmeghpara Jul 6, 2019
bcac0ec
- Start implementing the pushThreadsInProfile function
rajmeghpara Jul 6, 2019
aefe23a
Implement fillThread function
rajmeghpara Jul 8, 2019
c0fbb06
Update the fileName
rajmeghpara Jul 8, 2019
6ff3c49
Process just first run and ignore others
rajmeghpara Jul 8, 2019
a87e5ef
Use frameAddress inside FrameTable
rajmeghpara Jul 9, 2019
bf542ea
Remove unnecessary comments and export statements
rajmeghpara Jul 9, 2019
fdae901
Replace spread operator
rajmeghpara Jul 9, 2019
95df0b0
Fix crashing of UI when clicking on 'Publish' button
rajmeghpara Jul 10, 2019
0c78726
Resolve comments
rajmeghpara Jul 11, 2019
7d83fa6
Change implementation of decodeUTF8 function
rajmeghpara Jul 11, 2019
30e9c3c
Remove unused sortBy function
rajmeghpara Jul 13, 2019
2e1f996
Change the name of fillThread function to getProcessedThread function
rajmeghpara Jul 13, 2019
d0b102d
Refactor funcTable's logic inside getProcessedThread function
rajmeghpara Jul 13, 2019
e533bc9
Refactor frameTable's logic inside getProcessedThread function
rajmeghpara Jul 13, 2019
5fbce59
Refactor stackTable's logic inside getProcessedThread function
rajmeghpara Jul 13, 2019
1f89292
Fix lint errors
rajmeghpara Jul 13, 2019
d2068b7
Fix flow errors
rajmeghpara Jul 13, 2019
a1ba6f5
Fix some flow errors and test cases inside index.js file
rajmeghpara Jul 13, 2019
680b409
Add (root) data into funcTable, frameTable and stackTable
rajmeghpara Jul 13, 2019
f723314
Import all the tables' type
rajmeghpara Jul 14, 2019
976dfdb
Fix flow errors
rajmeghpara Jul 14, 2019
f15612a
Add comments on top of each function to explain the behaviour
rajmeghpara Jul 14, 2019
326531e
Fix importing profiles from Instruments 10
rajmeghpara Jul 27, 2019
2c8bb4c
Fix flow errors
rajmeghpara Jul 27, 2019
459a4cb
Fix flow errors
rajmeghpara Jul 27, 2019
154eaf6
Fix flow errors
rajmeghpara Jul 28, 2019
167e097
Fix flow errors
rajmeghpara Jul 28, 2019
997d734
Change the pako version to 1.0.2
rajmeghpara Jul 31, 2019
b3a6bb2
Fix ESLint error
rajmeghpara Jul 31, 2019
d188674
Fix the final lint errors
rajmeghpara Aug 4, 2019
09fa898
Remove console.log() statements
rajmeghpara Aug 7, 2019
9524b69
Describe the reason for $FlowFixMe
rajmeghpara Aug 7, 2019
5e3d9ca
Update appendRecursive function
rajmeghpara Aug 10, 2019
538ce58
Remove the TODO items which are done
rajmeghpara Aug 10, 2019
f75dbd4
Rename the importRunFromInstrumentsTrace function to getSamples
rajmeghpara Aug 10, 2019
87f4cb6
Add tests for sample Instruments profile
rajmeghpara Aug 12, 2019
dbbcbc3
Add tests for the remaining Instruments versions
rajmeghpara Aug 15, 2019
f22b786
Resolve some comments
rajmeghpara Aug 16, 2019
654da4d
Create issues and mention in the code for some comments
rajmeghpara Aug 16, 2019
b317023
Resolve some comments
rajmeghpara Aug 23, 2019
d206b0e
Change the import statement for inflate function
rajmeghpara Aug 30, 2019
f22341a
Add check for getAsEntry function
rajmeghpara Aug 30, 2019
795e9e9
Resolve comments
rajmeghpara Oct 2, 2019
6159032
- Add check for directory inside isInstrumentsFile function
rajmeghpara Oct 19, 2019
9094fa3
- Extract relativePathLength variable
rajmeghpara Oct 19, 2019
f818b08
- Resolve comments
rajmeghpara Nov 17, 2019
6b02380
- Resolve comments
rajmeghpara Nov 17, 2019
448ee92
- Resolve comments
rajmeghpara Nov 23, 2019
ba962ea
- Resolve comments
rajmeghpara Nov 23, 2019
8e5c217
- Add description in index.js
rajmeghpara Dec 7, 2019
a144941
- Resolve comments
rajmeghpara Dec 13, 2019
065af02
- Add comment
rajmeghpara Dec 14, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"memoize-immutable": "^3.0.0",
"mixedtuplemap": "^1.0.0",
"namedtuplemap": "^1.0.0",
"pako": "~1.0.2",
"photon-colors": "3.3.2",
"prop-types": "^15.7.2",
"query-string": "^6.3.0",
Expand Down
13 changes: 11 additions & 2 deletions src/actions/receive-profile.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ import { getProfileOrNull } from '../selectors/profile';
import { getView } from '../selectors/app';
import { setDataSource } from './profile-view';

import {
convertInstrumentsProfile,
isInstrumentsProfile,
} from '../profile-logic/import/instruments';
import type {
FunctionsUpdatePerThread,
FuncToFuncMap,
Expand Down Expand Up @@ -910,8 +914,13 @@ export function retrieveProfileFromFile(
// extensions (eg .profile). So we can't rely on the mime type to
// decide how to handle them. We'll try to parse them as a plain JSON
// file.
const text = await fileReader(file).asText();
const profile = await unserializeProfileOfArbitraryFormat(text);
let profile;
if (isInstrumentsProfile(file)) {
Comment thread
rajmeghpara marked this conversation as resolved.
profile = await convertInstrumentsProfile(file);
} else {
const text = await fileReader(file).asText();
profile = await unserializeProfileOfArbitraryFormat(text);
}
if (profile === undefined) {
throw new Error('Unable to parse the profile.');
}
Expand Down
28 changes: 27 additions & 1 deletion src/components/app/Home.js
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,33 @@ class Home extends React.PureComponent<HomeProps, HomeState> {
return;
}

const { files } = event.dataTransfer;
const { files, items } = event.dataTransfer;

if (items && items.length > 0) {
const [firstItem] = items;
let webkitEntry = null;

if ('webkitGetAsEntry' in firstItem) {
// DataTransferItem.webkitGetAsEntry() is a non-standard function for now.
// I have checked in Firefox and Chrome. It's working fine in it.
// We will remove FlowFixMe once it becomes a standard function (issue #2217)
// $FlowFixMe webkitGetAsEntry is not present in DataTransferItem
webkitEntry = firstItem.webkitGetAsEntry();
} else if ('getAsEntry' in firstItem) {
// $FlowFixMe getAsEntry is not present in DataTransferItem (issue #2230)
webkitEntry = firstItem.getAsEntry();
}

if (
webkitEntry !== null &&
webkitEntry.isDirectory &&
webkitEntry.name.endsWith('.trace')
) {
this.props.retrieveProfileFromFile(webkitEntry);
return;
}
}
Comment thread
rajmeghpara marked this conversation as resolved.

if (files.length > 0) {
this.props.retrieveProfileFromFile(files[0]);
}
Expand Down
65 changes: 65 additions & 0 deletions src/profile-logic/import/instruments/BinReader.js
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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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?
Then maybe the BinReader should be LittleEndianBinReader ?

}
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)
);
}
}
220 changes: 220 additions & 0 deletions src/profile-logic/import/instruments/BinaryPlistParser.js
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
Comment thread
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 };

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please use brackets

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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 {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this any ?
Also, what is this method doing? Please give examples in a comment.

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);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does that mean, both ? This really isn't clear to me what this return value represents compared to the parameters. (same applies below)


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;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The 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
);
}
}
}
Loading