import {Fragment, useMemo, useState} from 'react';
import styled from '@emotion/styled';
import Button from 'sentry/components/button';
import ButtonBar from 'sentry/components/buttonBar';
import {SectionHeading} from 'sentry/components/charts/styles';
import Count from 'sentry/components/count';
import GridEditable, {
COL_WIDTH_UNDEFINED,
GridColumnOrder,
} from 'sentry/components/gridEditable';
import PerformanceDuration from 'sentry/components/performanceDuration';
import {ArrayLinks} from 'sentry/components/profiling/arrayLinks';
import {IconChevron} from 'sentry/icons';
import {t} from 'sentry/locale';
import space from 'sentry/styles/space';
import {Project} from 'sentry/types';
import {FunctionCall} from 'sentry/types/profiling/core';
import {Container, NumberContainer} from 'sentry/utils/discover/styles';
import {getShortEventId} from 'sentry/utils/events';
import {formatPercentage} from 'sentry/utils/formatters';
import {generateProfileFlamegraphRouteWithQuery} from 'sentry/utils/profiling/routes';
import {renderTableHead} from 'sentry/utils/profiling/tableRenderer';
import {useLocation} from 'sentry/utils/useLocation';
import useOrganization from 'sentry/utils/useOrganization';
interface FunctionsTableProps {
error: string | null;
functionCalls: FunctionCall[];
isLoading: boolean;
project: Project;
limit?: number;
}
function FunctionsTable(props: FunctionsTableProps) {
const limit = props.limit ?? 5;
const [offset, setOffset] = useState(0);
const location = useLocation();
const organization = useOrganization();
const allFunctions: TableDataRow[] = useMemo(() => {
return props.functionCalls.map(functionCall => ({
symbol: functionCall.symbol,
image: functionCall.image,
p50Duration: functionCall.duration_ns.p50,
p75Duration: functionCall.duration_ns.p75,
p90Duration: functionCall.duration_ns.p90,
p95Duration: functionCall.duration_ns.p95,
p99Duration: functionCall.duration_ns.p99,
mainThreadPercent: functionCall.main_thread_percent,
p50Frequency: functionCall.frequency.p50,
p75Frequency: functionCall.frequency.p75,
p90Frequency: functionCall.frequency.p90,
p95Frequency: functionCall.frequency.p95,
p99Frequency: functionCall.frequency.p99,
profileIdToThreadId: Object.entries(functionCall.profile_id_to_thread_id).map(
([profileId, threadId]) => {
return {
value: getShortEventId(profileId),
target: generateProfileFlamegraphRouteWithQuery({
orgSlug: organization.slug,
projectSlug: props.project.slug,
profileId,
query: {tid: threadId.toString()},
}),
};
}
),
}));
}, [organization.slug, props.project.slug, props.functionCalls]);
const functions: TableDataRow[] = useMemo(() => {
return allFunctions.slice(offset, offset + limit);
}, [allFunctions, limit, offset]);
return (
{t('Suspect Functions')}
}
aria-label={t('Previous')}
size="xsmall"
disabled={offset === 0}
onClick={() => setOffset(offset - limit)}
/>
}
aria-label={t('Next')}
size="xsmall"
disabled={offset + limit >= allFunctions.length}
onClick={() => setOffset(offset + limit)}
/>
COLUMNS[key])}
columnSortBy={[]}
grid={{
renderHeadCell: renderTableHead(RIGHT_ALIGNED_COLUMNS),
renderBodyCell: renderFunctionsTableCell,
}}
location={location}
/>
);
}
const RIGHT_ALIGNED_COLUMNS = new Set([
'p50Duration',
'p75Duration',
'p90Duration',
'p95Duration',
'p99Duration',
'mainThreadPercent',
'p50Frequency',
'p75Frequency',
'p90Frequency',
'p95Frequency',
'p99Frequency',
]);
function renderFunctionsTableCell(
column: TableColumn,
dataRow: TableDataRow,
rowIndex: number,
columnIndex: number
) {
return (
);
}
interface ProfilingFunctionsTableCellProps {
column: TableColumn;
columnIndex: number;
dataRow: TableDataRow;
rowIndex: number;
}
function ProfilingFunctionsTableCell({
column,
dataRow,
}: ProfilingFunctionsTableCellProps) {
const value = dataRow[column.key];
switch (column.key) {
case 'p50Frequency':
case 'p75Frequency':
case 'p90Frequency':
case 'p95Frequency':
case 'p99Frequency':
return (
);
case 'mainThreadPercent':
return {formatPercentage(value)};
case 'p50Duration':
case 'p75Duration':
case 'p90Duration':
case 'p95Duration':
case 'p99Duration':
return (
);
case 'profileIdToThreadId':
return ;
default:
return {value};
}
}
type TableColumnKey =
| 'symbol'
| 'image'
| 'p50Duration'
| 'p75Duration'
| 'p90Duration'
| 'p95Duration'
| 'p99Duration'
| 'mainThreadPercent'
| 'p50Frequency'
| 'p75Frequency'
| 'p90Frequency'
| 'p95Frequency'
| 'p99Frequency'
| 'profileIdToThreadId';
type TableDataRow = Record;
type TableColumn = GridColumnOrder;
const COLUMN_ORDER: TableColumnKey[] = [
'symbol',
'image',
'p75Duration',
'p99Duration',
'mainThreadPercent',
'p75Frequency',
'p99Frequency',
'profileIdToThreadId',
];
// TODO: looks like these column names change depending on the platform?
const COLUMNS: Record = {
symbol: {
key: 'symbol',
name: t('Symbol'),
width: COL_WIDTH_UNDEFINED,
},
image: {
key: 'image',
name: t('Binary'),
width: COL_WIDTH_UNDEFINED,
},
p50Duration: {
key: 'p50Duration',
name: t('P50 Duration'),
width: COL_WIDTH_UNDEFINED,
},
p75Duration: {
key: 'p75Duration',
name: t('P75 Duration'),
width: COL_WIDTH_UNDEFINED,
},
p90Duration: {
key: 'p90Duration',
name: t('P90 Duration'),
width: COL_WIDTH_UNDEFINED,
},
p95Duration: {
key: 'p95Duration',
name: t('P95 Duration'),
width: COL_WIDTH_UNDEFINED,
},
p99Duration: {
key: 'p99Duration',
name: t('P99 Duration'),
width: COL_WIDTH_UNDEFINED,
},
mainThreadPercent: {
key: 'mainThreadPercent',
name: t('Main Thread %'),
width: COL_WIDTH_UNDEFINED,
},
p50Frequency: {
key: 'p50Frequency',
name: t('P50 Frequency'),
width: COL_WIDTH_UNDEFINED,
},
p75Frequency: {
key: 'p75Frequency',
name: t('P75 Frequency'),
width: COL_WIDTH_UNDEFINED,
},
p90Frequency: {
key: 'p90Frequency',
name: t('P90 Frequency'),
width: COL_WIDTH_UNDEFINED,
},
p95Frequency: {
key: 'p95Frequency',
name: t('P95 Frequency'),
width: COL_WIDTH_UNDEFINED,
},
p99Frequency: {
key: 'p99Frequency',
name: t('P99 Frequency'),
width: COL_WIDTH_UNDEFINED,
},
profileIdToThreadId: {
key: 'profileIdToThreadId',
name: t('Example Profiles'),
width: COL_WIDTH_UNDEFINED,
},
};
const TableHeader = styled('div')`
display: flex;
justify-content: space-between;
margin-bottom: ${space(1)};
`;
export {FunctionsTable};