mirror of
https://github.com/jlfwong/speedscope.git
synced 2024-12-24 02:43:06 +03:00
Support importing from Firefox (#26)
This commit is contained in:
parent
caba525465
commit
7ca4a41a6a
@ -13,6 +13,7 @@ import {Flamechart} from './flamechart'
|
||||
import {FlamechartView} from './flamechart-view'
|
||||
import {FontFamily, FontSize, Colors} from './style'
|
||||
import {getHashParams, HashParams} from './hash-params'
|
||||
import {importFromFirefox} from './import/firefox'
|
||||
|
||||
declare function require(x: string): any
|
||||
const exampleProfileURL = require('./sample/perf-vertx-stacks-01-collapsed-all.txt')
|
||||
@ -57,7 +58,10 @@ function importProfile(contents: string, fileName: string): Profile | null {
|
||||
// Second pass: Try to guess what file format it is based on structure
|
||||
try {
|
||||
const parsed = JSON.parse(contents)
|
||||
if (Array.isArray(parsed) && parsed[parsed.length - 1].name === 'CpuProfile') {
|
||||
if (parsed['systemHost'] && parsed['systemHost']['name'] == 'Firefox') {
|
||||
console.log('Importing as Firefox profile')
|
||||
return importFromFirefox(parsed)
|
||||
} else if (Array.isArray(parsed) && parsed[parsed.length - 1].name === 'CpuProfile') {
|
||||
console.log('Importing as Chrome CPU Profile')
|
||||
return importFromChromeTimeline(parsed)
|
||||
} else if ('nodes' in parsed && 'samples' in parsed && 'timeDeltas' in parsed) {
|
||||
|
233
import/firefox.ts
Normal file
233
import/firefox.ts
Normal file
@ -0,0 +1,233 @@
|
||||
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
|
||||
}
|
1
sample/firefox.json
Normal file
1
sample/firefox.json
Normal file
File diff suppressed because one or more lines are too long
1
sample/simple-firefox.html
Normal file
1
sample/simple-firefox.html
Normal file
@ -0,0 +1 @@
|
||||
<script src="./simple.js"></script>
|
32
sample/simple-firefox.js
Normal file
32
sample/simple-firefox.js
Normal file
@ -0,0 +1,32 @@
|
||||
function a() {
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
b()
|
||||
c()
|
||||
}
|
||||
}
|
||||
|
||||
function b() {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
d()
|
||||
}
|
||||
}
|
||||
|
||||
function c() {
|
||||
for (let i = 0; i < 10; i++) {
|
||||
d()
|
||||
}
|
||||
}
|
||||
|
||||
function d() {
|
||||
let prod = 1
|
||||
for (let i = 1; i < 1000; i++) {
|
||||
prod *= i
|
||||
}
|
||||
return prod
|
||||
}
|
||||
|
||||
console.profile('a')
|
||||
a()
|
||||
setTimeout(() => {
|
||||
console.profileEnd('a')
|
||||
}, 0)
|
1
sample/simple-firefox.json
Normal file
1
sample/simple-firefox.json
Normal file
File diff suppressed because one or more lines are too long
4078
sample/simple-firefox.pretty.json
Normal file
4078
sample/simple-firefox.pretty.json
Normal file
File diff suppressed because it is too large
Load Diff
1
sample/simple.html
Normal file
1
sample/simple.html
Normal file
@ -0,0 +1 @@
|
||||
<script src="./simple.js"></script>
|
Loading…
Reference in New Issue
Block a user