flamegraphContextProvider.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. import {useEffect} from 'react';
  2. import {DeepPartial} from 'sentry/types/utils';
  3. import {defined} from 'sentry/utils';
  4. import {Flamegraph} from 'sentry/utils/profiling/flamegraph';
  5. import {FlamegraphFrame} from 'sentry/utils/profiling/flamegraphFrame';
  6. import {Rect} from 'sentry/utils/profiling/gl/utils';
  7. import {useUndoableReducer} from 'sentry/utils/useUndoableReducer';
  8. import {useProfileGroup} from 'sentry/views/profiling/profileGroupProvider';
  9. import {FlamegraphProfiles} from './reducers/flamegraphProfiles';
  10. import {
  11. DEFAULT_FLAMEGRAPH_STATE,
  12. FlamegraphState,
  13. FlamegraphStateDispatchContext,
  14. flamegraphStateReducer,
  15. FlamegraphStateValueContext,
  16. } from './flamegraphContext';
  17. type FlamegraphCandidate = {
  18. score: number;
  19. threadId: number | null;
  20. };
  21. function scoreFlamegraph(
  22. flamegraph: Flamegraph,
  23. focusFrame: FlamegraphProfiles['highlightFrame']
  24. ): number {
  25. if (focusFrame === null) {
  26. return 0;
  27. }
  28. let score = 0;
  29. const frames: FlamegraphFrame[] = [...flamegraph.root.children];
  30. while (frames.length > 0) {
  31. const frame = frames.pop()!;
  32. if (
  33. frame.frame.name === focusFrame.name &&
  34. frame.frame.image === focusFrame.package
  35. ) {
  36. score += frame.node.totalWeight;
  37. }
  38. for (let i = 0; i < frame.children.length; i++) {
  39. frames.push(frame.children[i]);
  40. }
  41. }
  42. return score;
  43. }
  44. function isValidHighlightFrame(
  45. frame: Partial<FlamegraphProfiles['highlightFrame']> | null | undefined
  46. ): frame is NonNullable<FlamegraphProfiles['highlightFrame']> {
  47. return !!frame && typeof frame.name === 'string';
  48. }
  49. interface FlamegraphStateProviderProps {
  50. children: React.ReactNode;
  51. initialState?: DeepPartial<FlamegraphState>;
  52. }
  53. export function FlamegraphStateProvider(
  54. props: FlamegraphStateProviderProps
  55. ): React.ReactElement {
  56. const [profileGroup] = useProfileGroup();
  57. const [state, dispatch, {nextState, previousState}] = useUndoableReducer(
  58. flamegraphStateReducer,
  59. {
  60. profiles: {
  61. highlightFrame: isValidHighlightFrame(
  62. props.initialState?.profiles?.highlightFrame
  63. )
  64. ? (props.initialState?.profiles
  65. ?.highlightFrame as FlamegraphProfiles['highlightFrame'])
  66. : isValidHighlightFrame(DEFAULT_FLAMEGRAPH_STATE.profiles.highlightFrame)
  67. ? DEFAULT_FLAMEGRAPH_STATE.profiles.highlightFrame
  68. : null,
  69. selectedRoot: null,
  70. threadId:
  71. props.initialState?.profiles?.threadId ??
  72. DEFAULT_FLAMEGRAPH_STATE.profiles.threadId,
  73. },
  74. position: {
  75. view: (props.initialState?.position?.view ??
  76. DEFAULT_FLAMEGRAPH_STATE.position.view) as Rect,
  77. },
  78. preferences: {
  79. layout:
  80. props.initialState?.preferences?.layout ??
  81. DEFAULT_FLAMEGRAPH_STATE.preferences.layout,
  82. colorCoding:
  83. props.initialState?.preferences?.colorCoding ??
  84. DEFAULT_FLAMEGRAPH_STATE.preferences.colorCoding,
  85. sorting:
  86. props.initialState?.preferences?.sorting ??
  87. DEFAULT_FLAMEGRAPH_STATE.preferences.sorting,
  88. view:
  89. props.initialState?.preferences?.view ??
  90. DEFAULT_FLAMEGRAPH_STATE.preferences.view,
  91. xAxis:
  92. props.initialState?.preferences?.xAxis ??
  93. DEFAULT_FLAMEGRAPH_STATE.preferences.xAxis,
  94. },
  95. search: {
  96. ...DEFAULT_FLAMEGRAPH_STATE.search,
  97. query: props.initialState?.search?.query ?? DEFAULT_FLAMEGRAPH_STATE.search.query,
  98. },
  99. }
  100. );
  101. useEffect(() => {
  102. if (state.profiles.threadId === null) {
  103. if (state.profiles.highlightFrame && profileGroup.type === 'resolved') {
  104. const candidate = profileGroup.data.profiles.reduce<FlamegraphCandidate>(
  105. (prevCandidate, profile) => {
  106. const flamegraph = new Flamegraph(profile, profile.threadId, {
  107. inverted: false,
  108. leftHeavy: false,
  109. configSpace: undefined,
  110. });
  111. const score = scoreFlamegraph(flamegraph, state.profiles.highlightFrame);
  112. return score <= prevCandidate.score
  113. ? prevCandidate
  114. : {
  115. score,
  116. threadId: profile.threadId,
  117. };
  118. },
  119. {score: 0, threadId: null}
  120. );
  121. if (typeof candidate.threadId === 'number') {
  122. dispatch({type: 'set thread id', payload: candidate.threadId});
  123. return;
  124. }
  125. }
  126. if (
  127. profileGroup.type === 'resolved' &&
  128. typeof profileGroup.data.activeProfileIndex === 'number'
  129. ) {
  130. const threadID =
  131. profileGroup.data.profiles[profileGroup.data.activeProfileIndex].threadId;
  132. if (defined(threadID)) {
  133. dispatch({
  134. type: 'set thread id',
  135. payload: threadID,
  136. });
  137. }
  138. }
  139. }
  140. }, [
  141. props.initialState?.profiles?.threadId,
  142. profileGroup,
  143. state,
  144. dispatch,
  145. state.profiles.highlightFrame,
  146. ]);
  147. return (
  148. <FlamegraphStateValueContext.Provider value={[state, {nextState, previousState}]}>
  149. <FlamegraphStateDispatchContext.Provider value={dispatch}>
  150. {props.children}
  151. </FlamegraphStateDispatchContext.Provider>
  152. </FlamegraphStateValueContext.Provider>
  153. );
  154. }