2018-01-07 22:46:44 +03:00
|
|
|
|
import {h} from 'preact'
|
2018-01-22 22:22:24 +03:00
|
|
|
|
import * as regl from 'regl'
|
2017-12-23 21:37:56 +03:00
|
|
|
|
import {StyleSheet, css} from 'aphrodite'
|
|
|
|
|
import {ReloadableComponent} from './reloadable'
|
|
|
|
|
|
|
|
|
|
import {importFromBGFlameGraph} from './import/bg-flamegraph'
|
|
|
|
|
import {importFromStackprof} from './import/stackprof'
|
2018-01-08 06:50:27 +03:00
|
|
|
|
import {importFromChromeTimeline, importFromChromeCPUProfile} from './import/chrome'
|
2018-01-22 22:22:24 +03:00
|
|
|
|
import { RectangleBatch, RectangleBatchRenderer } from './rectangle-batch-renderer'
|
2017-12-23 21:37:56 +03:00
|
|
|
|
|
2018-01-08 05:36:23 +03:00
|
|
|
|
import {Profile, Frame} from './profile'
|
2017-12-26 20:59:42 +03:00
|
|
|
|
import {Flamechart} from './flamechart'
|
|
|
|
|
import { FlamechartView } from './flamechart-view'
|
2018-01-07 22:45:05 +03:00
|
|
|
|
import { FontFamily, FontSize, Colors } from './style'
|
2018-01-08 05:36:23 +03:00
|
|
|
|
import { FrameColorGenerator } from './color'
|
2018-01-22 22:22:24 +03:00
|
|
|
|
import { Vec2, Rect } from './math'
|
2017-12-23 21:37:56 +03:00
|
|
|
|
|
|
|
|
|
const enum SortOrder {
|
|
|
|
|
CHRONO,
|
2018-01-07 04:31:48 +03:00
|
|
|
|
LEFT_HEAVY
|
2017-12-23 21:37:56 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
interface ApplicationState {
|
|
|
|
|
profile: Profile | null
|
|
|
|
|
flamechart: Flamechart | null
|
2018-01-22 22:22:24 +03:00
|
|
|
|
flamechartRectBatch: RectangleBatch | null
|
2017-12-23 21:37:56 +03:00
|
|
|
|
sortedFlamechart: Flamechart | null
|
2018-01-22 22:22:24 +03:00
|
|
|
|
sortedFlamechartRectBatch: RectangleBatch | null
|
2017-12-23 21:37:56 +03:00
|
|
|
|
sortOrder: SortOrder
|
2018-01-19 21:50:22 +03:00
|
|
|
|
loading: boolean
|
2017-12-23 21:37:56 +03:00
|
|
|
|
}
|
|
|
|
|
|
2018-01-08 03:18:41 +03:00
|
|
|
|
interface ToolbarProps extends ApplicationState {
|
|
|
|
|
setSortOrder(order: SortOrder): void
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-16 20:59:22 +03:00
|
|
|
|
function importProfile(contents: string, fileName: string): Profile | null {
|
|
|
|
|
try {
|
|
|
|
|
// First pass: Check known file format names to infer the file type
|
|
|
|
|
if (fileName.endsWith('.cpuprofile')) {
|
|
|
|
|
console.log('Importing as Chrome CPU Profile')
|
|
|
|
|
return importFromChromeCPUProfile(JSON.parse(contents))
|
|
|
|
|
} else if (fileName.endsWith('.chrome.json') || /Profile-\d{8}T\d{6}/.exec(fileName)) {
|
|
|
|
|
console.log('Importing as Chrome Timeline')
|
|
|
|
|
return importFromChromeTimeline(JSON.parse(contents))
|
|
|
|
|
} else if (fileName.endsWith('.stackprof.json')) {
|
|
|
|
|
console.log('Importing as stackprof profile')
|
|
|
|
|
return importFromStackprof(JSON.parse(contents))
|
|
|
|
|
} else if (fileName.endsWith('.txt')) {
|
|
|
|
|
console.log('Importing as collapsed stack format')
|
|
|
|
|
return importFromBGFlameGraph(contents)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Second pass: Try to guess what file format it is based on structure
|
|
|
|
|
try {
|
|
|
|
|
const parsed = JSON.parse(contents)
|
|
|
|
|
if (Array.isArray(parsed) && parsed[parsed.length - 1].name === "CpuProfile") {
|
|
|
|
|
console.log('Importing as Chrome CPU Profile')
|
|
|
|
|
return importFromChromeTimeline(parsed)
|
|
|
|
|
} else if ('nodes' in parsed && 'samples' in parsed && 'timeDeltas' in parsed) {
|
|
|
|
|
console.log('Importing as Chrome Timeline')
|
|
|
|
|
return importFromChromeCPUProfile(parsed)
|
|
|
|
|
} else if ('mode' in parsed && 'frames' in parsed) {
|
|
|
|
|
console.log('Importing as stackprof profile')
|
|
|
|
|
return importFromStackprof(parsed)
|
|
|
|
|
}
|
|
|
|
|
} catch (e) {
|
|
|
|
|
// Format is not JSON
|
|
|
|
|
|
|
|
|
|
// If every line ends with a space followed by a number, it's probably
|
|
|
|
|
// the collapsed stack format.
|
|
|
|
|
const lineCount = contents.split(/\n/).length
|
|
|
|
|
if (lineCount > 1 && lineCount === contents.split(/ \d+\n/).length) {
|
|
|
|
|
console.log('Importing as collapsed stack format')
|
|
|
|
|
return importFromBGFlameGraph(contents)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error(e)
|
|
|
|
|
return null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-08 03:18:41 +03:00
|
|
|
|
export class Toolbar extends ReloadableComponent<ToolbarProps, void> {
|
|
|
|
|
setTimeOrder = () => {
|
|
|
|
|
this.props.setSortOrder(SortOrder.CHRONO)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
setLeftHeavyOrder = () => {
|
|
|
|
|
this.props.setSortOrder(SortOrder.LEFT_HEAVY)
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-30 05:15:12 +03:00
|
|
|
|
render() {
|
2018-01-08 03:18:41 +03:00
|
|
|
|
const help = (
|
|
|
|
|
<div className={css(style.toolbarTab)}>
|
|
|
|
|
<a href="https://github.com/jlfwong/speedscope#usage" className={css(style.noLinkStyle)} target="_blank">
|
|
|
|
|
<span className={css(style.emoji)}>❓</span>Help
|
|
|
|
|
</a>
|
|
|
|
|
</div>
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if (!this.props.profile) {
|
|
|
|
|
return <div className={css(style.toolbar)}>
|
|
|
|
|
<div className={css(style.toolbarLeft)}>{help}</div>
|
|
|
|
|
🔬speedscope
|
|
|
|
|
</div>
|
|
|
|
|
}
|
2017-12-30 05:15:12 +03:00
|
|
|
|
return <div className={css(style.toolbar)}>
|
2018-01-08 03:18:41 +03:00
|
|
|
|
<div className={css(style.toolbarLeft)}>
|
|
|
|
|
<div className={css(style.toolbarTab, this.props.sortOrder === SortOrder.CHRONO && style.toolbarTabActive)} onClick={this.setTimeOrder}>
|
|
|
|
|
<span className={css(style.emoji)}>🕰</span>Time Order
|
|
|
|
|
</div>
|
|
|
|
|
<div className={css(style.toolbarTab, this.props.sortOrder === SortOrder.LEFT_HEAVY && style.toolbarTabActive)} onClick={this.setLeftHeavyOrder}>
|
|
|
|
|
<span className={css(style.emoji)}>⬅️</span>Left Heavy
|
|
|
|
|
</div>
|
|
|
|
|
{help}
|
|
|
|
|
</div>
|
|
|
|
|
{this.props.profile.getName()}
|
|
|
|
|
<div className={css(style.toolbarRight)}>🔬speedscope</div>
|
2017-12-30 05:15:12 +03:00
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-22 22:22:24 +03:00
|
|
|
|
interface GLCanvasProps {
|
|
|
|
|
setGL(gl: regl.Instance | null): void
|
|
|
|
|
}
|
|
|
|
|
export class GLCanvas extends ReloadableComponent<GLCanvasProps, void> {
|
|
|
|
|
private canvas: HTMLCanvasElement | null
|
|
|
|
|
private gl: regl.Instance | null
|
|
|
|
|
|
|
|
|
|
private ref = (canvas?: Element) => {
|
|
|
|
|
if (canvas instanceof HTMLCanvasElement) {
|
|
|
|
|
this.canvas = canvas
|
|
|
|
|
this.gl = regl(canvas)
|
|
|
|
|
} else {
|
|
|
|
|
this.gl = null
|
|
|
|
|
}
|
|
|
|
|
this.props.setGL(this.gl)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private maybeResize() {
|
|
|
|
|
if (!this.canvas || !this.gl) return
|
|
|
|
|
let { width, height } = this.canvas.getBoundingClientRect()
|
|
|
|
|
width = Math.floor(width) * window.devicePixelRatio
|
|
|
|
|
height = Math.floor(height) * window.devicePixelRatio
|
|
|
|
|
|
|
|
|
|
// Still initializing: don't resize yet
|
|
|
|
|
if (width < 4 || height < 4) return
|
|
|
|
|
const oldWidth = this.canvas.width
|
|
|
|
|
const oldHeight = this.canvas.height
|
|
|
|
|
|
|
|
|
|
// Already at the right size
|
|
|
|
|
if (width === oldWidth && height === oldHeight) return
|
|
|
|
|
|
|
|
|
|
this.canvas.width = width
|
|
|
|
|
this.canvas.height = height
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onWindowResize = () => {
|
|
|
|
|
this.maybeResize()
|
|
|
|
|
window.addEventListener('resize', this.onWindowResize)
|
|
|
|
|
}
|
|
|
|
|
componentDidMount() {
|
|
|
|
|
window.addEventListener('resize', this.onWindowResize)
|
|
|
|
|
requestAnimationFrame(() => this.maybeResize())
|
|
|
|
|
}
|
|
|
|
|
componentWillUnmount() {
|
|
|
|
|
window.removeEventListener('resize', this.onWindowResize)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
render() {
|
|
|
|
|
return <canvas className={css(style.glCanvasView)} ref={this.ref} width={1} height={1} />
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function rectangleBatchForFlamechart(gl: regl.Instance, flamechart: Flamechart) {
|
|
|
|
|
console.time('rectangle batch generation')
|
|
|
|
|
|
|
|
|
|
const batch = new RectangleBatch(gl)
|
|
|
|
|
|
|
|
|
|
const layers = flamechart.getLayers()
|
|
|
|
|
for (let i = 0; i < layers.length; i++) {
|
|
|
|
|
const layer = layers[i]
|
|
|
|
|
for (let flamechartFrame of layer) {
|
|
|
|
|
const configSpaceBounds = new Rect(
|
|
|
|
|
new Vec2(flamechartFrame.start, i + 1),
|
|
|
|
|
new Vec2(flamechartFrame.end - flamechartFrame.start, 1)
|
|
|
|
|
)
|
|
|
|
|
const color = flamechart.getColorForFrame(flamechartFrame.node.frame)
|
|
|
|
|
batch.addRect(configSpaceBounds, color)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GPU upload
|
|
|
|
|
batch.uploadToGPU()
|
|
|
|
|
|
|
|
|
|
console.timeEnd('rectangle batch generation')
|
|
|
|
|
|
|
|
|
|
return batch
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2017-12-26 19:49:25 +03:00
|
|
|
|
export class Application extends ReloadableComponent<{}, ApplicationState> {
|
2017-12-23 21:37:56 +03:00
|
|
|
|
constructor() {
|
|
|
|
|
super()
|
|
|
|
|
this.state = {
|
2018-01-19 21:50:22 +03:00
|
|
|
|
loading: false,
|
2017-12-23 21:37:56 +03:00
|
|
|
|
profile: null,
|
|
|
|
|
flamechart: null,
|
2018-01-22 22:22:24 +03:00
|
|
|
|
flamechartRectBatch: null,
|
2017-12-23 21:37:56 +03:00
|
|
|
|
sortedFlamechart: null,
|
2018-01-22 22:22:24 +03:00
|
|
|
|
sortedFlamechartRectBatch: null,
|
2017-12-23 21:37:56 +03:00
|
|
|
|
sortOrder: SortOrder.CHRONO
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-07 22:45:05 +03:00
|
|
|
|
loadFromString(fileName: string, contents: string) {
|
2018-01-22 22:22:24 +03:00
|
|
|
|
if (!this.gl) return
|
|
|
|
|
|
2018-01-07 22:45:05 +03:00
|
|
|
|
console.time('import')
|
2018-01-16 20:59:22 +03:00
|
|
|
|
const profile = importProfile(contents, fileName)
|
|
|
|
|
if (profile == null) {
|
2018-01-19 21:50:22 +03:00
|
|
|
|
this.setState({ loading: false })
|
2018-01-16 20:59:22 +03:00
|
|
|
|
// TODO(jlfwong): Make this a nicer overlay
|
|
|
|
|
alert('Unrecognized format! See documentation about supported formats.')
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-08 03:18:41 +03:00
|
|
|
|
profile.setName(fileName)
|
|
|
|
|
document.title = `${fileName} - speedscope`
|
2018-01-08 05:36:23 +03:00
|
|
|
|
|
|
|
|
|
const frames: Frame[] = []
|
|
|
|
|
profile.forEachFrame(f => frames.push(f))
|
|
|
|
|
const colorGenerator = new FrameColorGenerator(frames)
|
|
|
|
|
|
|
|
|
|
const flamechart = new Flamechart({
|
|
|
|
|
getTotalWeight: profile.getTotalWeight.bind(profile),
|
|
|
|
|
forEachCall: profile.forEachCall.bind(profile),
|
|
|
|
|
formatValue: profile.formatValue.bind(profile),
|
|
|
|
|
getColorForFrame: colorGenerator.getColorForFrame.bind(colorGenerator)
|
|
|
|
|
})
|
2018-01-22 22:22:24 +03:00
|
|
|
|
const flamechartRectBatch = rectangleBatchForFlamechart(this.gl, flamechart)
|
2018-01-08 05:36:23 +03:00
|
|
|
|
|
2018-01-07 22:45:05 +03:00
|
|
|
|
const sortedFlamechart = new Flamechart({
|
|
|
|
|
getTotalWeight: profile.getTotalNonIdleWeight.bind(profile),
|
|
|
|
|
forEachCall: profile.forEachCallGrouped.bind(profile),
|
|
|
|
|
formatValue: profile.formatValue.bind(profile),
|
2018-01-08 05:36:23 +03:00
|
|
|
|
getColorForFrame: colorGenerator.getColorForFrame.bind(colorGenerator)
|
2018-01-07 22:45:05 +03:00
|
|
|
|
})
|
2018-01-22 22:22:24 +03:00
|
|
|
|
const sortedFlamechartRectBatch = rectangleBatchForFlamechart(this.gl, sortedFlamechart)
|
|
|
|
|
|
2018-01-16 20:59:22 +03:00
|
|
|
|
console.timeEnd('import')
|
2018-01-08 05:36:23 +03:00
|
|
|
|
|
2018-01-17 09:27:31 +03:00
|
|
|
|
console.time('first setState')
|
2018-01-22 22:22:24 +03:00
|
|
|
|
this.setState({
|
|
|
|
|
profile,
|
|
|
|
|
flamechart,
|
|
|
|
|
flamechartRectBatch,
|
|
|
|
|
sortedFlamechart,
|
|
|
|
|
sortedFlamechartRectBatch,
|
|
|
|
|
loading: false
|
|
|
|
|
}, () => {
|
2018-01-17 09:27:31 +03:00
|
|
|
|
console.timeEnd('first setState')
|
2018-01-07 22:45:05 +03:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loadFromFile(file: File) {
|
2018-01-19 21:50:22 +03:00
|
|
|
|
this.setState({ loading: true }, () => {
|
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
|
const reader = new FileReader
|
|
|
|
|
reader.addEventListener('loadend', () => {
|
|
|
|
|
this.loadFromString(file.name, reader.result)
|
|
|
|
|
})
|
|
|
|
|
reader.readAsText(file)
|
|
|
|
|
})
|
2017-12-23 21:37:56 +03:00
|
|
|
|
})
|
2018-01-07 22:45:05 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loadExample = () => {
|
2018-01-19 21:50:22 +03:00
|
|
|
|
this.setState({ loading: true })
|
2018-01-07 22:45:05 +03:00
|
|
|
|
fetch('dist/perf-vertx-stacks-01-collapsed-all.txt').then(resp => resp.text()).then(data => {
|
|
|
|
|
this.loadFromString('perf-vertx-stacks-01-collapsed-all.txt', data)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onDrop = (ev: DragEvent) => {
|
|
|
|
|
this.loadFromFile(ev.dataTransfer.files.item(0))
|
2017-12-23 21:37:56 +03:00
|
|
|
|
ev.preventDefault()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onDragOver = (ev: DragEvent) => {
|
|
|
|
|
ev.preventDefault()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
onWindowKeyPress = (ev: KeyboardEvent) => {
|
2018-01-07 04:31:48 +03:00
|
|
|
|
if (ev.key === '1') {
|
2017-12-23 21:37:56 +03:00
|
|
|
|
this.setState({
|
2018-01-07 04:31:48 +03:00
|
|
|
|
sortOrder: SortOrder.CHRONO
|
|
|
|
|
})
|
|
|
|
|
} else if (ev.key === '2') {
|
|
|
|
|
this.setState({
|
|
|
|
|
sortOrder: SortOrder.LEFT_HEAVY
|
2017-12-23 21:37:56 +03:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
componentDidMount() {
|
|
|
|
|
window.addEventListener('keypress', this.onWindowKeyPress)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
componentWillUnmount() {
|
|
|
|
|
window.removeEventListener('keypress', this.onWindowKeyPress)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
flamechartView: FlamechartView | null
|
|
|
|
|
flamechartRef = (view: FlamechartView | null) => this.flamechartView = view
|
|
|
|
|
subcomponents() {
|
|
|
|
|
return {
|
|
|
|
|
flamechart: this.flamechartView
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-07 22:45:05 +03:00
|
|
|
|
onFileSelect = (ev: Event) => {
|
|
|
|
|
this.loadFromFile((ev.target as HTMLInputElement).files!.item(0))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
renderLanding() {
|
|
|
|
|
return <div className={css(style.landingContainer)}>
|
|
|
|
|
<div className={css(style.landingMessage)}>
|
2018-01-08 06:50:27 +03:00
|
|
|
|
<p className={css(style.landingP)}>👋 Hi there! Welcome to 🔬speedscope, an interactive{' '}
|
|
|
|
|
<a className={css(style.link)} href="http://www.brendangregg.com/FlameGraphs/cpuflamegraphs.html">flamegraph</a> visualizer.
|
|
|
|
|
Use it to help you make your software faster.</p>
|
2018-01-07 22:45:05 +03:00
|
|
|
|
<p className={css(style.landingP)}>Drag and drop a profile file onto this window to get started,
|
|
|
|
|
click the big blue button below to browse for a profile to explore, or{' '}
|
|
|
|
|
<a className={css(style.link)} onClick={this.loadExample}>click here</a>{' '}
|
|
|
|
|
to load an example profile.</p>
|
|
|
|
|
|
|
|
|
|
<div className={css(style.browseButtonContainer)}>
|
|
|
|
|
<input type="file" name="file" id="file" onChange={this.onFileSelect} className={css(style.hide)} />
|
|
|
|
|
<label for="file" className={css(style.browseButton)}>Browse</label>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<p className={css(style.landingP)}>See the <a className={css(style.link)}
|
|
|
|
|
href="https://github.com/jlfwong/speedscope#usage" target="_blank">documentation</a> for
|
|
|
|
|
information about supported file formats, keyboard shortcuts, and how
|
|
|
|
|
to navigate around the profile.</p>
|
|
|
|
|
|
|
|
|
|
<p className={css(style.landingP)}>speedscope is open source.
|
|
|
|
|
Please <a className={css(style.link)} target="_blank" href="https://github.com/jlfwong/speedscope/issues">report any issues on GitHub</a>.</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-19 21:50:22 +03:00
|
|
|
|
renderLoadingBar() {
|
|
|
|
|
return <div className={css(style.loading)}></div>
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-08 03:18:41 +03:00
|
|
|
|
setSortOrder = (sortOrder: SortOrder) => {
|
|
|
|
|
this.setState({ sortOrder })
|
|
|
|
|
}
|
|
|
|
|
|
2018-01-22 22:22:24 +03:00
|
|
|
|
private gl: regl.Instance | null = null
|
|
|
|
|
private rectangleBatchRenderer: RectangleBatchRenderer | null = null
|
|
|
|
|
private setGL = (gl: regl.Instance | null) => {
|
|
|
|
|
if (gl) {
|
|
|
|
|
this.gl = gl
|
|
|
|
|
this.rectangleBatchRenderer = new RectangleBatchRenderer(gl)
|
|
|
|
|
} else {
|
|
|
|
|
this.gl = null
|
|
|
|
|
this.rectangleBatchRenderer = null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-12-23 21:37:56 +03:00
|
|
|
|
render() {
|
2018-01-22 22:22:24 +03:00
|
|
|
|
const {flamechart, flamechartRectBatch, sortedFlamechart, sortedFlamechartRectBatch, sortOrder, loading} = this.state
|
2017-12-23 21:37:56 +03:00
|
|
|
|
const flamechartToView = sortOrder == SortOrder.CHRONO ? flamechart : sortedFlamechart
|
2018-01-22 22:22:24 +03:00
|
|
|
|
const rectangleBatch = sortOrder == SortOrder.CHRONO ? flamechartRectBatch : sortedFlamechartRectBatch
|
2017-12-23 21:37:56 +03:00
|
|
|
|
|
|
|
|
|
return <div onDrop={this.onDrop} onDragOver={this.onDragOver} className={css(style.root)}>
|
2018-01-22 22:22:24 +03:00
|
|
|
|
<GLCanvas setGL={this.setGL} />
|
2018-01-08 03:18:41 +03:00
|
|
|
|
<Toolbar setSortOrder={this.setSortOrder} {...this.state} />
|
2018-01-19 21:50:22 +03:00
|
|
|
|
{loading ?
|
|
|
|
|
this.renderLoadingBar() :
|
2018-01-22 22:22:24 +03:00
|
|
|
|
this.gl && flamechartToView && this.rectangleBatchRenderer && rectangleBatch ?
|
|
|
|
|
<FlamechartView
|
|
|
|
|
gl={this.gl}
|
|
|
|
|
renderer={this.rectangleBatchRenderer}
|
|
|
|
|
rectangles={rectangleBatch}
|
|
|
|
|
ref={this.flamechartRef}
|
|
|
|
|
flamechart={flamechartToView} /> :
|
2018-01-19 21:50:22 +03:00
|
|
|
|
this.renderLanding()}
|
2017-12-23 21:37:56 +03:00
|
|
|
|
</div>
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const style = StyleSheet.create({
|
2018-01-22 22:22:24 +03:00
|
|
|
|
glCanvasView: {
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
width: '100vw',
|
|
|
|
|
height: '100vh',
|
|
|
|
|
zIndex: -1,
|
|
|
|
|
pointerEvents: 'none'
|
|
|
|
|
},
|
2018-01-19 21:50:22 +03:00
|
|
|
|
loading: {
|
|
|
|
|
height: 3,
|
|
|
|
|
marginBottom: -3,
|
|
|
|
|
background: Colors.DARK_BLUE,
|
|
|
|
|
transformOrigin: '0% 50%',
|
|
|
|
|
animationName: [{
|
|
|
|
|
from: {
|
|
|
|
|
transform: `scaleX(0)`
|
|
|
|
|
},
|
|
|
|
|
to: {
|
|
|
|
|
transform: `scaleX(1)`
|
|
|
|
|
}
|
|
|
|
|
}],
|
|
|
|
|
animationTimingFunction: "cubic-bezier(0, 1, 0, 1)",
|
|
|
|
|
animationDuration: "30s"
|
|
|
|
|
},
|
2017-12-23 21:37:56 +03:00
|
|
|
|
root: {
|
|
|
|
|
width: '100vw',
|
|
|
|
|
height: '100vh',
|
2017-12-30 05:15:12 +03:00
|
|
|
|
overflow: 'hidden',
|
|
|
|
|
display: 'flex',
|
|
|
|
|
flexDirection: 'column',
|
|
|
|
|
position: 'relative',
|
2018-01-08 06:50:27 +03:00
|
|
|
|
fontFamily: FontFamily.MONOSPACE,
|
|
|
|
|
lineHeight: '20px'
|
2017-12-30 05:15:12 +03:00
|
|
|
|
},
|
2018-01-07 22:45:05 +03:00
|
|
|
|
landingContainer: {
|
|
|
|
|
display: 'flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
justifyContent: 'center',
|
|
|
|
|
flex: 1
|
|
|
|
|
},
|
|
|
|
|
landingMessage: {
|
|
|
|
|
maxWidth: 600
|
|
|
|
|
},
|
|
|
|
|
landingP: {
|
|
|
|
|
marginBottom: 16
|
|
|
|
|
},
|
|
|
|
|
hide: {
|
|
|
|
|
display: 'none'
|
|
|
|
|
},
|
|
|
|
|
browseButtonContainer: {
|
|
|
|
|
display: 'flex',
|
|
|
|
|
alignItems: 'center',
|
|
|
|
|
justifyContent: 'center'
|
|
|
|
|
},
|
|
|
|
|
browseButton: {
|
|
|
|
|
marginBottom: 16,
|
|
|
|
|
height: 72,
|
|
|
|
|
flex: 1,
|
|
|
|
|
maxWidth: 256,
|
|
|
|
|
textAlign: 'center',
|
|
|
|
|
fontSize: FontSize.BIG_BUTTON,
|
|
|
|
|
lineHeight: '72px',
|
|
|
|
|
background: Colors.DARK_BLUE,
|
|
|
|
|
color: 'white',
|
|
|
|
|
cursor: 'pointer'
|
|
|
|
|
},
|
|
|
|
|
link: {
|
|
|
|
|
color: Colors.LIGHT_BLUE,
|
|
|
|
|
cursor: 'pointer',
|
|
|
|
|
textDecoration: 'none'
|
2018-01-08 03:18:41 +03:00
|
|
|
|
},
|
|
|
|
|
toolbar: {
|
|
|
|
|
height: 18,
|
|
|
|
|
background: 'black',
|
|
|
|
|
color: 'white',
|
|
|
|
|
textAlign: 'center',
|
|
|
|
|
fontFamily: FontFamily.MONOSPACE,
|
|
|
|
|
fontSize: FontSize.TITLE,
|
|
|
|
|
lineHeight: '18px',
|
|
|
|
|
userSelect: 'none'
|
|
|
|
|
},
|
|
|
|
|
toolbarLeft: {
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
height: 18,
|
|
|
|
|
overflow: 'hidden',
|
|
|
|
|
top: 0,
|
|
|
|
|
left: 0,
|
|
|
|
|
marginRight: 2,
|
|
|
|
|
textAlign: 'left',
|
|
|
|
|
},
|
|
|
|
|
toolbarRight: {
|
|
|
|
|
height: 18,
|
|
|
|
|
overflow: 'hidden',
|
|
|
|
|
position: 'absolute',
|
|
|
|
|
top: 0,
|
|
|
|
|
right: 0,
|
|
|
|
|
marginRight: 2,
|
|
|
|
|
textAlign: 'right',
|
|
|
|
|
},
|
|
|
|
|
toolbarTab: {
|
|
|
|
|
background: Colors.DARK_GRAY,
|
|
|
|
|
marginTop: 2,
|
|
|
|
|
height: 16,
|
|
|
|
|
lineHeight: '16px',
|
|
|
|
|
paddingLeft: 2,
|
|
|
|
|
paddingRight: 8,
|
|
|
|
|
display: 'inline-block',
|
|
|
|
|
marginLeft: 2,
|
|
|
|
|
':hover': {
|
|
|
|
|
background: Colors.GRAY,
|
|
|
|
|
cursor: 'pointer'
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
toolbarTabActive: {
|
|
|
|
|
background: Colors.LIGHT_BLUE,
|
|
|
|
|
':hover': {
|
|
|
|
|
background: Colors.LIGHT_BLUE
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
noLinkStyle: {
|
|
|
|
|
textDecoration: 'none',
|
|
|
|
|
color: 'inherit'
|
|
|
|
|
},
|
|
|
|
|
emoji: {
|
|
|
|
|
display: 'inline-block',
|
|
|
|
|
verticalAlign: 'middle',
|
|
|
|
|
paddingTop: '0px',
|
|
|
|
|
marginRight: '0.3em'
|
2017-12-23 21:37:56 +03:00
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
|