Skip to content

Commit

Permalink
[APM] Reparenting spans to support inferred spans (#63695)
Browse files Browse the repository at this point in the history
* reparening spans

* adding unit test

* adding unit test
  • Loading branch information
cauemarcondes committed Apr 17, 2020
1 parent 1992716 commit 675c589
Show file tree
Hide file tree
Showing 5 changed files with 918 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,212 @@ describe('waterfall_helpers', () => {
expect(waterfall.errorsCount).toEqual(0);
expect(waterfall).toMatchSnapshot();
});
it('should reparent spans', () => {
const traceItems = [
{
processor: { event: 'transaction' },
trace: { id: 'myTraceId' },
service: { name: 'opbeans-node' },
transaction: {
duration: { us: 49660 },
name: 'GET /api',
id: 'myTransactionId1'
},
timestamp: { us: 1549324795784006 }
} as Transaction,
{
parent: { id: 'mySpanIdD' },
processor: { event: 'span' },
trace: { id: 'myTraceId' },
service: { name: 'opbeans-ruby' },
transaction: { id: 'myTransactionId1' },
timestamp: { us: 1549324795825633 },
span: {
duration: { us: 481 },
name: 'SELECT FROM products',
id: 'mySpanIdB'
},
child_ids: ['mySpanIdA', 'mySpanIdC']
} as Span,
{
parent: { id: 'mySpanIdD' },
processor: { event: 'span' },
trace: { id: 'myTraceId' },
service: { name: 'opbeans-ruby' },
transaction: { id: 'myTransactionId1' },
span: {
duration: { us: 6161 },
name: 'Api::ProductsController#index',
id: 'mySpanIdA'
},
timestamp: { us: 1549324795824504 }
} as Span,
{
parent: { id: 'mySpanIdD' },
processor: { event: 'span' },
trace: { id: 'myTraceId' },
service: { name: 'opbeans-ruby' },
transaction: { id: 'myTransactionId1' },
span: {
duration: { us: 532 },
name: 'SELECT FROM product',
id: 'mySpanIdC'
},
timestamp: { us: 1549324795827905 }
} as Span,
{
parent: { id: 'myTransactionId1' },
processor: { event: 'span' },
trace: { id: 'myTraceId' },
service: { name: 'opbeans-node' },
transaction: { id: 'myTransactionId1' },
span: {
duration: { us: 47557 },
name: 'GET opbeans-ruby:3000/api/products',
id: 'mySpanIdD'
},
timestamp: { us: 1549324795785760 }
} as Span
];
const entryTransactionId = 'myTransactionId1';
const waterfall = getWaterfall(
{
trace: { items: traceItems, errorDocs: [], exceedsMax: false },
errorsPerTransaction: {}
},
entryTransactionId
);
const getIdAndParentId = (item: IWaterfallItem) => ({
id: item.id,
parentId: item.parent?.id
});
expect(waterfall.items.length).toBe(5);
expect(getIdAndParentId(waterfall.items[0])).toEqual({
id: 'myTransactionId1',
parentId: undefined
});
expect(getIdAndParentId(waterfall.items[1])).toEqual({
id: 'mySpanIdD',
parentId: 'myTransactionId1'
});
expect(getIdAndParentId(waterfall.items[2])).toEqual({
id: 'mySpanIdB',
parentId: 'mySpanIdD'
});
expect(getIdAndParentId(waterfall.items[3])).toEqual({
id: 'mySpanIdA',
parentId: 'mySpanIdB'
});
expect(getIdAndParentId(waterfall.items[4])).toEqual({
id: 'mySpanIdC',
parentId: 'mySpanIdB'
});
expect(waterfall.errorItems.length).toBe(0);
expect(waterfall.errorsCount).toEqual(0);
});
it("shouldn't reparent spans when child id isn't found", () => {
const traceItems = [
{
processor: { event: 'transaction' },
trace: { id: 'myTraceId' },
service: { name: 'opbeans-node' },
transaction: {
duration: { us: 49660 },
name: 'GET /api',
id: 'myTransactionId1'
},
timestamp: { us: 1549324795784006 }
} as Transaction,
{
parent: { id: 'mySpanIdD' },
processor: { event: 'span' },
trace: { id: 'myTraceId' },
service: { name: 'opbeans-ruby' },
transaction: { id: 'myTransactionId1' },
timestamp: { us: 1549324795825633 },
span: {
duration: { us: 481 },
name: 'SELECT FROM products',
id: 'mySpanIdB'
},
child_ids: ['incorrectId', 'mySpanIdC']
} as Span,
{
parent: { id: 'mySpanIdD' },
processor: { event: 'span' },
trace: { id: 'myTraceId' },
service: { name: 'opbeans-ruby' },
transaction: { id: 'myTransactionId1' },
span: {
duration: { us: 6161 },
name: 'Api::ProductsController#index',
id: 'mySpanIdA'
},
timestamp: { us: 1549324795824504 }
} as Span,
{
parent: { id: 'mySpanIdD' },
processor: { event: 'span' },
trace: { id: 'myTraceId' },
service: { name: 'opbeans-ruby' },
transaction: { id: 'myTransactionId1' },
span: {
duration: { us: 532 },
name: 'SELECT FROM product',
id: 'mySpanIdC'
},
timestamp: { us: 1549324795827905 }
} as Span,
{
parent: { id: 'myTransactionId1' },
processor: { event: 'span' },
trace: { id: 'myTraceId' },
service: { name: 'opbeans-node' },
transaction: { id: 'myTransactionId1' },
span: {
duration: { us: 47557 },
name: 'GET opbeans-ruby:3000/api/products',
id: 'mySpanIdD'
},
timestamp: { us: 1549324795785760 }
} as Span
];
const entryTransactionId = 'myTransactionId1';
const waterfall = getWaterfall(
{
trace: { items: traceItems, errorDocs: [], exceedsMax: false },
errorsPerTransaction: {}
},
entryTransactionId
);
const getIdAndParentId = (item: IWaterfallItem) => ({
id: item.id,
parentId: item.parent?.id
});
expect(waterfall.items.length).toBe(5);
expect(getIdAndParentId(waterfall.items[0])).toEqual({
id: 'myTransactionId1',
parentId: undefined
});
expect(getIdAndParentId(waterfall.items[1])).toEqual({
id: 'mySpanIdD',
parentId: 'myTransactionId1'
});
expect(getIdAndParentId(waterfall.items[2])).toEqual({
id: 'mySpanIdA',
parentId: 'mySpanIdD'
});
expect(getIdAndParentId(waterfall.items[3])).toEqual({
id: 'mySpanIdB',
parentId: 'mySpanIdD'
});
expect(getIdAndParentId(waterfall.items[4])).toEqual({
id: 'mySpanIdC',
parentId: 'mySpanIdB'
});
expect(waterfall.errorItems.length).toBe(0);
expect(waterfall.errorsCount).toEqual(0);
});
});

describe('getWaterfallItems', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,29 @@ const getWaterfallItems = (items: TraceAPIResponse['trace']['items']) =>
}
});

/**
* Changes the parent_id of items based on the child_ids property.
* Solves the problem of Inferred spans that are created as child of trace spans
* when it actually should be its parent.
* @param waterfallItems
*/
const reparentSpans = (waterfallItems: IWaterfallItem[]) => {
return waterfallItems.map(waterfallItem => {
if (waterfallItem.docType === 'span') {
const { child_ids: childIds } = waterfallItem.doc;
if (childIds) {
childIds.forEach(childId => {
const item = waterfallItems.find(_item => _item.id === childId);
if (item) {
item.parentId = waterfallItem.id;
}
});
}
}
return waterfallItem;
});
};

const getChildrenGroupedByParentId = (waterfallItems: IWaterfallItem[]) =>
groupBy(waterfallItems, item => (item.parentId ? item.parentId : ROOT_ID));

Expand Down Expand Up @@ -306,7 +329,9 @@ export function getWaterfall(

const waterfallItems: IWaterfallItem[] = getWaterfallItems(trace.items);

const childrenByParentId = getChildrenGroupedByParentId(waterfallItems);
const childrenByParentId = getChildrenGroupedByParentId(
reparentSpans(waterfallItems)
);

const entryWaterfallTransaction = getEntryWaterfallTransaction(
entryTransactionId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
urlParams,
simpleTrace,
traceWithErrors,
traceChildStartBeforeParent
traceChildStartBeforeParent,
inferredSpans
} from './waterfallContainer.stories.data';
import { getWaterfall } from './Waterfall/waterfall_helpers/waterfall_helpers';

Expand Down Expand Up @@ -74,3 +75,22 @@ storiesOf('app/TransactionDetails/Waterfall', module).add(
},
{ info: { source: false } }
);

storiesOf('app/TransactionDetails/Waterfall', module).add(
'inferred spans',
() => {
const waterfall = getWaterfall(
inferredSpans as TraceAPIResponse,
'f2387d37260d00bd'
);
return (
<WaterfallContainer
location={location}
urlParams={urlParams}
waterfall={waterfall}
exceedsMax={false}
/>
);
},
{ info: { source: false } }
);
Loading

0 comments on commit 675c589

Please sign in to comment.