import {useMemo} from 'react'; import {useTheme} from '@emotion/react'; import styled from '@emotion/styled'; import {AreaChart} from 'sentry/components/charts/areaChart'; import ChartZoom from 'sentry/components/charts/chartZoom'; import {HeaderTitle} from 'sentry/components/charts/styles'; import Panel from 'sentry/components/panels/panel'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {PageFilters} from 'sentry/types'; import {Series} from 'sentry/types/echarts'; import {axisLabelFormatter, tooltipFormatter} from 'sentry/utils/discover/charts'; import {aggregateOutputType} from 'sentry/utils/discover/fields'; import {useProfileEventsStats} from 'sentry/utils/profiling/hooks/useProfileEventsStats'; import useRouter from 'sentry/utils/useRouter'; interface ProfileChartsProps { query: string; referrer: string; compact?: boolean; hideCount?: boolean; selection?: PageFilters; } // We want p99 to be before p75 because echarts renders the series in order. // So if p75 is before p99, p99 will be rendered on top of p75 which will // cover it up. const SERIES_ORDER = ['count()', 'p99()', 'p95()', 'p75()'] as const; export function ProfileCharts({ query, referrer, selection, hideCount, compact = false, }: ProfileChartsProps) { const router = useRouter(); const theme = useTheme(); const seriesOrder = useMemo(() => { if (hideCount) { return SERIES_ORDER.filter(s => s !== 'count()'); } return SERIES_ORDER; }, [hideCount]); const profileStats = useProfileEventsStats({ query, referrer, yAxes: seriesOrder, }); const series: Series[] = useMemo(() => { if (profileStats.status !== 'success') { return []; } // the timestamps in the response is in seconds but echarts expects // a timestamp in milliseconds, so multiply by 1e3 to do the conversion const timestamps = profileStats.data[0].timestamps.map(ts => ts * 1e3); const allSeries = profileStats.data[0].data .filter(rawData => seriesOrder.includes(rawData.axis)) .map(rawData => { if (timestamps.length !== rawData.values.length) { throw new Error('Invalid stats response'); } if (rawData.axis === 'count()') { return { data: rawData.values.map((value, i) => ({ name: timestamps[i]!, // the response value contains nulls when no data is // available, use 0 to represent it value: value ?? 0, })), seriesName: rawData.axis, xAxisIndex: 0, yAxisIndex: 0, }; } return { data: rawData.values.map((value, i) => ({ name: timestamps[i]!, // the response value contains nulls when no data // is available, use 0 to represent it value: value ?? 0, })), seriesName: rawData.axis, xAxisIndex: 1, yAxisIndex: 1, }; }); allSeries.sort((a, b) => { const idxA = seriesOrder.indexOf(a.seriesName as any); const idxB = seriesOrder.indexOf(b.seriesName as any); return idxA - idxB; }); return allSeries; }, [profileStats, seriesOrder]); return ( {zoomRenderProps => ( {!hideCount && ( {t('Profiles by Count')} )} {t('Profiles Duration')} tooltipFormatter(value, aggregateOutputType(label)), }} isGroupedByDate showTimeInTooltip {...zoomRenderProps} /> )} ); } const StyledPanel = styled(Panel)` padding-top: ${space(2)}; `; const TitleContainer = styled('div')` width: 100%; display: flex; flex-direction: row; `; const StyledHeaderTitle = styled(HeaderTitle)<{compact?: boolean}>` flex-grow: 1; margin-left: ${space(2)}; font-size: ${p => (p.compact ? p.theme.fontSizeSmall : undefined)}; `;