threads.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. import {Fragment, useEffect, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import isNil from 'lodash/isNil';
  4. import {EventDataSection} from 'sentry/components/events/eventDataSection';
  5. import {getLockReason} from 'sentry/components/events/interfaces/threads/threadSelector/lockReason';
  6. import {
  7. getMappedThreadState,
  8. getThreadStateHelpText,
  9. ThreadStates,
  10. } from 'sentry/components/events/interfaces/threads/threadSelector/threadStates';
  11. import Pill from 'sentry/components/pill';
  12. import Pills from 'sentry/components/pills';
  13. import QuestionTooltip from 'sentry/components/questionTooltip';
  14. import TextOverflow from 'sentry/components/textOverflow';
  15. import {IconClock, IconInfo, IconLock, IconPlay, IconTimer} from 'sentry/icons';
  16. import {t} from 'sentry/locale';
  17. import {space} from 'sentry/styles/space';
  18. import {
  19. EntryType,
  20. Event,
  21. Organization,
  22. Project,
  23. StackType,
  24. StackView,
  25. Thread,
  26. } from 'sentry/types';
  27. import {defined} from 'sentry/utils';
  28. import {PermalinkTitle, TraceEventDataSection} from '../traceEventDataSection';
  29. import {ExceptionContent} from './crashContent/exception';
  30. import {StackTraceContent} from './crashContent/stackTrace';
  31. import ThreadSelector from './threads/threadSelector';
  32. import findBestThread from './threads/threadSelector/findBestThread';
  33. import getThreadException from './threads/threadSelector/getThreadException';
  34. import getThreadStacktrace from './threads/threadSelector/getThreadStacktrace';
  35. import NoStackTraceMessage from './noStackTraceMessage';
  36. import {inferPlatform, isStacktraceNewestFirst} from './utils';
  37. type ExceptionProps = React.ComponentProps<typeof ExceptionContent>;
  38. type Props = Pick<ExceptionProps, 'groupingCurrentLevel' | 'hasHierarchicalGrouping'> & {
  39. data: {
  40. values?: Array<Thread>;
  41. };
  42. event: Event;
  43. organization: Organization;
  44. projectSlug: Project['slug'];
  45. };
  46. function getIntendedStackView(
  47. thread: Thread,
  48. exception: ReturnType<typeof getThreadException>
  49. ): StackView {
  50. if (exception) {
  51. return exception.values.find(value => !!value.stacktrace?.hasSystemFrames)
  52. ? StackView.APP
  53. : StackView.FULL;
  54. }
  55. const stacktrace = getThreadStacktrace(false, thread);
  56. return stacktrace?.hasSystemFrames ? StackView.APP : StackView.FULL;
  57. }
  58. export function getThreadStateIcon(state: ThreadStates | undefined) {
  59. if (isNil(state)) {
  60. return null;
  61. }
  62. switch (state) {
  63. case ThreadStates.BLOCKED:
  64. return <IconLock isSolid />;
  65. case ThreadStates.TIMED_WAITING:
  66. return <IconTimer />;
  67. case ThreadStates.WAITING:
  68. return <IconClock />;
  69. case ThreadStates.RUNNABLE:
  70. return <IconPlay />;
  71. default:
  72. return <IconInfo />;
  73. }
  74. }
  75. // We want to set the active thread every time the event changes because the best thread might not be the same between events
  76. const useActiveThreadState = (
  77. event: Event,
  78. threads: Thread[]
  79. ): [Thread | undefined, (newState: Thread | undefined) => void] => {
  80. const bestThread = threads.length ? findBestThread(threads) : undefined;
  81. const [activeThread, setActiveThread] = useState<Thread | undefined>(() => bestThread);
  82. useEffect(() => {
  83. setActiveThread(bestThread);
  84. // eslint-disable-next-line react-hooks/exhaustive-deps
  85. }, [event.id]);
  86. return [activeThread, setActiveThread];
  87. };
  88. export function Threads({
  89. data,
  90. event,
  91. projectSlug,
  92. hasHierarchicalGrouping,
  93. groupingCurrentLevel,
  94. organization,
  95. }: Props) {
  96. const threads = data.values ?? [];
  97. const [activeThread, setActiveThread] = useActiveThreadState(event, threads);
  98. const stackTraceNotFound = !threads.length;
  99. const hasMoreThanOneThread = threads.length > 1;
  100. const exception = getThreadException(event, activeThread);
  101. const entryIndex = exception
  102. ? event.entries.findIndex(entry => entry.type === EntryType.EXCEPTION)
  103. : event.entries.findIndex(entry => entry.type === EntryType.THREADS);
  104. const meta = event._meta?.entries?.[entryIndex]?.data?.values;
  105. const stackView = activeThread
  106. ? getIntendedStackView(activeThread, exception)
  107. : undefined;
  108. function renderPills() {
  109. const {
  110. id,
  111. name,
  112. current,
  113. crashed,
  114. state: threadState,
  115. heldLocks,
  116. } = activeThread ?? {};
  117. if (isNil(id) || !name) {
  118. return null;
  119. }
  120. const threadStateDisplay = getMappedThreadState(threadState);
  121. const lockReason = getLockReason(heldLocks);
  122. return (
  123. <Pills>
  124. {!isNil(id) && <Pill name={t('id')} value={String(id)} />}
  125. {!!name?.trim() && <Pill name={t('name')} value={name} />}
  126. {current !== undefined && <Pill name={t('was active')} value={current} />}
  127. {crashed !== undefined && (
  128. <Pill name={t('errored')} className={crashed ? 'false' : 'true'}>
  129. {crashed ? t('yes') : t('no')}
  130. </Pill>
  131. )}
  132. {!isNil(threadStateDisplay) && (
  133. <Pill name={t('state')} value={threadStateDisplay} />
  134. )}
  135. {defined(lockReason) && <Pill name={t('lock reason')} value={lockReason} />}
  136. </Pills>
  137. );
  138. }
  139. function renderContent({
  140. display,
  141. recentFirst,
  142. fullStackTrace,
  143. }: Parameters<React.ComponentProps<typeof TraceEventDataSection>['children']>[0]) {
  144. const stackType = display.includes('minified')
  145. ? StackType.MINIFIED
  146. : StackType.ORIGINAL;
  147. if (exception) {
  148. return (
  149. <ExceptionContent
  150. stackType={stackType}
  151. stackView={
  152. display.includes('raw-stack-trace')
  153. ? StackView.RAW
  154. : fullStackTrace
  155. ? StackView.FULL
  156. : StackView.APP
  157. }
  158. projectSlug={projectSlug}
  159. newestFirst={recentFirst}
  160. event={event}
  161. platform={platform}
  162. values={exception.values}
  163. groupingCurrentLevel={groupingCurrentLevel}
  164. hasHierarchicalGrouping={hasHierarchicalGrouping}
  165. meta={meta}
  166. threadId={activeThread?.id}
  167. />
  168. );
  169. }
  170. const stackTrace = getThreadStacktrace(
  171. stackType !== StackType.ORIGINAL,
  172. activeThread
  173. );
  174. if (stackTrace) {
  175. return (
  176. <StackTraceContent
  177. stacktrace={stackTrace}
  178. stackView={
  179. display.includes('raw-stack-trace')
  180. ? StackView.RAW
  181. : fullStackTrace
  182. ? StackView.FULL
  183. : StackView.APP
  184. }
  185. newestFirst={recentFirst}
  186. event={event}
  187. platform={platform}
  188. groupingCurrentLevel={groupingCurrentLevel}
  189. hasHierarchicalGrouping={hasHierarchicalGrouping}
  190. meta={meta}
  191. threadId={activeThread?.id}
  192. />
  193. );
  194. }
  195. return (
  196. <NoStackTraceMessage
  197. message={activeThread?.crashed ? t('Thread Errored') : undefined}
  198. />
  199. );
  200. }
  201. const platform = inferPlatform(event, activeThread);
  202. const threadStateDisplay = getMappedThreadState(activeThread?.state);
  203. const {id: activeThreadId, name: activeThreadName} = activeThread ?? {};
  204. const hideThreadTags = isNil(activeThreadId) || !activeThreadName;
  205. return (
  206. <Fragment>
  207. {hasMoreThanOneThread && organization.features.includes('anr-improvements') && (
  208. <Fragment>
  209. <Grid>
  210. <EventDataSection type={EntryType.THREADS} title={t('Threads')}>
  211. {activeThread && (
  212. <Wrapper>
  213. <ThreadSelector
  214. threads={threads}
  215. activeThread={activeThread}
  216. event={event}
  217. onChange={thread => {
  218. setActiveThread(thread);
  219. }}
  220. exception={exception}
  221. />
  222. </Wrapper>
  223. )}
  224. </EventDataSection>
  225. {activeThread && activeThread.state && (
  226. <EventDataSection type={EntryType.THREAD_STATE} title={t('Thread State')}>
  227. <ThreadStateWrapper>
  228. {getThreadStateIcon(threadStateDisplay)}
  229. <ThreadState>{threadStateDisplay}</ThreadState>
  230. {threadStateDisplay && (
  231. <QuestionTooltip
  232. position="top"
  233. size="xs"
  234. containerDisplayMode="block"
  235. title={getThreadStateHelpText(threadStateDisplay)}
  236. />
  237. )}
  238. {<LockReason>{getLockReason(activeThread?.heldLocks)}</LockReason>}
  239. </ThreadStateWrapper>
  240. </EventDataSection>
  241. )}
  242. </Grid>
  243. {!hideThreadTags && (
  244. <EventDataSection type={EntryType.THREAD_TAGS} title={t('Thread Tags')}>
  245. {renderPills()}
  246. </EventDataSection>
  247. )}
  248. </Fragment>
  249. )}
  250. <TraceEventDataSection
  251. type={EntryType.THREADS}
  252. projectSlug={projectSlug}
  253. eventId={event.id}
  254. recentFirst={isStacktraceNewestFirst()}
  255. fullStackTrace={stackView === StackView.FULL}
  256. title={
  257. hasMoreThanOneThread &&
  258. activeThread &&
  259. !organization.features.includes('anr-improvements') ? (
  260. <ThreadSelector
  261. threads={threads}
  262. activeThread={activeThread}
  263. event={event}
  264. onChange={thread => {
  265. setActiveThread(thread);
  266. }}
  267. exception={exception}
  268. fullWidth
  269. />
  270. ) : (
  271. <PermalinkTitle>
  272. {hasMoreThanOneThread ? t('Thread Stack Trace') : t('Stack Trace')}
  273. </PermalinkTitle>
  274. )
  275. }
  276. platform={platform}
  277. hasMinified={
  278. !!exception?.values?.find(value => value.rawStacktrace) ||
  279. !!activeThread?.rawStacktrace
  280. }
  281. hasVerboseFunctionNames={
  282. !!exception?.values?.some(
  283. value =>
  284. !!value.stacktrace?.frames?.some(
  285. frame =>
  286. !!frame.rawFunction &&
  287. !!frame.function &&
  288. frame.rawFunction !== frame.function
  289. )
  290. ) ||
  291. !!activeThread?.stacktrace?.frames?.some(
  292. frame =>
  293. !!frame.rawFunction &&
  294. !!frame.function &&
  295. frame.rawFunction !== frame.function
  296. )
  297. }
  298. hasAbsoluteFilePaths={
  299. !!exception?.values?.some(
  300. value => !!value.stacktrace?.frames?.some(frame => !!frame.filename)
  301. ) || !!activeThread?.stacktrace?.frames?.some(frame => !!frame.filename)
  302. }
  303. hasAbsoluteAddresses={
  304. !!exception?.values?.some(
  305. value => !!value.stacktrace?.frames?.some(frame => !!frame.instructionAddr)
  306. ) || !!activeThread?.stacktrace?.frames?.some(frame => !!frame.instructionAddr)
  307. }
  308. hasAppOnlyFrames={
  309. !!exception?.values?.some(
  310. value => !!value.stacktrace?.frames?.some(frame => frame.inApp !== true)
  311. ) || !!activeThread?.stacktrace?.frames?.some(frame => frame.inApp !== true)
  312. }
  313. hasNewestFirst={
  314. !!exception?.values?.some(
  315. value => (value.stacktrace?.frames ?? []).length > 1
  316. ) || (activeThread?.stacktrace?.frames ?? []).length > 1
  317. }
  318. stackTraceNotFound={stackTraceNotFound}
  319. wrapTitle={false}
  320. >
  321. {childrenProps => (
  322. <Fragment>
  323. {!organization.features.includes('anr-improvements') && renderPills()}
  324. {renderContent(childrenProps)}
  325. </Fragment>
  326. )}
  327. </TraceEventDataSection>
  328. </Fragment>
  329. );
  330. }
  331. const Grid = styled('div')`
  332. display: grid;
  333. grid-template-columns: auto 1fr;
  334. `;
  335. const ThreadStateWrapper = styled('div')`
  336. display: flex;
  337. position: relative;
  338. flex-direction: row;
  339. align-items: flex-start;
  340. gap: ${space(0.5)};
  341. `;
  342. const ThreadState = styled(TextOverflow)`
  343. max-width: 100%;
  344. text-align: left;
  345. font-weight: bold;
  346. `;
  347. const LockReason = styled(TextOverflow)`
  348. font-weight: 400;
  349. color: ${p => p.theme.gray300};
  350. `;
  351. const Wrapper = styled('div')`
  352. align-items: center;
  353. flex-wrap: wrap;
  354. flex-grow: 1;
  355. justify-content: flex-start;
  356. `;