Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(trace-view-eap): Rendering trace waterfall from eap spans #86211

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion static/app/views/performance/newTraceDetails/trace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ import {useTraceState, useTraceStateDispatch} from './traceState/traceStateProvi
import {
isAutogroupedNode,
isCollapsedNode,
isEAPSpanNode,
isMissingInstrumentationNode,
isSpanNode,
isTraceErrorNode,
Expand Down Expand Up @@ -664,7 +665,7 @@ function RenderTraceRow(props: {
return <TraceTransactionRow {...rowProps} node={node} />;
}

if (isSpanNode(node)) {
if (isSpanNode(node) || isEAPSpanNode(node)) {
return <TraceSpanRow {...rowProps} node={node} />;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,5 +217,20 @@ export function useTrace(
}
);

// const eapTraceQuery = useApiQuery<TraceTree.EAPTrace>(
// [
// `/organizations/${organization.slug}/trace/${options.traceSlug ?? ''}/`,
// {
// query: {
// timestamp: queryParams.timestamp,
// },
// },
// ],
// {
// staleTime: Infinity,
// enabled: !!options.traceSlug && !!organization.slug && mode !== 'demo',
// }
// );

return mode === 'demo' ? demoTrace : traceQuery;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {defined} from 'sentry/utils';
import {useLocation} from 'sentry/utils/useLocation';
import useProjects from 'sentry/utils/useProjects';
import {InterimSection} from 'sentry/views/issueDetails/streamline/interimSection';
import {isEAPSpanNode} from 'sentry/views/performance/newTraceDetails/traceGuards';
import {ProfileGroupProvider} from 'sentry/views/profiling/profileGroupProvider';
import {ProfileContext, ProfilesProvider} from 'sentry/views/profiling/profilesProvider';

Expand All @@ -44,14 +45,14 @@ function SpanNodeDetailHeader({
onTabScrollToNode,
project,
}: {
node: TraceTreeNode<TraceTree.Span>;
node: TraceTreeNode<TraceTree.Span> | TraceTreeNode<TraceTree.EAPSpan>;
onTabScrollToNode: (node: TraceTreeNode<any>) => void;
organization: Organization;
project: Project | undefined;
}) {
const hasNewTraceUi = useHasTraceNewUi();

if (!hasNewTraceUi) {
if (!hasNewTraceUi && !isEAPSpanNode(node)) {
return (
<LegacySpanNodeDetailHeader
node={node}
Expand All @@ -62,14 +63,15 @@ function SpanNodeDetailHeader({
);
}

const spanId = isEAPSpanNode(node) ? node.value.event_id : node.value.span_id;
return (
<TraceDrawerComponents.HeaderContainer>
<TraceDrawerComponents.Title>
<TraceDrawerComponents.LegacyTitleText>
<TraceDrawerComponents.TitleText>{t('Span')}</TraceDrawerComponents.TitleText>
<TraceDrawerComponents.SubtitleWithCopyButton
subTitle={`ID: ${node.value.span_id}`}
clipboardText={node.value.span_id}
subTitle={`ID: ${spanId}`}
clipboardText={spanId}
/>
</TraceDrawerComponents.LegacyTitleText>
</TraceDrawerComponents.Title>
Expand Down Expand Up @@ -246,7 +248,9 @@ export function SpanNodeDetails({
organization,
onTabScrollToNode,
onParentClick,
}: TraceTreeNodeDetailsProps<TraceTreeNode<TraceTree.Span>>) {
}: TraceTreeNodeDetailsProps<
TraceTreeNode<TraceTree.Span> | TraceTreeNode<TraceTree.EAPSpan>
>) {
const location = useLocation();
const hasNewTraceUi = useHasTraceNewUi();
const {projects} = useProjects();
Expand All @@ -259,6 +263,19 @@ export function SpanNodeDetails({
const profileId =
typeof profileMeta === 'string' ? profileMeta : profileMeta.profiler_id;

if (isEAPSpanNode(node)) {
return (
<TraceDrawerComponents.DetailContainer>
<SpanNodeDetailHeader
node={node}
organization={organization}
project={project}
onTabScrollToNode={onTabScrollToNode}
/>
</TraceDrawerComponents.DetailContainer>
);
}

return (
<TraceDrawerComponents.DetailContainer>
<SpanNodeDetailHeader
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {ReplayRecord} from 'sentry/views/replays/types';

import {
isAutogroupedNode,
isEAPSpanNode,
isMissingInstrumentationNode,
isSpanNode,
isTraceErrorNode,
Expand Down Expand Up @@ -31,7 +32,7 @@ export function TraceTreeNodeDetails(props: TraceTreeNodeDetailsProps<any>) {
return <TransactionNodeDetails {...props} />;
}

if (isSpanNode(props.node)) {
if (isSpanNode(props.node) || isEAPSpanNode(props.node)) {
return <SpanNodeDetails {...props} />;
}

Expand Down
32 changes: 27 additions & 5 deletions static/app/views/performance/newTraceDetails/traceGuards.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import type {TraceSplitResults} from 'sentry/utils/performance/quickTrace/types';

import {MissingInstrumentationNode} from './traceModels/missingInstrumentationNode';
import {ParentAutogroupNode} from './traceModels/parentAutogroupNode';
import {SiblingAutogroupNode} from './traceModels/siblingAutogroupNode';
Expand All @@ -21,10 +23,20 @@ export function isSpanNode(
);
}

export function isEAPSpanNode(
node: TraceTreeNode<TraceTree.NodeValue>
): node is TraceTreeNode<TraceTree.EAPSpan> {
return !!(node.value && 'is_transaction' in node.value);
}

export function isTransactionNode(
node: TraceTreeNode<TraceTree.NodeValue>
): node is TraceTreeNode<TraceTree.Transaction> {
return !!(node.value && 'transaction' in node.value) && !isAutogroupedNode(node);
return (
!!(node.value && 'transaction' in node.value) &&
!isAutogroupedNode(node) &&
!isEAPSpanNode(node)
);
}

export function isParentAutogroupedNode(
Expand Down Expand Up @@ -65,13 +77,19 @@ export function isRootNode(

export function isTraceNode(
node: TraceTreeNode<TraceTree.NodeValue>
): node is TraceTreeNode<TraceTree.Trace> {
): node is TraceTreeNode<TraceSplitResults<TraceTree.Transaction>> {
return !!(
node.value &&
('orphan_errors' in node.value || 'transactions' in node.value)
);
}

export function isEAPTraceNode(
node: TraceTreeNode<TraceTree.NodeValue>
): node is TraceTreeNode<TraceTree.EAPTrace> {
return !!node.value && Array.isArray(node.value) && !isTraceNode(node);
}

export function shouldAddMissingInstrumentationSpan(sdk: string | undefined): boolean {
if (!sdk) {
return true;
Expand Down Expand Up @@ -104,9 +122,13 @@ export function shouldAddMissingInstrumentationSpan(sdk: string | undefined): bo
}
}

export function isJavascriptSDKTransaction(transaction: TraceTree.Transaction): boolean {
return /javascript|angular|astro|backbone|ember|gatsby|nextjs|react|remix|svelte|vue/.test(
transaction.sdk_name
export function isJavascriptSDKEvent(value: TraceTree.NodeValue): boolean {
return (
!!value &&
'sdk_name' in value &&
/javascript|angular|astro|backbone|ember|gatsby|nextjs|react|remix|svelte|vue/.test(
value.sdk_name
)
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ import {useModuleURLBuilder} from 'sentry/views/insights/common/utils/useModuleU
import {useDomainViewFilters} from 'sentry/views/insights/pages/useFilters';
import {useTraceStateDispatch} from 'sentry/views/performance/newTraceDetails/traceState/traceStateProvider';

import {isRootTransaction} from '../../traceDetails/utils';
import {isRootEvent} from '../../traceDetails/utils';
import type {TraceMetaQueryResults} from '../traceApi/useTraceMeta';
import TraceConfigurations from '../traceConfigurations';
import {isTraceNode} from '../traceGuards';
Expand Down Expand Up @@ -152,7 +152,7 @@ export const getRepresentativeTransaction = (

for (const transaction of traceNode.value.transactions || []) {
// If we find a root transaction, we can stop looking and use it for the title.
if (!firstRootTransaction && isRootTransaction(transaction)) {
if (!firstRootTransaction && isRootEvent(transaction)) {
firstRootTransaction = transaction;
break;
} else if (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,20 +12,22 @@ import type {
TracePerformanceIssue as TracePerformanceIssueType,
TraceSplitResults,
} from 'sentry/utils/performance/quickTrace/types';
import {isTraceSplitResult} from 'sentry/utils/performance/quickTrace/utils';
import {collectTraceMeasurements} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree.measurements';
import type {TracePreferencesState} from 'sentry/views/performance/newTraceDetails/traceState/tracePreferences';
import type {ReplayTrace} from 'sentry/views/replays/detail/trace/useReplayTraces';
import type {ReplayRecord} from 'sentry/views/replays/types';

import {isRootTransaction} from '../../traceDetails/utils';
import {isRootEvent} from '../../traceDetails/utils';
import {getTraceQueryParams} from '../traceApi/useTrace';
import type {TraceMetaQueryResults} from '../traceApi/useTraceMeta';
import {
getPageloadTransactionChildCount,
isAutogroupedNode,
isBrowserRequestSpan,
isCollapsedNode,
isJavascriptSDKTransaction,
isEAPTraceNode,
isJavascriptSDKEvent,
isMissingInstrumentationNode,
isPageloadTransactionNode,
isParentAutogroupedNode,
Expand Down Expand Up @@ -118,6 +120,23 @@ export declare namespace TraceTree {
['trace timeline change']: (view: [number, number]) => void;
}

type EAPSpan = {
children: EAPSpan[];
duration: number;
end_timestamp: number;
event_id: string;
is_transaction: boolean;
op: string;
parent_span_id: string;
project_id: number;
project_slug: string;
start_timestamp: number;
transaction: string;
description?: string;
};

type EAPTrace = EAPSpan[];

// Raw node values
interface Span extends RawSpanType {
measurements?: Record<string, Measurement>;
Expand All @@ -128,7 +147,7 @@ export declare namespace TraceTree {
sdk_name: string;
}

type Trace = TraceSplitResults<Transaction>;
type Trace = TraceSplitResults<Transaction> | EAPTrace;
type TraceError = TraceErrorType;
type TracePerformanceIssue = TracePerformanceIssueType;
type Profile = {profile_id: string} | {profiler_id: string};
Expand All @@ -143,6 +162,7 @@ export declare namespace TraceTree {
| Transaction
| TraceError
| Span
| EAPSpan
| MissingInstrumentationSpan
| SiblingAutogroup
| ChildrenAutogroup
Expand Down Expand Up @@ -292,7 +312,7 @@ export class TraceTree extends TraceTreeEventDispatcher {

function visit(
parent: TraceTreeNode<TraceTree.NodeValue | null>,
value: TraceTree.Transaction | TraceTree.TraceError
value: TraceTree.Transaction | TraceTree.TraceError | TraceTree.EAPSpan
) {
tree.eventsCount++;
tree.projects.set(value.project_id, {
Expand Down Expand Up @@ -323,7 +343,11 @@ export class TraceTree extends TraceTreeEventDispatcher {
parent.children.push(node);

if (node.value && 'children' in node.value) {
for (const child of node.value.children) {
// EAP spans are not sorted by default
const children = node.value.children.sort(
(a, b) => a.start_timestamp - b.start_timestamp
);
for (const child of children) {
visit(node, child);
}
}
Expand Down Expand Up @@ -1750,25 +1774,31 @@ export class TraceTree extends TraceTreeEventDispatcher {
throw new TypeError('Not trace node');
}

const traceStats = trace.value.transactions?.reduce<{
javascriptRootTransactions: TraceTree.Transaction[];
orphans: number;
roots: number;
}>(
(stats, transaction) => {
if (isRootTransaction(transaction)) {
stats.roots++;

if (isJavascriptSDKTransaction(transaction)) {
stats.javascriptRootTransactions.push(transaction);
}
} else {
stats.orphans++;
const traceStats = isEAPTraceNode(trace)
? {
javascriptRootTransactions: trace.value.filter(isJavascriptSDKEvent),
orphans: trace.value.filter(span => span.parent_span_id !== null).length,
roots: trace.value.filter(span => span.parent_span_id === null).length,
}
return stats;
},
{roots: 0, orphans: 0, javascriptRootTransactions: []}
) ?? {roots: 0, orphans: 0, javascriptRootTransactions: []};
: trace.value.transactions?.reduce<{
javascriptRootTransactions: TraceTree.Transaction[];
orphans: number;
roots: number;
}>(
(stats, transaction) => {
if (isRootEvent(transaction)) {
stats.roots++;

if (isJavascriptSDKEvent(transaction)) {
stats.javascriptRootTransactions.push(transaction);
}
} else {
stats.orphans++;
}
return stats;
},
{roots: 0, orphans: 0, javascriptRootTransactions: []}
) ?? {roots: 0, orphans: 0, javascriptRootTransactions: []};

if (traceStats.roots === 0) {
if (traceStats.orphans > 0) {
Expand Down Expand Up @@ -1997,9 +2027,18 @@ function traceQueueIterator(
root: TraceTreeNode<TraceTree.NodeValue>,
visitor: (
parent: TraceTreeNode<TraceTree.NodeValue>,
value: TraceTree.Transaction | TraceTree.TraceError
value: TraceTree.Transaction | TraceTree.TraceError | TraceTree.EAPSpan
) => void
) {
if (!isTraceSplitResult(trace)) {
// Finish this
const spans = [...trace].sort((a, b) => a.start_timestamp - b.start_timestamp);
for (const span of spans) {
visitor(root, span);
}
return;
}

let tIdx = 0;
let oIdx = 0;

Expand Down
Loading
Loading