import React from 'react';
import { connect } from 'react-redux';
import { Controls as PlayerControls, connectPlayer } from 'Player';
import {
AreaChart,
Area,
ComposedChart,
Line,
XAxis,
YAxis,
Tooltip,
ResponsiveContainer,
ReferenceLine,
CartesianGrid,
Label,
} from 'recharts';
import { Checkbox } from 'UI';
import { durationFromMsFormatted } from 'App/date';
import { formatBytes } from 'App/utils';
import stl from './performance.module.css';
import BottomBlock from '../BottomBlock';
import InfoLine from '../BottomBlock/InfoLine';
const CPU_VISUAL_OFFSET = 10;
const FPS_COLOR = '#C5E5E7';
const FPS_STROKE_COLOR = '#92C7CA';
const FPS_LOW_COLOR = 'pink';
const FPS_VERY_LOW_COLOR = 'red';
const CPU_COLOR = '#A8D1DE';
const CPU_STROKE_COLOR = '#69A5B8';
const USED_HEAP_COLOR = '#A9ABDC';
const USED_HEAP_STROKE_COLOR = '#8588CF';
const TOTAL_HEAP_STROKE_COLOR = '#4A4EB7';
const NODES_COUNT_COLOR = '#C6A9DC';
const NODES_COUNT_STROKE_COLOR = '#7360AC';
const HIDDEN_SCREEN_COLOR = '#CCC';
const CURSOR_COLOR = '#394EFF';
const Gradient = ({ color, id }) => (
);
const TOTAL_HEAP = 'Allocated Heap';
const USED_HEAP = 'JS Heap';
const FPS = 'Framerate';
const CPU = 'CPU Load';
const NODES_COUNT = 'Nodes Сount';
const FPSTooltip = ({ active, payload }) => {
if (!active || !payload || payload.length < 3) {
return null;
}
if (payload[0].value === null) {
return (
{'Page is not active. User switched the tab or hid the window.'}
);
}
let style;
if (payload[1].value != null && payload[1].value > 0) {
style = { color: FPS_LOW_COLOR };
}
if (payload[2].value != null && payload[2].value > 0) {
style = { color: FPS_VERY_LOW_COLOR };
}
return (
{`${FPS}: `}
{Math.trunc(payload[0].value)}
);
};
const CPUTooltip = ({ active, payload }) => {
if (!active || payload.length < 1 || payload[0].value === null) {
return null;
}
return (
{`${CPU}: `}
{payload[0].value - CPU_VISUAL_OFFSET}
{'%'}
);
};
const HeapTooltip = ({ active, payload }) => {
if (!active || payload.length < 2) return null;
return (
{`${TOTAL_HEAP}: `}
{formatBytes(payload[0].value)}
{`${USED_HEAP}: `}
{formatBytes(payload[1].value)}
);
};
const NodesCountTooltip = ({ active, payload }) => {
if (!active || !payload || payload.length === 0) return null;
return (
{`${NODES_COUNT}: `}
{payload[0].value}
);
};
const TICKS_COUNT = 10;
function generateTicks(data: Array): Array {
if (data.length === 0) return [];
const minTime = data[0].time;
const maxTime = data[data.length - 1].time;
const ticks = [];
const tickGap = (maxTime - minTime) / (TICKS_COUNT + 1);
for (let i = 0; i < TICKS_COUNT; i++) {
const tick = tickGap * (i + 1) + minTime;
ticks.push(tick);
}
return ticks;
}
const LOW_FPS = 30;
const VERY_LOW_FPS = 20;
const LOW_FPS_MARKER_VALUE = 5;
const HIDDEN_SCREEN_MARKER_VALUE = 20;
function addFpsMetadata(data) {
return [...data].map((point, i) => {
let fpsVeryLowMarker = null;
let fpsLowMarker = null;
let hiddenScreenMarker = 0;
if (point.fps != null) {
fpsVeryLowMarker = 0;
fpsLowMarker = 0;
if (point.fps < VERY_LOW_FPS) {
fpsVeryLowMarker = LOW_FPS_MARKER_VALUE;
} else if (point.fps < LOW_FPS) {
fpsLowMarker = LOW_FPS_MARKER_VALUE;
}
}
if (
point.fps == null ||
(i > 0 && data[i - 1].fps == null) //||
//(i < data.length-1 && data[i + 1].fps == null)
) {
hiddenScreenMarker = HIDDEN_SCREEN_MARKER_VALUE;
}
if (point.cpu != null) {
point.cpu += CPU_VISUAL_OFFSET;
}
return {
...point,
fpsLowMarker,
fpsVeryLowMarker,
hiddenScreenMarker,
};
});
}
@connect((state) => ({
userDeviceHeapSize: state.getIn(['sessions', 'current', 'userDeviceHeapSize']),
userDeviceMemorySize: state.getIn(['sessions', 'current', 'userDeviceMemorySize']),
}))
export default class Performance extends React.PureComponent {
_timeTicks = generateTicks(this.props.performanceChartData);
_data = addFpsMetadata(this.props.performanceChartData);
// state = {
// totalHeap: false,
// usedHeap: true,
// fps: true,
// }
// onCheckboxClick = (e, { name, checked }) => this.setState({ [ name ]: checked })
onDotClick = ({ index }) => {
const point = this._data[index];
if (!!point) {
PlayerControls.jump(point.time);
}
};
onChartClick = (e) => {
if (e === null) return;
const { activeTooltipIndex } = e;
const point = this._data[activeTooltipIndex];
if (!!point) {
PlayerControls.jump(point.time);
}
};
render() {
const {
userDeviceHeapSize,
userDeviceMemorySize,
connType,
connBandwidth,
performanceChartTime,
avaliability = {},
} = this.props;
const { fps, cpu, heap, nodes } = avaliability;
const avaliableCount = [fps, cpu, heap, nodes].reduce((c, av) => (av ? c + 1 : c), 0);
const height = avaliableCount === 0 ? '0' : `${100 / avaliableCount}%`;
return (
Performance
{/* */}
= 1000 ? `${connBandwidth / 1000} Mbps` : `${connBandwidth} Kbps`
}
display={connBandwidth != null}
/>
{fps && (
{/* */}
{/* */}
{/* */}
{/* */}
)}
{cpu && (
{/* */}
''}
domain={[0, 'dataMax']}
ticks={this._timeTicks}
>
)}
{heap && (
{/* */}
''} // tick={false} + this._timeTicks to cartesian array
domain={[0, 'dataMax']}
ticks={this._timeTicks}
>
max * 1.2]}
/>
)}
{nodes && (
{/* */}
''}
domain={[0, 'dataMax']}
ticks={this._timeTicks}
>
max * 1.2]}
/>
)}
);
}
}
export const ConnectedPerformance = connectPlayer((state) => ({
performanceChartTime: state.performanceChartTime,
performanceChartData: state.performanceChartData,
connType: state.connType,
connBandwidth: state.connBandwidth,
avaliability: state.performanceAvaliability,
}))(Performance);