var BrowserPerfRunnerApp = React.createClass({ propTypes: { tests: React.PropTypes.array.isRequired, react: React.PropTypes.array.isRequired, maxTime: React.PropTypes.number, onCompleteEach: React.PropTypes.func, onComplete: React.PropTypes.func, onError: React.PropTypes.func, headless: React.PropTypes.bool }, getInitialState: function() { var queue = []; this.props.tests.forEach(function(testName) { this.props.react.forEach(function(version) { queue.push({ test: testName, react: version }); }, this); }, this); return { queue: queue, results: {} }; }, handleResults: function(results) { this.state.results[results.test + '@' + results.react] = results; this.replaceState(this.state); }, handleComplete: function(queueItem) { queueItem.completed = true; if (!this.props.onCompleteEach) { return; } // Can't get the resultsForAllVersions if there are still some queued var incompleteCount = 0; for (var index = this.state.queue.length; --index >= 0;) { if (this.state.queue[index].completed) { continue; } if (this.state.queue[index].test === queueItem.test) { return; } incompleteCount++; } var resultsForAllVersions = Object.keys(this.state.results) .filter(function(key) { return key.indexOf(queueItem.test) === 0; }) .map(function(key) { return this.state.results[key]; }, this); this.props.onCompleteEach(resultsForAllVersions); if (this.props.onComplete && incompleteCount === 0) { this.props.onComplete(this.state.results); } }, render: function() { var grid = null; if (!this.props.headless) { grid = GridViewTable({ rows: this.props.tests, cols: this.props.react, renderCell: BrowserPerfRunnerApp.renderBenchmarkCell, value: this.state.results }); } return React.DOM.div(null, BenchmarkQueue({ initialQueue: this.state.queue, onChange: this.handleResults, maxTime: this.props.maxTime, onCompleteEach: this.handleComplete, onError: this.props.onError }), grid ); } }); BrowserPerfRunnerApp.renderBenchmarkCell = function(props, row, col) { if (col == null && row == null) { return React.DOM.th(null); } if (row == null) { return React.DOM.th({style:{verticalAlign:'top', textAlign:'center'}}, col); } var benchmarks = Object.keys(props.value) .filter(function(key) { return key.indexOf(row) === 0; }) .map(function(key) { return props.value[key]; }) .filter(function(benchmark) { return benchmark && !benchmark.isRunning && benchmark.stats; }); if (col == null) { return React.DOM.th({style:{verticalAlign:'top', textAlign:'right'}}, React.DOM.a({href:'?test=' + row}, benchmarks[0] && benchmarks[0].name || row) ); } var key = row + '@' + col; var benchmark = props.value[key]; if (!(benchmark && benchmark.stats)) { return React.DOM.td({key:key}); } var colors = [ '000000', 'AA0000', '00AA00', 'AA5500', '0000AA', 'AA00AA', '00AAAA', 'AAAAAA', '555555', 'FF5555', '55FF55', 'FFFF55', '5555FF', 'FF55FF', '55FFFF', 'FFFFFF' ]; function chartValue(value) { return Math.round(valueFromRangeToRange(value, chartValue.min, chartValue.max, 0, 100)); } chartValue.min = Math.min.apply(Math, benchmarks.map(function(benchmark) { return Math.min.apply(Math, benchmark.stats.sample); })); chartValue.max = Math.max.apply(Math, benchmarks.map(function(benchmark) { return Math.max.apply(Math, benchmark.stats.sample); })); var means = benchmarks.map(function(benchmark) { return benchmark.stats.mean; }); benchmarks.forEach(function(benchmark) { benchmark.isTheWinner = benchmark.stats.mean <= Math.min.apply(Math, means); }); var chartValues = benchmarks.map(function(benchmark) { // benchmark.stats.sample.sort(function(a,b) {return b - a;}); return benchmark.stats.sample.map(chartValue).join(','); }).join('|'); return ( React.DOM.td({key:key, style:{textAlign:'center', width:234, verticalAlign:'top'}}, benchmark.error && benchmark.error.message || '', React.DOM.div({style: benchmark.isTheWinner ? {backgroundColor:'#0A5', color:'#AFA'} : {backgroundColor:'transparent', color:'inherit'}}, Math.round(1 / benchmark.stats.mean * 100) / 100, ' op/s ', React.DOM.strong(null, Math.round(benchmark.stats.mean * 1000 * 100) / 100, ' ms/op '), React.DOM.small(null, '(±' + (Math.round(benchmark.stats.rme * 10) / 10) + '%)') ), benchmark.isRunning && 'Running' || React.DOM.img({ style: { borderWidth: 2, borderStyle: 'solid', color: '#' + colors[benchmarks.indexOf(benchmark)] }, width: 230, height: 50, src: 'https://chart.googleapis.com/chart?cht=ls&chs=460x100&chd=t:' + chartValues + '&chco=' + colors.join(',') }) ) ); }; function valueFromRangeToRange(value, fromMin, fromMax, toMin, toMax) { var fromRange = fromMax - fromMin; var toRange = toMax - toMin; return (((value - fromMin) * toRange) / fromRange) + toMin; } var GridViewTable = React.createClass({ propTypes: { rows: React.PropTypes.array.isRequired, cols: React.PropTypes.array.isRequired, renderCell: React.PropTypes.func.isRequired }, _renderCell: function(col) { return this.props.renderCell({value:this.props.value}, this._row, col); }, _renderRow: function(row) { this._row = row; return React.DOM.tr({key:row}, this._renderCell(null, 0), this.props.cols.map(this._renderCell, this) ); }, render: function() { return React.DOM.table(null, this._renderRow(null, 0), this.props.rows.map(this._renderRow, this) ); } });