import {useMemo} from 'react'; import {useTheme} from '@emotion/react'; import type {AreaChartProps} from 'sentry/components/charts/areaChart'; import {AreaChart} from 'sentry/components/charts/areaChart'; import Grid from 'sentry/components/charts/components/grid'; import {ChartTooltip} from 'sentry/components/charts/components/tooltip'; import XAxis from 'sentry/components/charts/components/xAxis'; import YAxis from 'sentry/components/charts/components/yAxis'; import type {useReplayContext} from 'sentry/components/replays/replayContext'; import {formatTime} from 'sentry/components/replays/utils'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {Series} from 'sentry/types/echarts'; import {formatBytesBase2} from 'sentry/utils'; import {getFormattedDate} from 'sentry/utils/dates'; import type {MemoryFrame} from 'sentry/utils/replays/types'; import toArray from 'sentry/utils/toArray'; interface Props extends Pick< ReturnType, 'currentTime' | 'currentHoverTime' | 'setCurrentTime' | 'setCurrentHoverTime' > { durationMs: number; memoryFrames: MemoryFrame[]; startOffsetMs: number; } export default function MemoryChart({ currentHoverTime, currentTime, durationMs, memoryFrames, setCurrentHoverTime, setCurrentTime, startOffsetMs, }: Props) { const theme = useTheme(); const chartOptions: Omit = useMemo( () => ({ autoHeightResize: true, height: 'auto', grid: Grid({ // makes space for the title top: '40px', left: space(1), right: space(1), }), tooltip: ChartTooltip({ appendToBody: true, trigger: 'axis', renderMode: 'html', chartId: 'replay-memory-chart', formatter: values => { const firstValue = Array.isArray(values) ? values[0] : values; const seriesTooltips = toArray(values).map( value => `
${value.marker}${value.seriesName} ${formatBytesBase2(value.data[1])}
` ); return `
${seriesTooltips.join('')}
`; }, }), xAxis: XAxis({ type: 'time', axisLabel: { formatter: (time: number) => formatTime(time, false), }, theme, }), yAxis: YAxis({ type: 'value', name: t('Heap Size'), theme, nameTextStyle: { padding: [8, 8, 8, -25], fontSize: theme.fontSizeLarge, fontWeight: 600, lineHeight: 1.2, fontFamily: theme.text.family, color: theme.gray300, }, // input is in bytes, minInterval is a megabyte minInterval: 1024 * 1024, // maxInterval is a terabyte maxInterval: Math.pow(1024, 4), // format the axis labels to be whole number values axisLabel: { formatter: value => formatBytesBase2(value, 0), }, }), onMouseOver: ({data}) => { if (data[0]) { setCurrentHoverTime(data[0]); } }, onMouseOut: () => { setCurrentHoverTime(undefined); }, onClick: ({data}) => { if (data.value) { setCurrentTime(data.value); } }, }), [setCurrentHoverTime, setCurrentTime, startOffsetMs, theme] ); const staticSeries: Series[] = useMemo( () => [ { id: 'usedMemory', seriesName: t('Used Heap Memory'), data: memoryFrames.map(frame => ({ value: frame.data.memory.usedJSHeapSize, name: frame.offsetMs, })), stack: 'heap-memory', lineStyle: {opacity: 0, width: 2}, }, { id: 'replayStart', seriesName: 'Replay Start', data: [{value: 0, name: 0}], lineStyle: {opacity: 0, width: 0}, }, { id: 'replayEnd', seriesName: 'Replay End', data: [{value: 0, name: durationMs}], lineStyle: {opacity: 0, width: 0}, }, ], [durationMs, memoryFrames] ); const dynamicSeries = useMemo( (): Series[] => [ { id: 'currentTime', seriesName: t('Current player time'), data: [], markLine: { symbol: ['', ''], data: [{xAxis: currentTime}], label: {show: false}, lineStyle: {type: 'solid', color: theme.purple300, width: 2}, }, }, { id: 'hoverTime', seriesName: t('Hover player time'), data: [], markLine: { symbol: ['', ''], data: currentHoverTime ? [{xAxis: currentHoverTime}] : [], label: {show: false}, lineStyle: {type: 'solid', color: theme.purple200, width: 2}, }, }, ], [currentTime, currentHoverTime, theme.purple200, theme.purple300] ); const series = useMemo( () => staticSeries.concat(dynamicSeries), [dynamicSeries, staticSeries] ); return (
); }