mirror of
https://github.com/jlfwong/speedscope.git
synced 2024-11-22 22:14:25 +03:00
Conversion to more efficient flamechart construction methodology
This commit is contained in:
parent
9fbef41305
commit
5932111819
@ -22,12 +22,13 @@ interface FlamechartFrameLabel {
|
||||
}
|
||||
|
||||
function binarySearch(lo: number, hi: number, f: (val: number) => number, target: number, targetRangeSize = 1): [number, number] {
|
||||
console.assert(!isNaN(targetRangeSize) && !isNaN(target))
|
||||
while (true) {
|
||||
if (hi - lo <= targetRangeSize) return [lo, hi]
|
||||
const mid = (hi + lo) / 2
|
||||
const val = f(mid)
|
||||
if (val < target) lo = mid
|
||||
if (val > target) hi = mid
|
||||
else hi = mid
|
||||
}
|
||||
}
|
||||
|
||||
|
104
flamechart.ts
104
flamechart.ts
@ -1,5 +1,7 @@
|
||||
import {Profile, Frame, CallTreeNode} from './profile'
|
||||
|
||||
import { lastOf } from './utils'
|
||||
|
||||
interface FlamechartFrame {
|
||||
node: CallTreeNode
|
||||
start: number
|
||||
@ -23,73 +25,6 @@ export class Flamechart {
|
||||
getFrameColors() { return this.frameColors }
|
||||
getMinFrameWidth() { return this.minFrameWidth }
|
||||
|
||||
private appendFrame(layerIndex: number, node: CallTreeNode, timeDelta: number, parent: FlamechartFrame | null) {
|
||||
while (layerIndex >= this.layers.length) this.layers.push([])
|
||||
const flamechartFrame: FlamechartFrame = {
|
||||
node: node,
|
||||
start: this.duration,
|
||||
end: this.duration + timeDelta,
|
||||
parent,
|
||||
children: []
|
||||
}
|
||||
this.layers[layerIndex].push(flamechartFrame)
|
||||
if (parent) {
|
||||
parent.children.push(flamechartFrame)
|
||||
}
|
||||
return flamechartFrame
|
||||
}
|
||||
|
||||
private appendSample(stack: CallTreeNode[], timeDelta: number) {
|
||||
let parent: FlamechartFrame | null = null
|
||||
for (let i = 0; i < stack.length; i++) {
|
||||
parent = this.appendFrame(i, stack[i], timeDelta, parent)
|
||||
}
|
||||
this.duration += timeDelta
|
||||
}
|
||||
|
||||
private static shouldMergeFrames(first: FlamechartFrame, second: FlamechartFrame): boolean {
|
||||
if (first.node !== second.node) return false
|
||||
if (first.parent !== second.parent) return false
|
||||
if (first.end !== second.start) return false
|
||||
return true
|
||||
}
|
||||
|
||||
private static mergeFrames(frames: FlamechartFrame[]): FlamechartFrame {
|
||||
const frame: FlamechartFrame = {
|
||||
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
|
||||
}
|
||||
return frame
|
||||
}
|
||||
|
||||
private static mergeAdjacentFrames(layer: StackLayer): StackLayer {
|
||||
const ret: StackLayer = []
|
||||
|
||||
let partition: FlamechartFrame[] = []
|
||||
|
||||
for (let flamechartFrame of layer) {
|
||||
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
|
||||
}
|
||||
|
||||
private selectFrameColors(profile: Profile) {
|
||||
const frames: Frame[] = []
|
||||
|
||||
@ -164,16 +99,35 @@ export class Flamechart {
|
||||
}
|
||||
|
||||
constructor(private profile: Profile) {
|
||||
profile.forEachSample(this.appendSample.bind(this))
|
||||
this.layers = this.layers.map(Flamechart.mergeAdjacentFrames)
|
||||
this.minFrameWidth = Infinity
|
||||
for (let layer of this.layers) {
|
||||
for (let frame of layer) {
|
||||
const width = frame.end - frame.start
|
||||
if (!width) continue
|
||||
this.minFrameWidth = Math.min(this.minFrameWidth, width)
|
||||
const stack: FlamechartFrame[] = []
|
||||
const openFrame = (node: CallTreeNode, value: number) => {
|
||||
const parent = lastOf(stack)
|
||||
const frame: FlamechartFrame = {
|
||||
node,
|
||||
parent,
|
||||
children: [],
|
||||
start: value,
|
||||
end: value,
|
||||
}
|
||||
if (parent) {
|
||||
parent.children.push(frame)
|
||||
}
|
||||
stack.push(frame)
|
||||
}
|
||||
|
||||
this.minFrameWidth = Infinity
|
||||
const closeFrame = (node: CallTreeNode, value: number) => {
|
||||
console.assert(stack.length > 0)
|
||||
const stackTop = stack.pop()!
|
||||
stackTop.end = value
|
||||
const layerIndex = stack.length
|
||||
while (this.layers.length <= layerIndex) this.layers.push([])
|
||||
this.layers[layerIndex].push(stackTop)
|
||||
this.minFrameWidth = Math.min(this.minFrameWidth, stackTop.end - stackTop.start)
|
||||
}
|
||||
|
||||
this.duration = profile.getDuration()
|
||||
profile.forEachCall(openFrame, closeFrame)
|
||||
this.selectFrameColors(profile)
|
||||
}
|
||||
}
|
41
profile.ts
41
profile.ts
@ -1,3 +1,5 @@
|
||||
import { lastOf } from './utils'
|
||||
|
||||
export interface FrameInfo {
|
||||
key: string | number
|
||||
|
||||
@ -84,10 +86,6 @@ function getOrInsert<K, V>(map: Map<K, V>, k: K, v: V): V {
|
||||
return map.get(k)!
|
||||
}
|
||||
|
||||
function lastOf<T>(ts: T[]): T | undefined {
|
||||
return ts[ts.length-1]
|
||||
}
|
||||
|
||||
export class Profile {
|
||||
// Duration of the entire profile, in microseconds
|
||||
private duration: number
|
||||
@ -140,6 +138,41 @@ export class Profile {
|
||||
}
|
||||
}
|
||||
|
||||
forEachCall(
|
||||
openFrame: (node: CallTreeNode, value: number) => void,
|
||||
closeFrame: (node: CallTreeNode, value: number) => void
|
||||
) {
|
||||
let prevStack: CallTreeNode[] = []
|
||||
let value = 0
|
||||
|
||||
let sampleIndex = 0
|
||||
for (let stackTop of this.samples) {
|
||||
// Close frames that are no longer open
|
||||
while (prevStack.length > 0 && lastOf(prevStack) != stackTop) {
|
||||
closeFrame(prevStack.pop()!, value)
|
||||
}
|
||||
|
||||
// Open frames that are now becoming open
|
||||
const toOpen: CallTreeNode[] = []
|
||||
for (let node: CallTreeNode | null = stackTop; node && node != lastOf(prevStack); node = node.parent) {
|
||||
toOpen.push(node)
|
||||
}
|
||||
|
||||
for (let i = toOpen.length - 1; i >= 0; i--) {
|
||||
const node = toOpen[i]
|
||||
openFrame(node, value)
|
||||
}
|
||||
|
||||
prevStack = prevStack.concat(toOpen)
|
||||
value += this.timeDeltas[sampleIndex++]
|
||||
}
|
||||
|
||||
// Close frames that are open at the end of the trace
|
||||
for (let i = prevStack.length - 1; i >= 0; i--) {
|
||||
closeFrame(prevStack[i], value)
|
||||
}
|
||||
}
|
||||
|
||||
forEachFrame(fn: (frame: Frame) => void) {
|
||||
this.frames.forEach(fn)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user