Vishal Sharma 983ca1ec6a
feat: introduce getSubTreeSpans function in clickhouse query builder & introduce smart trace detail algorithm (#1648)
* perf: introduce smart trace detail algorithm
* fix: remove hardcoded levels and handle null levels
* feat: add support for broken trees
* feat: use spanLimit env variable
* fix: handle missing root span
* add root spanId and root name
* use permanent table
* add kind, events and tagmap support
* fix query formation
* increase context timeout to 600s
* perf improvement
* handle error
* return tableName as response to query
* support multiple queries tableName
* perf: improve memory and latency
* feat: add getSubTree custom func and smart trace detail algo to ee
* fix: create new functions for ee
* chore: refactor codebase
* chore: refactor frontend code


Co-authored-by: Ankit Nayan <ankit@signoz.io>
Co-authored-by: Palash Gupta <palashgdev@gmail.com>
2022-11-24 18:18:19 +05:30

277 lines
8.1 KiB
TypeScript

import { FilterOutlined } from '@ant-design/icons';
import { Button, Col } from 'antd';
import {
StyledCol,
StyledDiv,
StyledDivider,
StyledRow,
StyledSpace,
StyledTypography,
} from 'components/Styled';
import * as StyledStyles from 'components/Styled/styles';
import GanttChart, { ITraceMetaData } from 'container/GantChart';
import { getNodeById } from 'container/GantChart/utils';
import Timeline from 'container/Timeline';
import TraceFlameGraph from 'container/TraceFlameGraph';
import dayjs from 'dayjs';
import useUrlQuery from 'hooks/useUrlQuery';
import { spanServiceNameToColorMapping } from 'lib/getRandomColor';
import history from 'lib/history';
import { map } from 'lodash-es';
import { SPAN_DETAILS_LEFT_COL_WIDTH } from 'pages/TraceDetail/constants';
import React, { useEffect, useMemo, useState } from 'react';
import { ITraceForest, PayloadProps } from 'types/api/trace/getTraceItem';
import { getSpanTreeMetadata } from 'utils/getSpanTreeMetadata';
import { spanToTreeUtil } from 'utils/spanToTree';
import MissingSpansMessage from './Missingtrace';
import SelectedSpanDetails from './SelectedSpanDetails';
import * as styles from './styles';
import { FlameGraphMissingSpansContainer, GanttChartWrapper } from './styles';
import {
formUrlParams,
getSortedData,
getTreeLevelsCount,
IIntervalUnit,
INTERVAL_UNITS,
} from './utils';
function TraceDetail({ response }: TraceDetailProps): JSX.Element {
const spanServiceColors = useMemo(
() => spanServiceNameToColorMapping(response[0].events),
[response],
);
const urlQuery = useUrlQuery();
const [spanId] = useState<string | null>(urlQuery.get('spanId'));
const [intervalUnit, setIntervalUnit] = useState<IIntervalUnit>(
INTERVAL_UNITS[0],
);
// const [searchSpanString, setSearchSpanString] = useState('');
const [activeHoverId, setActiveHoverId] = useState<string>('');
const [activeSelectedId, setActiveSelectedId] = useState<string>(spanId || '');
const { levelDown, levelUp } = useMemo(
() => ({
levelDown: urlQuery.get('levelDown'),
levelUp: urlQuery.get('levelUp'),
}),
[urlQuery],
);
const [treesData, setTreesData] = useState<ITraceForest>(
spanToTreeUtil(response[0].events),
);
const { treesData: tree, ...traceMetaData } = useMemo(() => {
const sortedTreesData: ITraceForest = {
spanTree: map(treesData.spanTree, (tree) => getSortedData(tree)),
missingSpanTree: map(
treesData.missingSpanTree,
(tree) => getSortedData(tree) || [],
),
};
// Note: Handle undefined
/*eslint-disable */
return getSpanTreeMetadata(sortedTreesData, spanServiceColors);
/* eslint-enable */
}, [treesData, spanServiceColors]);
const [globalTraceMetadata] = useState<ITraceMetaData>({
...traceMetaData,
});
useEffect(() => {
if (activeSelectedId) {
history.replace({
pathname: history.location.pathname,
search: `${formUrlParams({
spanId: activeSelectedId,
levelUp,
levelDown,
})}`,
});
}
}, [activeSelectedId, levelDown, levelUp]);
const getSelectedNode = useMemo(() => {
return getNodeById(activeSelectedId, treesData);
}, [activeSelectedId, treesData]);
// const onSearchHandler = (value: string) => {
// setSearchSpanString(value);
// setTreeData(spanToTreeUtil(response[0].events));
// };
const onFocusSelectedSpanHandler = (): void => {
const treeNode = getNodeById(activeSelectedId, tree);
if (treeNode) {
setTreesData(treeNode);
}
};
const onResetHandler = (): void => {
setTreesData(spanToTreeUtil(response[0].events));
};
const hasMissingSpans = useMemo(
(): boolean =>
tree.missingSpanTree &&
Array.isArray(tree.missingSpanTree) &&
tree.missingSpanTree.length > 0,
[tree],
);
return (
<StyledRow styledclass={[StyledStyles.Flex({ flex: 1 })]}>
<StyledCol flex="auto" styledclass={styles.leftContainer}>
<StyledRow styledclass={styles.flameAndTimelineContainer}>
<StyledCol
styledclass={styles.traceMetaDataContainer}
flex={`${SPAN_DETAILS_LEFT_COL_WIDTH}px`}
>
<StyledTypography.Title styledclass={[styles.removeMargin]} level={5}>
Trace Details
</StyledTypography.Title>
<StyledTypography.Text styledclass={[styles.removeMargin]}>
{traceMetaData.totalSpans} Span
</StyledTypography.Text>
{hasMissingSpans && <MissingSpansMessage />}
</StyledCol>
<Col flex="auto">
{map(tree.spanTree, (tree) => {
return (
<TraceFlameGraph
key={tree as never}
treeData={tree}
traceMetaData={traceMetaData}
hoveredSpanId={activeHoverId}
selectedSpanId={activeSelectedId}
onSpanHover={setActiveHoverId}
onSpanSelect={setActiveSelectedId}
missingSpanTree={false}
/>
);
})}
{hasMissingSpans && (
<FlameGraphMissingSpansContainer>
{map(tree.missingSpanTree, (tree) => {
return (
<TraceFlameGraph
key={tree as never}
treeData={tree}
traceMetaData={{
...traceMetaData,
levels: getTreeLevelsCount(tree),
}}
hoveredSpanId={activeHoverId}
selectedSpanId={activeSelectedId}
onSpanHover={setActiveHoverId}
onSpanSelect={setActiveSelectedId}
missingSpanTree
/>
);
})}
</FlameGraphMissingSpansContainer>
)}
</Col>
</StyledRow>
<StyledRow styledclass={[styles.traceDateAndTimelineContainer]}>
<StyledCol
flex={`${SPAN_DETAILS_LEFT_COL_WIDTH}px`}
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
{tree &&
traceMetaData.globalStart &&
dayjs(traceMetaData.globalStart).format('hh:mm:ss a MM/DD')}
</StyledCol>
<StyledCol flex="auto" styledclass={[styles.timelineContainer]}>
<Timeline
globalTraceMetadata={globalTraceMetadata}
traceMetaData={traceMetaData}
setIntervalUnit={setIntervalUnit}
/>
</StyledCol>
<StyledDivider styledclass={[styles.verticalSeparator]} />
</StyledRow>
<StyledRow
styledclass={[
styles.traceDetailContentSpacing,
StyledStyles.Spacing({
margin: '1.5rem 1rem 0.5rem',
}),
]}
>
<Col flex={`${SPAN_DETAILS_LEFT_COL_WIDTH}px`} />
<Col flex="auto">
<StyledSpace styledclass={[styles.floatRight]}>
<Button onClick={onFocusSelectedSpanHandler} icon={<FilterOutlined />}>
Focus on selected span
</Button>
<Button type="default" onClick={onResetHandler}>
Reset Focus
</Button>
</StyledSpace>
</Col>
</StyledRow>
<StyledDiv styledclass={[styles.ganttChartContainer]}>
<GanttChartWrapper>
{map([...tree.spanTree, ...tree.missingSpanTree], (tree) => (
<GanttChart
key={tree as never}
traceMetaData={traceMetaData}
data={tree}
activeSelectedId={activeSelectedId}
activeHoverId={activeHoverId}
setActiveHoverId={setActiveHoverId}
setActiveSelectedId={setActiveSelectedId}
spanId={spanId || ''}
intervalUnit={intervalUnit}
/>
))}
{/* {map(tree.missingSpanTree, (tree) => (
<GanttChart
key={tree as never}
traceMetaData={traceMetaData}
data={tree}
activeSelectedId={activeSelectedId}
activeHoverId={activeHoverId}
setActiveHoverId={setActiveHoverId}
setActiveSelectedId={setActiveSelectedId}
spanId={spanId || ''}
intervalUnit={intervalUnit}
/>
))} */}
</GanttChartWrapper>
</StyledDiv>
</StyledCol>
<Col>
<StyledDivider styledclass={[styles.verticalSeparator]} type="vertical" />
</Col>
<StyledCol md={5} sm={5} styledclass={[styles.selectedSpanDetailContainer]}>
<SelectedSpanDetails
tree={[
...(getSelectedNode.spanTree ? getSelectedNode.spanTree : []),
...(getSelectedNode.missingSpanTree
? getSelectedNode.missingSpanTree
: []),
]
.filter(Boolean)
.find((tree) => tree)}
/>
</StyledCol>
</StyledRow>
);
}
interface TraceDetailProps {
response: PayloadProps;
}
export default TraceDetail;