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 (
{/* */}
''}
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('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);