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(new-trace): Added logic for red errored rows. #66194

Merged
merged 6 commits into from Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
34 changes: 24 additions & 10 deletions static/app/views/performance/newTraceDetails/trace.tsx
Expand Up @@ -487,14 +487,15 @@ function RenderRow(props: {
}}
>
<div
className="TraceLeftColumnInner"
className={`TraceLeftColumnInner ${props.node.has_error ? 'Errored' : ''}`}
style={{
paddingLeft: props.node.depth * props.viewManager.row_depth_padding,
}}
>
<div className="TraceChildrenCountWrapper">
<Connectors node={props.node} viewManager={props.viewManager} />
<ChildrenCountButton
errored={props.node.has_error}
expanded={!props.node.expanded}
onClick={e => props.onExpand(e, props.node, !props.node.expanded)}
>
Expand Down Expand Up @@ -544,6 +545,9 @@ function RenderRow(props: {
}

if (isTransactionNode(props.node)) {
const errored =
props.node.value.errors.length > 0 ||
props.node.value.performance_issues.length > 0;
return (
<div
key={props.index}
Expand Down Expand Up @@ -571,19 +575,21 @@ function RenderRow(props: {
}}
>
<div
className="TraceLeftColumnInner"
className={`TraceLeftColumnInner ${errored ? 'Errored' : ''}`}
style={{
paddingLeft: props.node.depth * props.viewManager.row_depth_padding,
}}
>
<div
className={`TraceChildrenCountWrapper ${
props.node.isOrphaned ? 'Orphaned' : ''
}`}
}
`}
>
<Connectors node={props.node} viewManager={props.viewManager} />
{props.node.children.length > 0 ? (
<ChildrenCountButton
errored={errored}
expanded={props.node.expanded || props.node.zoomedIn}
onClick={e => props.onExpand(e, props.node, !props.node.expanded)}
>
Expand All @@ -595,12 +601,12 @@ function RenderRow(props: {
<span className="TraceOperation">{props.node.value['transaction.op']}</span>
<strong className="TraceEmDash"> — </strong>
<span>{props.node.value.transaction}</span>
{props.node.canFetchData ? (
<button onClick={e => props.onZoomIn(e, props.node, !props.node.zoomedIn)}>
{props.node.zoomedIn ? 'Zoom Out' : 'Zoom In'}
</button>
) : null}
</div>
{props.node.canFetchData ? (
<button onClick={e => props.onZoomIn(e, props.node, !props.node.zoomedIn)}>
{props.node.zoomedIn ? 'Zoom Out' : 'Zoom In'}
</button>
) : null}
</div>
<div
ref={r =>
Expand Down Expand Up @@ -630,6 +636,7 @@ function RenderRow(props: {
}

if (isSpanNode(props.node)) {
const errored = props.node.value.relatedErrors.length > 0;
return (
<div
key={props.index}
Expand Down Expand Up @@ -657,7 +664,7 @@ function RenderRow(props: {
}}
>
<div
className="TraceLeftColumnInner"
className={`TraceLeftColumnInner ${errored ? 'Errored' : ''}`}
style={{
paddingLeft: props.node.depth * props.viewManager.row_depth_padding,
}}
Expand Down Expand Up @@ -1094,9 +1101,13 @@ function ChildrenCountButton(props: {
children: React.ReactNode;
expanded: boolean;
onClick: (e: React.MouseEvent) => void;
errored?: boolean;
}) {
return (
<button className="TraceChildrenCount" onClick={props.onClick}>
<button
className={`TraceChildrenCount ${props.errored ? 'Errored' : ''}`}
onClick={props.onClick}
>
{props.children}
<IconChevron
size="xs"
Expand Down Expand Up @@ -1448,6 +1459,9 @@ const TraceStylingWrapper = styled('div')`
box-shadow: ${p => p.theme.dropShadowLight};
margin-right: ${space(1)};

&.Errored {
border: 2px solid ${p => p.theme.error};
}
svg {
width: 7px;
transition: none;
Expand Down
78 changes: 78 additions & 0 deletions static/app/views/performance/newTraceDetails/traceTree.spec.tsx
Expand Up @@ -1551,6 +1551,31 @@ describe('TraceTree', () => {
expect(root.children.length).toBe(1);
});

it('collects errored children for sibling autogrouped node', () => {
const root = new TraceTreeNode(null, makeSpan({description: 'span1'}), {
project_slug: '',
event_id: '',
});

for (let i = 0; i < 5; i++) {
const node = new TraceTreeNode(root, makeSpan({description: 'span', op: 'db'}), {
project_slug: '',
event_id: '',
});
node.value.relatedErrors = [makeTraceError()];
root.children.push(node);
}

expect(root.children.length).toBe(5);

TraceTree.AutogroupSiblingSpanNodes(root);

expect(root.children.length).toBe(1);
assertAutogroupedNode(root.children[0]);
expect(root.children[0].has_error).toBe(true);
expect(root.children[0].errored_children).toHaveLength(5);
});

it('adds autogrouped siblings as children under autogrouped node', () => {
const root = new TraceTreeNode(null, makeSpan({description: 'span1'}), {
project_slug: '',
Expand Down Expand Up @@ -1658,6 +1683,59 @@ describe('TraceTree', () => {
);
});

it('collects errored children for parent autogrouped node', () => {
// db db db
// http -> parent autogroup (3) -> parent autogroup (3)
// http http
// http http
// http

const root: TraceTreeNode<TraceTree.Span> = new TraceTreeNode(
null,
makeSpan({
description: `span1`,
span_id: `1`,
op: 'db',
}),
{project_slug: '', event_id: ''}
);

let last: TraceTreeNode<any> = root;

for (let i = 0; i < 3; i++) {
const node = new TraceTreeNode(
last,
makeSpan({
description: `span${i}`,
span_id: `${i}`,
op: 'http',
}),
{
project_slug: '',
event_id: '',
}
);
node.value.relatedErrors = [makeTraceError()];
last.children.push(node);
last = node;
}

if (!root) {
throw new Error('root is null');
}

expect(root.children.length).toBe(1);
expect(root.children[0].children.length).toBe(1);

TraceTree.AutogroupDirectChildrenSpanNodes(root);

expect(root.children.length).toBe(1);

assertAutogroupedNode(root.children[0]);
expect(root.children[0].has_error).toBe(true);
expect(root.children[0].errored_children).toHaveLength(3);
});

it('autogrouping direct children skips rendering intermediary nodes', () => {
// db db db
// http autogrouped (3) autogrouped (3)
Expand Down
79 changes: 66 additions & 13 deletions static/app/views/performance/newTraceDetails/traceTree.tsx
Expand Up @@ -413,7 +413,9 @@ export class TraceTree {
const spanNodeValue: TraceTree.Span = {
...span,
event: data as EventTransaction,
relatedErrors: childTxn ? getRelatedErrorsOrIssues(span, childTxn.value) : [],
relatedErrors: childTxn
? getSpanErrorsOrIssuesFromTransaction(span, childTxn.value)
: [],
childTxn: childTxn?.value,
};
const node: TraceTreeNode<TraceTree.Span> = new TraceTreeNode(null, spanNodeValue, {
Expand Down Expand Up @@ -478,17 +480,28 @@ export class TraceTree {
const head = node;
let tail = node;
let groupMatchCount = 0;
const erroredChildren: TraceTreeNode<TraceTree.NodeValue>[] = [];

while (
tail &&
tail.children.length === 1 &&
isSpanNode(tail.children[0]) &&
tail.children[0].value.op === head.value.op
) {
if (tail.value?.relatedErrors.length > 0) {
erroredChildren.push(tail);
}

groupMatchCount++;
tail = tail.children[0];
}

// Checking the tail node for errors as it is not included in the grouping
// while loop, but is hidden when the autogrouped node is collapsed
if (tail.value?.relatedErrors.length > 0) {
erroredChildren.push(tail);
}

if (groupMatchCount < 1) {
for (const child of head.children) {
queue.push(child);
Expand Down Expand Up @@ -517,6 +530,7 @@ export class TraceTree {
}

autoGroupedNode.groupCount = groupMatchCount + 1;
autoGroupedNode.errored_children = erroredChildren;
autoGroupedNode.space = [
head.value.start_timestamp * autoGroupedNode.multiplier,
(tail.value.timestamp - head.value.start_timestamp) * autoGroupedNode.multiplier,
Expand Down Expand Up @@ -585,7 +599,17 @@ export class TraceTree {
autoGroupedNode.groupCount = matchCount + 1;
const start = index - matchCount;
for (let j = start; j < start + matchCount + 1; j++) {
autoGroupedNode.children.push(node.children[j]);
const child = node.children[j];
if (!isSpanNode(child)) {
throw new TypeError(
'Expected child of autogrouped node to be a span node.'
);
}

if (child.value?.relatedErrors.length > 0) {
autoGroupedNode.errored_children.push(child);
}
autoGroupedNode.children.push(child);
autoGroupedNode.children[autoGroupedNode.children.length - 1].parent =
autoGroupedNode;
}
Expand Down Expand Up @@ -1217,6 +1241,7 @@ export class MissingInstrumentationNode extends TraceTreeNode<TraceTree.MissingI
export class ParentAutogroupNode extends TraceTreeNode<TraceTree.ChildrenAutogroup> {
head: TraceTreeNode<TraceTree.Span>;
tail: TraceTreeNode<TraceTree.Span>;
errored_children: TraceTreeNode<TraceTree.NodeValue>[] = [];
groupCount: number = 0;

constructor(
Expand All @@ -1239,10 +1264,15 @@ export class ParentAutogroupNode extends TraceTreeNode<TraceTree.ChildrenAutogro
}
return this.tail.children;
}

get has_error(): boolean {
return this.errored_children.length > 0;
}
}

export class SiblingAutogroupNode extends TraceTreeNode<TraceTree.SiblingAutogroup> {
groupCount: number = 0;
errored_children: TraceTreeNode<TraceTree.NodeValue>[] = [];

constructor(
parent: TraceTreeNode<TraceTree.NodeValue> | null,
Expand All @@ -1252,6 +1282,10 @@ export class SiblingAutogroupNode extends TraceTreeNode<TraceTree.SiblingAutogro
super(parent, node, metadata);
this.expanded = false;
}

get has_error(): boolean {
return this.errored_children.length > 0;
}
}

function partialTransaction(
Expand Down Expand Up @@ -1381,20 +1415,39 @@ function nodeToId(n: TraceTreeNode<TraceTree.NodeValue>): TraceTree.NodePath {
throw new Error('Not implemented');
}

function getRelatedErrorsOrIssues(
// Returns a list of errors or performance issues related to the txn
// with ids matching the span id
function getSpanErrorsOrIssuesFromTransaction(
span: RawSpanType,
currentEvent: TraceTree.Transaction
txn: TraceTree.Transaction
): TraceErrorOrIssue[] {
const performanceIssues = currentEvent.performance_issues.filter(
issue =>
issue.span.some(id => id === span.span_id) ||
issue.suspect_spans.some(suspectSpanId => suspectSpanId === span.span_id)
);
if (!txn.performance_issues.length && !txn.errors.length) {
return [];
}

const errorsOrIssues: TraceErrorOrIssue[] = [];

for (const perfIssue of txn.performance_issues) {
for (const s of perfIssue.span) {
if (s === span.span_id) {
errorsOrIssues.push(perfIssue);
}
}

for (const suspect of perfIssue.suspect_spans) {
if (suspect === span.span_id) {
errorsOrIssues.push(perfIssue);
}
}
}

for (const error of txn.errors) {
if (error.span === span.span_id) {
errorsOrIssues.push(error);
}
}

return [
...currentEvent.errors.filter(error => error.span === span.span_id),
...performanceIssues, // Spans can be shown when embedded in performance issues
];
return errorsOrIssues;
}

function printNode(t: TraceTreeNode<TraceTree.NodeValue>, offset: number): string {
Expand Down