Created
June 24, 2017 14:36
-
-
Save Kineolyan/78ad838b4426a46b050596d330fc5530 to your computer and use it in GitHub Desktop.
fj-agent
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width"> | |
<title>JS Bin</title> | |
<style id="jsbin-css"> | |
.bar-container { | |
height: 15px; | |
} | |
.bar-container + .bar-container { | |
border-top: 1px solid black; | |
} | |
.bar { | |
display: inline-block; | |
height: 15px; | |
background-color: green; | |
margin-bottom: 0; | |
margin-top: 0; | |
} | |
.node-bar { | |
background-color: red; | |
} | |
.selected-container .node-bar { | |
background-color: purple; | |
} | |
</style> | |
</head> | |
<script src="https://fb.me/react-15.1.0.js"></script> | |
<script src="https://fb.me/react-dom-15.1.0.js"></script> | |
<body> | |
<div id="root"></div> | |
<script id="jsbin-javascript"> | |
const el = React.createElement; | |
const elp = (type, ...content) => el(type, null, ...content); | |
const roots = [ | |
{ | |
id: 'sync1', | |
name: 'Sync 1', | |
start: -1, | |
stop: 10, | |
children: [ | |
{ | |
id: 'cc11', | |
name: 'CC 11', | |
start: 20, | |
stop: 30, | |
children: [] | |
}, { | |
id: 'cc12', | |
name: 'CC 12', | |
start: 25, | |
stop: 50, | |
children: [ | |
{ | |
id: 'cc121', | |
name: 'CC 121', | |
start: 30, | |
stop: 121, | |
children: [] | |
}, { | |
id: 'cc122', | |
name: 'CC 122', | |
start: 40, | |
stop: 76, | |
children: [] | |
} | |
] | |
} | |
] | |
}, | |
{ | |
id: 'sync2', | |
name: 'Sync 2', | |
start: 135, | |
stop: 209, | |
children: [ | |
{ | |
id: 'cc21', | |
name: 'CC 21', | |
start: 145, | |
stop: 456, | |
children: [] | |
}, { | |
id: 'cc22', | |
name: 'CC 22', | |
start: 153, | |
stop: 178, | |
children: [] | |
} | |
] | |
} | |
]; | |
const nodes = {}; | |
function processNodes(v) { | |
v.forEach(n => { | |
nodes[n.id] = n; | |
// Convert time from secs to nanosecs. | |
if (n.start > 0) { n.start *= 1000000; } | |
n.stop *= 1000000; | |
n.duration = n.start > 0 ? n.stop - n.start : 0; | |
processNodes(n.children); | |
}); | |
} | |
processNodes(roots); | |
// Start exportable code from there | |
function exploreNodes(node, action) { | |
const nodes = [node]; | |
let it; | |
while ((it = nodes.shift()) !== undefined) { | |
if (action(it) === false) { | |
return; | |
} | |
nodes.push(...it.children); | |
} | |
} | |
function getNodeFrame(node) { | |
return { | |
start: node.start > 0 ? node.start : node.stop, | |
stop: node.stop | |
}; | |
} | |
function getNodeWindow(node) { | |
let start = Number.MAX_VALUE; | |
let stop = 0; | |
exploreNodes(node, n => { | |
const nodeTime = getNodeFrame(n); | |
if (nodeTime.start > 0 && nodeTime.start < start) { | |
start = nodeTime.start; | |
} | |
if (nodeTime.stop > stop) { | |
stop = nodeTime.stop; | |
} | |
}); | |
if (start < 0) { start = stop; } | |
return {start, stop}; | |
} | |
class Analysis extends React.Component { | |
constructor(props) { | |
super(props); | |
this.state = { | |
needle: '', | |
timeNode: null | |
}; | |
this.cbks = { | |
updateNeedle: event => this.setState({needle: event.target.value}), | |
setTimeNode: nodeId => this.setState({timeNode: nodeId}) | |
}; | |
} | |
renderFilters() { | |
return elp( | |
'div', | |
el('input', { | |
placeholder: 'Class to search', | |
value: this.state.needle, | |
onChange: this.cbks.updateNeedle | |
})); | |
} | |
renderTimeline() { | |
if (this.state.timeNode) { | |
const node = this.props.nodes[this.state.timeNode]; | |
return el(Timeline, {node}); | |
} | |
} | |
renderChains() { | |
const needle = this.state.needle; | |
let roots = this.props.roots; | |
if (needle) { | |
roots = roots.filter(r => { | |
let toKeep = false; | |
exploreNodes(r, n => { | |
if (n.name.includes(needle)) { | |
toKeep = true; | |
return false; | |
} | |
}) | |
return toKeep; | |
}) | |
} | |
return el(Chains, {roots, filter: {needle}, onTimeNode: this.cbks.setTimeNode}); | |
} | |
render() { | |
return elp( | |
'div', | |
this.renderFilters(), | |
this.renderChains(), | |
this.renderTimeline()); | |
} | |
} | |
class Chains extends React.Component { | |
renderNode(node) { | |
const {start, stop} = getNodeWindow(node); | |
let nameDisplay; | |
const needle = this.props.filter.needle; | |
if (needle) { | |
let needleIdx = 0; | |
let idx; | |
const parts = []; | |
while ((idx = node.name.indexOf(needle, needleIdx)) >= 0) { | |
parts.push(node.name.substring(needleIdx, idx)); | |
parts.push(el('span', {style: {backgroundColor: 'yellow'}}, needle)); | |
needleIdx = idx + needle.length; | |
} | |
parts.push(node.name.substring(needleIdx)); | |
nameDisplay = elp('b', ...parts); | |
} else { | |
nameDisplay = elp('b', node.name); | |
} | |
return elp( | |
'div', | |
nameDisplay, | |
` (${node.id})`, | |
el('span', {onClick: () => this.props.onTimeNode(node.id)}, ' (-:)'), | |
el('br'), | |
this.renderTimings(node.start, node.stop, node.duration), | |
el('br'), | |
node.children.length > 0 | |
? `Operation ${this.renderTimings(start, stop, stop - start)}` | |
: null); | |
} | |
renderTimings(start, stop, duration) { | |
start = start > 0 ? start : stop; | |
return `${this.renderNsTime(duration)} ms [ ${this.renderNsTime(start)} -> ${this.renderNsTime(stop)} ]` | |
} | |
renderNsTime(time) { | |
return (time / 1000000).toFixed(2); | |
} | |
renderNodes(nodes) { | |
if (nodes.length > 0) { | |
const items = nodes.map(node => el( | |
'li', | |
{key: node.id}, | |
this.renderNode(node), | |
this.renderNodes(node.children))); | |
return elp('ul', items); | |
} | |
} | |
renderData() { | |
return elp( | |
'div', | |
this.props.roots ? this.renderNodes(this.props.roots) : null); | |
} | |
render() { | |
return elp( | |
'div', | |
this.renderData()); | |
} | |
} | |
class Timeline extends React.Component { | |
constructor(props) { | |
super(props); | |
this.state = { | |
selectedNode: null | |
}; | |
} | |
renderBar(min, max, node) { | |
const {start, stop} = getNodeFrame(node); | |
const range = max - min; | |
const barParts = []; | |
if (range !== 0) { | |
let values = [ | |
start - min, | |
stop - start, | |
max - stop | |
] | |
// .map(size => size + 0.02 * range); | |
const total = values.reduce((a, b) => a + b, 0); | |
let percents = values | |
.map(size => 100 * size / total) | |
.map(percent => `${percent.toFixed(2)}%`); | |
// if (values[1] < 1) { | |
// percents = percents.map(p => `calc(${p} - 2px)`); | |
// percents[1] = '4px'; | |
// } | |
// console.log(values, percents) | |
const [beforeP, barP, afterP] = percents; | |
const before = el( | |
'div', | |
{style: {width: beforeP}, className: 'bar'}); | |
const bar = el( | |
'div', | |
{style: {width: barP}, className: 'bar node-bar'}); | |
const after = el( | |
'div', | |
{style: {width: afterP}, className: 'bar'}); | |
barParts.push(before, bar, after); | |
} else { | |
barParts.push( | |
el( | |
'div', | |
{ | |
style: {width: '100%'}, | |
className: 'bar node-bar', | |
onClick: () => console.log(node.name)})); | |
} | |
let containerClasses = 'bar-container'; | |
if (node.id === this.state.selectedNode) { | |
containerClasses += ' selected-container'; | |
} | |
return el( | |
'div', | |
{className: containerClasses, onClick: () => this.setState({selectedNode: node.id})}, | |
...barParts); | |
} | |
renderTimeLine() { | |
const {start: min, stop: max} = getNodeWindow(this.props.node); | |
const bars = []; | |
exploreNodes(this.props.node, node => { | |
const bar = this.renderBar(min, max, node); | |
bars.push(bar); | |
}); | |
return elp( | |
'div', | |
...bars); | |
} | |
detailSelectedNode() { | |
if (this.state.selectedNode) { | |
let node; | |
exploreNodes(this.props.node, n => { | |
if (n.id === this.state.selectedNode) { | |
node = n; | |
return false; | |
} | |
}); | |
return elp( | |
'div', | |
elp('b', node.name)); | |
} | |
} | |
render() { | |
return el( | |
'div', | |
{width: '100%'}, | |
'Rendering timeline for node ', | |
elp('b', this.props.node.name), | |
this.renderTimeLine(), | |
this.detailSelectedNode()); | |
} | |
} | |
ReactDOM.render( | |
el(Analysis, {roots, nodes}), | |
document.getElementById('root')); | |
</script> | |
<script id="jsbin-source-css" type="text/css">.bar-container { | |
height: 15px; | |
} | |
.bar-container + .bar-container { | |
border-top: 1px solid black; | |
} | |
.bar { | |
display: inline-block; | |
height: 15px; | |
background-color: green; | |
margin-bottom: 0; | |
margin-top: 0; | |
} | |
.node-bar { | |
background-color: red; | |
} | |
.selected-container .node-bar { | |
background-color: purple; | |
}</script> | |
<script id="jsbin-source-javascript" type="text/javascript">const el = React.createElement; | |
const elp = (type, ...content) => el(type, null, ...content); | |
const roots = [ | |
{ | |
id: 'sync1', | |
name: 'Sync 1', | |
start: -1, | |
stop: 10, | |
children: [ | |
{ | |
id: 'cc11', | |
name: 'CC 11', | |
start: 20, | |
stop: 30, | |
children: [] | |
}, { | |
id: 'cc12', | |
name: 'CC 12', | |
start: 25, | |
stop: 50, | |
children: [ | |
{ | |
id: 'cc121', | |
name: 'CC 121', | |
start: 30, | |
stop: 121, | |
children: [] | |
}, { | |
id: 'cc122', | |
name: 'CC 122', | |
start: 40, | |
stop: 76, | |
children: [] | |
} | |
] | |
} | |
] | |
}, | |
{ | |
id: 'sync2', | |
name: 'Sync 2', | |
start: 135, | |
stop: 209, | |
children: [ | |
{ | |
id: 'cc21', | |
name: 'CC 21', | |
start: 145, | |
stop: 456, | |
children: [] | |
}, { | |
id: 'cc22', | |
name: 'CC 22', | |
start: 153, | |
stop: 178, | |
children: [] | |
} | |
] | |
} | |
]; | |
const nodes = {}; | |
function processNodes(v) { | |
v.forEach(n => { | |
nodes[n.id] = n; | |
// Convert time from secs to nanosecs. | |
if (n.start > 0) { n.start *= 1000000; } | |
n.stop *= 1000000; | |
n.duration = n.start > 0 ? n.stop - n.start : 0; | |
processNodes(n.children); | |
}); | |
} | |
processNodes(roots); | |
// Start exportable code from there | |
function exploreNodes(node, action) { | |
const nodes = [node]; | |
let it; | |
while ((it = nodes.shift()) !== undefined) { | |
if (action(it) === false) { | |
return; | |
} | |
nodes.push(...it.children); | |
} | |
} | |
function getNodeFrame(node) { | |
return { | |
start: node.start > 0 ? node.start : node.stop, | |
stop: node.stop | |
}; | |
} | |
function getNodeWindow(node) { | |
let start = Number.MAX_VALUE; | |
let stop = 0; | |
exploreNodes(node, n => { | |
const nodeTime = getNodeFrame(n); | |
if (nodeTime.start > 0 && nodeTime.start < start) { | |
start = nodeTime.start; | |
} | |
if (nodeTime.stop > stop) { | |
stop = nodeTime.stop; | |
} | |
}); | |
if (start < 0) { start = stop; } | |
return {start, stop}; | |
} | |
class Analysis extends React.Component { | |
constructor(props) { | |
super(props); | |
this.state = { | |
needle: '', | |
timeNode: null | |
}; | |
this.cbks = { | |
updateNeedle: event => this.setState({needle: event.target.value}), | |
setTimeNode: nodeId => this.setState({timeNode: nodeId}) | |
}; | |
} | |
renderFilters() { | |
return elp( | |
'div', | |
el('input', { | |
placeholder: 'Class to search', | |
value: this.state.needle, | |
onChange: this.cbks.updateNeedle | |
})); | |
} | |
renderTimeline() { | |
if (this.state.timeNode) { | |
const node = this.props.nodes[this.state.timeNode]; | |
return el(Timeline, {node}); | |
} | |
} | |
renderChains() { | |
const needle = this.state.needle; | |
let roots = this.props.roots; | |
if (needle) { | |
roots = roots.filter(r => { | |
let toKeep = false; | |
exploreNodes(r, n => { | |
if (n.name.includes(needle)) { | |
toKeep = true; | |
return false; | |
} | |
}) | |
return toKeep; | |
}) | |
} | |
return el(Chains, {roots, filter: {needle}, onTimeNode: this.cbks.setTimeNode}); | |
} | |
render() { | |
return elp( | |
'div', | |
this.renderFilters(), | |
this.renderChains(), | |
this.renderTimeline()); | |
} | |
} | |
class Chains extends React.Component { | |
renderNode(node) { | |
const {start, stop} = getNodeWindow(node); | |
let nameDisplay; | |
const needle = this.props.filter.needle; | |
if (needle) { | |
let needleIdx = 0; | |
let idx; | |
const parts = []; | |
while ((idx = node.name.indexOf(needle, needleIdx)) >= 0) { | |
parts.push(node.name.substring(needleIdx, idx)); | |
parts.push(el('span', {style: {backgroundColor: 'yellow'}}, needle)); | |
needleIdx = idx + needle.length; | |
} | |
parts.push(node.name.substring(needleIdx)); | |
nameDisplay = elp('b', ...parts); | |
} else { | |
nameDisplay = elp('b', node.name); | |
} | |
return elp( | |
'div', | |
nameDisplay, | |
` (${node.id})`, | |
el('span', {onClick: () => this.props.onTimeNode(node.id)}, ' (-:)'), | |
el('br'), | |
this.renderTimings(node.start, node.stop, node.duration), | |
el('br'), | |
node.children.length > 0 | |
? `Operation ${this.renderTimings(start, stop, stop - start)}` | |
: null); | |
} | |
renderTimings(start, stop, duration) { | |
start = start > 0 ? start : stop; | |
return `${this.renderNsTime(duration)} ms [ ${this.renderNsTime(start)} -> ${this.renderNsTime(stop)} ]` | |
} | |
renderNsTime(time) { | |
return (time / 1000000).toFixed(2); | |
} | |
renderNodes(nodes) { | |
if (nodes.length > 0) { | |
const items = nodes.map(node => el( | |
'li', | |
{key: node.id}, | |
this.renderNode(node), | |
this.renderNodes(node.children))); | |
return elp('ul', items); | |
} | |
} | |
renderData() { | |
return elp( | |
'div', | |
this.props.roots ? this.renderNodes(this.props.roots) : null); | |
} | |
render() { | |
return elp( | |
'div', | |
this.renderData()); | |
} | |
} | |
class Timeline extends React.Component { | |
constructor(props) { | |
super(props); | |
this.state = { | |
selectedNode: null | |
}; | |
} | |
renderBar(min, max, node) { | |
const {start, stop} = getNodeFrame(node); | |
const range = max - min; | |
const barParts = []; | |
if (range !== 0) { | |
let values = [ | |
start - min, | |
stop - start, | |
max - stop | |
] | |
// .map(size => size + 0.02 * range); | |
const total = values.reduce((a, b) => a + b, 0); | |
let percents = values | |
.map(size => 100 * size / total) | |
.map(percent => `${percent.toFixed(2)}%`); | |
// if (values[1] < 1) { | |
// percents = percents.map(p => `calc(${p} - 2px)`); | |
// percents[1] = '4px'; | |
// } | |
// console.log(values, percents) | |
const [beforeP, barP, afterP] = percents; | |
const before = el( | |
'div', | |
{style: {width: beforeP}, className: 'bar'}); | |
const bar = el( | |
'div', | |
{style: {width: barP}, className: 'bar node-bar'}); | |
const after = el( | |
'div', | |
{style: {width: afterP}, className: 'bar'}); | |
barParts.push(before, bar, after); | |
} else { | |
barParts.push( | |
el( | |
'div', | |
{ | |
style: {width: '100%'}, | |
className: 'bar node-bar', | |
onClick: () => console.log(node.name)})); | |
} | |
let containerClasses = 'bar-container'; | |
if (node.id === this.state.selectedNode) { | |
containerClasses += ' selected-container'; | |
} | |
return el( | |
'div', | |
{className: containerClasses, onClick: () => this.setState({selectedNode: node.id})}, | |
...barParts); | |
} | |
renderTimeLine() { | |
const {start: min, stop: max} = getNodeWindow(this.props.node); | |
const bars = []; | |
exploreNodes(this.props.node, node => { | |
const bar = this.renderBar(min, max, node); | |
bars.push(bar); | |
}); | |
return elp( | |
'div', | |
...bars); | |
} | |
detailSelectedNode() { | |
if (this.state.selectedNode) { | |
let node; | |
exploreNodes(this.props.node, n => { | |
if (n.id === this.state.selectedNode) { | |
node = n; | |
return false; | |
} | |
}); | |
return elp( | |
'div', | |
elp('b', node.name)); | |
} | |
} | |
render() { | |
return el( | |
'div', | |
{width: '100%'}, | |
'Rendering timeline for node ', | |
elp('b', this.props.node.name), | |
this.renderTimeLine(), | |
this.detailSelectedNode()); | |
} | |
} | |
ReactDOM.render( | |
el(Analysis, {roots, nodes}), | |
document.getElementById('root'));</script></body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment