Alternative sorting solution

This commit is contained in:
Jamie Wong 2017-12-31 19:51:26 -05:00
parent 9ee5b53eda
commit ffc82360c0
5 changed files with 98 additions and 44 deletions

View File

@ -57,7 +57,11 @@ 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)
const sortedFlamechart = new Flamechart({
getTotalWeight: profile.getTotalNonIdleWeight.bind(profile),
forEachCall: profile.forEachCallGrouped.bind(profile),
forEachFrame: profile.forEachFrame.bind(profile)
})
this.setState({ profile, flamechart, sortedFlamechart }, () => {
console.timeEnd('import')
})

View File

@ -1,4 +1,4 @@
import {Profile, Frame, CallTreeNode} from './profile'
import {Frame, CallTreeNode} from './profile'
import { lastOf } from './utils'

View File

@ -92,6 +92,8 @@ export function importFromChrome(events: TimelineEvent[]) {
// 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) {
if (node.callFrame.functionName === '(root)') continue
if (node.callFrame.functionName === '(idle)') continue
stack.push({
key: node.id,
name: node.callFrame.functionName || "(anonymous)",

View File

@ -61,48 +61,62 @@ export class CallTreeNode extends HasWeights {
}
}
export interface ProfilingEvent {
// Name of the event, e.g. "SQL Query"
name: string
// Details (e.g. the SQL query)
details?: string
// Bottom of the stack of the call-tree
stack: CallTreeNode
// Elapsed time since the start of the profile,
// in microseconds
start: number
end: number
// Color, if specified to associate with this event.
// If unspecified, will be generated based on the name.
color?: string
}
function getOrInsert<K, V>(map: Map<K, V>, k: K, v: V): V {
if (!map.has(k)) map.set(k, v)
return map.get(k)!
}
const rootFrame = new Frame({
key: '(speedscope root)',
name: '(speedscope root)',
})
export class Profile {
// Duration of the entire profile, in microseconds
private duration: number
private totalWeight: number
private frames = new Map<string | number, Frame>()
private calltreeRoots: CallTreeNode[] = []
private appendOrderCalltreeRoot = new CallTreeNode(rootFrame, null)
private groupedCalltreeRoot = new CallTreeNode(rootFrame, null)
// List of references to CallTreeNodes at the top of the
// stack at the time of the sample.
private samples: CallTreeNode[] = []
private weights: number[] = []
constructor(duration: number) {
this.duration = duration
constructor(totalWeight: number) {
this.totalWeight = totalWeight
}
getTotalWeight() { return this.duration }
getTotalWeight() { return this.totalWeight }
getTotalNonIdleWeight() {
return this.groupedCalltreeRoot.children.reduce((n, c) => n + c.getTotalTime(), 0)
}
forEachCallGrouped(
openFrame: (node: CallTreeNode, value: number) => void,
closeFrame: (value: number) => void
) {
function visit(node: CallTreeNode, start: number) {
if (node.frame !== rootFrame) {
openFrame(node, start)
}
let childTime = 0
const children = [...node.children]
children.sort((a, b) => a.getTotalTime() > b.getTotalTime() ? -1 : 1)
children.forEach(function (child) {
visit(child, start + childTime)
childTime += child.getTotalTime()
})
if (node.frame !== rootFrame) {
closeFrame(start + node.getTotalTime())
}
}
visit(this.groupedCalltreeRoot, 0)
}
forEachCall(
openFrame: (node: CallTreeNode, value: number) => void,
@ -113,20 +127,35 @@ export class Profile {
let sampleIndex = 0
for (let stackTop of this.samples) {
// Find lowest common ancestor of the current stack and the previous one
let lca: CallTreeNode | 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 (
lca = stackTop;
lca && lca.frame != rootFrame && prevStack.indexOf(lca) === -1;
lca = lca.parent
) {}
// Close frames that are no longer open
while (prevStack.length > 0 && lastOf(prevStack) != stackTop) {
while (prevStack.length > 0 && lastOf(prevStack) != lca) {
prevStack.pop()
closeFrame(value)
}
// Open frames that are now becoming open
const toOpen: CallTreeNode[] = []
for (let node: CallTreeNode | null = stackTop; node && node != lastOf(prevStack); node = node.parent) {
for (
let node: CallTreeNode | null = stackTop;
node && node.frame != rootFrame && node != lca;
node = node.parent
) {
toOpen.push(node)
}
toOpen.reverse()
for (let i = toOpen.length - 1; i >= 0; i--) {
const node = toOpen[i]
for (let node of toOpen) {
openFrame(node, value)
}
@ -144,34 +173,48 @@ export class Profile {
this.frames.forEach(fn)
}
appendSample(stack: FrameInfo[], weight: number) {
_appendSample(stack: FrameInfo[], weight: number, useAppendOrder: boolean) {
if (isNaN(weight)) throw new Error('invalid weight')
let node: CallTreeNode | null = null
let children = this.calltreeRoots
let node = useAppendOrder ? this.appendOrderCalltreeRoot : this.groupedCalltreeRoot
let framesInStack = new Set<Frame>()
for (let frameInfo of stack) {
const frame = getOrInsert(this.frames, frameInfo.key, new Frame(frameInfo))
const last = lastOf(children)
const last = useAppendOrder ? lastOf(node.children) : node.children.find(c => c.frame === frame)
if (last && last.frame == frame) {
node = last
} else {
const parent = node
node = new CallTreeNode(frame, node)
children.push(node)
parent.children.push(node)
}
node.addToTotalWeight(weight)
// TODO(jlfwong): Do this in a set to avoid
// multiple-counting recursive calls
node.frame.addToTotalWeight(weight)
children = node.children
// It's possible for the same frame to occur multiple
// times in the same call stack due to either direct
// or indirect recursion. We want to avoid counting that
// frame multiple times for a single sample, we so just
// track all of the unique frames that participated in
// this call stack, then add to their weight at the end.
framesInStack.add(node.frame)
}
node.addToSelfWeight(weight)
if (node) {
node.addToSelfWeight(weight)
if (useAppendOrder) {
node.frame.addToSelfWeight(weight)
for (let frame of framesInStack) {
frame.addToTotalWeight(weight)
}
this.samples.push(node)
this.weights.push(weight)
}
}
appendSample(stack: FrameInfo[], weight: number) {
this._appendSample(stack, weight, true)
this._appendSample(stack, weight, false)
}
}

5
sample/simple.txt Normal file
View File

@ -0,0 +1,5 @@
a;b;c 1
a;b;c 1
a;b;d 4
a;b;c 3
a;b 5