import { Timed } from 'Player'; import { PerformanceChartPoint } from 'Player/mobile/managers/IOSPerformanceTrackManager'; import React, { useTransition } from 'react'; import { MobilePlayerContext, PlayerContext, } from 'App/components/Session/playerContext'; import { observer } from 'mobx-react-lite'; import { AreaChart, Area, ComposedChart, Line, XAxis, YAxis, Tooltip, ResponsiveContainer, ReferenceLine, Label, } from 'recharts'; import { durationFromMsFormatted } from 'App/date'; import { formatBytes } from 'App/utils'; import { Tooltip as TooltipANT, Segmented } from 'antd'; import { useStore } from 'App/mstore'; import stl from './performance.module.css'; import BottomBlock from '../BottomBlock'; import InfoLine from '../BottomBlock/InfoLine'; import { useTranslation } from 'react-i18next'; import { TFunction } from 'i18next'; 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'; function Gradient({ color, id }) { return ( ); } const TOTAL_HEAP = (t: TFunction) => t('Allocated Heap'); const USED_HEAP = (t: TFunction) => t('JS Heap'); const FPS = (t: TFunction) => t('Framerate'); const CPU = (t: TFunction) => t('CPU Load'); const NODES_COUNT = (t: TFunction) => t('Nodes Сount'); function FPSTooltip({ active, payload }) { const { t } = useTranslation(); if (!payload) return null; if (!active || !payload || payload.length < 3) { return null; } if (payload[0].value === null) { return (
{t('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(t)}: `} {Math.trunc(payload[0].value)}
); } function CPUTooltip({ active, payload }) { const { t } = useTranslation(); if (!payload) return null; if (!active || payload.length < 1 || payload[0].value === null) { return null; } return (
{`${CPU(t)}: `} {payload[0].value - CPU_VISUAL_OFFSET}%
); } function MobileCpuTooltip({ active, payload }) { const { t } = useTranslation(); if (!payload) return null; if (!active || payload.length < 1) { return null; } if (payload[0].value === null) { return (
{t('App is in the background.')}
); } return (
{`${CPU(t)}: `} {payload[0].value}%
); } function HeapTooltip({ active, payload }) { const { t } = useTranslation(); if (!payload) return null; if (!active || payload.length < 2) return null; return (

{`${TOTAL_HEAP(t)}: `} {formatBytes(payload[0].value)}

{`${USED_HEAP(t)}: `} {formatBytes(payload[1].value)}

); } function MobileMemoryTooltip({ active, payload }) { const { t } = useTranslation(); if (!payload) return null; if (!active || payload.length < 1 || payload[1].value === null) return null; return (

{t('Used Memory: ')} {formatBytes(payload[1].value)}

); } function NodesCountTooltip({ active, payload }) { const { t } = useTranslation(); if (!payload) return null; if (!active || !payload || payload.length === 0) return null; return (

{`${NODES_COUNT(t)}: `} {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, }; }); } function generateMobileChart( data: PerformanceChartPoint[], biggestMemSpike: number, ) { return data.map((p) => ({ ...p, isBackground: p.isBackground ? 50 : 0, isMemBackground: p.isBackground ? biggestMemSpike : 0, })); } export const MobilePerformance = observer(() => { const { t } = useTranslation(); const { player, store } = React.useContext(MobilePlayerContext); const [_timeTicks, setTicks] = React.useState([]); const [_data, setData] = React.useState([]); const { sessionStore } = useStore(); const { performanceChartTime = 0, performanceChartData = [] } = store.get(); React.useEffect(() => { // setTicks(generateTicks(performanceChartData)); setTicks(performanceChartData.map((p) => p.time)); const biggestMemSpike = performanceChartData.reduce((acc, p) => { if (p.memory && p.memory > acc) return p.memory; return acc; }, 0); setData(generateMobileChart(performanceChartData, biggestMemSpike)); }, []); const onDotClick = ({ index: pointer }: { index: number }) => { const point = _data[pointer]; if (point) { player.jump(point.time); } }; const onChartClick = (e: any) => { if (e === null) return; const { activeTooltipIndex } = e; const point = _data[activeTooltipIndex]; if (point) { player.jump(point.time); } }; const availableCount = 2; const height = `${100 / availableCount}%`; return (
{t('Performance')}
{/* */} ''} domain={[0, 'dataMax']} ticks={_timeTicks} > ''} // tick={false} + _timeTicks to cartesian array domain={[0, 'dataMax']} ticks={_timeTicks} > max * 1.2]} /> {/* */}
); }); function Performance() { const { t } = useTranslation(); const { sessionStore } = useStore(); const userDeviceHeapSize = sessionStore.current.userDeviceHeapSize || 0; const { player, store } = React.useContext(PlayerContext); const [_timeTicks, setTicks] = React.useState([]); const [_data, setData] = React.useState([]); const { // connType, // connBandwidth, tabStates, currentTab, } = store.get(); const { performanceChartTime = [], performanceChartData = [], performanceAvailability: availability = {}, } = tabStates[currentTab]; React.useEffect(() => { setTicks(generateTicks(performanceChartData)); setData(addFpsMetadata(performanceChartData)); }, [currentTab]); const onDotClick = ({ index: pointer }: { index: number }) => { const point = _data[pointer]; if (point) { player.jump(point.time); } }; const onChartClick = (e: any) => { if (e === null) return; const { activeTooltipIndex } = e; const point = _data[activeTooltipIndex]; if (point) { player.jump(point.time); } }; const { fps, cpu, heap, nodes } = availability; const availableCount = [fps, cpu, heap, nodes].reduce( (c, av) => (av ? c + 1 : c), 0, ); const height = availableCount === 0 ? '0' : `${100 / availableCount}%`; return (
{t('Performance')}
{t('All Tabs')} ), value: 'all', disabled: true, }, { label: t('Current Tab'), value: 'current' }, ]} defaultValue="current" size="small" className="rounded-full font-medium" />
{fps && ( )} {cpu && ( {/* */} ''} domain={[0, 'dataMax']} ticks={_timeTicks} > )} {heap && ( ''} // tick={false} + _timeTicks to cartesian array domain={[0, 'dataMax']} ticks={_timeTicks} > max * 1.2]} /> )} {nodes && ( ''} domain={[0, 'dataMax']} ticks={_timeTicks} > max * 1.2]} /> )}
); } export const ConnectedPerformance = observer(Performance);