From 6ebe6d627ea6328537ad35d10d233ffbc085a665 Mon Sep 17 00:00:00 2001 From: Greg Tatum Date: Fri, 18 Dec 2020 11:47:25 -0600 Subject: [PATCH 1/9] Import MIT-licensed code from speedscope, with minimal changes This commit should not be reviewed as code written by the author, but rather as code that is imported pretty much directly. Only minimal changes were made like removing export declarations, or import syntax. This inlines the code from several different files as much as possible. --- src/profile-logic/import/instruments.js | 1124 +++++++++++++++++++ src/types/libdef/npm-custom/jszip_v3.x.x.js | 6 +- src/utils/flow.js | 7 + 3 files changed, 1136 insertions(+), 1 deletion(-) create mode 100644 src/profile-logic/import/instruments.js diff --git a/src/profile-logic/import/instruments.js b/src/profile-logic/import/instruments.js new file mode 100644 index 0000000000..d98752a7ed --- /dev/null +++ b/src/profile-logic/import/instruments.js @@ -0,0 +1,1124 @@ +/* eslint-disable no-nested-ternary */ +/* 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 file is adapted from the Speedscope project to convert to a Firefox Profiler format. +// https://github.com/jlfwong/speedscope/blob/6a979bb568f548dd347dba5b18e69e4401a2dda3/src/import/instruments.ts +// The original license for that project is duplicated below. + +// MIT License +// +// Copyright (c) 2018 Jamie Wong +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +// This file contains methods to import data from OS X Instruments.app +// https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/index.html + +function parseTSV(contents: string): T[] { + const lines = contents.split('\n').map(l => l.split('\t')) + + const headerLine = lines.shift() + if (!headerLine) return [] + + const indexToField = new Map() + for (let i = 0; i < headerLine.length; i++) { + indexToField.set(i, headerLine[i]) + } + + const ret: T[] = [] + for (let line of lines) { + const row = {} as T + for (let i = 0; i < line.length; i++) { + ;(row as any)[indexToField.get(i)!] = line[i] + } + ret.push(row) + } + return ret +} + +interface PastedTimeProfileRow { + Weight?: string + 'Source Path'?: string + 'Symbol Name'?: string +} + +interface PastedAllocationsProfileRow { + 'Bytes Used'?: string + 'Source Path'?: string + 'Symbol Name'?: string +} + +type FrameInfo = { + key: string | number, + + // Name of the frame. May be a method name, e.g. + // "ActiveRecord##to_hash" + name: string, + + // JSZipFile path of the code corresponding to this + // call stack frame. + file?: string, + + // Line in the given file where this frame occurs + line?: number, + + // Column in the file + col?: number, +}; + +interface FrameInfoWithWeight extends FrameInfo { + endValue: number; +} + +function getWeight(deepCopyRow: any): number { + if ('Bytes Used' in deepCopyRow) { + const bytesUsedString = deepCopyRow['Bytes Used'] + const parts = /\s*(\d+(?:[.]\d+)?) (\w+)\s+(?:\d+(?:[.]\d+))%/.exec(bytesUsedString) + if (!parts) return 0 + const value = parseInt(parts[1], 10) + const units = parts[2] + + switch (units) { + case 'Bytes': + return value + case 'KB': + return 1024 * value + case 'MB': + return 1024 * 1024 * value + case 'GB': + return 1024 * 1024 * 1024 * value + } + throw new Error(`Unrecognized units ${units}`) + } + + if ('Weight' in deepCopyRow || 'Running Time' in deepCopyRow) { + const weightString = deepCopyRow['Weight'] || deepCopyRow['Running Time'] + const parts = /\s*(\d+(?:[.]\d+)?) ?(\w+)\s+(?:\d+(?:[.]\d+))%/.exec(weightString) + if (!parts) return 0 + const value = parseInt(parts[1], 10) + const units = parts[2] + + switch (units) { + case 'ms': + return value + case 's': + return 1000 * value + case 'min': + return 1000 * value + } + throw new Error(`Unrecognized units ${units}`) + } + + return -1 +} + +// Import from a deep copy made of a profile +export function importFromInstrumentsDeepCopy(contents: string): Profile { + debugger + const profile = new CallTreeProfileBuilder() + const rows = parseTSV(contents) + + const stack: FrameInfoWithWeight[] = [] + let cumulativeValue: number = 0 + + for (let row of rows) { + const symbolName = row['Symbol Name'] + if (!symbolName) continue + const trimmedSymbolName = symbolName.trim() + let stackDepth = symbolName.length - trimmedSymbolName.length + + if (stack.length - stackDepth < 0) { + throw new Error('Invalid format') + } + + let framesToLeave: FrameInfoWithWeight[] = [] + + while (stackDepth < stack.length) { + const stackTop = stack.pop()! + framesToLeave.push(stackTop) + } + + for (let frameToLeave of framesToLeave) { + cumulativeValue = Math.max(cumulativeValue, frameToLeave.endValue) + profile.leaveFrame(frameToLeave, cumulativeValue) + } + + const newFrameInfo: FrameInfoWithWeight = { + key: `${row['Source Path'] || ''}:${trimmedSymbolName}`, + name: trimmedSymbolName, + file: row['Source Path'], + endValue: cumulativeValue + getWeight(row), + } + + profile.enterFrame(newFrameInfo, cumulativeValue) + stack.push(newFrameInfo) + } + + while (stack.length > 0) { + const frameToLeave = stack.pop()! + cumulativeValue = Math.max(cumulativeValue, frameToLeave.endValue) + profile.leaveFrame(frameToLeave, cumulativeValue) + } + + if ('Bytes Used' in rows[0]) { + profile.setValueFormatter(new ByteFormatter()) + } else if ('Weight' in rows[0] || 'Running Time' in rows[0]) { + profile.setValueFormatter(new TimeFormatter('milliseconds')) + } + + return profile.build() +} + +interface TraceDirectoryTree { + name: string + files: Map + subdirectories: Map +} + +function getCoreDirForRun(tree: TraceDirectoryTree, selectedRun: number): TraceDirectoryTree { + const corespace = getOrThrow(tree.subdirectories, 'corespace') + const corespaceRunDir = getOrThrow(corespace.subdirectories, `run${selectedRun}`) + return getOrThrow(corespaceRunDir.subdirectories, 'core') +} + +class BinReader { + private bytePos: number = 0 + private view: DataView + constructor(buffer: ArrayBuffer) { + this.view = new DataView(buffer) + } + 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) + } + + // Note: we intentionally use Math.pow here rather than bit shifts + // because JavaScript doesn't have true 64 bit integers. + readUint32() { + this.bytePos += 4 + if (this.bytePos > this.view.byteLength) return 0 + return this.view.getUint32(this.bytePos - 4, true) + } + readUint48() { + this.bytePos += 6 + if (this.bytePos > this.view.byteLength) return 0 + + 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) + ) + } +} + +interface Sample { + timestamp: number + threadID: number + backtraceID: number +} + +async function getRawSampleList(core: TraceDirectoryTree): Promise { + const stores = getOrThrow(core.subdirectories, 'stores') + for (let storedir of stores.subdirectories.values()) { + const schemaFile = storedir.files.get('schema.xml') + if (!schemaFile) continue + const schema = await readAsText(schemaFile) + if (!/name="time-profile"/.exec(schema)) { + continue + } + const bulkstore = new BinReader( + await readAsArrayBuffer(getOrThrow(storedir.files, 'bulkstore')), + ) + // Ignore the first 3 words + bulkstore.readUint32() + bulkstore.readUint32() + bulkstore.readUint32() + const headerSize = bulkstore.readUint32() + const bytesPerEntry = bulkstore.readUint32() + + bulkstore.seek(headerSize) + + const samples: Sample[] = [] + while (true) { + // Schema as of Instruments 8.3.3 is a 6 byte timestamp, followed by a bunch + // of stuff we don't care about, followed by a 4 byte backtrace ID + const timestamp = bulkstore.readUint48() + if (timestamp === 0) break + + const threadID = bulkstore.readUint32() + + bulkstore.skip(bytesPerEntry - 6 - 4 - 4) + const backtraceID = bulkstore.readUint32() + samples.push({timestamp, threadID, backtraceID}) + } + return samples + } + throw new Error('Could not find sample list') +} + +async function getIntegerArrays(samples: Sample[], core: TraceDirectoryTree): Promise { + const uniquing = getOrThrow(core.subdirectories, 'uniquing') + const arrayUniquer = getOrThrow(uniquing.subdirectories, 'arrayUniquer') + const integeruniquerindex = getOrThrow(arrayUniquer.files, 'integeruniquer.index') + const integeruniquerdata = getOrThrow(arrayUniquer.files, 'integeruniquer.data') + + // integeruniquer.index is a binary file containing an array of [byte offset, MB offset] pairs + // that indicate where array data starts in the .data file + + // integeruniquer.data is a binary file containing an array of arrays of 64 bit integer. + // The schema is a 32 byte header followed by a stream of arrays. + // Each array consists of a 4 byte size N followed by N 8 byte little endian integers + + // This table contains the memory addresses of stack frames + + const indexreader = new BinReader(await readAsArrayBuffer(integeruniquerindex)) + const datareader = new BinReader(await readAsArrayBuffer(integeruniquerdata)) + + // Header we don't care about + indexreader.seek(32) + + let arrays: number[][] = [] + + while (indexreader.hasMore()) { + const byteOffset = indexreader.readUint32() + indexreader.readUint32() * (1024 * 1024) + + if (byteOffset === 0) { + // The first entry in the index table seems to just indicate the offset of + // the header into the data file + continue + } + + datareader.seek(byteOffset) + + let length = datareader.readUint32() + let array: number[] = [] + + while (length--) { + array.push(datareader.readUint64()) + } + arrays.push(array) + } + + return arrays +} + +interface SymbolInfo { + symbolName: string | null + sourcePath: string | null + addressToLine: Map +} + +interface FormTemplateRunData { + number: number + addressToFrameMap: Map +} + +interface FormTemplateData { + version: number + selectedRunNumber: number + instrument: string + runs: FormTemplateRunData[] +} + +async function readFormTemplate(tree: TraceDirectoryTree): Promise { + const formTemplate = getOrThrow(tree.files, 'form.template') + const archive = readInstrumentsKeyedArchive(await readAsArrayBuffer(formTemplate)) + + const version = archive['com.apple.xray.owner.template.version'] + let selectedRunNumber = 1 + if ('com.apple.xray.owner.template' in archive) { + selectedRunNumber = archive['com.apple.xray.owner.template'].get('_selectedRunNumber') + } + let instrument = archive['$1'] + if ('stubInfoByUUID' in archive) { + instrument = Array.from(archive['stubInfoByUUID'].keys())[0] + } + const allRunData = archive['com.apple.xray.run.data'] + + const runs: FormTemplateRunData[] = [] + for (let runNumber of allRunData.runNumbers) { + const runData = getOrThrow>(allRunData.runData, runNumber) + + const symbolsByPid = getOrThrow>( + runData, + 'symbolsByPid', + ) + + const addressToFrameMap = new Map() + + // TODO(jlfwong): Deal with profiles with conflicting addresses? + for (let symbols of symbolsByPid.values()) { + for (let symbol of symbols.symbols) { + if (!symbol) continue + const {sourcePath, symbolName, addressToLine} = symbol + for (let address of addressToLine.keys()) { + getOrInsert(addressToFrameMap, address, () => { + const name = symbolName || `0x${zeroPad(address.toString(16), 16)}` + const frame: FrameInfo = { + key: `${sourcePath}:${name}`, + name: name, + } + if (sourcePath) { + frame.file = sourcePath + } + return frame + }) + } + } + + runs.push({ + number: runNumber, + addressToFrameMap, + }) + } + } + + return { + version, + instrument, + selectedRunNumber, + runs, + } +} + +// Import from a .trace file saved from Mac Instruments.app +export async function importFromInstrumentsTrace( + entry: FileSystemDirectoryEntry, +): Promise { + debugger + const tree = await extractDirectoryTree(entry) + + const {version, runs, instrument, selectedRunNumber} = await readFormTemplate(tree) + if (instrument !== 'com.apple.xray.instrument-type.coresampler2') { + throw new Error( + `The only supported instrument from .trace import is "com.apple.xray.instrument-type.coresampler2". Got ${instrument}`, + ) + } + console.log('version: ', version) + console.log(`Importing time profile`) + + const profiles: Profile[] = [] + let indexToView = 0 + + for (let run of runs) { + const {addressToFrameMap, number} = run + const group = await importRunFromInstrumentsTrace({ + fileName: entry.name, + tree, + addressToFrameMap, + runNumber: number, + }) + + if (run.number === selectedRunNumber) { + indexToView = profiles.length + group.indexToView + } + + profiles.push(...group.profiles) + } + + return {name: entry.name, indexToView, profiles} +} + +interface ProfileGroup { + name: string; + indexToView: number; + profiles: Profile[]; +} + +export async function importRunFromInstrumentsTrace(args: { + fileName: string + tree: TraceDirectoryTree + addressToFrameMap: Map + runNumber: number +}): Promise { + const {fileName, tree, addressToFrameMap, runNumber} = args + const core = getCoreDirForRun(tree, runNumber) + let samples = await getRawSampleList(core) + const arrays = await getIntegerArrays(samples, core) + + // We'll try to guess which thread is the main thread by assuming + // it's the one with the most samples. + const sampleCountByThreadID = new Map() + for (let sample of samples) { + sampleCountByThreadID.set( + sample.threadID, + getOrElse(sampleCountByThreadID, sample.threadID, () => 0) + 1, + ) + } + const counts = Array.from(sampleCountByThreadID.entries()) + sortBy(counts, c => -c[1]) + const threadIDs = counts.map(c => c[0]) + + return { + name: fileName, + indexToView: 0, + profiles: threadIDs.map(threadID => + importThreadFromInstrumentsTrace({ + threadID, + fileName, + arrays, + addressToFrameMap, + samples, + }), + ), + } +} + +export function importThreadFromInstrumentsTrace(args: { + fileName: string + addressToFrameMap: Map + threadID: number + arrays: number[][] + samples: Sample[] +}): Profile { + let {fileName, addressToFrameMap, arrays, threadID, samples} = args + + const backtraceIDtoStack = new Map() + samples = samples.filter(s => s.threadID === threadID) + + const profile = new StackListProfileBuilder(lastOf(samples)!.timestamp) + profile.setName(`${fileName} - thread ${threadID}`) + + function appendRecursive(k: number, stack: FrameInfo[]) { + const frame = addressToFrameMap.get(k) + if (frame) { + stack.push(frame) + } else if (k in arrays) { + for (let addr of arrays[k]) { + appendRecursive(addr, stack) + } + } else { + const rawAddressFrame: FrameInfo = { + key: k, + name: `0x${zeroPad(k.toString(16), 16)}`, + } + addressToFrameMap.set(k, rawAddressFrame) + stack.push(rawAddressFrame) + } + } + + let lastTimestamp: null | number = null + for (let sample of samples) { + const stackForSample = getOrInsert(backtraceIDtoStack, sample.backtraceID, id => { + const stack: FrameInfo[] = [] + appendRecursive(id, stack) + stack.reverse() + return stack + }) + + if (lastTimestamp === null) { + // The first sample is sometimes fairly late in the profile for some reason. + // We'll just say nothing was known to be on the stack in that time. + profile.appendSampleWithWeight([], sample.timestamp) + lastTimestamp = sample.timestamp + } + + if (sample.timestamp < lastTimestamp) { + throw new Error('Timestamps out of order!') + } + + profile.appendSampleWithWeight(stackForSample, sample.timestamp - lastTimestamp) + lastTimestamp = sample.timestamp + } + + profile.setValueFormatter(new TimeFormatter('nanoseconds')) + return profile.build() +} + +export function readInstrumentsKeyedArchive(buffer: ArrayBuffer): any { + const byteArray = new Uint8Array(buffer) + const parsedPlist = parseBinaryPlist(byteArray) + const data = expandKeyedArchive(parsedPlist, ($classname, object) => { + switch ($classname) { + case 'NSTextStorage': + case 'NSParagraphStyle': + case 'NSFont': + // Stuff that's irrelevant for constructing a flamegraph + return null + + case 'PFTSymbolData': { + const ret = Object.create(null) + ret.symbolName = object.$0 + ret.sourcePath = object.$1 + ret.addressToLine = new Map() + for (let i = 3; ; i += 2) { + const address = object['$' + i] + const line = object['$' + (i + 1)] + if (address == null || line == null) { + break + } + ret.addressToLine.set(address, line) + } + return ret + } + + case 'PFTOwnerData': { + const ret = Object.create(null) + ret.ownerName = object.$0 + ret.ownerPath = object.$1 + return ret + } + + case 'PFTPersistentSymbols': { + const ret = Object.create(null) + const symbolCount = object.$4 + + ret.threadNames = object.$3 + ret.symbols = [] + for (let i = 1; i < symbolCount; i++) { + ret.symbols.push(object['$' + (4 + i)]) + } + return ret + } + + case 'XRRunListData': { + const ret = Object.create(null) + ret.runNumbers = object.$0 + ret.runData = object.$1 + return ret + } + + case 'XRIntKeyedDictionary': { + const ret = new Map() + const size = object.$0 + for (let i = 0; i < size; i++) { + const key = object['$' + (1 + 2 * i)] + const value = object['$' + (1 + (2 * i + 1))] + ret.set(key, value) + } + return ret + } + + case 'XRCore': { + const ret = Object.create(null) + ret.number = object.$0 + ret.name = object.$1 + return ret + } + } + return object + }) + return data +} + +//////////////////////////////////////////////////////////////////////////////// + +export function decodeUTF8(bytes: Uint8Array): string { + let text = String.fromCharCode.apply(String, Array.from(bytes)) + if (text.slice(-1) === '\0') text = text.slice(0, -1) // Remove a single trailing null character if present + return decodeURIComponent(escape(text)) +} + +function isArray(value: any): boolean { + return value instanceof Array +} + +function isDictionary(value: any): boolean { + return value !== null && typeof value === 'object' && Object.getPrototypeOf(value) === null +} + +function followUID(objects: any[], value: any): any { + return value instanceof UID ? objects[value.index] : value +} + +function expandKeyedArchive( + root: any, + interpretClass: ($classname: string, obj: any) => any = x => x, +): any { + // Sanity checks + if ( + root.$version !== 100000 || + root.$archiver !== 'NSKeyedArchiver' || + !isDictionary(root.$top) || + !isArray(root.$objects) + ) { + throw new Error('Invalid keyed archive') + } + + // Substitute NSNull + if (root.$objects[0] === '$null') { + root.$objects[0] = null + } + + // Pattern-match Objective-C constructs + for (let i = 0; i < root.$objects.length; i++) { + root.$objects[i] = paternMatchObjectiveC(root.$objects, root.$objects[i], interpretClass) + } + + // Reconstruct the DAG from the parse tree + let visit = (object: any) => { + if (object instanceof UID) { + return root.$objects[object.index] + } else if (isArray(object)) { + for (let i = 0; i < object.length; i++) { + object[i] = visit(object[i]) + } + } else if (isDictionary(object)) { + for (let key in object) { + object[key] = visit(object[key]) + } + } else if (object instanceof Map) { + const clone = new Map(object) + object.clear() + for (let [k, v] of clone.entries()) { + object.set(visit(k), visit(v)) + } + } + return object + } + for (let i = 0; i < root.$objects.length; i++) { + visit(root.$objects[i]) + } + return visit(root.$top) +} + +function paternMatchObjectiveC( + objects: any[], + value: any, + interpretClass: ($classname: string, obj: any) => any = x => x, +): any { + if (isDictionary(value) && value.$class) { + let name = followUID(objects, value.$class).$classname + switch (name) { + case 'NSDecimalNumberPlaceholder': { + let length: number = value['NS.length'] + let exponent: number = value['NS.exponent'] + let byteOrder: number = value['NS.mantissa.bo'] + let negative: boolean = value['NS.negative'] + let mantissa = new Uint16Array(new Uint8Array(value['NS.mantissa']).buffer) + let decimal = 0 + + for (let i = 0; i < length; i++) { + let digit = mantissa[i] + + if (byteOrder !== 1) { + // I assume this is how this works but I am unable to test it + digit = ((digit & 0xff00) >> 8) | ((digit & 0x00ff) << 8) + } + + decimal += digit * Math.pow(65536, i) + } + + decimal *= Math.pow(10, exponent) + return negative ? -decimal : decimal + } + + // Replace NSData with a Uint8Array + case 'NSData': + case 'NSMutableData': + return value['NS.bytes'] || value['NS.data'] + + // Replace NSString with a string + case 'NSString': + case 'NSMutableString': + if (value['NS.string']) return value['NS.string'] + if (value['NS.bytes']) return decodeUTF8(value['NS.bytes']) + console.warn(`Unexpected ${name} format: `, value) + return null + + // Replace NSArray with an Array + case 'NSArray': + case 'NSMutableArray': + if ('NS.objects' in value) { + return value['NS.objects'] + } + let array: any[] = [] + while (true) { + let object = 'NS.object.' + array.length + if (!(object in value)) { + break + } + array.push(value[object]) + } + return array + + case '_NSKeyedCoderOldStyleArray': { + const count = value['NS.count'] + + // const size = value['NS.size'] + // Types are encoded as single printable characters. + // See: https://github.com/apple/swift-corelibs-foundation/blob/76995e8d3d8c10f3f3ec344dace43426ab941d0e/Foundation/NSObjCRuntime.swift#L19 + // const type = String.fromCharCode(value['NS.type']) + + let array: any[] = [] + for (let i = 0; i < count; i++) { + const element = value['$' + i] + array.push(element) + } + return array + } + + case 'NSDictionary': + case 'NSMutableDictionary': + let map = new Map() + if ('NS.keys' in value && 'NS.objects' in value) { + for (let i = 0; i < value['NS.keys'].length; i++) { + map.set(value['NS.keys'][i], value['NS.objects'][i]) + } + } else { + while (true) { + let key = 'NS.key.' + map.size + let object = 'NS.object.' + map.size + if (!(key in value) || !(object in value)) { + break + } + map.set(value[key], value[object]) + } + } + return map + + default: + const converted = interpretClass(name, value) + if (converted !== value) return converted + } + } + return value +} + +//////////////////////////////////////////////////////////////////////////////// + +export class UID { + constructor(public index: number) {} +} + +function parseBinaryPlist(bytes: Uint8Array): any { + let text = 'bplist00' + for (let i = 0; i < 8; i++) { + if (bytes[i] !== text.charCodeAt(i)) { + throw new Error('File is not a binary plist') + } + } + return new BinaryPlistParser( + new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength), + ).parseRoot() +} + +interface LengthAndOffset { + length: number + offset: number +} + +// See http://opensource.apple.com/source/CF/CF-550/CFBinaryPList.c for details +class BinaryPlistParser { + referenceSize = 0 + objects: number[] = [] + offsetTable: number[] = [] + + constructor(public view: DataView) {} + + parseRoot(): any { + let trailer = this.view.byteLength - 32 + let 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 + let objectCount = this.view.getUint32(trailer + 12, false) + let rootIndex = this.view.getUint32(trailer + 20, false) + let tableOffset = this.view.getUint32(trailer + 28, false) + + // Parse all offsets before starting to parse objects + for (let i = 0; i < objectCount; i++) { + this.offsetTable.push(this.parseInteger(tableOffset, offsetSize)) + tableOffset += offsetSize + } + + // Parse the root object assuming the graph is a tree + return this.parseObject(this.offsetTable[rootIndex]) + } + + parseLengthAndOffset(offset: number, extra: number): LengthAndOffset { + if (extra !== 0x0f) return {length: extra, offset: 0} + let marker = this.view.getUint8(offset++) + if ((marker & 0xf0) !== 0x10) + throw new Error('Unexpected non-integer length at offset ' + offset) + let size = 1 << (marker & 0x0f) + return {length: this.parseInteger(offset, size), offset: size + 1} + } + + parseSingleton(offset: number, extra: number): any { + 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) + let seconds = this.view.getFloat64(offset, false) + return new Date(978307200000 + seconds * 1000) // Starts from January 1st, 2001 + } + + parseData(offset: number, extra: number): Uint8Array { + let both = this.parseLengthAndOffset(offset, extra) + return new Uint8Array(this.view.buffer, offset + both.offset, both.length) + } + + parseStringASCII(offset: number, extra: number): string { + let 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 { + let 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[] { + let both = this.parseLengthAndOffset(offset, extra) + let array: any[] = [] + let 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 { + let both = this.parseLengthAndOffset(offset, extra) + let dictionary = Object.create(null) + let size = this.referenceSize + let nextKey = offset + both.offset + let nextValue = nextKey + both.length * size + for (let i = 0; i < both.length; i++) { + let key = this.parseObject(this.offsetTable[this.parseInteger(nextKey, size)]) + let 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 { + let marker = this.view.getUint8(offset++) + let extra = marker & 0x0f + 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) + } + throw new Error('Unexpected marker ' + marker + ' at offset ' + --offset) + } +} + +type ValueUnit = + | 'none' + | 'nanoseconds' + | 'microseconds' + | 'milliseconds' + | 'seconds' + | 'bytes'; + +interface ValueFormatter { + unit: ValueUnit + format(v: number): string +} + +class TimeFormatter implements ValueFormatter { + private multiplier: number + + constructor(public unit: 'nanoseconds' | 'microseconds' | 'milliseconds' | 'seconds') { + if (unit === 'nanoseconds') this.multiplier = 1e-9 + else if (unit === 'microseconds') this.multiplier = 1e-6 + else if (unit === 'milliseconds') this.multiplier = 1e-3 + else this.multiplier = 1 + } + + formatUnsigned(v: number) { + const s = v * this.multiplier + + if (s / 60 >= 1) { + const minutes = Math.floor(s / 60) + const seconds = Math.floor(s - minutes * 60).toString() + return `${minutes}:${zeroPad(seconds, 2)}` + } + if (s / 1 >= 1) return `${s.toFixed(2)}s` + if (s / 1e-3 >= 1) return `${(s / 1e-3).toFixed(2)}ms` + if (s / 1e-6 >= 1) return `${(s / 1e-6).toFixed(2)}µs` + else return `${(s / 1e-9).toFixed(2)}ns` + } + + format(v: number) { + return `${v < 0 ? '-' : ''}${this.formatUnsigned(Math.abs(v))}` + } +} + +class ByteFormatter implements ValueFormatter { + unit: FileFormat.ValueUnit = 'bytes' + + format(v: number) { + if (v < 1024) return `${v.toFixed(0)} B` + v /= 1024 + if (v < 1024) return `${v.toFixed(2)} KB` + v /= 1024 + if (v < 1024) return `${v.toFixed(2)} MB` + v /= 1024 + return `${v.toFixed(2)} GB` + } +} + +function zeroPad(s: string, width: number) { + return new Array(Math.max(width - s.length, 0) + 1).join('0') + s +} + +function getOrInsert(map: Map, k: K, fallback: (k: K) => V): V { + if (!map.has(k)) map.set(k, fallback(k)) + return map.get(k)! +} + +function getOrElse(map: Map, k: K, fallback: (k: K) => V): V { + if (!map.has(k)) return fallback(k) + return map.get(k)! +} + +function getOrThrow(map: Map, k: K): V { + if (!map.has(k)) { + throw new Error(`Expected key ${k}`) + } + return map.get(k)! +} + +async function extractDirectoryTree(entry: FileSystemDirectoryEntry): Promise { + const node: TraceDirectoryTree = { + name: entry.name, + files: new Map(), + subdirectories: new Map(), + } + + const children = await new Promise((resolve, reject) => { + entry.createReader().readEntries((entries: any[]) => { + resolve(entries) + }, reject) + }) + + for (let child of children) { + if (child.isDirectory) { + const subtree = await extractDirectoryTree(child as FileSystemDirectoryEntry) + node.subdirectories.set(subtree.name, subtree) + } else { + const file = await new Promise((resolve, reject) => { + ;(child as FileSystemFileEntry).file(resolve, reject) + }) + node.files.set(file.name, file) + } + } + + return node +} + +function sortBy(ts: T[], key: (t: T) => number | string): void { + function comparator(a: T, b: T) { + const keyA = key(a) + const keyB = key(b) + return keyA < keyB ? -1 : keyA > keyB ? 1 : 0 + } + ts.sort(comparator) +} + +function lastOf(ts: T[]): T | null { + return ts[ts.length - 1] || null +} diff --git a/src/types/libdef/npm-custom/jszip_v3.x.x.js b/src/types/libdef/npm-custom/jszip_v3.x.x.js index 7c94ede4a0..2859ed5fd7 100644 --- a/src/types/libdef/npm-custom/jszip_v3.x.x.js +++ b/src/types/libdef/npm-custom/jszip_v3.x.x.js @@ -13,9 +13,13 @@ * functionality is used. */ +type AsyncString = (key: 'string') => Promise; +type AsyncUint8Array = (key: 'uint8array') => Promise; +type AsyncArrayBuffer = (key: 'arraybuffer') => Promise; + declare module 'jszip' { declare type JSZipFile = { - async: (key: 'string') => Promise, + async: AsyncString & AsyncUint8Array & AsyncArrayBuffer, name: string, // The absolute path of the file. dir: boolean, // true if this is a directory. date: any, // The last modification date. diff --git a/src/utils/flow.js b/src/utils/flow.js index 103bb51d8b..4828ad8a95 100644 --- a/src/utils/flow.js +++ b/src/utils/flow.js @@ -189,3 +189,10 @@ export function ensureExists(item: ?T, message: ?string): T { export function getFirstItemFromSet(set: Set): T | void { return set.values().next().value; } + +/** + * Returns the first item from Map in a type friendly manner. + */ +export function getFirstItemFromMap(map: Map): T | void { + return map.values().next().value; +} From 6df0c815a8cf8548e7e10a3d066385aba5791ec2 Mon Sep 17 00:00:00 2001 From: Greg Tatum Date: Fri, 18 Dec 2020 13:03:23 -0600 Subject: [PATCH 2/9] Comment out importFromInstrumentsDeepCopy This code is useful if we want to support this feature, but for now, just comment it out so that Flow doesn't complain. --- src/profile-logic/import/instruments.js | 295 +++++++++++++----------- 1 file changed, 161 insertions(+), 134 deletions(-) diff --git a/src/profile-logic/import/instruments.js b/src/profile-logic/import/instruments.js index d98752a7ed..a75084d1af 100644 --- a/src/profile-logic/import/instruments.js +++ b/src/profile-logic/import/instruments.js @@ -30,42 +30,53 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. +import JSZip, { type JSZipFile } from 'jszip'; +import { + ensureExists, + objectValues, + getFirstItemFromMap, +} from 'firefox-profiler/utils/flow'; +import type { ObjectMap, Profile } from 'firefox-profiler/types'; + // This file contains methods to import data from OS X Instruments.app // https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/index.html -function parseTSV(contents: string): T[] { - const lines = contents.split('\n').map(l => l.split('\t')) - - const headerLine = lines.shift() - if (!headerLine) return [] - - const indexToField = new Map() - for (let i = 0; i < headerLine.length; i++) { - indexToField.set(i, headerLine[i]) - } - - const ret: T[] = [] - for (let line of lines) { - const row = {} as T - for (let i = 0; i < line.length; i++) { - ;(row as any)[indexToField.get(i)!] = line[i] - } - ret.push(row) - } - return ret -} - -interface PastedTimeProfileRow { - Weight?: string - 'Source Path'?: string - 'Symbol Name'?: string -} - -interface PastedAllocationsProfileRow { - 'Bytes Used'?: string - 'Source Path'?: string - 'Symbol Name'?: string -} +// TODO - importFromInstrumentsDeepCopy +// function parseTSV(contents: string): T[] { +// const lines = contents.split('\n').map(l => l.split('\t')); + +// const headerLine = lines.shift(); +// if (!headerLine) { +// return []; +// } + +// const indexToField = new Map(); +// for (let i = 0; i < headerLine.length; i++) { +// indexToField.set(i, headerLine[i]); +// } + +// const ret: T[] = []; +// for (const line of lines) { +// const row = {}; +// for (let i = 0; i < line.length; i++) { +// row[ensureExists(indexToField.get(i))] = line[i]; +// } +// ret.push(coerce(row)); +// } +// return ret; +// } +// +// type PastedTimeProfileRow = { +// Weight?: string, +// 'Source Path'?: string, +// 'Symbol Name'?: string, +// }; +// +// type PastedAllocationsProfileRow = { +// 'Bytes Used'?: string, +// 'Source Path'?: string, +// 'Symbol Name'?: string, +// }; type FrameInfo = { key: string | number, @@ -85,108 +96,124 @@ type FrameInfo = { col?: number, }; -interface FrameInfoWithWeight extends FrameInfo { - endValue: number; -} - -function getWeight(deepCopyRow: any): number { - if ('Bytes Used' in deepCopyRow) { - const bytesUsedString = deepCopyRow['Bytes Used'] - const parts = /\s*(\d+(?:[.]\d+)?) (\w+)\s+(?:\d+(?:[.]\d+))%/.exec(bytesUsedString) - if (!parts) return 0 - const value = parseInt(parts[1], 10) - const units = parts[2] - - switch (units) { - case 'Bytes': - return value - case 'KB': - return 1024 * value - case 'MB': - return 1024 * 1024 * value - case 'GB': - return 1024 * 1024 * 1024 * value - } - throw new Error(`Unrecognized units ${units}`) - } - - if ('Weight' in deepCopyRow || 'Running Time' in deepCopyRow) { - const weightString = deepCopyRow['Weight'] || deepCopyRow['Running Time'] - const parts = /\s*(\d+(?:[.]\d+)?) ?(\w+)\s+(?:\d+(?:[.]\d+))%/.exec(weightString) - if (!parts) return 0 - const value = parseInt(parts[1], 10) - const units = parts[2] - - switch (units) { - case 'ms': - return value - case 's': - return 1000 * value - case 'min': - return 1000 * value - } - throw new Error(`Unrecognized units ${units}`) - } - - return -1 -} - +// TODO - importFromInstrumentsDeepCopy +// interface FrameInfoWithWeight extends FrameInfo { +// endValue: number; +// } + +// TODO - importFromInstrumentsDeepCopy +// function getWeight(deepCopyRow: any): number { +// if ('Bytes Used' in deepCopyRow) { +// const bytesUsedString = deepCopyRow['Bytes Used']; +// const parts = /\s*(\d+(?:[.]\d+)?) (\w+)\s+(?:\d+(?:[.]\d+))%/.exec( +// bytesUsedString +// ); +// if (!parts) { +// return 0; +// } +// const value = parseInt(parts[1], 10); +// const units = parts[2]; + +// switch (units) { +// case 'Bytes': +// return value; +// case 'KB': +// return 1024 * value; +// case 'MB': +// return 1024 * 1024 * value; +// case 'GB': +// return 1024 * 1024 * 1024 * value; +// default: +// throw new Error(`Unrecognized units ${units}`); +// } +// } + +// if ('Weight' in deepCopyRow || 'Running Time' in deepCopyRow) { +// const weightString = deepCopyRow.Weight || deepCopyRow['Running Time']; +// const parts = /\s*(\d+(?:[.]\d+)?) ?(\w+)\s+(?:\d+(?:[.]\d+))%/.exec( +// weightString +// ); +// if (!parts) { +// return 0; +// } +// const value = parseInt(parts[1], 10); +// const units = parts[2]; + +// switch (units) { +// case 'ms': +// return value; +// case 's': +// return 1000 * value; +// case 'min': +// return 1000 * value; +// default: +// throw new Error(`Unrecognized units ${units}`); +// } +// } + +// return -1; +// } + +// TODO - This is currently not migrated. // Import from a deep copy made of a profile -export function importFromInstrumentsDeepCopy(contents: string): Profile { - debugger - const profile = new CallTreeProfileBuilder() - const rows = parseTSV(contents) - - const stack: FrameInfoWithWeight[] = [] - let cumulativeValue: number = 0 - - for (let row of rows) { - const symbolName = row['Symbol Name'] - if (!symbolName) continue - const trimmedSymbolName = symbolName.trim() - let stackDepth = symbolName.length - trimmedSymbolName.length - - if (stack.length - stackDepth < 0) { - throw new Error('Invalid format') - } - - let framesToLeave: FrameInfoWithWeight[] = [] - - while (stackDepth < stack.length) { - const stackTop = stack.pop()! - framesToLeave.push(stackTop) - } - - for (let frameToLeave of framesToLeave) { - cumulativeValue = Math.max(cumulativeValue, frameToLeave.endValue) - profile.leaveFrame(frameToLeave, cumulativeValue) - } - - const newFrameInfo: FrameInfoWithWeight = { - key: `${row['Source Path'] || ''}:${trimmedSymbolName}`, - name: trimmedSymbolName, - file: row['Source Path'], - endValue: cumulativeValue + getWeight(row), - } - - profile.enterFrame(newFrameInfo, cumulativeValue) - stack.push(newFrameInfo) - } - - while (stack.length > 0) { - const frameToLeave = stack.pop()! - cumulativeValue = Math.max(cumulativeValue, frameToLeave.endValue) - profile.leaveFrame(frameToLeave, cumulativeValue) - } - - if ('Bytes Used' in rows[0]) { - profile.setValueFormatter(new ByteFormatter()) - } else if ('Weight' in rows[0] || 'Running Time' in rows[0]) { - profile.setValueFormatter(new TimeFormatter('milliseconds')) - } - - return profile.build() -} +// export function importFromInstrumentsDeepCopy(contents: string): Profile { +// const profile = new CallTreeProfileBuilder(); +// const rows = parseTSV( +// contents +// ); + +// const stack: FrameInfoWithWeight[] = []; +// let cumulativeValue: number = 0; + +// for (const row of rows) { +// const symbolName = row['Symbol Name']; +// if (!symbolName) { +// continue; +// } +// const trimmedSymbolName = symbolName.trim(); +// const stackDepth = symbolName.length - trimmedSymbolName.length; + +// if (stack.length - stackDepth < 0) { +// throw new Error('Invalid format'); +// } + +// const framesToLeave: FrameInfoWithWeight[] = []; + +// while (stackDepth < stack.length) { +// const stackTop = ensureExists(stack.pop()); +// framesToLeave.push(stackTop); +// } + +// for (const frameToLeave of framesToLeave) { +// cumulativeValue = Math.max(cumulativeValue, frameToLeave.endValue); +// profile.leaveFrame(frameToLeave, cumulativeValue); +// } + +// const newFrameInfo: FrameInfoWithWeight = { +// key: `${row['Source Path'] || ''}:${trimmedSymbolName}`, +// name: trimmedSymbolName, +// file: row['Source Path'], +// endValue: cumulativeValue + getWeight(row), +// }; + +// profile.enterFrame(newFrameInfo, cumulativeValue); +// stack.push(newFrameInfo); +// } + +// while (stack.length > 0) { +// const frameToLeave = ensureExists(stack.pop()); +// cumulativeValue = Math.max(cumulativeValue, frameToLeave.endValue); +// profile.leaveFrame(frameToLeave, cumulativeValue); +// } + +// if ('Bytes Used' in rows[0]) { +// profile.setValueFormatter(new ByteFormatter()); +// } else if ('Weight' in rows[0] || 'Running Time' in rows[0]) { +// profile.setValueFormatter(new TimeFormatter('milliseconds')); +// } + +// return profile.build(); +// } interface TraceDirectoryTree { name: string From 1149a9a76bdff0cc589d7fc7ab049e38bb74f185 Mon Sep 17 00:00:00 2001 From: Greg Tatum Date: Fri, 18 Dec 2020 13:19:23 -0600 Subject: [PATCH 3/9] Fix most prettier and eslint issues This commit represents trivial lint and style fixes on the code. No behavior was changed here. --- src/profile-logic/import/instruments.js | 810 ++++++++++++++---------- 1 file changed, 462 insertions(+), 348 deletions(-) diff --git a/src/profile-logic/import/instruments.js b/src/profile-logic/import/instruments.js index a75084d1af..f2565fa157 100644 --- a/src/profile-logic/import/instruments.js +++ b/src/profile-logic/import/instruments.js @@ -216,117 +216,144 @@ type FrameInfo = { // } interface TraceDirectoryTree { - name: string - files: Map - subdirectories: Map + name: string; + files: Map; + subdirectories: Map; } -function getCoreDirForRun(tree: TraceDirectoryTree, selectedRun: number): TraceDirectoryTree { - const corespace = getOrThrow(tree.subdirectories, 'corespace') - const corespaceRunDir = getOrThrow(corespace.subdirectories, `run${selectedRun}`) - return getOrThrow(corespaceRunDir.subdirectories, 'core') +function getCoreDirForRun( + tree: TraceDirectoryTree, + selectedRun: number +): TraceDirectoryTree { + const corespace = getOrThrow(tree.subdirectories, 'corespace'); + const corespaceRunDir = getOrThrow( + corespace.subdirectories, + `run${selectedRun}` + ); + return getOrThrow(corespaceRunDir.subdirectories, 'core'); } class BinReader { - private bytePos: number = 0 - private view: DataView + bytePos: number = 0; + view: DataView; constructor(buffer: ArrayBuffer) { - this.view = new DataView(buffer) + this.view = new DataView(buffer); } seek(pos: number) { - this.bytePos = pos + this.bytePos = pos; } skip(byteCount: number) { - this.bytePos += byteCount + this.bytePos += byteCount; } hasMore() { - return this.bytePos < this.view.byteLength + return this.bytePos < this.view.byteLength; } bytesLeft() { - return this.view.byteLength - this.bytePos + return this.view.byteLength - this.bytePos; } readUint8() { - this.bytePos++ - if (this.bytePos > this.view.byteLength) return 0 - return this.view.getUint8(this.bytePos - 1) + this.bytePos++; + if (this.bytePos > this.view.byteLength) { + return 0; + } + return this.view.getUint8(this.bytePos - 1); } // Note: we intentionally use Math.pow here rather than bit shifts // because JavaScript doesn't have true 64 bit integers. readUint32() { - this.bytePos += 4 - if (this.bytePos > this.view.byteLength) return 0 - return this.view.getUint32(this.bytePos - 4, true) + this.bytePos += 4; + if (this.bytePos > this.view.byteLength) { + return 0; + } + return this.view.getUint32(this.bytePos - 4, true); } readUint48() { - this.bytePos += 6 - if (this.bytePos > this.view.byteLength) return 0 + this.bytePos += 6; + if (this.bytePos > this.view.byteLength) { + return 0; + } 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 + 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) - ) + ); } } interface Sample { - timestamp: number - threadID: number - backtraceID: number + timestamp: number; + threadID: number; + backtraceID: number; } async function getRawSampleList(core: TraceDirectoryTree): Promise { - const stores = getOrThrow(core.subdirectories, 'stores') - for (let storedir of stores.subdirectories.values()) { - const schemaFile = storedir.files.get('schema.xml') - if (!schemaFile) continue - const schema = await readAsText(schemaFile) + const stores = getOrThrow(core.subdirectories, 'stores'); + for (const storedir of stores.subdirectories.values()) { + const schemaFile = storedir.files.get('schema.xml'); + if (!schemaFile) { + continue; + } + const schema = await schemaFile.async('string'); if (!/name="time-profile"/.exec(schema)) { - continue + continue; } const bulkstore = new BinReader( await readAsArrayBuffer(getOrThrow(storedir.files, 'bulkstore')), ) // Ignore the first 3 words - bulkstore.readUint32() - bulkstore.readUint32() - bulkstore.readUint32() - const headerSize = bulkstore.readUint32() - const bytesPerEntry = bulkstore.readUint32() + bulkstore.readUint32(); + bulkstore.readUint32(); + bulkstore.readUint32(); + const headerSize = bulkstore.readUint32(); + const bytesPerEntry = bulkstore.readUint32(); - bulkstore.seek(headerSize) + bulkstore.seek(headerSize); - const samples: Sample[] = [] + const samples: Sample[] = []; while (true) { // Schema as of Instruments 8.3.3 is a 6 byte timestamp, followed by a bunch // of stuff we don't care about, followed by a 4 byte backtrace ID - const timestamp = bulkstore.readUint48() - if (timestamp === 0) break + const timestamp = bulkstore.readUint48(); + if (timestamp === 0) { + break; + } - const threadID = bulkstore.readUint32() + const threadID = bulkstore.readUint32(); - bulkstore.skip(bytesPerEntry - 6 - 4 - 4) - const backtraceID = bulkstore.readUint32() - samples.push({timestamp, threadID, backtraceID}) + bulkstore.skip(bytesPerEntry - 6 - 4 - 4); + const backtraceID = bulkstore.readUint32(); + samples.push({ timestamp, threadID, backtraceID }); } - return samples + return samples; } - throw new Error('Could not find sample list') + throw new Error('Could not find sample list'); } -async function getIntegerArrays(samples: Sample[], core: TraceDirectoryTree): Promise { - const uniquing = getOrThrow(core.subdirectories, 'uniquing') - const arrayUniquer = getOrThrow(uniquing.subdirectories, 'arrayUniquer') - const integeruniquerindex = getOrThrow(arrayUniquer.files, 'integeruniquer.index') - const integeruniquerdata = getOrThrow(arrayUniquer.files, 'integeruniquer.data') +async function getIntegerArrays( + samples: Sample[], + core: TraceDirectoryTree +): Promise { + const uniquing = getOrThrow(core.subdirectories, 'uniquing'); + const arrayUniquer = getOrThrow(uniquing.subdirectories, 'arrayUniquer'); + const integeruniquerindex = getOrThrow( + arrayUniquer.files, + 'integeruniquer.index' + ); + const integeruniquerdata = getOrThrow( + arrayUniquer.files, + 'integeruniquer.data' + ); // integeruniquer.index is a binary file containing an array of [byte offset, MB offset] pairs // that indicate where array data starts in the .data file @@ -341,63 +368,68 @@ async function getIntegerArrays(samples: Sample[], core: TraceDirectoryTree): Pr const datareader = new BinReader(await readAsArrayBuffer(integeruniquerdata)) // Header we don't care about - indexreader.seek(32) + indexreader.seek(32); - let arrays: number[][] = [] + const arrays: number[][] = []; while (indexreader.hasMore()) { - const byteOffset = indexreader.readUint32() + indexreader.readUint32() * (1024 * 1024) + const byteOffset = + indexreader.readUint32() + indexreader.readUint32() * (1024 * 1024); if (byteOffset === 0) { // The first entry in the index table seems to just indicate the offset of // the header into the data file - continue + continue; } - datareader.seek(byteOffset) + datareader.seek(byteOffset); - let length = datareader.readUint32() - let array: number[] = [] + let length = datareader.readUint32(); + const array: number[] = []; while (length--) { - array.push(datareader.readUint64()) + array.push(datareader.readUint64()); } - arrays.push(array) + arrays.push(array); } - return arrays + return arrays; } -interface SymbolInfo { - symbolName: string | null - sourcePath: string | null - addressToLine: Map -} +type SymbolInfo = { + symbolName: string | null, + sourcePath: string | null, + addressToLine: Map, +}; -interface FormTemplateRunData { - number: number - addressToFrameMap: Map -} +type FormTemplateRunData = { + number: number, + addressToFrameMap: Map, +}; -interface FormTemplateData { - version: number - selectedRunNumber: number - instrument: string - runs: FormTemplateRunData[] -} +type FormTemplateData = { + version: number, + selectedRunNumber: number, + instrument: string, + runs: FormTemplateRunData[], +}; -async function readFormTemplate(tree: TraceDirectoryTree): Promise { - const formTemplate = getOrThrow(tree.files, 'form.template') - const archive = readInstrumentsKeyedArchive(await readAsArrayBuffer(formTemplate)) +async function readFormTemplate( + tree: TraceDirectoryTree +): Promise { + const formTemplate = getOrThrow(tree.files, 'form.template'); + const archive = readInstrumentsKeyedArchive(await readAsArrayBuffer(formTemplate)); - const version = archive['com.apple.xray.owner.template.version'] - let selectedRunNumber = 1 + const version = archive['com.apple.xray.owner.template.version']; + let selectedRunNumber = 1; if ('com.apple.xray.owner.template' in archive) { - selectedRunNumber = archive['com.apple.xray.owner.template'].get('_selectedRunNumber') + selectedRunNumber = archive['com.apple.xray.owner.template'].get( + '_selectedRunNumber' + ); } - let instrument = archive['$1'] + let instrument = archive.$1; if ('stubInfoByUUID' in archive) { - instrument = Array.from(archive['stubInfoByUUID'].keys())[0] + instrument = Array.from(archive.stubInfoByUUID.keys())[0]; } const allRunData = archive['com.apple.xray.run.data'] @@ -435,7 +467,7 @@ async function readFormTemplate(tree: TraceDirectoryTree): Promise - runNumber: number + fileName: string, + tree: TraceDirectoryTree, + addressToFrameMap: Map, + runNumber: number, }): Promise { - const {fileName, tree, addressToFrameMap, runNumber} = args - const core = getCoreDirForRun(tree, runNumber) - let samples = await getRawSampleList(core) - const arrays = await getIntegerArrays(samples, core) + const { fileName, tree, addressToFrameMap, runNumber } = args; + const core = getCoreDirForRun(tree, runNumber); + const samples = await getRawSampleList(core); + const arrays = await getIntegerArrays(samples, core); // We'll try to guess which thread is the main thread by assuming // it's the one with the most samples. - const sampleCountByThreadID = new Map() - for (let sample of samples) { + const sampleCountByThreadID = new Map(); + for (const sample of samples) { sampleCountByThreadID.set( sample.threadID, - getOrElse(sampleCountByThreadID, sample.threadID, () => 0) + 1, - ) + getOrElse(sampleCountByThreadID, sample.threadID, () => 0) + 1 + ); } - const counts = Array.from(sampleCountByThreadID.entries()) - sortBy(counts, c => -c[1]) - const threadIDs = counts.map(c => c[0]) + const counts = Array.from(sampleCountByThreadID.entries()); + sortBy(counts, c => -c[1]); + const threadIDs = counts.map(c => c[0]); return { name: fileName, @@ -525,70 +557,78 @@ export async function importRunFromInstrumentsTrace(args: { arrays, addressToFrameMap, samples, - }), + }) ), } } export function importThreadFromInstrumentsTrace(args: { - fileName: string - addressToFrameMap: Map - threadID: number - arrays: number[][] - samples: Sample[] + fileName: string, + addressToFrameMap: Map, + threadID: number, + arrays: number[][], + samples: Sample[], }): Profile { - let {fileName, addressToFrameMap, arrays, threadID, samples} = args + const { fileName, addressToFrameMap, arrays, threadID } = args; + let { samples } = args; - const backtraceIDtoStack = new Map() - samples = samples.filter(s => s.threadID === threadID) + const backtraceIDtoStack = new Map(); + samples = samples.filter(s => s.threadID === threadID); const profile = new StackListProfileBuilder(lastOf(samples)!.timestamp) profile.setName(`${fileName} - thread ${threadID}`) function appendRecursive(k: number, stack: FrameInfo[]) { - const frame = addressToFrameMap.get(k) + const frame = addressToFrameMap.get(k); if (frame) { - stack.push(frame) + stack.push(frame); } else if (k in arrays) { - for (let addr of arrays[k]) { - appendRecursive(addr, stack) + for (const addr of arrays[k]) { + appendRecursive(addr, stack); } } else { const rawAddressFrame: FrameInfo = { key: k, name: `0x${zeroPad(k.toString(16), 16)}`, - } - addressToFrameMap.set(k, rawAddressFrame) - stack.push(rawAddressFrame) + }; + addressToFrameMap.set(k, rawAddressFrame); + stack.push(rawAddressFrame); } } - let lastTimestamp: null | number = null - for (let sample of samples) { - const stackForSample = getOrInsert(backtraceIDtoStack, sample.backtraceID, id => { - const stack: FrameInfo[] = [] - appendRecursive(id, stack) - stack.reverse() - return stack - }) + let lastTimestamp: null | number = null; + for (const sample of samples) { + const stackForSample = getOrInsert( + backtraceIDtoStack, + sample.backtraceID, + id => { + const stack: FrameInfo[] = []; + appendRecursive(id, stack); + stack.reverse(); + return stack; + } + ); if (lastTimestamp === null) { // The first sample is sometimes fairly late in the profile for some reason. // We'll just say nothing was known to be on the stack in that time. - profile.appendSampleWithWeight([], sample.timestamp) - lastTimestamp = sample.timestamp + profile.appendSampleWithWeight([], sample.timestamp); + lastTimestamp = sample.timestamp; } if (sample.timestamp < lastTimestamp) { - throw new Error('Timestamps out of order!') + throw new Error('Timestamps out of order!'); } - profile.appendSampleWithWeight(stackForSample, sample.timestamp - lastTimestamp) - lastTimestamp = sample.timestamp + profile.appendSampleWithWeight( + stackForSample, + sample.timestamp - lastTimestamp + ); + lastTimestamp = sample.timestamp; } - profile.setValueFormatter(new TimeFormatter('nanoseconds')) - return profile.build() + profile.setValueFormatter(new TimeFormatter('nanoseconds')); + return profile.build(); } export function readInstrumentsKeyedArchive(buffer: ArrayBuffer): any { @@ -600,96 +640,103 @@ export function readInstrumentsKeyedArchive(buffer: ArrayBuffer): any { case 'NSParagraphStyle': case 'NSFont': // Stuff that's irrelevant for constructing a flamegraph - return null + return null; case 'PFTSymbolData': { - const ret = Object.create(null) - ret.symbolName = object.$0 - ret.sourcePath = object.$1 - ret.addressToLine = new Map() + const ret = Object.create(null); + ret.symbolName = object.$0; + ret.sourcePath = object.$1; + ret.addressToLine = new Map(); for (let i = 3; ; i += 2) { - const address = object['$' + i] - const line = object['$' + (i + 1)] + const address = object['$' + i]; + const line = object['$' + (i + 1)]; if (address == null || line == null) { - break + break; } - ret.addressToLine.set(address, line) + ret.addressToLine.set(address, line); } - return ret + return ret; } case 'PFTOwnerData': { - const ret = Object.create(null) - ret.ownerName = object.$0 - ret.ownerPath = object.$1 - return ret + const ret = Object.create(null); + ret.ownerName = object.$0; + ret.ownerPath = object.$1; + return ret; } case 'PFTPersistentSymbols': { - const ret = Object.create(null) - const symbolCount = object.$4 + const ret = Object.create(null); + const symbolCount = object.$4; - ret.threadNames = object.$3 - ret.symbols = [] + ret.threadNames = object.$3; + ret.symbols = []; for (let i = 1; i < symbolCount; i++) { - ret.symbols.push(object['$' + (4 + i)]) + ret.symbols.push(object['$' + (4 + i)]); } - return ret + return ret; } case 'XRRunListData': { - const ret = Object.create(null) - ret.runNumbers = object.$0 - ret.runData = object.$1 - return ret + const ret = Object.create(null); + ret.runNumbers = object.$0; + ret.runData = object.$1; + return ret; } case 'XRIntKeyedDictionary': { - const ret = new Map() - const size = object.$0 + const ret = new Map(); + const size = object.$0; for (let i = 0; i < size; i++) { - const key = object['$' + (1 + 2 * i)] - const value = object['$' + (1 + (2 * i + 1))] - ret.set(key, value) + const key = object['$' + (1 + 2 * i)]; + const value = object['$' + (1 + (2 * i + 1))]; + ret.set(key, value); } - return ret + return ret; } case 'XRCore': { - const ret = Object.create(null) - ret.number = object.$0 - ret.name = object.$1 - return ret + const ret = Object.create(null); + ret.number = object.$0; + ret.name = object.$1; + return ret; } + default: + return object; } - return object - }) - return data + }); + return data; } //////////////////////////////////////////////////////////////////////////////// export function decodeUTF8(bytes: Uint8Array): string { - let text = String.fromCharCode.apply(String, Array.from(bytes)) - if (text.slice(-1) === '\0') text = text.slice(0, -1) // Remove a single trailing null character if present - return decodeURIComponent(escape(text)) + let text = String.fromCharCode.apply(String, Array.from(bytes)); + if (text.slice(-1) === '\0') { + text = text.slice(0, -1); + } // Remove a single trailing null character if present + return decodeURIComponent(escape(text)); } function isArray(value: any): boolean { - return value instanceof Array + return value instanceof Array; } function isDictionary(value: any): boolean { - return value !== null && typeof value === 'object' && Object.getPrototypeOf(value) === null + return ( + value !== null && + typeof value === 'object' && + Object.getPrototypeOf(value) === null + ); } function followUID(objects: any[], value: any): any { - return value instanceof UID ? objects[value.index] : value + return value instanceof UID ? objects[value.index] : value; } function expandKeyedArchive( root: any, - interpretClass: ($classname: string, obj: any) => any = x => x, + interpretClass: ($classname: string, obj: any) => any = x => x ): any { // Sanity checks if ( @@ -698,147 +745,159 @@ function expandKeyedArchive( !isDictionary(root.$top) || !isArray(root.$objects) ) { - throw new Error('Invalid keyed archive') + throw new Error('Invalid keyed archive'); } // Substitute NSNull if (root.$objects[0] === '$null') { - root.$objects[0] = null + root.$objects[0] = null; } // Pattern-match Objective-C constructs for (let i = 0; i < root.$objects.length; i++) { - root.$objects[i] = paternMatchObjectiveC(root.$objects, root.$objects[i], interpretClass) + root.$objects[i] = paternMatchObjectiveC( + root.$objects, + root.$objects[i], + interpretClass + ); } // Reconstruct the DAG from the parse tree - let visit = (object: any) => { + const visit = (object: any) => { if (object instanceof UID) { - return root.$objects[object.index] + return root.$objects[object.index]; } else if (isArray(object)) { for (let i = 0; i < object.length; i++) { - object[i] = visit(object[i]) + object[i] = visit(object[i]); } } else if (isDictionary(object)) { - for (let key in object) { - object[key] = visit(object[key]) + for (const key in object) { + object[key] = visit(object[key]); } } else if (object instanceof Map) { - const clone = new Map(object) - object.clear() - for (let [k, v] of clone.entries()) { - object.set(visit(k), visit(v)) + const clone = new Map(object); + object.clear(); + for (const [k, v] of clone.entries()) { + object.set(visit(k), visit(v)); } } - return object - } + return object; + }; for (let i = 0; i < root.$objects.length; i++) { - visit(root.$objects[i]) + visit(root.$objects[i]); } - return visit(root.$top) + return visit(root.$top); } function paternMatchObjectiveC( objects: any[], value: any, - interpretClass: ($classname: string, obj: any) => any = x => x, + interpretClass: ($classname: string, obj: any) => any = x => x ): any { if (isDictionary(value) && value.$class) { - let name = followUID(objects, value.$class).$classname + const name = followUID(objects, value.$class).$classname; switch (name) { case 'NSDecimalNumberPlaceholder': { - let length: number = value['NS.length'] - let exponent: number = value['NS.exponent'] - let byteOrder: number = value['NS.mantissa.bo'] - let negative: boolean = value['NS.negative'] - let mantissa = new Uint16Array(new Uint8Array(value['NS.mantissa']).buffer) - let decimal = 0 + const length: number = value['NS.length']; + const exponent: number = value['NS.exponent']; + const byteOrder: number = value['NS.mantissa.bo']; + const negative: boolean = value['NS.negative']; + const mantissa = new Uint16Array( + new Uint8Array(value['NS.mantissa']).buffer + ); + let decimal = 0; for (let i = 0; i < length; i++) { - let digit = mantissa[i] + let digit = mantissa[i]; if (byteOrder !== 1) { // I assume this is how this works but I am unable to test it - digit = ((digit & 0xff00) >> 8) | ((digit & 0x00ff) << 8) + digit = ((digit & 0xff00) >> 8) | ((digit & 0x00ff) << 8); } - decimal += digit * Math.pow(65536, i) + decimal += digit * Math.pow(65536, i); } - decimal *= Math.pow(10, exponent) - return negative ? -decimal : decimal + decimal *= Math.pow(10, exponent); + return negative ? -decimal : decimal; } // Replace NSData with a Uint8Array case 'NSData': case 'NSMutableData': - return value['NS.bytes'] || value['NS.data'] + return value['NS.bytes'] || value['NS.data']; // Replace NSString with a string case 'NSString': - case 'NSMutableString': - if (value['NS.string']) return value['NS.string'] - if (value['NS.bytes']) return decodeUTF8(value['NS.bytes']) - console.warn(`Unexpected ${name} format: `, value) - return null - + case 'NSMutableString': { + if (value['NS.string']) { + return value['NS.string']; + } + if (value['NS.bytes']) { + return decodeUTF8(value['NS.bytes']); + } + console.warn(`Unexpected ${name} format: `, value); + return null; + } // Replace NSArray with an Array case 'NSArray': - case 'NSMutableArray': + case 'NSMutableArray': { if ('NS.objects' in value) { - return value['NS.objects'] + return value['NS.objects']; } - let array: any[] = [] + const array: any[] = []; while (true) { - let object = 'NS.object.' + array.length + const object = 'NS.object.' + array.length; if (!(object in value)) { - break + break; } - array.push(value[object]) + array.push(value[object]); } - return array - + return array; + } case '_NSKeyedCoderOldStyleArray': { - const count = value['NS.count'] + const count = value['NS.count']; // const size = value['NS.size'] // Types are encoded as single printable characters. // See: https://github.com/apple/swift-corelibs-foundation/blob/76995e8d3d8c10f3f3ec344dace43426ab941d0e/Foundation/NSObjCRuntime.swift#L19 // const type = String.fromCharCode(value['NS.type']) - let array: any[] = [] + const array: any[] = []; for (let i = 0; i < count; i++) { - const element = value['$' + i] - array.push(element) + const element = value['$' + i]; + array.push(element); } - return array + return array; } - case 'NSDictionary': - case 'NSMutableDictionary': - let map = new Map() + case 'NSMutableDictionary': { + const map = new Map(); if ('NS.keys' in value && 'NS.objects' in value) { for (let i = 0; i < value['NS.keys'].length; i++) { - map.set(value['NS.keys'][i], value['NS.objects'][i]) + map.set(value['NS.keys'][i], value['NS.objects'][i]); } } else { while (true) { - let key = 'NS.key.' + map.size - let object = 'NS.object.' + map.size + const key = 'NS.key.' + map.size; + const object = 'NS.object.' + map.size; if (!(key in value) || !(object in value)) { - break + break; } - map.set(value[key], value[object]) + map.set(value[key], value[object]); } } - return map - - default: - const converted = interpretClass(name, value) - if (converted !== value) return converted + return map; + } + default: { + const converted = interpretClass(name, value); + if (converted !== value) { + return converted; + } + } } } - return value + return value; } //////////////////////////////////////////////////////////////////////////////// @@ -848,76 +907,94 @@ export class UID { } function parseBinaryPlist(bytes: Uint8Array): any { - let text = 'bplist00' + const text = 'bplist00'; for (let i = 0; i < 8; i++) { if (bytes[i] !== text.charCodeAt(i)) { throw new Error('File is not a binary plist') } } return new BinaryPlistParser( - new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength), - ).parseRoot() + new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength) + ).parseRoot(); } interface LengthAndOffset { - length: number - offset: number + length: number; + offset: number; } // See http://opensource.apple.com/source/CF/CF-550/CFBinaryPList.c for details class BinaryPlistParser { - referenceSize = 0 - objects: number[] = [] - offsetTable: number[] = [] + referenceSize = 0; + objects: number[] = []; + offsetTable: number[] = []; + view: DataView; - constructor(public view: DataView) {} + constructor(view: DataView) { + this.view = view; + } parseRoot(): any { - let trailer = this.view.byteLength - 32 - let offsetSize = this.view.getUint8(trailer + 6) - this.referenceSize = this.view.getUint8(trailer + 7) + 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 - let objectCount = this.view.getUint32(trailer + 12, false) - let rootIndex = this.view.getUint32(trailer + 20, false) - let tableOffset = this.view.getUint32(trailer + 28, false) + const objectCount = this.view.getUint32(trailer + 12, false); + const rootIndex = this.view.getUint32(trailer + 20, false); + let tableOffset = this.view.getUint32(trailer + 28, false); // Parse all offsets before starting to parse objects for (let i = 0; i < objectCount; i++) { - this.offsetTable.push(this.parseInteger(tableOffset, offsetSize)) - tableOffset += offsetSize + this.offsetTable.push(this.parseInteger(tableOffset, offsetSize)); + tableOffset += offsetSize; } // Parse the root object assuming the graph is a tree - return this.parseObject(this.offsetTable[rootIndex]) + return this.parseObject(this.offsetTable[rootIndex]); } parseLengthAndOffset(offset: number, extra: number): LengthAndOffset { - if (extra !== 0x0f) return {length: extra, offset: 0} - let marker = this.view.getUint8(offset++) - if ((marker & 0xf0) !== 0x10) - throw new Error('Unexpected non-integer length at offset ' + offset) - let size = 1 << (marker & 0x0f) - return {length: this.parseInteger(offset, size), offset: size + 1} + if (extra !== 0x0f) { + return { length: extra, offset: 0 }; + } + 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 { - 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) + 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 === 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) { @@ -926,109 +1003,132 @@ class BinaryPlistParser { 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) + 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) + 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) - let seconds = this.view.getFloat64(offset, false) - return new Date(978307200000 + seconds * 1000) // Starts from January 1st, 2001 + 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 { - let both = this.parseLengthAndOffset(offset, extra) - return new Uint8Array(this.view.buffer, offset + both.offset, both.length) + const both = this.parseLengthAndOffset(offset, extra); + return new Uint8Array(this.view.buffer, offset + both.offset, both.length); } parseStringASCII(offset: number, extra: number): string { - let both = this.parseLengthAndOffset(offset, extra) - let text = '' - offset += both.offset + 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++)) + text += String.fromCharCode(this.view.getUint8(offset++)); } - return text + return text; } parseStringUTF16(offset: number, extra: number): string { - let both = this.parseLengthAndOffset(offset, extra) - let text = '' - offset += both.offset + 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 + text += String.fromCharCode(this.view.getUint16(offset, false)); + offset += 2; } - return text + return text; } parseUID(offset: number, size: number): UID { - return new UID(this.parseInteger(offset, size)) + return new UID(this.parseInteger(offset, size)); } parseArray(offset: number, extra: number): any[] { - let both = this.parseLengthAndOffset(offset, extra) - let array: any[] = [] - let size = this.referenceSize - offset += both.offset + 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 + array.push( + this.parseObject(this.offsetTable[this.parseInteger(offset, size)]) + ); + offset += size; } - return array + return array; } - parseDictionary(offset: number, extra: number): Object { - let both = this.parseLengthAndOffset(offset, extra) - let dictionary = Object.create(null) - let size = this.referenceSize - let nextKey = offset + both.offset - let nextValue = nextKey + both.length * size + parseDictionary(offset: number, extra: number): ObjectMap { + const both = this.parseLengthAndOffset(offset, extra); + const dictionary: ObjectMap = 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++) { - let key = this.parseObject(this.offsetTable[this.parseInteger(nextKey, size)]) - let 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 + 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 + return dictionary; } parseObject(offset: number): any { - let marker = this.view.getUint8(offset++) - let extra = marker & 0x0f + const marker = this.view.getUint8(offset++); + const extra = marker & 0x0f; switch (marker >> 4) { case 0x0: - return this.parseSingleton(offset, extra) + return this.parseSingleton(offset, extra); case 0x1: - return this.parseInteger(offset, 1 << extra) + return this.parseInteger(offset, 1 << extra); case 0x2: - return this.parseFloat(offset, 1 << extra) + return this.parseFloat(offset, 1 << extra); case 0x3: - return this.parseDate(offset, 1 << extra) + return this.parseDate(offset, 1 << extra); case 0x4: - return this.parseData(offset, extra) + return this.parseData(offset, extra); case 0x5: - return this.parseStringASCII(offset, extra) + return this.parseStringASCII(offset, extra); case 0x6: - return this.parseStringUTF16(offset, extra) + return this.parseStringUTF16(offset, extra); case 0x8: - return this.parseUID(offset, extra + 1) + return this.parseUID(offset, extra + 1); case 0xa: - return this.parseArray(offset, extra) + return this.parseArray(offset, extra); case 0xd: - return this.parseDictionary(offset, extra) + return this.parseDictionary(offset, extra); + default: + throw new Error( + 'Unexpected marker ' + marker + ' at offset ' + --offset + ); } - throw new Error('Unexpected marker ' + marker + ' at offset ' + --offset) } } @@ -1041,36 +1141,50 @@ type ValueUnit = | 'bytes'; interface ValueFormatter { - unit: ValueUnit - format(v: number): string + unit: ValueUnit; + format(v: number): string; } class TimeFormatter implements ValueFormatter { - private multiplier: number + multiplier: number; + unit: ValueUnit; + + constructor(unit: ValueUnit) { + if (unit === 'nanoseconds') { + this.multiplier = 1e-9; + } else if (unit === 'microseconds') { + this.multiplier = 1e-6; + } else if (unit === 'milliseconds') { + this.multiplier = 1e-3; + } else { + this.multiplier = 1; + } - constructor(public unit: 'nanoseconds' | 'microseconds' | 'milliseconds' | 'seconds') { - if (unit === 'nanoseconds') this.multiplier = 1e-9 - else if (unit === 'microseconds') this.multiplier = 1e-6 - else if (unit === 'milliseconds') this.multiplier = 1e-3 - else this.multiplier = 1 + this.unit = unit; } formatUnsigned(v: number) { - const s = v * this.multiplier + const s = v * this.multiplier; if (s / 60 >= 1) { - const minutes = Math.floor(s / 60) - const seconds = Math.floor(s - minutes * 60).toString() - return `${minutes}:${zeroPad(seconds, 2)}` + const minutes = Math.floor(s / 60); + const seconds = Math.floor(s - minutes * 60).toString(); + return `${minutes}:${zeroPad(seconds, 2)}`; + } + if (s / 1 >= 1) { + return `${s.toFixed(2)}s`; + } + if (s / 1e-3 >= 1) { + return `${(s / 1e-3).toFixed(2)}ms`; + } + if (s / 1e-6 >= 1) { + return `${(s / 1e-6).toFixed(2)}µs`; } - if (s / 1 >= 1) return `${s.toFixed(2)}s` - if (s / 1e-3 >= 1) return `${(s / 1e-3).toFixed(2)}ms` - if (s / 1e-6 >= 1) return `${(s / 1e-6).toFixed(2)}µs` - else return `${(s / 1e-9).toFixed(2)}ns` + return `${(s / 1e-9).toFixed(2)}ns`; } format(v: number) { - return `${v < 0 ? '-' : ''}${this.formatUnsigned(Math.abs(v))}` + return `${v < 0 ? '-' : ''}${this.formatUnsigned(Math.abs(v))}`; } } @@ -1143,9 +1257,9 @@ function sortBy(ts: T[], key: (t: T) => number | string): void { const keyB = key(b) return keyA < keyB ? -1 : keyA > keyB ? 1 : 0 } - ts.sort(comparator) + ts.sort(comparator); } function lastOf(ts: T[]): T | null { - return ts[ts.length - 1] || null + return ts[ts.length - 1] || null; } From 051bc3abb793bad15f9bc5b20d383bf5e15d7fab Mon Sep 17 00:00:00 2001 From: Greg Tatum Date: Fri, 18 Dec 2020 13:22:06 -0600 Subject: [PATCH 4/9] Fix the utilities to work for Flow --- src/profile-logic/import/instruments.js | 33 ++++++++++++++++--------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/profile-logic/import/instruments.js b/src/profile-logic/import/instruments.js index f2565fa157..021c25cf93 100644 --- a/src/profile-logic/import/instruments.js +++ b/src/profile-logic/import/instruments.js @@ -1203,24 +1203,32 @@ class ByteFormatter implements ValueFormatter { } function zeroPad(s: string, width: number) { - return new Array(Math.max(width - s.length, 0) + 1).join('0') + s + return new Array(Math.max(width - s.length, 0) + 1).join('0') + s; } function getOrInsert(map: Map, k: K, fallback: (k: K) => V): V { - if (!map.has(k)) map.set(k, fallback(k)) - return map.get(k)! + let value = map.get(k); + if (value === undefined) { + value = fallback(k); + map.set(k, value); + } + return value; } function getOrElse(map: Map, k: K, fallback: (k: K) => V): V { - if (!map.has(k)) return fallback(k) - return map.get(k)! + const value = map.get(k); + if (value === undefined) { + return fallback(k); + } + return value; } function getOrThrow(map: Map, k: K): V { - if (!map.has(k)) { - throw new Error(`Expected key ${k}`) + const value = map.get(k); + if (value === undefined) { + throw new Error(`Expected key ${String(k)}`); } - return map.get(k)! + return value; } async function extractDirectoryTree(entry: FileSystemDirectoryEntry): Promise { @@ -1253,9 +1261,12 @@ async function extractDirectoryTree(entry: FileSystemDirectoryEntry): Promise(ts: T[], key: (t: T) => number | string): void { function comparator(a: T, b: T) { - const keyA = key(a) - const keyB = key(b) - return keyA < keyB ? -1 : keyA > keyB ? 1 : 0 + const keyA = key(a); + const keyB = key(b); + // Disabled eslint rule as this is imported from speedscope. + + // $FlowFixMe - Cannot compare number to string + return keyA < keyB ? -1 : keyA > keyB ? 1 : 0; } ts.sort(comparator); } From 675d7ab01f5b1d3908d39319e19803752fd7cd51 Mon Sep 17 00:00:00 2001 From: Greg Tatum Date: Fri, 18 Dec 2020 13:23:23 -0600 Subject: [PATCH 5/9] Update the code to accept a JSZip file for processing This is the first real new code. It changes the implementation from using the non-standard webkitEntries API, the JSZip API. --- src/profile-logic/import/instruments.js | 256 +++++++++++++++--------- 1 file changed, 159 insertions(+), 97 deletions(-) diff --git a/src/profile-logic/import/instruments.js b/src/profile-logic/import/instruments.js index 021c25cf93..e0172ffd1b 100644 --- a/src/profile-logic/import/instruments.js +++ b/src/profile-logic/import/instruments.js @@ -309,8 +309,8 @@ async function getRawSampleList(core: TraceDirectoryTree): Promise { continue; } const bulkstore = new BinReader( - await readAsArrayBuffer(getOrThrow(storedir.files, 'bulkstore')), - ) + await getOrThrow(storedir.files, 'bulkstore').async('arraybuffer') + ); // Ignore the first 3 words bulkstore.readUint32(); bulkstore.readUint32(); @@ -364,8 +364,12 @@ async function getIntegerArrays( // This table contains the memory addresses of stack frames - const indexreader = new BinReader(await readAsArrayBuffer(integeruniquerindex)) - const datareader = new BinReader(await readAsArrayBuffer(integeruniquerdata)) + const indexreader = new BinReader( + await integeruniquerindex.async('arraybuffer') + ); + const datareader = new BinReader( + await integeruniquerdata.async('arraybuffer') + ); // Header we don't care about indexreader.seek(32); @@ -414,11 +418,14 @@ type FormTemplateData = { runs: FormTemplateRunData[], }; +type SymbolsByPid = Map; + async function readFormTemplate( tree: TraceDirectoryTree ): Promise { const formTemplate = getOrThrow(tree.files, 'form.template'); - const archive = readInstrumentsKeyedArchive(await readAsArrayBuffer(formTemplate)); + const formTemplateData = await formTemplate.async('uint8array'); + const archive = readInstrumentsKeyedArchive(formTemplateData); const version = archive['com.apple.xray.owner.template.version']; let selectedRunNumber = 1; @@ -431,36 +438,42 @@ async function readFormTemplate( if ('stubInfoByUUID' in archive) { instrument = Array.from(archive.stubInfoByUUID.keys())[0]; } - const allRunData = archive['com.apple.xray.run.data'] - - const runs: FormTemplateRunData[] = [] - for (let runNumber of allRunData.runNumbers) { - const runData = getOrThrow>(allRunData.runData, runNumber) - - const symbolsByPid = getOrThrow>( - runData, - 'symbolsByPid', - ) - - const addressToFrameMap = new Map() - - // TODO(jlfwong): Deal with profiles with conflicting addresses? - for (let symbols of symbolsByPid.values()) { - for (let symbol of symbols.symbols) { - if (!symbol) continue - const {sourcePath, symbolName, addressToLine} = symbol - for (let address of addressToLine.keys()) { - getOrInsert(addressToFrameMap, address, () => { - const name = symbolName || `0x${zeroPad(address.toString(16), 16)}` - const frame: FrameInfo = { - key: `${sourcePath}:${name}`, - name: name, - } - if (sourcePath) { - frame.file = sourcePath - } - return frame - }) + const allRunData = archive['com.apple.xray.run.data']; + + const runs: FormTemplateRunData[] = []; + for (const runNumber of allRunData.runNumbers) { + const runData = getOrThrow>( + allRunData.runData, + runNumber + ); + + const symbolsByPid: SymbolsByPid | void = runData.get('symbolsByPid'); + + // Build a frame map if the symbols are there. + const addressToFrameMap = new Map(); + if (symbolsByPid) { + // TODO(jlfwong): Deal with profiles with conflicting addresses? + for (const symbols of symbolsByPid.values()) { + for (const symbol of symbols.symbols) { + if (!symbol) { + continue; + } + const { sourcePath, symbolName, addressToLine } = symbol; + for (const address of addressToLine.keys()) { + getOrInsert(addressToFrameMap, address, () => { + const name = + symbolName || `0x${zeroPad(address.toString(16), 16)}`; + const key: string = `${sourcePath || 'null'}:${name}`; + const frame: FrameInfo = { + key, + name: name, + }; + if (sourcePath) { + frame.file = sourcePath; + } + return frame; + }); + } } } @@ -476,45 +489,49 @@ async function readFormTemplate( instrument, selectedRunNumber, runs, - } + }; } // Import from a .trace file saved from Mac Instruments.app -export async function importFromInstrumentsTrace( - entry: FileSystemDirectoryEntry, -): Promise { - debugger - const tree = await extractDirectoryTree(entry) - - const {version, runs, instrument, selectedRunNumber} = await readFormTemplate(tree) +export async function processInstrumentsProfile( + file: File, + zip: JSZip +): Promise { + const tree = await extractDirectoryTree(zip); + const formTemplate = await readFormTemplate(tree); + if (!formTemplate) { + return null; + } + const { version, runs, instrument, selectedRunNumber } = formTemplate; if (instrument !== 'com.apple.xray.instrument-type.coresampler2') { throw new Error( - `The only supported instrument from .trace import is "com.apple.xray.instrument-type.coresampler2". Got ${instrument}`, - ) + `The only supported instrument from .trace import is "com.apple.xray.instrument-type.coresampler2". Got ${instrument}` + ); } - console.log('version: ', version) - console.log(`Importing time profile`) + console.log('version: ', version); + console.log(`Importing time profile`); - const profiles: Profile[] = [] - let indexToView = 0 + const profiles: Profile[] = []; + let indexToView: number = 0; - for (let run of runs) { - const {addressToFrameMap, number} = run + for (const run of runs) { + const { addressToFrameMap, number } = run; const group = await importRunFromInstrumentsTrace({ - fileName: entry.name, + fileName: file.name, tree, addressToFrameMap, runNumber: number, - }) + }); if (run.number === selectedRunNumber) { - indexToView = profiles.length + group.indexToView + indexToView = profiles.length + group.indexToView; } - profiles.push(...group.profiles) + profiles.push(...group.profiles); } - return {name: entry.name, indexToView, profiles} + // $FlowFixMe - This is the original signature for speedscope. + return { name: file.name, indexToView, profiles }; } interface ProfileGroup { @@ -559,6 +576,12 @@ export async function importRunFromInstrumentsTrace(args: { samples, }) ), + }; +} + +class StackListProfileBuilder { + constructor(..._args: any[]) { + throw new Error('Speedscope StackListProfileBuilder is not imported.'); } } @@ -575,8 +598,17 @@ export function importThreadFromInstrumentsTrace(args: { const backtraceIDtoStack = new Map(); samples = samples.filter(s => s.threadID === threadID); - const profile = new StackListProfileBuilder(lastOf(samples)!.timestamp) - profile.setName(`${fileName} - thread ${threadID}`) + // TODO - The following parts of this function need to be implemented. + // eslint-disable-next-line no-constant-condition + if (true) { + const profile: any = {}; + return profile; + } + + const profile = new StackListProfileBuilder( + ensureExists(lastOf(samples)).timestamp + ); + profile.setName(`${fileName} - thread ${threadID}`); function appendRecursive(k: number, stack: FrameInfo[]) { const frame = addressToFrameMap.get(k); @@ -631,9 +663,8 @@ export function importThreadFromInstrumentsTrace(args: { return profile.build(); } -export function readInstrumentsKeyedArchive(buffer: ArrayBuffer): any { - const byteArray = new Uint8Array(buffer) - const parsedPlist = parseBinaryPlist(byteArray) +export function readInstrumentsKeyedArchive(byteArray: Uint8Array) { + const parsedPlist = parseBinaryPlist(byteArray); const data = expandKeyedArchive(parsedPlist, ($classname, object) => { switch ($classname) { case 'NSTextStorage': @@ -650,6 +681,8 @@ export function readInstrumentsKeyedArchive(buffer: ArrayBuffer): any { for (let i = 3; ; i += 2) { const address = object['$' + i]; const line = object['$' + (i + 1)]; + // Disabled since this is imported code, and it's a behavior change to rewrite it. + // eslint-disable-next-line eqeqeq if (address == null || line == null) { break; } @@ -711,6 +744,7 @@ export function readInstrumentsKeyedArchive(buffer: ArrayBuffer): any { //////////////////////////////////////////////////////////////////////////////// export function decodeUTF8(bytes: Uint8Array): string { + // eslint-disable-next-line prefer-spread let text = String.fromCharCode.apply(String, Array.from(bytes)); if (text.slice(-1) === '\0') { text = text.slice(0, -1); @@ -902,15 +936,18 @@ function paternMatchObjectiveC( //////////////////////////////////////////////////////////////////////////////// -export class UID { - constructor(public index: number) {} +class UID { + index: number; + constructor(index: number) { + this.index = index; + } } function parseBinaryPlist(bytes: Uint8Array): any { const text = 'bplist00'; for (let i = 0; i < 8; i++) { if (bytes[i] !== text.charCodeAt(i)) { - throw new Error('File is not a binary plist') + throw new Error('File is not a binary plist'); } } return new BinaryPlistParser( @@ -1188,20 +1225,6 @@ class TimeFormatter implements ValueFormatter { } } -class ByteFormatter implements ValueFormatter { - unit: FileFormat.ValueUnit = 'bytes' - - format(v: number) { - if (v < 1024) return `${v.toFixed(0)} B` - v /= 1024 - if (v < 1024) return `${v.toFixed(2)} KB` - v /= 1024 - if (v < 1024) return `${v.toFixed(2)} MB` - v /= 1024 - return `${v.toFixed(2)} GB` - } -} - function zeroPad(s: string, width: number) { return new Array(Math.max(width - s.length, 0) + 1).join('0') + s; } @@ -1231,32 +1254,71 @@ function getOrThrow(map: Map, k: K): V { return value; } -async function extractDirectoryTree(entry: FileSystemDirectoryEntry): Promise { - const node: TraceDirectoryTree = { - name: entry.name, +async function extractDirectoryTree(zip: JSZip): Promise { + const tree: TraceDirectoryTree = { + name: '', files: new Map(), subdirectories: new Map(), - } + }; - const children = await new Promise((resolve, reject) => { - entry.createReader().readEntries((entries: any[]) => { - resolve(entries) - }, reject) - }) + for (const file of objectValues(zip.files)) { + const parts = file.name + // Split the directory into parts. + .split('/') + // Filter out any trailing empty strings. This can happen when a '/' is at the end + // of a path. + .filter(part => part !== ''); + + let fileName = null; + if (!file.dir) { + fileName = parts.pop(); + } - for (let child of children) { - if (child.isDirectory) { - const subtree = await extractDirectoryTree(child as FileSystemDirectoryEntry) - node.subdirectories.set(subtree.name, subtree) - } else { - const file = await new Promise((resolve, reject) => { - ;(child as FileSystemFileEntry).file(resolve, reject) - }) - node.files.set(file.name, file) + if (parts[0] === '__MACOSX') { + // When zipping on macOS, some OS-only files are hidden away in this folder, + // but the contents match the original directory structure. Remove that folder + // name so the original files will match up. + // + // ├── Launch_inv_list.trace + // │ └── Trace1.Run + // │ └── ... + // ├── __MACOSX + // │ └── Launch_inv_list.trace + // │ └── form.template + parts.shift(); + } + + let node = tree; + for (const part of parts) { + node = getOrInsert(node.subdirectories, part, () => ({ + name: part, + files: new Map(), + subdirectories: new Map(), + })); + } + + if (fileName !== null) { + node.files.set(fileName, file); + } + } + + if (tree.subdirectories.size === 1) { + const subtree = ensureExists(getFirstItemFromMap(tree.subdirectories)); + if (subtree.name.endsWith('.trace')) { + // This assumes the .trace folder is the root of the zip + // ├── Launch_inv_list.trace + // │ └── Trace1.Run + // │ └── form.template + // │ └── ... + return subtree; } } - return node + // This assumes the directory contents of a .trace folder are zipped. + // ├── Trace1.Run + // ├── form.template + // ├── ... + return tree; } function sortBy(ts: T[], key: (t: T) => number | string): void { From 1b43e361dc6d20fe3f679d9bdc8846968eeb614d Mon Sep 17 00:00:00 2001 From: Greg Tatum Date: Fri, 18 Dec 2020 13:26:25 -0600 Subject: [PATCH 6/9] Hook up the new code to the profile loading actions --- src/actions/receive-profile.js | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/actions/receive-profile.js b/src/actions/receive-profile.js index 5aedb56ea6..cd45ecb6d4 100644 --- a/src/actions/receive-profile.js +++ b/src/actions/receive-profile.js @@ -76,6 +76,7 @@ import { assertExhaustiveCheck, ensureExists, } from 'firefox-profiler/utils/flow'; +import { processInstrumentsProfile } from 'firefox-profiler/profile-logic/import/instruments'; /** * This file collects all the actions that are used for receiving the profile in the @@ -1288,10 +1289,33 @@ export function retrieveProfileFromFile( { const buffer = await fileReader(file).asArrayBuffer(); const zip = await JSZip.loadAsync(buffer); - await dispatch(receiveZipFile(zip)); + const profile = await processInstrumentsProfile(file, zip); + if (profile) { + await withHistoryReplaceStateAsync(async () => { + await dispatch(viewProfile(profile)); + }); + } else { + await dispatch(receiveZipFile(zip)); + } } break; default: { + if ( + // Check if this is most likely a directory. + file.size === 0 && + file.type === '' && + // Instrument directories end in .trace + file.name.endsWith('.trace') + ) { + // Throw an error before attempting to use the fileReader, which would error + // on this case anyway. + throw new Error( + 'This appears to be an instruments trace directory. Loading files via ' + + 'directory is not supported. Please zip the entire directory into a ' + + 'single file and try again.' + ); + } + // Plain uncompressed profile files can have file names with uncommon // 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 From 100a91b37a90920794abaacd2a5c45fd693dbd3f Mon Sep 17 00:00:00 2001 From: Greg Tatum Date: Fri, 18 Dec 2020 14:02:39 -0600 Subject: [PATCH 7/9] Setup the infrastructure for instruments import test --- src/profile-logic/import/instruments.js | 10 +++- src/test/fixtures/mocks/file.js | 32 ++++++++++ .../fixtures/upgrades/test.instruments.zip | Bin 0 -> 268659 bytes src/test/store/receive-profile.test.js | 22 +------ src/test/unit/profile-conversion.test.js | 56 ++++++++++++++++++ src/types/libdef/npm-custom/jszip_v3.x.x.js | 2 +- 6 files changed, 98 insertions(+), 24 deletions(-) create mode 100644 src/test/fixtures/mocks/file.js create mode 100644 src/test/fixtures/upgrades/test.instruments.zip diff --git a/src/profile-logic/import/instruments.js b/src/profile-logic/import/instruments.js index e0172ffd1b..ea2e37af12 100644 --- a/src/profile-logic/import/instruments.js +++ b/src/profile-logic/import/instruments.js @@ -37,6 +37,7 @@ import { getFirstItemFromMap, } from 'firefox-profiler/utils/flow'; import type { ObjectMap, Profile } from 'firefox-profiler/types'; +import { getEmptyProfile, getEmptyThread } from '../data-structures'; // This file contains methods to import data from OS X Instruments.app // https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/index.html @@ -530,8 +531,13 @@ export async function processInstrumentsProfile( profiles.push(...group.profiles); } - // $FlowFixMe - This is the original signature for speedscope. - return { name: file.name, indexToView, profiles }; + // TODO - Actually import a profile. + // return { name: file.name, indexToView, profiles }; + + // This is just enough to not error when loading a profile. + const profile = getEmptyProfile(); + profile.threads.push(getEmptyThread()); + return profile; } interface ProfileGroup { diff --git a/src/test/fixtures/mocks/file.js b/src/test/fixtures/mocks/file.js new file mode 100644 index 0000000000..5464ffc0ff --- /dev/null +++ b/src/test/fixtures/mocks/file.js @@ -0,0 +1,32 @@ +/* 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 + +type MockFileConfig = {| + +type: string, + +payload: any, +|}; + +/** + * Bypass all of Flow's checks, and mock out the file interface. + */ +export function mockFile({ type, payload }: MockFileConfig): File { + const file = { + type, + _payload: payload, + }; + return (file: any); +} + +/** + * Bypass all of Flow's checks, and mock out the file reader. + */ +export function mockFileReader(mockFile: File) { + const payload = (mockFile: any)._payload; + return { + asText: () => Promise.resolve((payload: string)), + asArrayBuffer: () => Promise.resolve((payload: ArrayBuffer)), + }; +} diff --git a/src/test/fixtures/upgrades/test.instruments.zip b/src/test/fixtures/upgrades/test.instruments.zip new file mode 100644 index 0000000000000000000000000000000000000000..7c732bf6573f0206f8739c3c1ad7d930cf4578f8 GIT binary patch literal 268659 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%0AOdMX0RVLXu<|GxlYa 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�!hu3!9ktVmOlCQU^_kshQ& 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 fajjo{_f_Dafc_jHplHCK7%3p24~>AQP@w+ Promise.resolve((payload: string)), - asArrayBuffer: () => Promise.resolve((payload: ArrayBuffer)), - }; - } async function setupTestWithFile(mockFileOptions) { const file = mockFile(mockFileOptions); diff --git a/src/test/unit/profile-conversion.test.js b/src/test/unit/profile-conversion.test.js index 277f4aa483..ebc6aa620b 100644 --- a/src/test/unit/profile-conversion.test.js +++ b/src/test/unit/profile-conversion.test.js @@ -9,6 +9,15 @@ import type { TracingEventUnion, CpuProfileEvent, } from '../../profile-logic/import/chrome'; +import { retrieveProfileFromFile } from 'firefox-profiler/actions/receive-profile'; +import { blankStore } from '../fixtures/stores'; +import { + getProfileOrNull, + getProfile, + getView, +} from 'firefox-profiler/selectors'; +import { waitUntilState } from '../fixtures/utils'; +import { mockFile, mockFileReader } from '../fixtures/mocks/file'; describe('converting Linux perf profile', function() { it('should import a perf profile', async function() { @@ -138,3 +147,50 @@ describe('converting Google Chrome profile', function() { expect(profile).toMatchSnapshot(); }); }); + +describe('converting MacOS instruments', function() { + it('should import a zipped profile', async function() { + const fs = require('fs'); + const file = mockFile({ + type: 'application/zip', + payload: fs.readFileSync( + 'src/test/fixtures/upgrades/test.instruments.zip' + ), + }); + + const store = blankStore(); + const { dispatch, getState } = store; + + expect(getView(getState())).toEqual({ phase: 'INITIALIZING' }); + + const profileLoadingComplete = new Promise((resolve, reject) => { + store.subscribe(() => { + const view = getView(getState()); + switch (view.phase) { + case 'INITIALIZING': + case 'DATA_LOADED': + // Wait patiently. + break; + case 'FATAL_ERROR': + // There was an error loading the profile + reject(view.error); + break; + case 'PROFILE_LOADED': + resolve(); + break; + default: + console.error(view); + reject(new Error('Unexpected view')); + } + }); + }); + + dispatch(retrieveProfileFromFile(file, mockFileReader)); + + await profileLoadingComplete; + await waitUntilState(store, state => Boolean(getProfileOrNull(state))); + const profile = getProfile(getState()); + + expect(profile.meta.version).toEqual(GECKO_PROFILE_VERSION); + }); +}); diff --git a/src/types/libdef/npm-custom/jszip_v3.x.x.js b/src/types/libdef/npm-custom/jszip_v3.x.x.js index 2859ed5fd7..5f5f6bb874 100644 --- a/src/types/libdef/npm-custom/jszip_v3.x.x.js +++ b/src/types/libdef/npm-custom/jszip_v3.x.x.js @@ -38,7 +38,7 @@ declare module 'jszip' { files: {| [fileName: string]: JSZipFile |}; file: (fileName: string, contents: string) => void; generateAsync({ type: 'uint8array' }): Promise; - static loadAsync: (data: ArrayBuffer) => Promise; + static loadAsync: (data: ArrayBuffer | Buffer) => Promise; } declare module.exports: typeof JSZip; From 55a443877bb18219ccbaeaac57279d56765a7b9a Mon Sep 17 00:00:00 2001 From: Greg Tatum Date: Mon, 21 Dec 2020 08:06:55 -0600 Subject: [PATCH 8/9] WIP --- src/profile-logic/import/instruments.js | 220 +++++++++++++---------- src/test/unit/profile-conversion.test.js | 2 +- 2 files changed, 128 insertions(+), 94 deletions(-) diff --git a/src/profile-logic/import/instruments.js b/src/profile-logic/import/instruments.js index ea2e37af12..460c980b53 100644 --- a/src/profile-logic/import/instruments.js +++ b/src/profile-logic/import/instruments.js @@ -36,7 +36,14 @@ import { objectValues, getFirstItemFromMap, } from 'firefox-profiler/utils/flow'; -import type { ObjectMap, Profile } from 'firefox-profiler/types'; +import type { + ObjectMap, + Profile, + Thread, + Address, + IndexIntoFrameTable, + IndexIntoFuncTable, +} from 'firefox-profiler/types'; import { getEmptyProfile, getEmptyThread } from '../data-structures'; // This file contains methods to import data from OS X Instruments.app @@ -86,7 +93,7 @@ type FrameInfo = { // "ActiveRecord##to_hash" name: string, - // JSZipFile path of the code corresponding to this + // File path of the code corresponding to this // call stack frame. file?: string, @@ -401,23 +408,24 @@ async function getIntegerArrays( return arrays; } -type SymbolInfo = { +type SymbolInfo = {| symbolName: string | null, sourcePath: string | null, - addressToLine: Map, -}; + addressToLine: Map, +|}; -type FormTemplateRunData = { +type FormTemplateRunData = {| number: number, - addressToFrameMap: Map, -}; + thread: Thread, + addressToFrameIndex: Map, +|}; -type FormTemplateData = { +type FormTemplateData = {| version: number, selectedRunNumber: number, instrument: string, runs: FormTemplateRunData[], -}; +|}; type SymbolsByPid = Map; @@ -449,38 +457,72 @@ async function readFormTemplate( ); const symbolsByPid: SymbolsByPid | void = runData.get('symbolsByPid'); - + const symbolToFuncIndex = new Map(); // Build a frame map if the symbols are there. - const addressToFrameMap = new Map(); + const addressToFrameIndex = new Map(); + const thread = getEmptyThread(); + const { frameTable, stringTable, funcTable } = thread; + + // eslint-disable-next-line no-inner-declarations + function getFuncIndex( + sourcePath: string | null, + symbolName: string | null, + address: Address + ): IndexIntoFuncTable { + if (sourcePath === null) { + sourcePath = ''; + } + if (symbolName === null) { + symbolName = `0x${zeroPad(address.toString(16), 16)}`; + } + let funcIndex = symbolToFuncIndex.get(`${sourcePath}:${symbolName}`); + + if (funcIndex === undefined) { + funcTable.name.push(stringTable.indexForString(symbolName)); + funcTable.isJS.push(false); + funcTable.relevantForJS.push(false); + // TODO - Is this information here? + funcTable.resource.push(-1); + funcTable.fileName.push( + sourcePath ? stringTable.indexForString(sourcePath) : null + ); + funcIndex = funcTable.length++; + } + return funcIndex; + } + if (symbolsByPid) { - // TODO(jlfwong): Deal with profiles with conflicting addresses? for (const symbols of symbolsByPid.values()) { for (const symbol of symbols.symbols) { if (!symbol) { continue; } const { sourcePath, symbolName, addressToLine } = symbol; - for (const address of addressToLine.keys()) { - getOrInsert(addressToFrameMap, address, () => { - const name = - symbolName || `0x${zeroPad(address.toString(16), 16)}`; - const key: string = `${sourcePath || 'null'}:${name}`; - const frame: FrameInfo = { - key, - name: name, - }; - if (sourcePath) { - frame.file = sourcePath; - } - return frame; - }); + + for (const [address, line] of addressToLine.entries()) { + const frameIndex = addressToFrameIndex.get(address); + if (frameIndex === undefined) { + const funcIndex = getFuncIndex(sourcePath, symbolName, address); + + frameTable.address.push(address); + frameTable.category.push(null); + frameTable.subcategory.push(null); + + frameTable.func.push(funcIndex); + frameTable.innerWindowID.push(null); + frameTable.implementation.push(null); + frameTable.line.push(line); + frameTable.column.push(null); + frameTable.length++; + } } } } runs.push({ number: runNumber, - addressToFrameMap, + thread, + addressToFrameIndex, }); } } @@ -499,30 +541,29 @@ export async function processInstrumentsProfile( zip: JSZip ): Promise { const tree = await extractDirectoryTree(zip); - const formTemplate = await readFormTemplate(tree); + const formTemplate: FormTemplateData = await readFormTemplate(tree); if (!formTemplate) { return null; } - const { version, runs, instrument, selectedRunNumber } = formTemplate; + const { runs, instrument, selectedRunNumber } = formTemplate; if (instrument !== 'com.apple.xray.instrument-type.coresampler2') { throw new Error( `The only supported instrument from .trace import is "com.apple.xray.instrument-type.coresampler2". Got ${instrument}` ); } - console.log('version: ', version); - console.log(`Importing time profile`); - const profiles: Profile[] = []; + const profile: Profile = getEmptyProfile(); let indexToView: number = 0; for (const run of runs) { - const { addressToFrameMap, number } = run; - const group = await importRunFromInstrumentsTrace({ - fileName: file.name, + const { addressToFrameIndex, thread, number } = run; + await importRunFromInstrumentsTrace( + profile, + file.name, tree, - addressToFrameMap, - runNumber: number, - }); + addressToFrameIndex, + number + ); if (run.number === selectedRunNumber) { indexToView = profiles.length + group.indexToView; @@ -546,13 +587,13 @@ interface ProfileGroup { profiles: Profile[]; } -export async function importRunFromInstrumentsTrace(args: { +export async function importRunFromInstrumentsTrace( + profile: Profile, fileName: string, tree: TraceDirectoryTree, addressToFrameMap: Map, - runNumber: number, -}): Promise { - const { fileName, tree, addressToFrameMap, runNumber } = args; + runNumber: number +): Promise { const core = getCoreDirForRun(tree, runNumber); const samples = await getRawSampleList(core); const arrays = await getIntegerArrays(samples, core); @@ -570,83 +611,75 @@ export async function importRunFromInstrumentsTrace(args: { sortBy(counts, c => -c[1]); const threadIDs = counts.map(c => c[0]); + profile.threads = threadIDs.map(threadID => + importThreadFromInstrumentsTrace({ + threadID, + fileName, + arrays, + addressToFrameMap, + samples, + }) + ); + return { name: fileName, indexToView: 0, - profiles: threadIDs.map(threadID => - importThreadFromInstrumentsTrace({ - threadID, - fileName, - arrays, - addressToFrameMap, - samples, - }) - ), + profile, }; } -class StackListProfileBuilder { - constructor(..._args: any[]) { - throw new Error('Speedscope StackListProfileBuilder is not imported.'); - } -} - export function importThreadFromInstrumentsTrace(args: { fileName: string, addressToFrameMap: Map, threadID: number, arrays: number[][], samples: Sample[], -}): Profile { - const { fileName, addressToFrameMap, arrays, threadID } = args; - let { samples } = args; +}): Thread { + const { addressToFrameMap, arrays, threadID, samples } = args; - const backtraceIDtoStack = new Map(); - samples = samples.filter(s => s.threadID === threadID); + const thread = getEmptyThread(); + thread.tid = threadID; + thread.pid = threadID; - // TODO - The following parts of this function need to be implemented. - // eslint-disable-next-line no-constant-condition - if (true) { - const profile: any = {}; - return profile; - } - - const profile = new StackListProfileBuilder( - ensureExists(lastOf(samples)).timestamp - ); - profile.setName(`${fileName} - thread ${threadID}`); - - function appendRecursive(k: number, stack: FrameInfo[]) { - const frame = addressToFrameMap.get(k); + function appendRecursive(backtraceID: number, stack: FrameInfo[]) { + const frame = addressToFrameMap.get(backtraceID); if (frame) { stack.push(frame); - } else if (k in arrays) { - for (const addr of arrays[k]) { + } else if (backtraceID in arrays) { + for (const addr of arrays[backtraceID]) { appendRecursive(addr, stack); } } else { const rawAddressFrame: FrameInfo = { - key: k, - name: `0x${zeroPad(k.toString(16), 16)}`, + key: backtraceID, + name: `0x${zeroPad(backtraceID.toString(16), 16)}`, }; - addressToFrameMap.set(k, rawAddressFrame); + addressToFrameMap.set(backtraceID, rawAddressFrame); stack.push(rawAddressFrame); } } + const samplesForThisThread = samples.filter(s => s.threadID === threadID); + const backtraceIDtoStack = new Map(); + + for (const { timestamp, backtraceID } of samplesForThisThread) { + const stack = backtraceIDtoStack.get(backtraceID); + if (stack === undefined) { + const stack: FrameInfo[] = []; + appendRecursive(backtraceID, stack); + stack.reverse(); + return stack; + } + + thread.samples.length++; + thread.samples.time.push(sample.timestamp); + thread.samples.stack.push(null); + } + + return thread; + let lastTimestamp: null | number = null; for (const sample of samples) { - const stackForSample = getOrInsert( - backtraceIDtoStack, - sample.backtraceID, - id => { - const stack: FrameInfo[] = []; - appendRecursive(id, stack); - stack.reverse(); - return stack; - } - ); - if (lastTimestamp === null) { // The first sample is sometimes fairly late in the profile for some reason. // We'll just say nothing was known to be on the stack in that time. @@ -680,6 +713,7 @@ export function readInstrumentsKeyedArchive(byteArray: Uint8Array) { return null; case 'PFTSymbolData': { + console.log(`!!! object`, object); const ret = Object.create(null); ret.symbolName = object.$0; ret.sourcePath = object.$1; diff --git a/src/test/unit/profile-conversion.test.js b/src/test/unit/profile-conversion.test.js index ebc6aa620b..54240f1312 100644 --- a/src/test/unit/profile-conversion.test.js +++ b/src/test/unit/profile-conversion.test.js @@ -149,7 +149,7 @@ describe('converting Google Chrome profile', function() { }); describe('converting MacOS instruments', function() { - it('should import a zipped profile', async function() { + it.only('should import a zipped profile', async function() { const fs = require('fs'); const file = mockFile({ type: 'application/zip', From ba65c4c0af427ef974b5f81817ef274be9b4a3ed Mon Sep 17 00:00:00 2001 From: Greg Tatum Date: Mon, 21 Dec 2020 16:55:37 -0600 Subject: [PATCH 9/9] WIP - But it is working --- src/profile-logic/import/instruments.js | 960 ++++++++++++------------ 1 file changed, 465 insertions(+), 495 deletions(-) diff --git a/src/profile-logic/import/instruments.js b/src/profile-logic/import/instruments.js index 460c980b53..2401d0f0c4 100644 --- a/src/profile-logic/import/instruments.js +++ b/src/profile-logic/import/instruments.js @@ -43,186 +43,14 @@ import type { Address, IndexIntoFrameTable, IndexIntoFuncTable, + Tid, + Pid, } from 'firefox-profiler/types'; import { getEmptyProfile, getEmptyThread } from '../data-structures'; // This file contains methods to import data from OS X Instruments.app // https://developer.apple.com/library/content/documentation/DeveloperTools/Conceptual/InstrumentsUserGuide/index.html -// TODO - importFromInstrumentsDeepCopy -// function parseTSV(contents: string): T[] { -// const lines = contents.split('\n').map(l => l.split('\t')); - -// const headerLine = lines.shift(); -// if (!headerLine) { -// return []; -// } - -// const indexToField = new Map(); -// for (let i = 0; i < headerLine.length; i++) { -// indexToField.set(i, headerLine[i]); -// } - -// const ret: T[] = []; -// for (const line of lines) { -// const row = {}; -// for (let i = 0; i < line.length; i++) { -// row[ensureExists(indexToField.get(i))] = line[i]; -// } -// ret.push(coerce(row)); -// } -// return ret; -// } -// -// type PastedTimeProfileRow = { -// Weight?: string, -// 'Source Path'?: string, -// 'Symbol Name'?: string, -// }; -// -// type PastedAllocationsProfileRow = { -// 'Bytes Used'?: string, -// 'Source Path'?: string, -// 'Symbol Name'?: string, -// }; - -type FrameInfo = { - key: string | number, - - // Name of the frame. May be a method name, e.g. - // "ActiveRecord##to_hash" - name: string, - - // File path of the code corresponding to this - // call stack frame. - file?: string, - - // Line in the given file where this frame occurs - line?: number, - - // Column in the file - col?: number, -}; - -// TODO - importFromInstrumentsDeepCopy -// interface FrameInfoWithWeight extends FrameInfo { -// endValue: number; -// } - -// TODO - importFromInstrumentsDeepCopy -// function getWeight(deepCopyRow: any): number { -// if ('Bytes Used' in deepCopyRow) { -// const bytesUsedString = deepCopyRow['Bytes Used']; -// const parts = /\s*(\d+(?:[.]\d+)?) (\w+)\s+(?:\d+(?:[.]\d+))%/.exec( -// bytesUsedString -// ); -// if (!parts) { -// return 0; -// } -// const value = parseInt(parts[1], 10); -// const units = parts[2]; - -// switch (units) { -// case 'Bytes': -// return value; -// case 'KB': -// return 1024 * value; -// case 'MB': -// return 1024 * 1024 * value; -// case 'GB': -// return 1024 * 1024 * 1024 * value; -// default: -// throw new Error(`Unrecognized units ${units}`); -// } -// } - -// if ('Weight' in deepCopyRow || 'Running Time' in deepCopyRow) { -// const weightString = deepCopyRow.Weight || deepCopyRow['Running Time']; -// const parts = /\s*(\d+(?:[.]\d+)?) ?(\w+)\s+(?:\d+(?:[.]\d+))%/.exec( -// weightString -// ); -// if (!parts) { -// return 0; -// } -// const value = parseInt(parts[1], 10); -// const units = parts[2]; - -// switch (units) { -// case 'ms': -// return value; -// case 's': -// return 1000 * value; -// case 'min': -// return 1000 * value; -// default: -// throw new Error(`Unrecognized units ${units}`); -// } -// } - -// return -1; -// } - -// TODO - This is currently not migrated. -// Import from a deep copy made of a profile -// export function importFromInstrumentsDeepCopy(contents: string): Profile { -// const profile = new CallTreeProfileBuilder(); -// const rows = parseTSV( -// contents -// ); - -// const stack: FrameInfoWithWeight[] = []; -// let cumulativeValue: number = 0; - -// for (const row of rows) { -// const symbolName = row['Symbol Name']; -// if (!symbolName) { -// continue; -// } -// const trimmedSymbolName = symbolName.trim(); -// const stackDepth = symbolName.length - trimmedSymbolName.length; - -// if (stack.length - stackDepth < 0) { -// throw new Error('Invalid format'); -// } - -// const framesToLeave: FrameInfoWithWeight[] = []; - -// while (stackDepth < stack.length) { -// const stackTop = ensureExists(stack.pop()); -// framesToLeave.push(stackTop); -// } - -// for (const frameToLeave of framesToLeave) { -// cumulativeValue = Math.max(cumulativeValue, frameToLeave.endValue); -// profile.leaveFrame(frameToLeave, cumulativeValue); -// } - -// const newFrameInfo: FrameInfoWithWeight = { -// key: `${row['Source Path'] || ''}:${trimmedSymbolName}`, -// name: trimmedSymbolName, -// file: row['Source Path'], -// endValue: cumulativeValue + getWeight(row), -// }; - -// profile.enterFrame(newFrameInfo, cumulativeValue); -// stack.push(newFrameInfo); -// } - -// while (stack.length > 0) { -// const frameToLeave = ensureExists(stack.pop()); -// cumulativeValue = Math.max(cumulativeValue, frameToLeave.endValue); -// profile.leaveFrame(frameToLeave, cumulativeValue); -// } - -// if ('Bytes Used' in rows[0]) { -// profile.setValueFormatter(new ByteFormatter()); -// } else if ('Weight' in rows[0] || 'Running Time' in rows[0]) { -// profile.setValueFormatter(new TimeFormatter('milliseconds')); -// } - -// return profile.build(); -// } - interface TraceDirectoryTree { name: string; files: Map; @@ -299,10 +127,13 @@ class BinReader { } } +type IndexIntoSomeThreadStructure = number; +type IndexIntoArrays = number; + interface Sample { timestamp: number; - threadID: number; - backtraceID: number; + threadId: IndexIntoSomeThreadStructure; + backtraceId: IndexIntoArrays; } async function getRawSampleList(core: TraceDirectoryTree): Promise { @@ -337,11 +168,14 @@ async function getRawSampleList(core: TraceDirectoryTree): Promise { break; } - const threadID = bulkstore.readUint32(); + // TODO(Greg) - I think this is wrong. In the example in the test this comes out + // as the value 4. I suspect we actually need an index into the threadSerialNumbers. + // In the example code, this value would be 0x00, which is hard to deduce. + const threadId = bulkstore.readUint32(); bulkstore.skip(bytesPerEntry - 6 - 4 - 4); - const backtraceID = bulkstore.readUint32(); - samples.push({ timestamp, threadID, backtraceID }); + const backtraceId = bulkstore.readUint32(); + samples.push({ timestamp, threadId, backtraceId }); } return samples; } @@ -414,24 +248,11 @@ type SymbolInfo = {| addressToLine: Map, |}; -type FormTemplateRunData = {| - number: number, - thread: Thread, - addressToFrameIndex: Map, -|}; - -type FormTemplateData = {| - version: number, - selectedRunNumber: number, - instrument: string, - runs: FormTemplateRunData[], -|}; +type SymbolsByPid = Map; -type SymbolsByPid = Map; - -async function readFormTemplate( +async function convertInstrumentsProfile( tree: TraceDirectoryTree -): Promise { +): Promise { const formTemplate = getOrThrow(tree.files, 'form.template'); const formTemplateData = await formTemplate.async('uint8array'); const archive = readInstrumentsKeyedArchive(formTemplateData); @@ -449,90 +270,255 @@ async function readFormTemplate( } const allRunData = archive['com.apple.xray.run.data']; - const runs: FormTemplateRunData[] = []; + const profile = getEmptyProfile(); + const otherCategory = profile.meta.categories.findIndex( + category => category.name === 'Other' + ); + if (otherCategory === -1) { + throw new Error('Could not find the Other category.'); + } + const otherSubCategory = profile.meta.categories[ + otherCategory + ].subcategories.indexOf('Other'); + if (otherSubCategory === -1) { + throw new Error('Could not find the Other subcategory.'); + } + for (const runNumber of allRunData.runNumbers) { const runData = getOrThrow>( allRunData.runData, runNumber ); - const symbolsByPid: SymbolsByPid | void = runData.get('symbolsByPid'); - const symbolToFuncIndex = new Map(); - // Build a frame map if the symbols are there. - const addressToFrameIndex = new Map(); - const thread = getEmptyThread(); - const { frameTable, stringTable, funcTable } = thread; - - // eslint-disable-next-line no-inner-declarations - function getFuncIndex( - sourcePath: string | null, - symbolName: string | null, - address: Address - ): IndexIntoFuncTable { - if (sourcePath === null) { - sourcePath = ''; + const core = getCoreDirForRun(tree, runNumber); + const samples = await getRawSampleList(core); + const arrays = await getIntegerArrays(samples, core); + + // This is easier to type as an Object. + const runDataObj: RunData = mapToObject( + runData, + // Keep the following as Map types: + new Set([ + 'symbolsByPid', + 'mach_time_info', + 'threadsByTID', + 'threadSerialNumbers', + ]) + ); + + console.log( + 'The runData object has all of the interesting data for the Instruments importer', + runDataObj + ); + console.log( + 'TODO - `threadsByTID` has the relevant thread information.', + runDataObj.threadsByTID + ); + console.log( + 'TODO - `threadSerialNumbers` I believe has the serialized thread ' + + 'information, which is stored in the sample information. Unfortunately, ' + + 'single-threaded data will always have a value of 0 for the index, which is ' + + 'hard to deduce from the sample data.', + runDataObj.threadSerialNumbers + ); + + const symbolsByPid: SymbolsByPid = ensureExists( + runData.get('symbolsByPid'), + 'Expected to find symbols information in the instruments profile, but none was found.' + ); + + // TODO(Greg) - The symbols are provided by PID, however, I don't have a way to map + // back to the true thread index. This is somewhat broken from the speedscope + // implementation. There is a risk that different PIDs share different addresses, + // and the symbolication can get messed up. The fix here is to figure out the correct + // thread offset in the sample. See the other TODO(Greg) notes. + const addressToSymbol: Map = new Map(); + for (const { symbols } of symbolsByPid.values()) { + for (const symbol of symbols) { + for (const address of symbol.addressToLine.keys()) { + addressToSymbol.set(address, symbol); + } } - if (symbolName === null) { - symbolName = `0x${zeroPad(address.toString(16), 16)}`; + } + + /* TODO + for (const { tid, process } of runDataObj.threadsByTID.values()) { + for (const { timestamp, threadId, backtraceId } of samples) { + // The threadId is wrong. In the test data its value is 4. I suspect the sample + // includes an index into the threadSerialNumbers. It would be good to record + // a multi-threaded profile, and deduce where this information is in the sample. + + // TODO - Match the samples here. } - let funcIndex = symbolToFuncIndex.get(`${sourcePath}:${symbolName}`); - - if (funcIndex === undefined) { - funcTable.name.push(stringTable.indexForString(symbolName)); - funcTable.isJS.push(false); - funcTable.relevantForJS.push(false); - // TODO - Is this information here? - funcTable.resource.push(-1); - funcTable.fileName.push( - sourcePath ? stringTable.indexForString(sourcePath) : null - ); - funcIndex = funcTable.length++; + } + */ + + const samplesByThreadId: Map< + IndexIntoSomeThreadStructure, + Sample[] + > = new Map(); + for (const sample of samples) { + let samples = samplesByThreadId.get(sample.threadId); + if (!samples) { + samples = []; + samplesByThreadId.set(sample.threadId, samples); } - return funcIndex; + samples.push(sample); } - if (symbolsByPid) { - for (const symbols of symbolsByPid.values()) { - for (const symbol of symbols.symbols) { - if (!symbol) { - continue; + for (const samples of samplesByThreadId.values()) { + const symbolToFuncIndex = new Map(); + const thread = getEmptyThread(); + profile.threads.push(thread); + const { + frameTable, + stringTable, + stackTable, + funcTable, + samples: samplesTable, + } = thread; + + // eslint-disable-next-line no-inner-declarations + function getFuncIndex( + address: Address, + symbol?: SymbolInfo + ): IndexIntoFuncTable { + const sourcePath: string = + symbol && symbol.sourcePath ? symbol.sourcePath : ''; + + const symbolName: string = + symbol && symbol.symbolName + ? symbol.symbolName + : `0x${zeroPad(address.toString(16), 16)}`; + const key = `${sourcePath}:${symbolName}`; + let funcIndex = symbolToFuncIndex.get(key); + if (funcIndex === undefined) { + funcTable.name.push(stringTable.indexForString(symbolName)); + funcTable.isJS.push(false); + funcTable.relevantForJS.push(false); + // TODO - Is this information here? + funcTable.resource.push(-1); + funcTable.fileName.push( + sourcePath ? stringTable.indexForString(sourcePath) : null + ); + funcIndex = funcTable.length++; + symbolToFuncIndex.set(key, funcIndex); + } + return funcIndex; + } + + for (const sample of samples) { + const symbols: Array = []; + const addresses: Address[] = []; + const idsOrAddresses: Array
= [ + sample.backtraceId, + ]; + + // Walk from tip to root of the stack. The stack is composed of lists of different + // addresses. An id here represents an index into the the integer arrays. For + // some reason this information is split across different sections of the integers + // array. Reconstruct the linear list from the tree data structure. + // + // Example: + // + // id + // ├── id + // │ ├── Address (tip) + // │ ├── Address + // │ └── Address + // ├── id + // │ ├── Address + // │ ├── Address + // │ ├── Address + // │ └── Address + // └── id + // ├── id + // │ ├── Address + // │ └── Address + // └── id + // ├── Address + // ├── Address + // └── Address (root) + while (idsOrAddresses.length > 0) { + const idOrAddress = idsOrAddresses.pop(); + if (idOrAddress in arrays) { + // This is an index into array. + const nextIdsOrAddresses = arrays[idOrAddress]; + + // Add the next ids or addresses to the list to consider, but in reverse + // order that they are listed. + for (let i = 0; i < nextIdsOrAddresses.length; i++) { + idsOrAddresses.push( + nextIdsOrAddresses[nextIdsOrAddresses.length - i - 1] + ); + } + } else { + // This is an address; + const address = idOrAddress; + symbols.push(addressToSymbol.get(address)); + addresses.push(address); } - const { sourcePath, symbolName, addressToLine } = symbol; - - for (const [address, line] of addressToLine.entries()) { - const frameIndex = addressToFrameIndex.get(address); - if (frameIndex === undefined) { - const funcIndex = getFuncIndex(sourcePath, symbolName, address); - - frameTable.address.push(address); - frameTable.category.push(null); - frameTable.subcategory.push(null); - - frameTable.func.push(funcIndex); - frameTable.innerWindowID.push(null); - frameTable.implementation.push(null); - frameTable.line.push(line); - frameTable.column.push(null); - frameTable.length++; + } + + // Convert the stack to the processed profile version. + let stackIndex = -1; + let prefix = null; + while (symbols.length > 0) { + debugger; + const symbol = symbols.pop(); + const address = addresses.pop(); + const funcIndex = getFuncIndex(address, symbol); + let frameIndex; + for ( + // Increase the stackIndex by one. For the first run, this will end up + // being a stackIndex of 0. For the consecutive runs, it will be the next + // stack index. + stackIndex++; + stackIndex < stackTable.length; + stackIndex++ + ) { + frameIndex = stackTable.frame[stackIndex]; + if ( + frameTable.func[frameIndex] === funcIndex && + stackTable.prefix[stackIndex] === prefix + ) { + break; } } + if (stackIndex === stackTable.length) { + const line = symbol ? symbol.addressToLine.get(address) : null; + + frameTable.address.push(address); + frameTable.category.push(otherCategory); + frameTable.subcategory.push(otherSubCategory); + frameTable.func.push(funcIndex); + frameTable.innerWindowID.push(null); + frameTable.implementation.push(null); + frameTable.line.push(line === undefined ? null : line); + frameTable.column.push(null); + frameIndex = frameTable.length; + frameTable.length++; + + stackTable.frame.push(frameIndex); + stackTable.category.push(otherCategory); + stackTable.category.push(otherSubCategory); + stackTable.prefix.push(prefix); + // The stack index already points to this spot. + stackTable.length++; + } + + prefix = stackIndex; } - } - runs.push({ - number: runNumber, - thread, - addressToFrameIndex, - }); + // Insert the sample. + samplesTable.time.push(sample.timestamp); + samplesTable.stack.push(stackIndex); + samplesTable.length++; + } } } - return { - version, - instrument, - selectedRunNumber, - runs, - }; + return profile; } // Import from a .trace file saved from Mac Instruments.app @@ -541,165 +527,7 @@ export async function processInstrumentsProfile( zip: JSZip ): Promise { const tree = await extractDirectoryTree(zip); - const formTemplate: FormTemplateData = await readFormTemplate(tree); - if (!formTemplate) { - return null; - } - const { runs, instrument, selectedRunNumber } = formTemplate; - if (instrument !== 'com.apple.xray.instrument-type.coresampler2') { - throw new Error( - `The only supported instrument from .trace import is "com.apple.xray.instrument-type.coresampler2". Got ${instrument}` - ); - } - - const profile: Profile = getEmptyProfile(); - let indexToView: number = 0; - - for (const run of runs) { - const { addressToFrameIndex, thread, number } = run; - await importRunFromInstrumentsTrace( - profile, - file.name, - tree, - addressToFrameIndex, - number - ); - - if (run.number === selectedRunNumber) { - indexToView = profiles.length + group.indexToView; - } - - profiles.push(...group.profiles); - } - - // TODO - Actually import a profile. - // return { name: file.name, indexToView, profiles }; - - // This is just enough to not error when loading a profile. - const profile = getEmptyProfile(); - profile.threads.push(getEmptyThread()); - return profile; -} - -interface ProfileGroup { - name: string; - indexToView: number; - profiles: Profile[]; -} - -export async function importRunFromInstrumentsTrace( - profile: Profile, - fileName: string, - tree: TraceDirectoryTree, - addressToFrameMap: Map, - runNumber: number -): Promise { - const core = getCoreDirForRun(tree, runNumber); - const samples = await getRawSampleList(core); - const arrays = await getIntegerArrays(samples, core); - - // We'll try to guess which thread is the main thread by assuming - // it's the one with the most samples. - const sampleCountByThreadID = new Map(); - for (const sample of samples) { - sampleCountByThreadID.set( - sample.threadID, - getOrElse(sampleCountByThreadID, sample.threadID, () => 0) + 1 - ); - } - const counts = Array.from(sampleCountByThreadID.entries()); - sortBy(counts, c => -c[1]); - const threadIDs = counts.map(c => c[0]); - - profile.threads = threadIDs.map(threadID => - importThreadFromInstrumentsTrace({ - threadID, - fileName, - arrays, - addressToFrameMap, - samples, - }) - ); - - return { - name: fileName, - indexToView: 0, - profile, - }; -} - -export function importThreadFromInstrumentsTrace(args: { - fileName: string, - addressToFrameMap: Map, - threadID: number, - arrays: number[][], - samples: Sample[], -}): Thread { - const { addressToFrameMap, arrays, threadID, samples } = args; - - const thread = getEmptyThread(); - thread.tid = threadID; - thread.pid = threadID; - - function appendRecursive(backtraceID: number, stack: FrameInfo[]) { - const frame = addressToFrameMap.get(backtraceID); - if (frame) { - stack.push(frame); - } else if (backtraceID in arrays) { - for (const addr of arrays[backtraceID]) { - appendRecursive(addr, stack); - } - } else { - const rawAddressFrame: FrameInfo = { - key: backtraceID, - name: `0x${zeroPad(backtraceID.toString(16), 16)}`, - }; - addressToFrameMap.set(backtraceID, rawAddressFrame); - stack.push(rawAddressFrame); - } - } - - const samplesForThisThread = samples.filter(s => s.threadID === threadID); - const backtraceIDtoStack = new Map(); - - for (const { timestamp, backtraceID } of samplesForThisThread) { - const stack = backtraceIDtoStack.get(backtraceID); - if (stack === undefined) { - const stack: FrameInfo[] = []; - appendRecursive(backtraceID, stack); - stack.reverse(); - return stack; - } - - thread.samples.length++; - thread.samples.time.push(sample.timestamp); - thread.samples.stack.push(null); - } - - return thread; - - let lastTimestamp: null | number = null; - for (const sample of samples) { - if (lastTimestamp === null) { - // The first sample is sometimes fairly late in the profile for some reason. - // We'll just say nothing was known to be on the stack in that time. - profile.appendSampleWithWeight([], sample.timestamp); - lastTimestamp = sample.timestamp; - } - - if (sample.timestamp < lastTimestamp) { - throw new Error('Timestamps out of order!'); - } - - profile.appendSampleWithWeight( - stackForSample, - sample.timestamp - lastTimestamp - ); - lastTimestamp = sample.timestamp; - } - - profile.setValueFormatter(new TimeFormatter('nanoseconds')); - return profile.build(); + return convertInstrumentsProfile(tree); } export function readInstrumentsKeyedArchive(byteArray: Uint8Array) { @@ -713,7 +541,6 @@ export function readInstrumentsKeyedArchive(byteArray: Uint8Array) { return null; case 'PFTSymbolData': { - console.log(`!!! object`, object); const ret = Object.create(null); ret.symbolName = object.$0; ret.sourcePath = object.$1; @@ -774,6 +601,40 @@ export function readInstrumentsKeyedArchive(byteArray: Uint8Array) { ret.name = object.$1; return ret; } + + case 'XRThread': { + // Without any of this manipulation, the data looks like this: + // { + // $class: { + // $classname: 'XRThread', + // $classes: ['XRThread', 'NSObject'], + // }, + // $0: Tid, // 63045358 + // $1: { + // $class: { + // $classname: 'XRBacktraceTypeAdapter', + // $classes: ['XRBacktraceTypeAdapter', 'NSObject'], + // }, + // }, + // $2: null, + // $3: [], + // $4: { + // deviceIdentifier: 'E9A531DB-182F-5246-8FFF-605E33A88AD6' + // documentUUID: '1E5A5900-FF1A-43A9-9250-1C372500A3E9' + // overrideName: 'Main Thread 0x3c1feee' + // execIconName: 'device.E9A531DB-182F-5246-8FFF-605E33A88AD6.simple' + // coreValueSignature: 1 + // execName: 'simple' + // pid: 26200 + // }, + // $5: 0, + // } + const ret = Object.create(null); + ret.tid = object.$0; + ret.process = object.$4; + return ret; + } + default: return object; } @@ -1209,62 +1070,6 @@ class BinaryPlistParser { } } -type ValueUnit = - | 'none' - | 'nanoseconds' - | 'microseconds' - | 'milliseconds' - | 'seconds' - | 'bytes'; - -interface ValueFormatter { - unit: ValueUnit; - format(v: number): string; -} - -class TimeFormatter implements ValueFormatter { - multiplier: number; - unit: ValueUnit; - - constructor(unit: ValueUnit) { - if (unit === 'nanoseconds') { - this.multiplier = 1e-9; - } else if (unit === 'microseconds') { - this.multiplier = 1e-6; - } else if (unit === 'milliseconds') { - this.multiplier = 1e-3; - } else { - this.multiplier = 1; - } - - this.unit = unit; - } - - formatUnsigned(v: number) { - const s = v * this.multiplier; - - if (s / 60 >= 1) { - const minutes = Math.floor(s / 60); - const seconds = Math.floor(s - minutes * 60).toString(); - return `${minutes}:${zeroPad(seconds, 2)}`; - } - if (s / 1 >= 1) { - return `${s.toFixed(2)}s`; - } - if (s / 1e-3 >= 1) { - return `${(s / 1e-3).toFixed(2)}ms`; - } - if (s / 1e-6 >= 1) { - return `${(s / 1e-6).toFixed(2)}µs`; - } - return `${(s / 1e-9).toFixed(2)}ns`; - } - - format(v: number) { - return `${v < 0 ? '-' : ''}${this.formatUnsigned(Math.abs(v))}`; - } -} - function zeroPad(s: string, width: number) { return new Array(Math.max(width - s.length, 0) + 1).join('0') + s; } @@ -1278,14 +1083,6 @@ function getOrInsert(map: Map, k: K, fallback: (k: K) => V): V { return value; } -function getOrElse(map: Map, k: K, fallback: (k: K) => V): V { - const value = map.get(k); - if (value === undefined) { - return fallback(k); - } - return value; -} - function getOrThrow(map: Map, k: K): V { const value = map.get(k); if (value === undefined) { @@ -1361,18 +1158,191 @@ async function extractDirectoryTree(zip: JSZip): Promise { return tree; } -function sortBy(ts: T[], key: (t: T) => number | string): void { - function comparator(a: T, b: T) { - const keyA = key(a); - const keyB = key(b); - // Disabled eslint rule as this is imported from speedscope. +/** + * Recursively convert a Map data structure into an object. This makes it easier + * to type the result of the instrument data. + */ +function mapToObject( + mixed: any, + keyAllowList = new Set(), + prevKey: string = '' +): any { + if (mixed instanceof Map) { + // This is a map, see if it needs to be converted to an object. + + const map: Map = mixed; + if (keyAllowList.has(prevKey)) { + // Don't convert this into an object, but do recurse into its child members. + const newMap = new Map(); + for (const [key, value] of map) { + newMap.set(key, mapToObject(value, keyAllowList, key)); + } + return newMap; + } + + // Convert the map into an Object. + const object: any = {}; + for (const [key, value] of map) { + object[key] = mapToObject(value, keyAllowList, key); + } + return object; + } - // $FlowFixMe - Cannot compare number to string - return keyA < keyB ? -1 : keyA > keyB ? 1 : 0; + // If this is an array, recurse into the members. + if (Array.isArray(mixed)) { + return mixed.map(v => mapToObject(v, keyAllowList)); + } + + // If this is an object already, recurse into its members. + if (mixed && typeof mixed === 'object') { + const object = {}; + for (const [key2, value2] of Object.entries(mixed)) { + object[key2] = mapToObject(value2, keyAllowList); + } + return object; } - ts.sort(comparator); -} -function lastOf(ts: T[]): T | null { - return ts[ts.length - 1] || null; + // This is a plain value, just return it. + return mixed; } + +type SymbolsByPid2 = Map< + Pid, + { + threadNames: string[], // [ 'Main Thread 0x3c1feee' ], + symbols: Array<{ + symbolName: string, // 'szone_malloc_should_clear', + sourcePath: string | null, // '/Users/jlfwong/code/speedscope/sample/cpp/simple.cpp', + addressToLine: Map, + }>, + } +>; + +type UnparsedClass = { + $classname: T, + $classes: mixed, +}; + +type RunData = { + symbolsByPid: SymbolsByPid2, + // Map { + // 'E9A531DB-182F-5246-8FFF-605E33A88AD6' => [ 2829117936992677, 1, 1 ] + // }, + mach_time_info: Map, + recordingOptions: { + windowLimit: number, // 0, + timeLimit: number, // 43200000000000, + supportsWindowedMode: boolean, + supportsImmediateMode: boolean, + $class: UnparsedClass<'XRRecordingOptions'>, + recordingMode: 1, + supportsDeferredMode: true, + }, + serialThreadNumbers: { '0': 0x3c1feee }, + osVersion: { + ProductBuildVersion: '16A323', + ProductCopyright: '1983-2016 Apple Inc.', + ProductVersion: '10.12', + ProductName: 'Mac OS X', + ProductUserVisibleVersion: '10.12', + }, + numberOfCpus: number, + log: mixed[], + processDatas: [ + { + specifiedEnvironment: {}, + _processProperties: mixed, + imageData: [Uint8Array], + _execname: '/Users/jlfwong/code/speedscope/sample/cpp/simple', + _pid: 26200, + bundleIdentifier: '/Users/jlfwong/code/speedscope/sample/cpp/simple', + specifiedType: 0, + _args: '', + _launchControlProperties: mixed, + deviceTemplateData: mixed, + }, + { + specifiedEnvironment: {}, + _execname: 'kernel_task', + _pid: 0, + processID: 0, + specifiedType: 0, + _launchControlProperties: {}, + imageData: [Uint8Array], + deviceTemplateData: mixed, + } + ], + // [ { number: 0, name: 'E9A531DB-182F-5246-8FFF-605E33A88AD6' }, ... ] + allCores: Array<{ number: 0, name: string }>, + // [ + // "Target Name: Jamie's Macbook Pro", + // 'Target Model: MacBook Pro', + // 'Target macOS: 10.12 (16A323)', + // '', + // 'Start Time: May 8, 2018, 10:10:57 PM', + // 'End Time: May 8, 2018, 10:11:01 PM', + // 'Duration: 4 seconds', + // '', + // 'Instruments: 8.3.3 (8E3004b)' + // ] + recordingInfoSummary: string[], + inspectionTime: number, // 4113218783 + target: { + specifiedEnvironment: {}, + _processProperties: { plist: mixed[] }, + imageData: Uint8Array, + _execname: '/Users/jlfwong/code/speedscope/sample/cpp/simple', + _pid: 26200, + bundleIdentifier: '/Users/jlfwong/code/speedscope/sample/cpp/simple', + specifiedType: 0, + _args: '', + _launchControlProperties: { + XRDeviceFileChooserWorkingDirectory: '', + DisableTALAutomaticTermination: false, + iODestinationKey: 0, + }, + deviceTemplateData: { + _deviceHostName: "Jamie's Macbook Pro", + _deviceVersion: '16A323', + _deviceIdentifier: 'E9A531DB-182F-5246-8FFF-605E33A88AD6', + _productVersion: '10.12', + _modelName: 'MacBook Pro', + _xrdeviceClassName: 'XRLocalDevice', + _rawDeviceDisplayName: "Jamie's Macbook Pro", + _productType: 'MacBookPro11,3', + _deviceDisplayName: "Jamie's Macbook Pro", + _modelUTI: 'com.apple.macbookpro-15-retina-display', + _deviceDescription: 'Jamies-Macbook-Pro', + _deviceSmallRepresentationIcon: [Uint8Array], + }, + }, + architecture: { + $class: { + $classname: 'XRArchitecture', + $classes: ['XRArchitecture', 'NSObject'], + }, + $0: 16777223, + $1: 3, + }, + threadSerialNumbers: Map, + // ['Target: simple', 'Recording Mode: Immediate', 'Time Limit: 12 hours', ''] + recordingSettingsSummary: string[], + threadSerialMax: number, + startTime: number, // 1525842657580993300 + runningTime: number, // 4113218784 + threadsByTID: Map< + Tid, + { + tid: Tid, // 0x03c1feee, + process: { + deviceIdentifier: string, // 'E9A531DB-182F-5246-8FFF-605E33A88AD6' + documentUUID: string, // '1E5A5900-FF1A-43A9-9250-1C372500A3E9' + overrideName: string, // 'Main Thread 0x3c1feee' + execIconName: string, // 'device.E9A531DB-182F-5246-8FFF-605E33A88AD6.simple' + coreValueSignature: number, // 1 + execName: string, // 'simple' + pid: Pid, // 0x6658, + }, + } + >, +};