mirror of
https://github.com/jlfwong/speedscope.git
synced 2024-11-22 22:14:25 +03:00
Rename times to weights, start refactor for sorted flamegraphs
This commit is contained in:
parent
5932111819
commit
9ee5b53eda
@ -57,7 +57,7 @@ export class Application extends ReloadableComponent<{}, ApplicationState> {
|
||||
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())
|
||||
const sortedFlamechart = new Flamechart(profile)
|
||||
this.setState({ profile, flamechart, sortedFlamechart }, () => {
|
||||
console.timeEnd('import')
|
||||
})
|
||||
|
@ -33,7 +33,7 @@ export class FlamechartMinimapView extends Component<FlamechartMinimapViewProps,
|
||||
|
||||
private configSpaceSize() {
|
||||
return new Vec2(
|
||||
this.props.flamechart.getDuration(),
|
||||
this.props.flamechart.getTotalWeight(),
|
||||
this.props.flamechart.getLayers().length
|
||||
)
|
||||
}
|
||||
|
@ -159,7 +159,7 @@ export class FlamechartPanZoomView extends ReloadableComponent<FlamechartPanZoom
|
||||
|
||||
private configSpaceSize() {
|
||||
return new Vec2(
|
||||
this.props.flamechart.getDuration(),
|
||||
this.props.flamechart.getTotalWeight(),
|
||||
this.props.flamechart.getLayers().length
|
||||
)
|
||||
}
|
||||
@ -582,7 +582,7 @@ export class FlamechartView extends ReloadableComponent<FlamechartViewProps, Fla
|
||||
}
|
||||
|
||||
formatTime(timeInNs: number) {
|
||||
const totalTimeNs = this.props.flamechart.getDuration()
|
||||
const totalTimeNs = this.props.flamechart.getTotalWeight()
|
||||
return `${(timeInNs / 1000).toFixed(2)}ms (${(100 * timeInNs/totalTimeNs).toFixed()}%)`
|
||||
}
|
||||
|
||||
|
@ -12,20 +12,32 @@ interface FlamechartFrame {
|
||||
|
||||
type StackLayer = FlamechartFrame[]
|
||||
|
||||
interface FlamechartDataSource {
|
||||
getTotalWeight(): number
|
||||
|
||||
forEachCall(
|
||||
openFrame: (node: CallTreeNode, value: number) => void,
|
||||
closeFrame: (value: number) => void
|
||||
): void
|
||||
|
||||
forEachFrame(fn: (frame: Frame) => void): void
|
||||
}
|
||||
|
||||
export class Flamechart {
|
||||
// Bottom to top
|
||||
private layers: StackLayer[] = []
|
||||
private duration: number = 0
|
||||
private totalWeight: number = 0
|
||||
private minFrameWidth: number = 1
|
||||
|
||||
private frameColors = new Map<Frame, [number, number, number]>()
|
||||
|
||||
getDuration() { return this.duration }
|
||||
getTotalWeight() { return this.totalWeight }
|
||||
getLayers() { return this.layers }
|
||||
getFrameColors() { return this.frameColors }
|
||||
getMinFrameWidth() { return this.minFrameWidth }
|
||||
|
||||
private selectFrameColors(profile: Profile) {
|
||||
// TODO(jlfwong): Move this a color generation file
|
||||
private selectFrameColors(source: FlamechartDataSource) {
|
||||
const frames: Frame[] = []
|
||||
|
||||
function parts(f: Frame) {
|
||||
@ -50,7 +62,7 @@ export class Flamechart {
|
||||
return aParts.join() > bParts.join() ? score : -score
|
||||
}
|
||||
|
||||
this.profile.forEachFrame(f => frames.push(f))
|
||||
this.source.forEachFrame(f => frames.push(f))
|
||||
frames.sort(compare)
|
||||
|
||||
const cumulativeScores: number[] = []
|
||||
@ -98,7 +110,7 @@ export class Flamechart {
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private profile: Profile) {
|
||||
constructor(private source: FlamechartDataSource) {
|
||||
const stack: FlamechartFrame[] = []
|
||||
const openFrame = (node: CallTreeNode, value: number) => {
|
||||
const parent = lastOf(stack)
|
||||
@ -116,7 +128,7 @@ export class Flamechart {
|
||||
}
|
||||
|
||||
this.minFrameWidth = Infinity
|
||||
const closeFrame = (node: CallTreeNode, value: number) => {
|
||||
const closeFrame = (value: number) => {
|
||||
console.assert(stack.length > 0)
|
||||
const stackTop = stack.pop()!
|
||||
stackTop.end = value
|
||||
@ -126,8 +138,8 @@ export class Flamechart {
|
||||
this.minFrameWidth = Math.min(this.minFrameWidth, stackTop.end - stackTop.start)
|
||||
}
|
||||
|
||||
this.duration = profile.getDuration()
|
||||
profile.forEachCall(openFrame, closeFrame)
|
||||
this.selectFrameColors(profile)
|
||||
this.totalWeight = source.getTotalWeight()
|
||||
source.forEachCall(openFrame, closeFrame)
|
||||
this.selectFrameColors(source)
|
||||
}
|
||||
}
|
116
profile.ts
116
profile.ts
@ -18,16 +18,16 @@ export interface FrameInfo {
|
||||
col?: number
|
||||
}
|
||||
|
||||
export class HasTimings {
|
||||
private selfTime = 0
|
||||
private totalTime = 0
|
||||
getSelfTime() { return this.selfTime }
|
||||
getTotalTime() { return this.totalTime }
|
||||
addToTotalTime(delta: number) { this.totalTime += delta }
|
||||
addToSelfTime(delta: number) { this.selfTime += delta }
|
||||
export class HasWeights {
|
||||
private selfWeight = 0
|
||||
private totalWeight = 0
|
||||
getSelfTime() { return this.selfWeight }
|
||||
getTotalTime() { return this.totalWeight }
|
||||
addToTotalWeight(delta: number) { this.totalWeight += delta }
|
||||
addToSelfWeight(delta: number) { this.selfWeight += delta }
|
||||
}
|
||||
|
||||
export class Frame extends HasTimings {
|
||||
export class Frame extends HasWeights {
|
||||
key: string | number
|
||||
|
||||
// Name of the frame. May be a method name, e.g.
|
||||
@ -54,7 +54,7 @@ export class Frame extends HasTimings {
|
||||
}
|
||||
}
|
||||
|
||||
export class CallTreeNode extends HasTimings {
|
||||
export class CallTreeNode extends HasWeights {
|
||||
children: CallTreeNode[] = []
|
||||
constructor(readonly frame: Frame, readonly parent: CallTreeNode | null) {
|
||||
super()
|
||||
@ -96,51 +96,17 @@ export class Profile {
|
||||
// List of references to CallTreeNodes at the top of the
|
||||
// stack at the time of the sample.
|
||||
private samples: CallTreeNode[] = []
|
||||
|
||||
// List of time elapsed since the preceding sample was taken.
|
||||
// The first elements it the time elapsed since the beginning
|
||||
// of recording that the sample was taken.
|
||||
// Times are in microseconds.
|
||||
// This array should be the same length as the "samples" array.
|
||||
private timeDeltas: number[] = []
|
||||
|
||||
// List of events recorded in parallel with the call
|
||||
// stack samples. Useful for overlaying IO events on
|
||||
// the same time axis as the sampling profile.
|
||||
private events: ProfilingEvent[] = []
|
||||
private weights: number[] = []
|
||||
|
||||
constructor(duration: number) {
|
||||
this.duration = duration
|
||||
}
|
||||
|
||||
getDuration() { return this.duration }
|
||||
getEvents() { return this.events }
|
||||
|
||||
forEachSample(fn: (stack: CallTreeNode[], timeDelta: number) => void) {
|
||||
const nodeToStack = new Map<CallTreeNode, CallTreeNode[]>()
|
||||
for (let i = 0; i < this.samples.length; i++) {
|
||||
let topOfStackNode: CallTreeNode = this.samples[i]
|
||||
|
||||
// Memoize
|
||||
if (!nodeToStack.has(topOfStackNode)) {
|
||||
const stack: CallTreeNode[] = []
|
||||
for (let node: CallTreeNode | null = topOfStackNode; node; node = node.parent) {
|
||||
stack.push(node)
|
||||
}
|
||||
|
||||
// Reverse to order from bottom-to-top
|
||||
stack.reverse()
|
||||
|
||||
nodeToStack.set(topOfStackNode, stack)
|
||||
}
|
||||
|
||||
fn(nodeToStack.get(topOfStackNode)!, this.timeDeltas[i])
|
||||
}
|
||||
}
|
||||
getTotalWeight() { return this.duration }
|
||||
|
||||
forEachCall(
|
||||
openFrame: (node: CallTreeNode, value: number) => void,
|
||||
closeFrame: (node: CallTreeNode, value: number) => void
|
||||
closeFrame: (value: number) => void
|
||||
) {
|
||||
let prevStack: CallTreeNode[] = []
|
||||
let value = 0
|
||||
@ -149,7 +115,8 @@ export class Profile {
|
||||
for (let stackTop of this.samples) {
|
||||
// Close frames that are no longer open
|
||||
while (prevStack.length > 0 && lastOf(prevStack) != stackTop) {
|
||||
closeFrame(prevStack.pop()!, value)
|
||||
prevStack.pop()
|
||||
closeFrame(value)
|
||||
}
|
||||
|
||||
// Open frames that are now becoming open
|
||||
@ -164,12 +131,12 @@ export class Profile {
|
||||
}
|
||||
|
||||
prevStack = prevStack.concat(toOpen)
|
||||
value += this.timeDeltas[sampleIndex++]
|
||||
value += this.weights[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)
|
||||
closeFrame(value)
|
||||
}
|
||||
}
|
||||
|
||||
@ -177,8 +144,8 @@ export class Profile {
|
||||
this.frames.forEach(fn)
|
||||
}
|
||||
|
||||
appendSample(stack: FrameInfo[], timeDelta: number) {
|
||||
if (isNaN(timeDelta)) throw new Error('invalid timeDelta')
|
||||
appendSample(stack: FrameInfo[], weight: number) {
|
||||
if (isNaN(weight)) throw new Error('invalid weight')
|
||||
let node: CallTreeNode | null = null
|
||||
let children = this.calltreeRoots
|
||||
|
||||
@ -191,49 +158,20 @@ export class Profile {
|
||||
node = new CallTreeNode(frame, node)
|
||||
children.push(node)
|
||||
}
|
||||
node.addToTotalTime(timeDelta)
|
||||
node.frame.addToTotalTime(timeDelta)
|
||||
node.addToTotalWeight(weight)
|
||||
|
||||
// TODO(jlfwong): Do this in a set to avoid
|
||||
// multiple-counting recursive calls
|
||||
node.frame.addToTotalWeight(weight)
|
||||
|
||||
children = node.children
|
||||
}
|
||||
|
||||
if (node) {
|
||||
node.addToSelfTime(timeDelta)
|
||||
node.frame.addToSelfTime(timeDelta)
|
||||
node.addToSelfWeight(weight)
|
||||
node.frame.addToSelfWeight(weight)
|
||||
this.samples.push(node)
|
||||
this.timeDeltas.push(timeDelta)
|
||||
this.weights.push(weight)
|
||||
}
|
||||
}
|
||||
|
||||
sortedAlphabetically(): Profile {
|
||||
function key(sample: CallTreeNode) {
|
||||
let k = ''
|
||||
let node: CallTreeNode | null = sample
|
||||
while (node) {
|
||||
k = node.frame.name + ':' + k
|
||||
node = node.parent
|
||||
}
|
||||
return k
|
||||
}
|
||||
|
||||
let sortedSamples: [CallTreeNode, number][] = []
|
||||
for (let i = 0; i < this.samples.length; i++) {
|
||||
sortedSamples.push([this.samples[i], this.timeDeltas[i]])
|
||||
}
|
||||
|
||||
sortedSamples.sort((a, b) => key(a[0]) < key(b[0]) ? -1 : 1)
|
||||
|
||||
const sortedProfile = new Profile(this.duration)
|
||||
for (const [stackTop, timeDelta] of sortedSamples) {
|
||||
const stack: FrameInfo[] = []
|
||||
function visit(node: CallTreeNode) {
|
||||
if (node.parent) visit(node.parent)
|
||||
const frameInfo = {...node.frame}
|
||||
frameInfo.key = frameInfo.name
|
||||
stack.push(frameInfo)
|
||||
}
|
||||
visit(stackTop)
|
||||
sortedProfile.appendSample(stack, timeDelta)
|
||||
}
|
||||
return sortedProfile
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user