mirror of
https://github.com/jlfwong/speedscope.git
synced 2024-12-04 16:43:54 +03:00
234 lines
4.9 KiB
TypeScript
234 lines
4.9 KiB
TypeScript
import {Profile, FrameInfo, TimeFormatter} from '../profile'
|
|
import {getOrInsert, lastOf} from '../utils'
|
|
|
|
interface Allocations {
|
|
frames: any[]
|
|
sites: any[]
|
|
sizes: any[]
|
|
timestamps: any[]
|
|
}
|
|
|
|
interface Configuration {
|
|
allocationsMaxLogLength: number
|
|
allocationsSampleProbability: number
|
|
bufferSize: number
|
|
sampleFrequency: number
|
|
withAllocations: boolean
|
|
withMarkers: boolean
|
|
withMemory: boolean
|
|
withTicks: boolean
|
|
}
|
|
|
|
interface Lib {
|
|
arch: string
|
|
breakpadId: string
|
|
debugName: string
|
|
debugPath: string
|
|
end: any
|
|
name: string
|
|
offset: number
|
|
path: string
|
|
start: any
|
|
}
|
|
|
|
interface Meta {
|
|
abi: string
|
|
asyncstack: number
|
|
debug: number
|
|
gcpoison: number
|
|
interval: number
|
|
misc: string
|
|
oscpu: string
|
|
platform: string
|
|
processType: number
|
|
product: string
|
|
shutdownTime?: any
|
|
stackwalk: number
|
|
startTime: number
|
|
toolkit: string
|
|
version: number
|
|
}
|
|
|
|
interface PausedRange {
|
|
endTime: number
|
|
reason: string
|
|
startTime: number
|
|
}
|
|
|
|
type Frame = [number] | [number, number | null, number | null, number, number]
|
|
|
|
interface FrameTable {
|
|
data: Frame[]
|
|
/*
|
|
schema: {
|
|
location: 0
|
|
implementation: 1
|
|
optimizations: 2
|
|
line: 3
|
|
category: 4
|
|
}
|
|
*/
|
|
}
|
|
|
|
interface MarkerMeta {
|
|
category: string
|
|
interval: string
|
|
type: string
|
|
}
|
|
type Marker = [number, number] | [number, number, MarkerMeta]
|
|
|
|
interface Markers {
|
|
data: Marker[]
|
|
/*
|
|
schema: {
|
|
name: 0
|
|
time: 1
|
|
data: 2
|
|
}
|
|
*/
|
|
}
|
|
|
|
type Sample = [number, number, number] | [number, number, number, number, number]
|
|
|
|
interface Samples {
|
|
data: Sample[]
|
|
/*
|
|
schema: {
|
|
stack: 0
|
|
time: 1
|
|
responsiveness: 2
|
|
rss: 3
|
|
uss: 4
|
|
}
|
|
*/
|
|
}
|
|
|
|
export interface StackTable {
|
|
data: [number | null, number][]
|
|
/*
|
|
schema: {
|
|
prefix: 0
|
|
frame: 1
|
|
}
|
|
*/
|
|
}
|
|
|
|
export interface Thread {
|
|
frameTable: FrameTable
|
|
markers: Markers
|
|
name: string
|
|
pid: number
|
|
processType: string
|
|
registerTime: number
|
|
samples: Samples
|
|
stackTable: StackTable
|
|
stringTable: string[]
|
|
tid: number
|
|
unregisterTime?: any
|
|
}
|
|
|
|
export interface FirefoxCPUProfile {
|
|
libs: Lib[]
|
|
meta: Meta
|
|
pausedRanges: PausedRange[]
|
|
processes: any[]
|
|
threads: Thread[]
|
|
}
|
|
|
|
export interface FirefoxProfile {
|
|
allocations: Allocations
|
|
configuration: Configuration
|
|
duration: number
|
|
fileType: string
|
|
frames: any[]
|
|
label: string
|
|
markers: any[]
|
|
memory: any[]
|
|
profile: FirefoxCPUProfile
|
|
ticks: any[]
|
|
version: number
|
|
}
|
|
|
|
export function importFromFirefox(firefoxProfile: FirefoxProfile): Profile {
|
|
const cpuProfile = firefoxProfile.profile
|
|
|
|
const thread =
|
|
cpuProfile.threads.length === 1
|
|
? cpuProfile.threads[0]
|
|
: cpuProfile.threads.filter(t => t.name === 'GeckoMain')[0]
|
|
|
|
const frameIdToFrameInfo = new Map<number, FrameInfo>()
|
|
|
|
function extractStack(sample: Sample): FrameInfo[] {
|
|
let stackFrameId: number | null = sample[0]
|
|
const ret: number[] = []
|
|
|
|
while (stackFrameId != null) {
|
|
const nextStackFrame: [number | null, number] = thread.stackTable.data[stackFrameId]
|
|
const [nextStackId, frameId] = nextStackFrame
|
|
ret.push(frameId)
|
|
stackFrameId = nextStackId
|
|
}
|
|
ret.reverse()
|
|
return ret
|
|
.map(f => {
|
|
const frameData = thread.frameTable.data[f]
|
|
const location = thread.stringTable[frameData[0]]
|
|
|
|
const match = /(.*)\s+\((.*?):?(\d+)?\)$/.exec(location)
|
|
|
|
if (!match) return null
|
|
|
|
if (match[2].startsWith('resource:') || match[2] === 'self-hosted') {
|
|
// Ignore Firefox-internals stuff
|
|
return null
|
|
}
|
|
|
|
return getOrInsert(frameIdToFrameInfo, f, () => ({
|
|
key: location,
|
|
name: match[1]!,
|
|
file: match[2]!,
|
|
line: match[3] ? parseInt(match[3]) : undefined,
|
|
}))
|
|
})
|
|
.filter(f => f != null) as FrameInfo[]
|
|
}
|
|
|
|
const profile = new Profile(firefoxProfile.duration)
|
|
|
|
let prevStack: FrameInfo[] = []
|
|
for (let sample of thread.samples.data) {
|
|
const stack = extractStack(sample)
|
|
const value = sample[1]
|
|
|
|
// Find lowest common ancestor of the current stack and the previous one
|
|
let lca: FrameInfo | null = null
|
|
|
|
// This is O(n^2), but n should be relatively small here (stack height),
|
|
// so hopefully this isn't much of a problem
|
|
for (let i = stack.length - 1; i >= 0 && prevStack.indexOf(stack[i]) === -1; i--) {}
|
|
|
|
// Close frames that are no longer open
|
|
while (prevStack.length > 0 && lastOf(prevStack) != lca) {
|
|
const closingFrame = prevStack.pop()!
|
|
profile.leaveFrame(closingFrame, value)
|
|
}
|
|
|
|
// Open frames that are now becoming open
|
|
const toOpen: FrameInfo[] = []
|
|
for (let i = stack.length - 1; i >= 0 && stack[i] != lca; i--) {
|
|
toOpen.push(stack[i])
|
|
}
|
|
toOpen.reverse()
|
|
|
|
for (let frame of toOpen) {
|
|
profile.enterFrame(frame, value)
|
|
}
|
|
|
|
prevStack = stack
|
|
}
|
|
|
|
profile.setValueFormatter(new TimeFormatter('milliseconds'))
|
|
return profile
|
|
}
|