frameStack.tsx 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. import {memo, useCallback, useMemo, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import Button from 'sentry/components/button';
  4. import {t} from 'sentry/locale';
  5. import space from 'sentry/styles/space';
  6. import {CanvasPoolManager} from 'sentry/utils/profiling/canvasScheduler';
  7. import {filterFlamegraphTree} from 'sentry/utils/profiling/filterFlamegraphTree';
  8. import {useFlamegraphProfilesValue} from 'sentry/utils/profiling/flamegraph/useFlamegraphProfiles';
  9. import {useFlamegraphTheme} from 'sentry/utils/profiling/flamegraph/useFlamegraphTheme';
  10. import {FlamegraphFrame} from 'sentry/utils/profiling/flamegraphFrame';
  11. import {useVerticallyResizableDrawer} from 'sentry/utils/profiling/hooks/useResizableDrawer';
  12. import {invertCallTree} from 'sentry/utils/profiling/profile/utils';
  13. import {FlamegraphRenderer} from 'sentry/utils/profiling/renderers/flamegraphRenderer';
  14. import {FrameStackTable} from './frameStackTable';
  15. interface FrameStackProps {
  16. canvasPoolManager: CanvasPoolManager;
  17. flamegraphRenderer: FlamegraphRenderer;
  18. }
  19. const FrameStack = memo(function FrameStack(props: FrameStackProps) {
  20. const theme = useFlamegraphTheme();
  21. const {selectedNode} = useFlamegraphProfilesValue();
  22. const [tab, setTab] = useState<'bottom up' | 'call order'>('call order');
  23. const [treeType, setTreeType] = useState<'all' | 'application' | 'system'>('all');
  24. const [recursion, setRecursion] = useState<'collapsed' | null>(null);
  25. const roots: FlamegraphFrame[] | null = useMemo(() => {
  26. if (!selectedNode) {
  27. return null;
  28. }
  29. const skipFunction: (f: FlamegraphFrame) => boolean =
  30. treeType === 'application'
  31. ? f => !f.frame.is_application
  32. : treeType === 'system'
  33. ? f => f.frame.is_application
  34. : () => false;
  35. const maybeFilteredRoots =
  36. treeType !== 'all'
  37. ? filterFlamegraphTree([selectedNode], skipFunction)
  38. : [selectedNode];
  39. if (tab === 'call order') {
  40. return maybeFilteredRoots;
  41. }
  42. return invertCallTree(maybeFilteredRoots);
  43. }, [selectedNode, tab, treeType]);
  44. const handleRecursionChange = useCallback(
  45. (evt: React.ChangeEvent<HTMLInputElement>) => {
  46. setRecursion(evt.currentTarget.checked ? 'collapsed' : null);
  47. },
  48. []
  49. );
  50. const onBottomUpClick = useCallback(() => {
  51. setTab('bottom up');
  52. }, []);
  53. const onCallOrderClick = useCallback(() => {
  54. setTab('call order');
  55. }, []);
  56. const onAllApplicationsClick = useCallback(() => {
  57. setTreeType('all');
  58. }, []);
  59. const onApplicationsClick = useCallback(() => {
  60. setTreeType('application');
  61. }, []);
  62. const onSystemsClick = useCallback(() => {
  63. setTreeType('system');
  64. }, []);
  65. const {height, onMouseDown} = useVerticallyResizableDrawer({
  66. initialHeight: (theme.SIZES.FLAMEGRAPH_DEPTH_OFFSET + 2) * theme.SIZES.BAR_HEIGHT,
  67. minHeight: 30,
  68. });
  69. return selectedNode ? (
  70. <FrameDrawer
  71. style={{
  72. height,
  73. }}
  74. >
  75. <FrameTabs>
  76. <li className={tab === 'bottom up' ? 'active' : undefined}>
  77. <Button
  78. data-title={t('Bottom Up')}
  79. priority="link"
  80. size="zero"
  81. onClick={onBottomUpClick}
  82. >
  83. {t('Bottom Up')}
  84. </Button>
  85. </li>
  86. <li className={tab === 'call order' ? 'active' : undefined}>
  87. <Button
  88. data-title={t('Call Order')}
  89. priority="link"
  90. size="zero"
  91. onClick={onCallOrderClick}
  92. >
  93. {t('Call Order')}
  94. </Button>
  95. </li>
  96. <Separator />
  97. <li className={treeType === 'all' ? 'active' : undefined}>
  98. <Button
  99. data-title={t('All Frames')}
  100. priority="link"
  101. size="zero"
  102. onClick={onAllApplicationsClick}
  103. >
  104. {t('All Frames')}
  105. </Button>
  106. </li>
  107. <li className={treeType === 'application' ? 'active' : undefined}>
  108. <Button
  109. data-title={t('Application Frames')}
  110. priority="link"
  111. size="zero"
  112. onClick={onApplicationsClick}
  113. >
  114. {t('Application Frames')}
  115. </Button>
  116. </li>
  117. <li className={treeType === 'system' ? 'active' : undefined}>
  118. <Button
  119. data-title={t('System Frames')}
  120. priority="link"
  121. size="zero"
  122. onClick={onSystemsClick}
  123. >
  124. {t('System Frames')}
  125. </Button>
  126. </li>
  127. <Separator />
  128. <li>
  129. <FrameDrawerLabel>
  130. <input
  131. type="checkbox"
  132. checked={recursion === 'collapsed'}
  133. onChange={handleRecursionChange}
  134. />
  135. {t('Collapse recursion')}
  136. </FrameDrawerLabel>
  137. </li>
  138. <li style={{flex: '1 1 100%', cursor: 'ns-resize'}} onMouseDown={onMouseDown} />
  139. </FrameTabs>
  140. <FrameStackTable
  141. {...props}
  142. recursion={recursion}
  143. roots={roots ?? []}
  144. referenceNode={selectedNode}
  145. canvasPoolManager={props.canvasPoolManager}
  146. />
  147. </FrameDrawer>
  148. ) : null;
  149. });
  150. const FrameDrawerLabel = styled('label')`
  151. display: flex;
  152. align-items: center;
  153. white-space: nowrap;
  154. margin-bottom: 0;
  155. height: 100%;
  156. font-weight: normal;
  157. > input {
  158. margin: 0 ${space(0.5)} 0 0;
  159. }
  160. `;
  161. const FrameDrawer = styled('div')`
  162. display: flex;
  163. flex-shrink: 0;
  164. flex-direction: column;
  165. `;
  166. const Separator = styled('li')`
  167. width: 1px;
  168. height: 66%;
  169. margin: 0 ${space(0.5)};
  170. background: ${p => p.theme.border};
  171. transform: translateY(29%);
  172. `;
  173. const FrameTabs = styled('ul')`
  174. display: flex;
  175. list-style-type: none;
  176. padding: 0 ${space(1)};
  177. margin: 0;
  178. border-top: 1px solid ${prop => prop.theme.border};
  179. background-color: ${props => props.theme.surface400};
  180. user-select: none;
  181. > li {
  182. font-size: ${p => p.theme.fontSizeSmall};
  183. margin-right: ${space(1)};
  184. button {
  185. border: none;
  186. border-top: 2px solid transparent;
  187. border-bottom: 2px solid transparent;
  188. border-radius: 0;
  189. margin: 0;
  190. padding: ${space(0.5)} 0;
  191. color: ${p => p.theme.textColor};
  192. &::after {
  193. display: block;
  194. content: attr(data-title);
  195. font-weight: bold;
  196. height: 1px;
  197. color: transparent;
  198. overflow: hidden;
  199. visibility: hidden;
  200. white-space: nowrap;
  201. }
  202. &:hover {
  203. color: ${p => p.theme.textColor};
  204. }
  205. }
  206. &.active button {
  207. font-weight: bold;
  208. border-bottom: 2px solid ${prop => prop.theme.active};
  209. }
  210. }
  211. `;
  212. const FRAME_WEIGHT_CELL_WIDTH_PX = 164;
  213. export const FrameCallersTableCell = styled('div')<{
  214. isSelected?: boolean;
  215. noPadding?: boolean;
  216. textAlign?: React.CSSProperties['textAlign'];
  217. }>`
  218. width: ${FRAME_WEIGHT_CELL_WIDTH_PX}px;
  219. position: relative;
  220. white-space: nowrap;
  221. flex-shrink: 0;
  222. padding: 0 ${p => (p.noPadding ? 0 : space(1))} 0 0;
  223. text-align: ${p => p.textAlign ?? 'initial'};
  224. &:first-child,
  225. &:nth-child(2) {
  226. position: sticky;
  227. z-index: 1;
  228. background-color: ${p => (p.isSelected ? p.theme.blue300 : p.theme.background)};
  229. }
  230. &:first-child {
  231. left: 0;
  232. }
  233. &:nth-child(2) {
  234. left: ${FRAME_WEIGHT_CELL_WIDTH_PX}px;
  235. }
  236. &:not(:last-child) {
  237. border-right: 1px solid ${p => p.theme.border};
  238. }
  239. `;
  240. export {FrameStack};