/** * {@inheritDoc} */ @Override public Object[] getElements(Object inputElement) { InvocationTreeElement element = validateInput(inputElement); if (element == null) { return new Object[0]; } return new Object[] { element.getDataElement() }; }
/** * {@inheritDoc} */ @Override public Object[] getChildren(Object inputElement) { InvocationTreeElement treeElement = InvocationTreeUtil.lookupTreeElement(lookupMap, inputElement); List<Object> objects = new ArrayList<>(); for (InvocationTreeElement its : treeElement.getChildren()) { objects.add(its.getDataElement()); } return objects.toArray(); }
/** * Builds the {@link #lookupMap} map of the given tree. This allows direct access to the tree * elements via data objects. * * @param tree * the tree * @return the lookup map for the given tree */ public static Map<Object, InvocationTreeElement> buildLookupMap(InvocationTreeElement tree) { return InvocationTreeUtil.getRoot(tree).asStream().collect(Collectors.toMap(e -> calculateLookupKey(e.getDataElement()), Function.identity())); }
/** * Returns whether the given element is considered as asynchronous. It will return always * <code>false</code> if the type is not {@link TreeElementType#SPAN}. * * @param element * the element to check * @return <code>true</code> if the element is considered as asynchronous */ public static boolean isConsideredAsync(InvocationTreeElement element) { if (!element.isSpan()) { return false; } return References.FOLLOWS_FROM.equals(((Span) element.getDataElement()).getReferenceType()); } }
/** * Returns all {@link InvocationSequenceData} which is contained in the given * {@link InvocationTreeElement} or in its child nodes. {@link InvocationTreeElement} of type * {@link InvocationTreeElement.TreeElementType#MISSING_SPAN} or * {@link InvocationTreeElement.TreeElementType#SPAN} will be ignored. * <p> * Note: the returned list will contain only root invocation sequences * * @param tree * the top element of the tree to extract the data * @return {@link List} of {@link InvocationSequenceData} */ public static List<InvocationSequenceData> getInvocationSequences(InvocationTreeElement tree) { if (tree == null) { return Collections.emptyList(); } return tree.asStream().filter(e -> e.getType() == TreeElementType.INVOCATION_SEQUENCE).map(e -> (InvocationSequenceData) e.getDataElement()).filter(i -> i.getParentSequence() == null) .collect(Collectors.toList()); }
/** * Returns exclusive duration of the Span contained in the given {@link InvocationTreeElement}. * Note: This method works only with {@link InvocationTreeElement} of type * {@link TreeElementType#SPAN}. * * @param element * the {@link InvocationTreeElement} to calculate the exclusive duration for * @return Returns exclusive duration. */ public static double calculateSpanExclusiveDuration(InvocationTreeElement element) { if (element == null) { return Double.NaN; } if (!element.isSpan()) { return 0D; } double childrenDuration = element.getChildren().stream().mapToDouble(InvocationTreeUtil::calculateSpanParentRelativeDuration).sum(); double exclusiveDuration = ((Span) element.getDataElement()).getDuration() - childrenDuration; return Math.max(exclusiveDuration, 0D); }
@Test public void spanDataElement() { Object data = mock(Span.class); InvocationTreeElement element = new InvocationTreeElement(data); assertThat(element.getDataElement(), is(data)); assertThat(element.getDataElement(), is(instanceOf(Span.class))); }
@Test public void invocationSequenceDataElement() { Object data = mock(InvocationSequenceData.class); InvocationTreeElement element = new InvocationTreeElement(data); assertThat(element.getDataElement(), is(data)); }
@Test public void spanIdentDataElement() { Object data = mock(SpanIdent.class); InvocationTreeElement element = new InvocationTreeElement(data); assertThat(element.getDataElement(), is(data)); }
/** * {@inheritDoc} */ @Override public Object getParent(Object element) { InvocationTreeElement ite = InvocationTreeUtil.lookupTreeElement(lookupMap, element); if ((ite == null) || (ite.getParent() == null)) { return null; } else { return ite.getParent().getDataElement(); } }
/** * Returns the relative duration of the span contained in the given element. If the element is * an asynchronous span, the method will return <code>0</code> because we cannot specify a * relative duration of an asynchronous span relative to its parent. * <p> * If the element is not of type {@link TreeElementType#SPAN} the sum of all direct child spans * are calculated. This means that when the child spans are not direct children in the tree but * are connected via several invocation sequence to the parent span we handle them as well. * * @param element * the element to calculate the duration * @return the relative duration of the span or the sum of all direct child spans */ private static double calculateSpanParentRelativeDuration(InvocationTreeElement element) { if (element.isSpan()) { if (InvocationTreeUtil.isConsideredAsync(element)) { return 0D; } else { return ((Span) element.getDataElement()).getDuration(); } } // This case is only necessary in full trees which do not consist of span elements only but // multiple spans are connected together over several invocation sequences. In a tree // consisting only of span elements, this case can never happen. double nestedDuration = element.getChildren().stream().mapToDouble(InvocationTreeUtil::calculateSpanParentRelativeDuration).sum(); return nestedDuration; }
@Test public void successful02() { Map<Object, InvocationTreeElement> lookupMap = InvocationTreeUtil.buildLookupMap(tree); InvocationTreeElement treeElement = InvocationTreeUtil.lookupTreeElement(lookupMap, span01); assertThat(treeElement.getDataElement(), is(span01)); assertThat(treeElement, is(tree)); }
@Test public void successful01() { Map<Object, InvocationTreeElement> lookupMap = InvocationTreeUtil.buildLookupMap(tree); InvocationTreeElement treeElement = InvocationTreeUtil.lookupTreeElement(lookupMap, _invoc02); assertThat(treeElement.getDataElement(), is(_invoc02)); }
@Test public void buildTraceWithoutSdkSpans() { Span span01 = createServerSpan(null); Span span02 = createServerSpan(span01); Span span03 = createSdkSpan(span01); InvocationSequenceData invoc01 = createSequence(span01); InvocationSequenceData _invoc02 = createSequence(span02); invoc01.getNestedSequences().add(_invoc02); doReturn(Arrays.asList(span01, span02, span03)).when(spanService).getSpans(1337L); doReturn(Arrays.asList(invoc01)).when(invocationService).getInvocationSequenceDetail(1337L); builder.setSpanService(spanService).setInvocationService(invocationService).setTraceId(1337L); InvocationTreeElement tree = builder.build(); Map<Object, InvocationTreeElement> lookupMap = InvocationTreeUtil.buildLookupMap(tree); assertThat(lookupMap.size(), is(4)); assertThat(tree.getDataElement(), is(span01)); for (Object data : new Object[] { span01, span02, invoc01, _invoc02 }) { assertThat(InvocationTreeUtil.lookupTreeElement(lookupMap, data), is(not(nullValue()))); } assertThat(InvocationTreeUtil.lookupTreeElement(lookupMap, span03), is(nullValue())); verify(spanService).getSpans(1337L); verify(invocationService).getInvocationSequenceDetail(1337L); verifyNoMoreInteractions(spanService, invocationService); }
@Test public void buildOneInvocationSequenceWithSpanWithoutSpanService() { Span span01 = createServerSpan(null); InvocationSequenceData invoc01 = createSequence(span01); InvocationSequenceData _invoc02 = createSequence(null); invoc01.getNestedSequences().add(_invoc02); builder.setMode(Mode.SINGLE).setInvocationSequence(invoc01); InvocationTreeElement tree = builder.build(); Map<Object, InvocationTreeElement> lookupMap = InvocationTreeUtil.buildLookupMap(tree); assertThat(lookupMap.size(), is(3)); assertThat(tree.getDataElement(), isA((Class) SpanIdent.class)); assertThat(tree.getChildren(), contains(hasProperty("dataElement", is(invoc01)))); verifyZeroInteractions(spanService, invocationService); }
/** * Resolves additional span details (e.g. has nested exception or SQL data) for the given * {@link InvocationTreeElement}. * * @param element * the {@link InvocationTreeElement} which details should be resolved * @param spanToInvocationSequenceMap * lookup map for mapping the spans to {@link InvocationSequenceData} */ private void resolveSpanDetails(InvocationTreeElement element, Map<Long, InvocationSequenceData> spanToInvocationSequenceMap) { if (!element.isSpan()) { return; } InvocationSequenceData sequenceData = spanToInvocationSequenceMap.get(((Span) element.getDataElement()).getSpanIdent().getId()); if (sequenceData != null) { element.setHasNestedSqls((sequenceData.isNestedSqlStatements() != null) && sequenceData.isNestedSqlStatements()); element.setHasNestedExceptions((sequenceData.isNestedExceptions() != null) && sequenceData.isNestedExceptions()); } }
@Test public void buildOneInvocationSequence() { InvocationSequenceData invoc01 = createSequence(null); InvocationSequenceData _invoc02 = createSequence(null); InvocationSequenceData _invoc03 = createSequence(null); InvocationSequenceData _invoc04 = createSequence(null); invoc01.getNestedSequences().add(_invoc02); _invoc02.getNestedSequences().add(_invoc03); invoc01.getNestedSequences().add(_invoc04); builder.setMode(Mode.SINGLE).setInvocationSequence(invoc01); InvocationTreeElement tree = builder.build(); Map<Object, InvocationTreeElement> lookupMap = InvocationTreeUtil.buildLookupMap(tree); assertThat(lookupMap.size(), is(4)); assertThat(tree.getDataElement(), is(invoc01)); assertThat(tree.getChildren(), contains(hasProperty("dataElement", is(_invoc02)), hasProperty("dataElement", is(_invoc04)))); assertThat(tree.getChildren().get(0).getChildren(), contains(hasProperty("dataElement", is(_invoc03)))); assertThat(tree.getChildren().get(1).getChildren(), is(empty())); verifyZeroInteractions(spanService, invocationService); }
@Test public void buildOneInvocationSequenceWithSpan() { Span span01 = createServerSpan(null); InvocationSequenceData invoc01 = createSequence(span01); InvocationSequenceData _invoc02 = createSequence(null); invoc01.getNestedSequences().add(_invoc02); doReturn(span01).when(spanService).get(span01.getSpanIdent()); builder.setSpanService(spanService).setMode(Mode.SINGLE).setInvocationSequence(invoc01); InvocationTreeElement tree = builder.build(); Map<Object, InvocationTreeElement> lookupMap = InvocationTreeUtil.buildLookupMap(tree); assertThat(lookupMap.size(), is(3)); assertThat(tree.getDataElement(), is(span01)); assertThat(tree.getChildren(), contains(hasProperty("dataElement", is(invoc01)))); verify(spanService).get(span01.getSpanIdent()); verifyNoMoreInteractions(spanService); verifyZeroInteractions(invocationService); }
/** * Returns exclusive duration as a percentage relative to its parents. Note: This method works * only with {@link InvocationTreeElement} of type {@link TreeElementType#SPAN}. * * @param element * the {@link InvocationTreeElement} to calculate the exclusive percentage for * @return Returns exclusive duration. */ public static double calculateSpanExclusivePercentage(InvocationTreeElement element) { if (element == null) { return Double.NaN; } InvocationTreeElement topElement = element; while ((topElement.getParent() != null) && !isConsideredAsync(topElement)) { topElement = topElement.getParent(); } if (topElement.isSpan()) { return InvocationTreeUtil.calculateSpanExclusiveDuration(element) / ((Span) topElement.getDataElement()).getDuration(); } return 0D; }
@Test public void buildTraceManyMissingSpans() { Span span01 = createServerSpan(null); Span span02 = createServerSpan(span01); Span span03 = createClientSpan(span01); Span span04 = createServerSpan(span01); InvocationSequenceData invoc01 = createSequence(span01); InvocationSequenceData _invoc02 = createSequence(span02); InvocationSequenceData _invoc03 = createSequence(span03); InvocationSequenceData _invoc04 = createSequence(span04); invoc01.getNestedSequences().addAll(Arrays.asList(_invoc02, _invoc03, _invoc04)); doReturn(Arrays.asList(span01)).when(spanService).getSpans(1337L); doReturn(Arrays.asList(invoc01)).when(invocationService).getInvocationSequenceDetail(1337L); builder.setSpanService(spanService).setInvocationService(invocationService).setTraceId(1337L); InvocationTreeElement tree = builder.build(); Map<Object, InvocationTreeElement> lookupMap = InvocationTreeUtil.buildLookupMap(tree); assertThat(lookupMap.size(), is(8)); assertThat(tree.getDataElement(), is(span01)); for (Object data : new Object[] { span01, span02, span03, span04, invoc01, _invoc02, _invoc03, _invoc04 }) { assertThat(InvocationTreeUtil.lookupTreeElement(lookupMap, data), is(not(nullValue()))); } verify(spanService).getSpans(1337L); verify(invocationService).getInvocationSequenceDetail(1337L); verifyNoMoreInteractions(spanService, invocationService); }