123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405 |
- import {Fragment, useEffect, useState} from 'react';
- import styled from '@emotion/styled';
- import ErrorBoundary from 'sentry/components/errorBoundary';
- import {EventDataSection} from 'sentry/components/events/eventDataSection';
- import {StacktraceBanners} from 'sentry/components/events/interfaces/crashContent/exception/banners/stacktraceBanners';
- import {getLockReason} from 'sentry/components/events/interfaces/threads/threadSelector/lockReason';
- import {
- getMappedThreadState,
- getThreadStateHelpText,
- ThreadStates,
- } from 'sentry/components/events/interfaces/threads/threadSelector/threadStates';
- import Pill from 'sentry/components/pill';
- import Pills from 'sentry/components/pills';
- import QuestionTooltip from 'sentry/components/questionTooltip';
- import TextOverflow from 'sentry/components/textOverflow';
- import {IconClock, IconInfo, IconLock, IconPlay, IconTimer} from 'sentry/icons';
- import {t} from 'sentry/locale';
- import {space} from 'sentry/styles/space';
- import type {Event, Organization, Project, Thread} from 'sentry/types';
- import {EntryType, StackType, StackView} from 'sentry/types';
- import {defined} from 'sentry/utils';
- import {PermalinkTitle, TraceEventDataSection} from '../traceEventDataSection';
- import {ExceptionContent} from './crashContent/exception';
- import {StackTraceContent} from './crashContent/stackTrace';
- import ThreadSelector from './threads/threadSelector';
- import findBestThread from './threads/threadSelector/findBestThread';
- import getThreadException from './threads/threadSelector/getThreadException';
- import getThreadStacktrace from './threads/threadSelector/getThreadStacktrace';
- import NoStackTraceMessage from './noStackTraceMessage';
- import {inferPlatform, isStacktraceNewestFirst} from './utils';
- type ExceptionProps = React.ComponentProps<typeof ExceptionContent>;
- type Props = Pick<ExceptionProps, 'groupingCurrentLevel' | 'hasHierarchicalGrouping'> & {
- data: {
- values?: Array<Thread>;
- };
- event: Event;
- organization: Organization;
- projectSlug: Project['slug'];
- };
- function getIntendedStackView(
- thread: Thread,
- exception: ReturnType<typeof getThreadException>
- ): StackView {
- if (exception) {
- return exception.values.find(value => !!value.stacktrace?.hasSystemFrames)
- ? StackView.APP
- : StackView.FULL;
- }
- const stacktrace = getThreadStacktrace(false, thread);
- return stacktrace?.hasSystemFrames ? StackView.APP : StackView.FULL;
- }
- export function getThreadStateIcon(state: ThreadStates | undefined) {
- if (state === null || state === undefined) {
- return null;
- }
- switch (state) {
- case ThreadStates.BLOCKED:
- return <IconLock isSolid />;
- case ThreadStates.TIMED_WAITING:
- return <IconTimer />;
- case ThreadStates.WAITING:
- return <IconClock />;
- case ThreadStates.RUNNABLE:
- return <IconPlay />;
- default:
- return <IconInfo />;
- }
- }
- // We want to set the active thread every time the event changes because the best thread might not be the same between events
- const useActiveThreadState = (
- event: Event,
- threads: Thread[]
- ): [Thread | undefined, (newState: Thread | undefined) => void] => {
- const bestThread = threads.length ? findBestThread(threads) : undefined;
- const [activeThread, setActiveThread] = useState<Thread | undefined>(() => bestThread);
- useEffect(() => {
- setActiveThread(bestThread);
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [event.id]);
- return [activeThread, setActiveThread];
- };
- export function Threads({
- data,
- event,
- projectSlug,
- hasHierarchicalGrouping,
- groupingCurrentLevel,
- organization,
- }: Props) {
- const threads = data.values ?? [];
- const [activeThread, setActiveThread] = useActiveThreadState(event, threads);
- const stackTraceNotFound = !threads.length;
- const hasMoreThanOneThread = threads.length > 1;
- const exception = getThreadException(event, activeThread);
- const entryIndex = exception
- ? event.entries.findIndex(entry => entry.type === EntryType.EXCEPTION)
- : event.entries.findIndex(entry => entry.type === EntryType.THREADS);
- const meta = event._meta?.entries?.[entryIndex]?.data?.values;
- const stackView = activeThread
- ? getIntendedStackView(activeThread, exception)
- : undefined;
- function renderPills() {
- const {
- id,
- name,
- current,
- crashed,
- state: threadState,
- heldLocks,
- } = activeThread ?? {};
- if (id === null || id === undefined || !name) {
- return null;
- }
- const threadStateDisplay = getMappedThreadState(threadState);
- const lockReason = getLockReason(heldLocks);
- return (
- <Pills>
- <Pill name={t('id')} value={id} />
- {!!name?.trim() && <Pill name={t('name')} value={name} />}
- {current !== undefined && <Pill name={t('was active')} value={current} />}
- {crashed !== undefined && (
- <Pill name={t('errored')} className={crashed ? 'false' : 'true'}>
- {crashed ? t('yes') : t('no')}
- </Pill>
- )}
- {threadStateDisplay !== undefined && (
- <Pill name={t('state')} value={threadStateDisplay} />
- )}
- {defined(lockReason) && <Pill name={t('lock reason')} value={lockReason} />}
- </Pills>
- );
- }
- function renderContent({
- display,
- recentFirst,
- fullStackTrace,
- }: Parameters<React.ComponentProps<typeof TraceEventDataSection>['children']>[0]) {
- const stackType = display.includes('minified')
- ? StackType.MINIFIED
- : StackType.ORIGINAL;
- if (exception) {
- return (
- <ExceptionContent
- stackType={stackType}
- stackView={
- display.includes('raw-stack-trace')
- ? StackView.RAW
- : fullStackTrace
- ? StackView.FULL
- : StackView.APP
- }
- projectSlug={projectSlug}
- newestFirst={recentFirst}
- event={event}
- values={exception.values}
- groupingCurrentLevel={groupingCurrentLevel}
- hasHierarchicalGrouping={hasHierarchicalGrouping}
- meta={meta}
- threadId={activeThread?.id}
- />
- );
- }
- const stackTrace = getThreadStacktrace(
- stackType !== StackType.ORIGINAL,
- activeThread
- );
- if (stackTrace) {
- return (
- <StackTraceContent
- stacktrace={stackTrace}
- stackView={
- display.includes('raw-stack-trace')
- ? StackView.RAW
- : fullStackTrace
- ? StackView.FULL
- : StackView.APP
- }
- newestFirst={recentFirst}
- event={event}
- platform={platform}
- groupingCurrentLevel={groupingCurrentLevel}
- hasHierarchicalGrouping={hasHierarchicalGrouping}
- meta={meta}
- threadId={activeThread?.id}
- />
- );
- }
- return (
- <NoStackTraceMessage
- message={activeThread?.crashed ? t('Thread Errored') : undefined}
- />
- );
- }
- const platform = inferPlatform(event, activeThread);
- const threadStateDisplay = getMappedThreadState(activeThread?.state);
- const {id: activeThreadId, name: activeThreadName} = activeThread ?? {};
- const hideThreadTags = activeThreadId === undefined || !activeThreadName;
- return (
- <Fragment>
- {hasMoreThanOneThread && organization.features.includes('anr-improvements') && (
- <Fragment>
- <Grid>
- <EventDataSection type={EntryType.THREADS} title={t('Threads')}>
- {activeThread && (
- <Wrapper>
- <ThreadSelector
- threads={threads}
- activeThread={activeThread}
- event={event}
- onChange={thread => {
- setActiveThread(thread);
- }}
- exception={exception}
- />
- </Wrapper>
- )}
- </EventDataSection>
- {activeThread?.state && (
- <EventDataSection type={EntryType.THREAD_STATE} title={t('Thread State')}>
- <ThreadStateWrapper>
- {getThreadStateIcon(threadStateDisplay)}
- <ThreadState>{threadStateDisplay}</ThreadState>
- {threadStateDisplay && (
- <QuestionTooltip
- position="top"
- size="xs"
- containerDisplayMode="block"
- title={getThreadStateHelpText(threadStateDisplay)}
- />
- )}
- <LockReason>{getLockReason(activeThread?.heldLocks)}</LockReason>
- </ThreadStateWrapper>
- </EventDataSection>
- )}
- </Grid>
- {!hideThreadTags && (
- <EventDataSection type={EntryType.THREAD_TAGS} title={t('Thread Tags')}>
- {renderPills()}
- </EventDataSection>
- )}
- </Fragment>
- )}
- <TraceEventDataSection
- type={EntryType.THREADS}
- projectSlug={projectSlug}
- eventId={event.id}
- recentFirst={isStacktraceNewestFirst()}
- fullStackTrace={stackView === StackView.FULL}
- title={
- hasMoreThanOneThread &&
- activeThread &&
- !organization.features.includes('anr-improvements') ? (
- <ThreadSelector
- threads={threads}
- activeThread={activeThread}
- event={event}
- onChange={thread => {
- setActiveThread(thread);
- }}
- exception={exception}
- fullWidth
- />
- ) : (
- <PermalinkTitle>
- {hasMoreThanOneThread ? t('Thread Stack Trace') : t('Stack Trace')}
- </PermalinkTitle>
- )
- }
- platform={platform}
- hasMinified={
- !!exception?.values?.find(value => value.rawStacktrace) ||
- !!activeThread?.rawStacktrace
- }
- hasVerboseFunctionNames={
- !!exception?.values?.some(
- value =>
- !!value.stacktrace?.frames?.some(
- frame =>
- !!frame.rawFunction &&
- !!frame.function &&
- frame.rawFunction !== frame.function
- )
- ) ||
- !!activeThread?.stacktrace?.frames?.some(
- frame =>
- !!frame.rawFunction &&
- !!frame.function &&
- frame.rawFunction !== frame.function
- )
- }
- hasAbsoluteFilePaths={
- !!exception?.values?.some(
- value => !!value.stacktrace?.frames?.some(frame => !!frame.filename)
- ) || !!activeThread?.stacktrace?.frames?.some(frame => !!frame.filename)
- }
- hasAbsoluteAddresses={
- !!exception?.values?.some(
- value => !!value.stacktrace?.frames?.some(frame => !!frame.instructionAddr)
- ) || !!activeThread?.stacktrace?.frames?.some(frame => !!frame.instructionAddr)
- }
- hasAppOnlyFrames={
- !!exception?.values?.some(
- value => !!value.stacktrace?.frames?.some(frame => frame.inApp !== true)
- ) || !!activeThread?.stacktrace?.frames?.some(frame => frame.inApp !== true)
- }
- hasNewestFirst={
- !!exception?.values?.some(
- value => (value.stacktrace?.frames ?? []).length > 1
- ) || (activeThread?.stacktrace?.frames ?? []).length > 1
- }
- stackTraceNotFound={stackTraceNotFound}
- wrapTitle={false}
- >
- {childrenProps => {
- // TODO(scttcper): These are duplicated from renderContent, should consolidate
- const stackType = childrenProps.display.includes('minified')
- ? StackType.MINIFIED
- : StackType.ORIGINAL;
- const isRaw = childrenProps.display.includes('raw-stack-trace');
- const stackTrace = getThreadStacktrace(
- stackType !== StackType.ORIGINAL,
- activeThread
- );
- return (
- <Fragment>
- {!organization.features.includes('anr-improvements') && renderPills()}
- {stackTrace && !isRaw && (
- <ErrorBoundary customComponent={null}>
- <StacktraceBanners event={event} stacktrace={stackTrace} />
- </ErrorBoundary>
- )}
- {renderContent(childrenProps)}
- </Fragment>
- );
- }}
- </TraceEventDataSection>
- </Fragment>
- );
- }
- const Grid = styled('div')`
- display: grid;
- grid-template-columns: auto 1fr;
- `;
- const ThreadStateWrapper = styled('div')`
- display: flex;
- position: relative;
- flex-direction: row;
- align-items: flex-start;
- gap: ${space(0.5)};
- `;
- const ThreadState = styled(TextOverflow)`
- max-width: 100%;
- text-align: left;
- font-weight: bold;
- `;
- const LockReason = styled(TextOverflow)`
- font-weight: 400;
- color: ${p => p.theme.gray300};
- `;
- const Wrapper = styled('div')`
- align-items: center;
- flex-wrap: wrap;
- flex-grow: 1;
- justify-content: flex-start;
- `;
|