threadsV2.tsx 12 KB

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