diff --git a/hypertrace-trace-enricher/enriched-span-constants/src/main/java/org/hypertrace/traceenricher/enrichedspan/constants/utils/EnrichedSpanUtils.java b/hypertrace-trace-enricher/enriched-span-constants/src/main/java/org/hypertrace/traceenricher/enrichedspan/constants/utils/EnrichedSpanUtils.java index dbcb1d824..99bb9e7f8 100644 --- a/hypertrace-trace-enricher/enriched-span-constants/src/main/java/org/hypertrace/traceenricher/enrichedspan/constants/utils/EnrichedSpanUtils.java +++ b/hypertrace-trace-enricher/enriched-span-constants/src/main/java/org/hypertrace/traceenricher/enrichedspan/constants/utils/EnrichedSpanUtils.java @@ -8,6 +8,7 @@ import java.util.Map; import java.util.Optional; import org.apache.avro.reflect.Nullable; +import org.apache.commons.codec.binary.StringUtils; import org.hypertrace.core.datamodel.AttributeValue; import org.hypertrace.core.datamodel.Event; import org.hypertrace.core.datamodel.shared.SpanAttributeUtils; @@ -368,4 +369,12 @@ public static List getSpaceIds(Event event) { .map(AttributeValue::getValueList) .orElseGet(Collections::emptyList); } + + /** Check whether these spans belongs to different services. */ + public static boolean areBothSpansFromDifferentService(Event event, Event parentEvent) { + if (event.getServiceName() == null || parentEvent.getServiceName() == null) { + return false; + } + return !StringUtils.equals(event.getServiceName(), parentEvent.getServiceName()); + } } diff --git a/hypertrace-trace-enricher/hypertrace-trace-enricher-api/src/main/java/org/hypertrace/traceenricher/trace/util/ApiTraceGraph.java b/hypertrace-trace-enricher/hypertrace-trace-enricher-api/src/main/java/org/hypertrace/traceenricher/trace/util/ApiTraceGraph.java index e1630c1a8..2e790c442 100644 --- a/hypertrace-trace-enricher/hypertrace-trace-enricher-api/src/main/java/org/hypertrace/traceenricher/trace/util/ApiTraceGraph.java +++ b/hypertrace-trace-enricher/hypertrace-trace-enricher-api/src/main/java/org/hypertrace/traceenricher/trace/util/ApiTraceGraph.java @@ -272,12 +272,11 @@ private ApiNode buildApiNode(StructuredTraceGraph graph, Event rootEvent) private void buildApiNodeEdges(StructuredTraceGraph graph) { // 1. get all the exit boundary events from an api node - // 2. exit boundary events are the only ones which can call a different api node - // 3. find all the children of exit boundary events, which will be entry boundary nodes of + // 2. find all the children of exit boundary events, which will be entry boundary nodes of // different api nodes - // 4. find all the api nodes based on children of exit boundary events from + // 3. find all the api nodes based on children of exit boundary events from // `entryBoundaryToApiNode` - // 5. connect the exit boundary and entry boundary of different api node with an edge + // 4. connect the exit boundary and entry boundary of different api node with an edge for (ApiNode apiNode : apiNodeList) { // exit boundary events of api node List exitBoundaryEvents = apiNode.getExitApiBoundaryEvents(); @@ -325,6 +324,42 @@ private void buildApiNodeEdges(StructuredTraceGraph graph) { } } } + // Sometimes an exit span might be missing for services like Istio, Kong. + // Only Entry spans will be populated for these services, + // an edge must be created between these services as well. + // 1. get entry boundary event from an api node. + // 2. find all the children of entry boundary event, which will be entry boundary nodes of + // different api nodes + // 3. Check both parent span and child belong to different service as well. + // 4. connect the entry boundary of different api nodes with an edge. + Optional entryBoundaryEvent = apiNode.getEntryApiBoundaryEvent(); + if (entryBoundaryEvent.isPresent()) { + List children = graph.getChildrenEvents(entryBoundaryEvent.get()); + if (children != null) { + for (Event child : children) { + // if the child of an entry boundary event is an entry api boundary type, + // which can happen if exit span missing and both belongs to different services. + if (EnrichedSpanUtils.isEntryApiBoundary(child) + && EnrichedSpanUtils.areBothSpansFromDifferentService( + child, entryBoundaryEvent.get())) { + ApiNode destinationApiNode = entryApiBoundaryEventIdToApiNode.get(child); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug( + "Edge between entry boundaries servicename: {} span: {} to servicename: {} span: {} of trace {}", + entryBoundaryEvent.get().getServiceName(), + HexUtils.getHex(entryBoundaryEvent.get().getEventId()), + child.getServiceName(), + HexUtils.getHex(child.getEventId()), + HexUtils.getHex(trace.getTraceId())); + } + Optional edgeBetweenApiNodes = + createEdgeBetweenApiNodes( + apiNode, destinationApiNode, entryBoundaryEvent.get(), child); + edgeBetweenApiNodes.ifPresent(apiNodeEventEdgeList::add); + } + } + } + } } } diff --git a/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/src/main/java/org/hypertrace/traceenricher/enrichment/enrichers/ApiBoundaryTypeAttributeEnricher.java b/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/src/main/java/org/hypertrace/traceenricher/enrichment/enrichers/ApiBoundaryTypeAttributeEnricher.java index d4132b539..cf4fdbda0 100644 --- a/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/src/main/java/org/hypertrace/traceenricher/enrichment/enrichers/ApiBoundaryTypeAttributeEnricher.java +++ b/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/src/main/java/org/hypertrace/traceenricher/enrichment/enrichers/ApiBoundaryTypeAttributeEnricher.java @@ -83,11 +83,12 @@ public void enrichEvent(StructuredTrace trace, Event event) { /* Determines if this is entry span is an entry to api 1. If the event is an ENTRY span, and - 2. If the direct parent span is either an EXIT Span or Null, then this is the entry point. + 2. If the direct parent span is either an EXIT Span or Null, or direct parent span service name differs from current span service name. */ Event parentEvent = graph.getParentEvent(event); - if (!EnrichedSpanUtils.isEntrySpan(parentEvent)) { + if (!EnrichedSpanUtils.isEntrySpan(parentEvent) + || EnrichedSpanUtils.areBothSpansFromDifferentService(event, parentEvent)) { addEnrichedAttribute( event, API_BOUNDARY_TYPE_ATTR_NAME, AttributeValueCreator.create(ENTRY_BOUNDARY_TYPE)); diff --git a/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/src/test/java/org/hypertrace/traceenricher/enrichment/enrichers/AbstractAttributeEnricherTest.java b/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/src/test/java/org/hypertrace/traceenricher/enrichment/enrichers/AbstractAttributeEnricherTest.java index b5f114072..21d4cb854 100644 --- a/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/src/test/java/org/hypertrace/traceenricher/enrichment/enrichers/AbstractAttributeEnricherTest.java +++ b/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/src/test/java/org/hypertrace/traceenricher/enrichment/enrichers/AbstractAttributeEnricherTest.java @@ -69,6 +69,7 @@ Event createMockEvent() { lenient() .when(e.getMetrics()) .thenReturn(Metrics.newBuilder().setMetricMap(new HashMap<>()).build()); + when(e.getServiceName()).thenReturn("service"); return e; } diff --git a/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/src/test/java/org/hypertrace/traceenricher/enrichment/enrichers/ApiBoundaryTypeAttributeEnricherTest.java b/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/src/test/java/org/hypertrace/traceenricher/enrichment/enrichers/ApiBoundaryTypeAttributeEnricherTest.java index f81272693..2f3be1baf 100644 --- a/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/src/test/java/org/hypertrace/traceenricher/enrichment/enrichers/ApiBoundaryTypeAttributeEnricherTest.java +++ b/hypertrace-trace-enricher/hypertrace-trace-enricher-impl/src/test/java/org/hypertrace/traceenricher/enrichment/enrichers/ApiBoundaryTypeAttributeEnricherTest.java @@ -118,6 +118,24 @@ public void test_enrichEvent_doubleEntryExitSpan_markTheOuterOneAsApiBoundary() Constants.getEnrichedSpanConstant(EXIT)); } + @Test + public void test_enrichEvent_doubleEntrySpan_differentServiceName() { + // OuterEntry -> inner Entry -> inner Exit -> outer Exit + mockDoubleEntryStructuredGraph(); + target.enrichEvent(trace, outerEntrySpan); + Assertions.assertEquals( + EnrichedSpanUtils.getApiBoundaryType(outerEntrySpan), + Constants.getEnrichedSpanConstant(ENTRY)); + target.enrichEvent(trace, innerEntrySpan); + Assertions.assertNull(EnrichedSpanUtils.getApiBoundaryType(innerEntrySpan)); + mockDoubleEntryStructuredGraph(); + when(innerEntrySpan.getServiceName()).thenReturn("service2"); + target.enrichEvent(trace, innerEntrySpan); + Assertions.assertEquals( + EnrichedSpanUtils.getApiBoundaryType(innerEntrySpan), + Constants.getEnrichedSpanConstant(ENTRY)); + } + @Test public void test_enrichEvent_loopToItsOwnService_findTwoAPIEntries() { // Entry to API 1 -> Exit From API 1 & Re-entry -> Entry to API 1 -> Exit to API 2