Skip to content

Commit 8798423

Browse files
[APM] Reparenting spans to support inferred spans (elastic#63695) (elastic#63939)
* reparening spans * adding unit test * adding unit test
1 parent bbad5ca commit 8798423

File tree

5 files changed

+918
-2
lines changed

5 files changed

+918
-2
lines changed

x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.test.ts

Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,212 @@ describe('waterfall_helpers', () => {
166166
expect(waterfall.errorsCount).toEqual(0);
167167
expect(waterfall).toMatchSnapshot();
168168
});
169+
it('should reparent spans', () => {
170+
const traceItems = [
171+
{
172+
processor: { event: 'transaction' },
173+
trace: { id: 'myTraceId' },
174+
service: { name: 'opbeans-node' },
175+
transaction: {
176+
duration: { us: 49660 },
177+
name: 'GET /api',
178+
id: 'myTransactionId1'
179+
},
180+
timestamp: { us: 1549324795784006 }
181+
} as Transaction,
182+
{
183+
parent: { id: 'mySpanIdD' },
184+
processor: { event: 'span' },
185+
trace: { id: 'myTraceId' },
186+
service: { name: 'opbeans-ruby' },
187+
transaction: { id: 'myTransactionId1' },
188+
timestamp: { us: 1549324795825633 },
189+
span: {
190+
duration: { us: 481 },
191+
name: 'SELECT FROM products',
192+
id: 'mySpanIdB'
193+
},
194+
child_ids: ['mySpanIdA', 'mySpanIdC']
195+
} as Span,
196+
{
197+
parent: { id: 'mySpanIdD' },
198+
processor: { event: 'span' },
199+
trace: { id: 'myTraceId' },
200+
service: { name: 'opbeans-ruby' },
201+
transaction: { id: 'myTransactionId1' },
202+
span: {
203+
duration: { us: 6161 },
204+
name: 'Api::ProductsController#index',
205+
id: 'mySpanIdA'
206+
},
207+
timestamp: { us: 1549324795824504 }
208+
} as Span,
209+
{
210+
parent: { id: 'mySpanIdD' },
211+
processor: { event: 'span' },
212+
trace: { id: 'myTraceId' },
213+
service: { name: 'opbeans-ruby' },
214+
transaction: { id: 'myTransactionId1' },
215+
span: {
216+
duration: { us: 532 },
217+
name: 'SELECT FROM product',
218+
id: 'mySpanIdC'
219+
},
220+
timestamp: { us: 1549324795827905 }
221+
} as Span,
222+
{
223+
parent: { id: 'myTransactionId1' },
224+
processor: { event: 'span' },
225+
trace: { id: 'myTraceId' },
226+
service: { name: 'opbeans-node' },
227+
transaction: { id: 'myTransactionId1' },
228+
span: {
229+
duration: { us: 47557 },
230+
name: 'GET opbeans-ruby:3000/api/products',
231+
id: 'mySpanIdD'
232+
},
233+
timestamp: { us: 1549324795785760 }
234+
} as Span
235+
];
236+
const entryTransactionId = 'myTransactionId1';
237+
const waterfall = getWaterfall(
238+
{
239+
trace: { items: traceItems, errorDocs: [], exceedsMax: false },
240+
errorsPerTransaction: {}
241+
},
242+
entryTransactionId
243+
);
244+
const getIdAndParentId = (item: IWaterfallItem) => ({
245+
id: item.id,
246+
parentId: item.parent?.id
247+
});
248+
expect(waterfall.items.length).toBe(5);
249+
expect(getIdAndParentId(waterfall.items[0])).toEqual({
250+
id: 'myTransactionId1',
251+
parentId: undefined
252+
});
253+
expect(getIdAndParentId(waterfall.items[1])).toEqual({
254+
id: 'mySpanIdD',
255+
parentId: 'myTransactionId1'
256+
});
257+
expect(getIdAndParentId(waterfall.items[2])).toEqual({
258+
id: 'mySpanIdB',
259+
parentId: 'mySpanIdD'
260+
});
261+
expect(getIdAndParentId(waterfall.items[3])).toEqual({
262+
id: 'mySpanIdA',
263+
parentId: 'mySpanIdB'
264+
});
265+
expect(getIdAndParentId(waterfall.items[4])).toEqual({
266+
id: 'mySpanIdC',
267+
parentId: 'mySpanIdB'
268+
});
269+
expect(waterfall.errorItems.length).toBe(0);
270+
expect(waterfall.errorsCount).toEqual(0);
271+
});
272+
it("shouldn't reparent spans when child id isn't found", () => {
273+
const traceItems = [
274+
{
275+
processor: { event: 'transaction' },
276+
trace: { id: 'myTraceId' },
277+
service: { name: 'opbeans-node' },
278+
transaction: {
279+
duration: { us: 49660 },
280+
name: 'GET /api',
281+
id: 'myTransactionId1'
282+
},
283+
timestamp: { us: 1549324795784006 }
284+
} as Transaction,
285+
{
286+
parent: { id: 'mySpanIdD' },
287+
processor: { event: 'span' },
288+
trace: { id: 'myTraceId' },
289+
service: { name: 'opbeans-ruby' },
290+
transaction: { id: 'myTransactionId1' },
291+
timestamp: { us: 1549324795825633 },
292+
span: {
293+
duration: { us: 481 },
294+
name: 'SELECT FROM products',
295+
id: 'mySpanIdB'
296+
},
297+
child_ids: ['incorrectId', 'mySpanIdC']
298+
} as Span,
299+
{
300+
parent: { id: 'mySpanIdD' },
301+
processor: { event: 'span' },
302+
trace: { id: 'myTraceId' },
303+
service: { name: 'opbeans-ruby' },
304+
transaction: { id: 'myTransactionId1' },
305+
span: {
306+
duration: { us: 6161 },
307+
name: 'Api::ProductsController#index',
308+
id: 'mySpanIdA'
309+
},
310+
timestamp: { us: 1549324795824504 }
311+
} as Span,
312+
{
313+
parent: { id: 'mySpanIdD' },
314+
processor: { event: 'span' },
315+
trace: { id: 'myTraceId' },
316+
service: { name: 'opbeans-ruby' },
317+
transaction: { id: 'myTransactionId1' },
318+
span: {
319+
duration: { us: 532 },
320+
name: 'SELECT FROM product',
321+
id: 'mySpanIdC'
322+
},
323+
timestamp: { us: 1549324795827905 }
324+
} as Span,
325+
{
326+
parent: { id: 'myTransactionId1' },
327+
processor: { event: 'span' },
328+
trace: { id: 'myTraceId' },
329+
service: { name: 'opbeans-node' },
330+
transaction: { id: 'myTransactionId1' },
331+
span: {
332+
duration: { us: 47557 },
333+
name: 'GET opbeans-ruby:3000/api/products',
334+
id: 'mySpanIdD'
335+
},
336+
timestamp: { us: 1549324795785760 }
337+
} as Span
338+
];
339+
const entryTransactionId = 'myTransactionId1';
340+
const waterfall = getWaterfall(
341+
{
342+
trace: { items: traceItems, errorDocs: [], exceedsMax: false },
343+
errorsPerTransaction: {}
344+
},
345+
entryTransactionId
346+
);
347+
const getIdAndParentId = (item: IWaterfallItem) => ({
348+
id: item.id,
349+
parentId: item.parent?.id
350+
});
351+
expect(waterfall.items.length).toBe(5);
352+
expect(getIdAndParentId(waterfall.items[0])).toEqual({
353+
id: 'myTransactionId1',
354+
parentId: undefined
355+
});
356+
expect(getIdAndParentId(waterfall.items[1])).toEqual({
357+
id: 'mySpanIdD',
358+
parentId: 'myTransactionId1'
359+
});
360+
expect(getIdAndParentId(waterfall.items[2])).toEqual({
361+
id: 'mySpanIdA',
362+
parentId: 'mySpanIdD'
363+
});
364+
expect(getIdAndParentId(waterfall.items[3])).toEqual({
365+
id: 'mySpanIdB',
366+
parentId: 'mySpanIdD'
367+
});
368+
expect(getIdAndParentId(waterfall.items[4])).toEqual({
369+
id: 'mySpanIdC',
370+
parentId: 'mySpanIdB'
371+
});
372+
expect(waterfall.errorItems.length).toBe(0);
373+
expect(waterfall.errorsCount).toEqual(0);
374+
});
169375
});
170376

171377
describe('getWaterfallItems', () => {

x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,29 @@ const getWaterfallItems = (items: TraceAPIResponse['trace']['items']) =>
236236
}
237237
});
238238

239+
/**
240+
* Changes the parent_id of items based on the child_ids property.
241+
* Solves the problem of Inferred spans that are created as child of trace spans
242+
* when it actually should be its parent.
243+
* @param waterfallItems
244+
*/
245+
const reparentSpans = (waterfallItems: IWaterfallItem[]) => {
246+
return waterfallItems.map(waterfallItem => {
247+
if (waterfallItem.docType === 'span') {
248+
const { child_ids: childIds } = waterfallItem.doc;
249+
if (childIds) {
250+
childIds.forEach(childId => {
251+
const item = waterfallItems.find(_item => _item.id === childId);
252+
if (item) {
253+
item.parentId = waterfallItem.id;
254+
}
255+
});
256+
}
257+
}
258+
return waterfallItem;
259+
});
260+
};
261+
239262
const getChildrenGroupedByParentId = (waterfallItems: IWaterfallItem[]) =>
240263
groupBy(waterfallItems, item => (item.parentId ? item.parentId : ROOT_ID));
241264

@@ -306,7 +329,9 @@ export function getWaterfall(
306329

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

309-
const childrenByParentId = getChildrenGroupedByParentId(waterfallItems);
332+
const childrenByParentId = getChildrenGroupedByParentId(
333+
reparentSpans(waterfallItems)
334+
);
310335

311336
const entryWaterfallTransaction = getEntryWaterfallTransaction(
312337
entryTransactionId,

x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/WaterfallContainer.stories.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import {
1414
urlParams,
1515
simpleTrace,
1616
traceWithErrors,
17-
traceChildStartBeforeParent
17+
traceChildStartBeforeParent,
18+
inferredSpans
1819
} from './waterfallContainer.stories.data';
1920
import { getWaterfall } from './Waterfall/waterfall_helpers/waterfall_helpers';
2021

@@ -74,3 +75,22 @@ storiesOf('app/TransactionDetails/Waterfall', module).add(
7475
},
7576
{ info: { source: false } }
7677
);
78+
79+
storiesOf('app/TransactionDetails/Waterfall', module).add(
80+
'inferred spans',
81+
() => {
82+
const waterfall = getWaterfall(
83+
inferredSpans as TraceAPIResponse,
84+
'f2387d37260d00bd'
85+
);
86+
return (
87+
<WaterfallContainer
88+
location={location}
89+
urlParams={urlParams}
90+
waterfall={waterfall}
91+
exceedsMax={false}
92+
/>
93+
);
94+
},
95+
{ info: { source: false } }
96+
);

0 commit comments

Comments
 (0)