Importing from brendangregg/FlameGraph format

This commit is contained in:
Jamie Wong 2017-12-07 22:27:28 -08:00
parent e4c378d8ea
commit 15aa924583
5 changed files with 66 additions and 21 deletions

View File

@ -259,10 +259,8 @@ export class FlamechartPanZoomView extends Component<FlamechartPanZoomViewProps,
labels: FlamechartFrameLabel[] = []
hoveredLabel: FlamechartFrameLabel | null = null
private preprocess() {
if (!this.canvas) return
const {flamechart} = this.props
private preprocess(flamechart: Flamechart) {
if (!this.canvas || !this.ctx) return
const configSpaceRects: Rect[] = []
const colors: vec3[] = []
@ -272,6 +270,7 @@ export class FlamechartPanZoomView extends Component<FlamechartPanZoomViewProps,
const frameColors = flamechart.getFrameColors()
this.labels = []
for (let i = 0; i < layers.length; i++) {
const layer = layers[i]
for (let flamechartFrame of layer) {
@ -289,14 +288,15 @@ export class FlamechartPanZoomView extends Component<FlamechartPanZoomViewProps,
}
}
this.ctx = this.canvas.getContext('webgl')!
this.renderer = rectangleBatchRenderer(this.ctx, configSpaceRects, colors)
this.configSpaceViewportRect = new Rect()
this.hoveredLabel = null
}
private canvasRef = (element?: Element) => {
if (element) {
this.canvas = element as HTMLCanvasElement
this.preprocess()
this.ctx = this.canvas.getContext('webgl')!
this.renderCanvas()
} else {
this.canvas = null
@ -442,14 +442,8 @@ export class FlamechartPanZoomView extends Component<FlamechartPanZoomViewProps,
// Still initializing: don't resize yet
if (width === 0 || height === 0) return
// Already at the right size
if (width === this.canvas.width && height === this.canvas.height) return
const oldWidth = this.canvas.width
const oldHeight = this.canvas.height
this.canvas.width = width
this.canvas.height = height
if (this.configSpaceViewportRect.isEmpty()) {
this.configSpaceViewportRect = new Rect(
@ -466,6 +460,13 @@ export class FlamechartPanZoomView extends Component<FlamechartPanZoomViewProps,
))
)
}
// Already at the right size
if (width === oldWidth && height === oldHeight) return
this.canvas.width = width
this.canvas.height = height
this.ctx.viewport(0, 0, width, height)
}
@ -488,6 +489,7 @@ export class FlamechartPanZoomView extends Component<FlamechartPanZoomViewProps,
// size.
requestAnimationFrame(() => this.renderCanvas())
} else {
if (!this.renderer) this.preprocess(this.props.flamechart)
this.renderRects()
this.renderLabels()
}
@ -607,7 +609,12 @@ export class FlamechartPanZoomView extends Component<FlamechartPanZoomViewProps,
}
shouldComponentUpdate() { return false }
componentWillReceiveProps() { this.renderCanvas() }
componentWillReceiveProps(nextProps: FlamechartPanZoomViewProps) {
if (this.props.flamechart !== nextProps.flamechart) {
this.renderer = null
}
this.renderCanvas()
}
componentDidMount() {
window.addEventListener('mouseup', this.onWindowMouseUp)
}
@ -662,6 +669,11 @@ export class FlamechartView extends Component<FlamechartViewProps, FlamechartVie
static TOOLTIP_WIDTH_MAX = 300
static TOOLTIP_HEIGHT_MAX = 75
formatTime(timeInNs: number) {
const totalTimeNs = this.props.flamechart.getDuration()
return `${(timeInNs / 1000).toFixed(2)}ms (${(100 * timeInNs/totalTimeNs).toFixed()}%)`
}
renderTooltip() {
if (!this.container) return null
@ -693,10 +705,10 @@ export class FlamechartView extends Component<FlamechartViewProps, FlamechartVie
return (
<div className={css(style.hoverTip)} style={positionStyle}>
<div className={css(style.hoverTipRow)}>{hoveredNode.frame.name}</div>
<div className={css(style.hoverTipRow)}>Total Time: {(hoveredNode.getTotalTime() / 1000).toFixed(2)}ms</div>
<div className={css(style.hoverTipRow)}>Self Time: {(hoveredNode.getSelfTime() / 1000).toFixed(2)}ms</div>
<div className={css(style.hoverTipRow)}>Cum. Total Time: {(hoveredNode.frame.getTotalTime() / 1000).toFixed(2)}ms</div>
<div className={css(style.hoverTipRow)}>Cum. Self Time: {(hoveredNode.frame.getSelfTime() / 1000).toFixed(2)}ms</div>
<div className={css(style.hoverTipRow)}>Total Time: {this.formatTime(hoveredNode.getTotalTime())}</div>
<div className={css(style.hoverTipRow)}>Self Time: {this.formatTime(hoveredNode.getSelfTime())}</div>
<div className={css(style.hoverTipRow)}>Cum. Total Time: {this.formatTime(hoveredNode.frame.getTotalTime())}</div>
<div className={css(style.hoverTipRow)}>Cum. Self Time: {this.formatTime(hoveredNode.frame.getSelfTime())}</div>
</div>
)
}
@ -729,6 +741,7 @@ const style = StyleSheet.create({
paddingTop: HOVERTIP_PADDING,
paddingBottom: HOVERTIP_PADDING,
pointerEvents: 'none',
userSelect: 'none',
fontSize: FontSize.LABEL,
fontFamily: FontFamily.MONOSPACE
},

30
import/bg-flamegraph.ts Normal file
View File

@ -0,0 +1,30 @@
// https://github.com/brendangregg/FlameGraph#2-fold-stacks
import {Profile, FrameInfo} from '../profile'
interface BGSample {
stack: FrameInfo[]
duration: number
}
function parseBGFoldedStacks(contents: string): BGSample[] {
const samples: BGSample[] = []
contents.replace(/^(.*) (\d+)$/mg, (match: string, stack: string, n: string) => {
samples.push({
stack: stack.split(';').map(name => ({key: name, name: name})),
duration: parseInt(n, 10)
})
return match
})
return samples
}
export function importFromBGFlameGraph(contents: string): Profile {
const parsed = parseBGFoldedStacks(contents)
const duration = parsed.reduce((prev: number, cur: BGSample) => prev + cur.duration, 0)
const profile = new Profile(duration)
for (let sample of parsed) {
profile.appendSample(sample.stack, sample.duration)
}
return profile
}

View File

@ -1,6 +1,6 @@
// https://github.com/tmm1/stackprof
import {Profile, Frame} from './profile'
import {Profile, FrameInfo} from '../profile'
interface StackprofFrame {
name: string
@ -25,7 +25,7 @@ export function importFromStackprof(contents: string): Profile {
for (let i = 0; i < raw.length;) {
const stackHeight = raw[i++]
const stack: Frame[] = []
const stack: FrameInfo[] = []
for (let j = 0; j < stackHeight; j++) {
const id = raw[i++]
stack.push({

View File

@ -1,7 +1,8 @@
import {h, render, Component} from 'preact'
import {StyleSheet, css} from 'aphrodite'
import {importFromStackprof} from './stackprof'
import {importFromBGFlameGraph} from './import/bg-flamegraph'
import {Profile} from './profile'
import {Flamechart, FlamechartView} from './flamechart'
@ -16,7 +17,7 @@ class Application extends Component<{}, ApplicaionState> {
onDrop = (ev: DragEvent) => {
const reader = new FileReader
reader.addEventListener('loadend', () => {
const profile = importFromStackprof(reader.result)
const profile = importFromBGFlameGraph(reader.result)
const flamechart = new Flamechart(profile)
this.setState({profile, flamechart})
})

View File

@ -142,6 +142,7 @@ export class Profile {
}
appendSample(stack: FrameInfo[], timeDelta: number) {
if (isNaN(timeDelta)) throw new Error('invalid timeDelta')
let node: CallTreeNode | null = null
let children = this.calltreeRoots