mirror of
https://github.com/jlfwong/speedscope.git
synced 2024-11-22 22:14:25 +03:00
Chrome timeline import, optimization of mergeAdjacentFrames
This commit is contained in:
parent
e5d7cecda6
commit
c240ac8694
@ -4,6 +4,7 @@ import {ReloadableComponent} from './reloadable'
|
||||
|
||||
import {importFromBGFlameGraph} from './import/bg-flamegraph'
|
||||
import {importFromStackprof} from './import/stackprof'
|
||||
import {importFromChrome} from './import/chrome'
|
||||
|
||||
import {Profile} from './profile'
|
||||
import {Flamechart} from './flamechart'
|
||||
@ -41,14 +42,25 @@ export class Application extends ReloadableComponent<{}, ApplicationState> {
|
||||
}
|
||||
}
|
||||
|
||||
importJSON(parsed: any): Profile {
|
||||
if (Array.isArray(parsed) && parsed[parsed.length - 1].name === "CpuProfile") {
|
||||
return importFromChrome(parsed)
|
||||
} else {
|
||||
return importFromStackprof(parsed)
|
||||
}
|
||||
}
|
||||
|
||||
onDrop = (ev: DragEvent) => {
|
||||
const file = ev.dataTransfer.files.item(0)
|
||||
const reader = new FileReader
|
||||
reader.addEventListener('loadend', () => {
|
||||
const profile = file.name.endsWith('json') ? importFromStackprof(reader.result) : importFromBGFlameGraph(reader.result)
|
||||
console.time('import')
|
||||
const profile = file.name.endsWith('json') ? this.importJSON(JSON.parse(reader.result)) : importFromBGFlameGraph(reader.result)
|
||||
const flamechart = new Flamechart(profile)
|
||||
const sortedFlamechart = new Flamechart(profile.sortedAlphabetically())
|
||||
this.setState({profile, flamechart, sortedFlamechart})
|
||||
this.setState({ profile, flamechart, sortedFlamechart }, () => {
|
||||
console.timeEnd('import')
|
||||
})
|
||||
})
|
||||
reader.readAsText(file)
|
||||
ev.preventDefault()
|
||||
|
@ -54,13 +54,13 @@ export class Flamechart {
|
||||
return true
|
||||
}
|
||||
|
||||
private static mergeFrames(first: FlamechartFrame, second: FlamechartFrame): FlamechartFrame {
|
||||
private static mergeFrames(frames: FlamechartFrame[]): FlamechartFrame {
|
||||
const frame: FlamechartFrame = {
|
||||
node: first.node,
|
||||
start: first.start,
|
||||
end: second.end,
|
||||
parent: first.parent,
|
||||
children: first.children.concat(second.children)
|
||||
node: frames[0].node,
|
||||
start: frames[0].start,
|
||||
end: frames[frames.length-1].end,
|
||||
parent: frames[0].parent,
|
||||
children: ([] as FlamechartFrame[]).concat(...frames.map(f => f.children))
|
||||
}
|
||||
for (let child of frame.children) {
|
||||
child.parent = frame
|
||||
@ -70,14 +70,22 @@ export class Flamechart {
|
||||
|
||||
private static mergeAdjacentFrames(layer: StackLayer): StackLayer {
|
||||
const ret: StackLayer = []
|
||||
|
||||
let partition: FlamechartFrame[] = []
|
||||
|
||||
for (let flamechartFrame of layer) {
|
||||
const prev = ret.length > 0 ? ret[ret.length - 1] : null
|
||||
if (prev && Flamechart.shouldMergeFrames(prev, flamechartFrame)) {
|
||||
ret.pop()
|
||||
ret.push(Flamechart.mergeFrames(prev, flamechartFrame))
|
||||
} else {
|
||||
ret.push(flamechartFrame)
|
||||
const prev: FlamechartFrame | undefined = partition[partition.length-1]
|
||||
if (prev && !Flamechart.shouldMergeFrames(prev, flamechartFrame)) {
|
||||
if (partition.length > 0) {
|
||||
ret.push(Flamechart.mergeFrames(partition))
|
||||
}
|
||||
partition = []
|
||||
}
|
||||
partition.push(flamechartFrame)
|
||||
}
|
||||
|
||||
if (partition.length > 0) {
|
||||
ret.push(Flamechart.mergeFrames(partition))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
108
import/chrome.ts
Normal file
108
import/chrome.ts
Normal file
@ -0,0 +1,108 @@
|
||||
import {Profile, FrameInfo} from '../profile'
|
||||
|
||||
interface TimelineEvent {
|
||||
pid: number,
|
||||
tid: number,
|
||||
ts: number,
|
||||
ph: string,
|
||||
cat: string,
|
||||
name: string,
|
||||
dur: number,
|
||||
tdur: number,
|
||||
tts: number,
|
||||
args: { [key: string]: any }
|
||||
}
|
||||
|
||||
interface PositionTickInfo {
|
||||
line: number,
|
||||
ticks: number
|
||||
}
|
||||
|
||||
interface CPUProfileNode {
|
||||
callFrame: {
|
||||
columnNumber: number,
|
||||
functionName: string,
|
||||
lineNumber: number,
|
||||
scriptId: string,
|
||||
url: string
|
||||
},
|
||||
hitCount: number
|
||||
id: number
|
||||
children?: number[]
|
||||
positionTicks?: PositionTickInfo[]
|
||||
parent?: CPUProfileNode
|
||||
}
|
||||
|
||||
interface CPUProfile {
|
||||
startTime: number,
|
||||
endTime: number,
|
||||
nodes: CPUProfileNode[],
|
||||
samples: number[],
|
||||
timeDeltas: number[]
|
||||
}
|
||||
|
||||
export function importFromChrome(events: TimelineEvent[]) {
|
||||
const profileEvent = events[events.length - 1]
|
||||
const chromeProfile = profileEvent.args.data.cpuProfile as CPUProfile
|
||||
const profile = new Profile(chromeProfile.endTime - chromeProfile.startTime)
|
||||
|
||||
const nodeById = new Map<number, CPUProfileNode>()
|
||||
for (let node of chromeProfile.nodes) {
|
||||
nodeById.set(node.id, node)
|
||||
}
|
||||
for (let node of chromeProfile.nodes) {
|
||||
if (!node.children) continue
|
||||
for (let childId of node.children) {
|
||||
const child = nodeById.get(childId)
|
||||
if (!child) continue
|
||||
child.parent = node
|
||||
}
|
||||
}
|
||||
|
||||
const samples: number[] = []
|
||||
const timeDeltas: number[] = []
|
||||
|
||||
let elapsed = 0
|
||||
let lastNodeId = NaN
|
||||
|
||||
// The chrome CPU profile format doesn't collapse identical samples. We'll do that
|
||||
// here to save a ton of work later doing mergers.
|
||||
for (let i = 0; i < chromeProfile.samples.length; i++) {
|
||||
const nodeId = chromeProfile.samples[i]
|
||||
if (nodeId != lastNodeId) {
|
||||
samples.push(nodeId)
|
||||
timeDeltas.push(elapsed)
|
||||
elapsed = 0
|
||||
}
|
||||
|
||||
elapsed += chromeProfile.timeDeltas[i]
|
||||
lastNodeId = nodeId
|
||||
}
|
||||
if (!isNaN(lastNodeId)) {
|
||||
samples.push(lastNodeId)
|
||||
timeDeltas.push(elapsed)
|
||||
}
|
||||
|
||||
for (let i = 0; i < samples.length; i++) {
|
||||
const timeDelta = timeDeltas[i]
|
||||
const nodeId = samples[i]
|
||||
let node = nodeById.get(nodeId)
|
||||
if (!node) continue
|
||||
|
||||
// TODO(jlfwong): This is silly and slow, but good enough for now
|
||||
const stack: FrameInfo[] = []
|
||||
for (let node = nodeById.get(nodeId); node; node = node.parent) {
|
||||
stack.push({
|
||||
key: node.id,
|
||||
name: node.callFrame.functionName,
|
||||
file: node.callFrame.url,
|
||||
line: node.callFrame.lineNumber,
|
||||
col: node.callFrame.columnNumber
|
||||
})
|
||||
}
|
||||
stack.reverse()
|
||||
|
||||
profile.appendSample(stack, timeDelta)
|
||||
}
|
||||
return profile
|
||||
}
|
@ -14,9 +14,7 @@ export interface StackprofProfile {
|
||||
raw_timestamp_deltas: number[]
|
||||
}
|
||||
|
||||
export function importFromStackprof(contents: string): Profile {
|
||||
const stackprofProfile = JSON.parse(contents) as StackprofProfile
|
||||
|
||||
export function importFromStackprof(stackprofProfile: StackprofProfile): Profile {
|
||||
const duration = stackprofProfile.raw_timestamp_deltas.reduce((a, b) => a + b, 0)
|
||||
const profile = new Profile(duration)
|
||||
|
||||
|
25917
sample/chrome-timeline.json
Normal file
25917
sample/chrome-timeline.json
Normal file
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user