1616
1717package com .navercorp .pinpoint .otlp .trace .collector .mapper ;
1818
19+ import com .google .protobuf .ByteString ;
1920import com .navercorp .pinpoint .common .server .bo .AgentInfoBo ;
2021import com .navercorp .pinpoint .common .server .bo .SpanBo ;
2122import com .navercorp .pinpoint .common .server .bo .SpanChunkBo ;
23+ import com .navercorp .pinpoint .common .server .bo .SpanEventBo ;
2224import com .navercorp .pinpoint .otlp .trace .collector .OtlpTraceCollectorRejectedSpan ;
2325import io .opentelemetry .proto .common .v1 .KeyValue ;
2426import io .opentelemetry .proto .trace .v1 .ResourceSpans ;
2830import org .apache .logging .log4j .Logger ;
2931import org .springframework .stereotype .Component ;
3032
33+ import java .util .ArrayList ;
34+ import java .util .HashMap ;
35+ import java .util .HashSet ;
3136import java .util .List ;
37+ import java .util .Map ;
38+ import java .util .Set ;
39+ import java .util .concurrent .TimeUnit ;
3240
3341@ Component
3442public class OtlpTraceMapper {
@@ -42,17 +50,23 @@ public class OtlpTraceMapper {
4250 public static final String LINK_METHOD_NAME = "Link" ;
4351
4452 private final OtlpTraceSpanMapper spanMapper ;
53+ private final OtlpTraceSpanEventMapper spanEventMapper ;
4554 private final OtlpTraceSpanChunkMapper spanChunkMapper ;
4655 private final OtlpAgentInfoMapper agentInfoMapper ;
4756
48- public OtlpTraceMapper (OtlpTraceSpanMapper spanMapper , OtlpTraceSpanChunkMapper spanChunkMapper , OtlpAgentInfoMapper agentInfoMapper ) {
57+ public OtlpTraceMapper (OtlpTraceSpanMapper spanMapper , OtlpTraceSpanEventMapper spanEventMapper , OtlpTraceSpanChunkMapper spanChunkMapper , OtlpAgentInfoMapper agentInfoMapper ) {
4958 this .spanMapper = spanMapper ;
59+ this .spanEventMapper = spanEventMapper ;
5060 this .spanChunkMapper = spanChunkMapper ;
5161 this .agentInfoMapper = agentInfoMapper ;
5262 }
5363
64+ // sort by traceId
65+ // find root span, server type, no parentSpanId
66+ // link span, find parentSpanId
5467 public OtlpTraceMapperData map (List <ResourceSpans > resourceSpanList ) {
5568 final OtlpTraceMapperData mapperData = new OtlpTraceMapperData ();
69+ int errorCount = 0 ;
5670 for (ResourceSpans resourceSpan : resourceSpanList ) {
5771 final IdAndName idAndName = getId (mapperData , resourceSpan );
5872 if (idAndName == null ) {
@@ -62,45 +76,53 @@ public OtlpTraceMapperData map(List<ResourceSpans> resourceSpanList) {
6276
6377 final List <KeyValue > attributesList = resourceSpan .getResource ().getAttributesList ();
6478 final List <ScopeSpans > scopeSpanList = resourceSpan .getScopeSpansList ();
65- for (ScopeSpans scopeSpan : scopeSpanList ) {
66- List <Span > spansList = scopeSpan .getSpansList ();
67- int errorCount = 0 ;
68- for (Span span : spansList ) {
79+ final Map <ByteString , List <Span >> spanMap = getSpanMap (scopeSpanList );
80+
81+ // find root span, server type, no parentSpanId
82+ for (Map .Entry <ByteString , List <Span >> entry : spanMap .entrySet ()) {
83+ List <Span > rootSpanList = new ArrayList <>();
84+ List <Span > childSpanList = new ArrayList <>();
85+ initRootAndChild (entry .getValue (), rootSpanList , childSpanList );
86+
87+ for (Span rootSpan : rootSpanList ) {
88+ try {
89+ final SpanBo spanBo = spanMapper .map (idAndName , rootSpan );
90+ final List <SpanEventBo > spanEventList = findLinkSpan (spanBo .getStartTime (), childSpanList , rootSpan .getSpanId (), 1 );
91+ spanBo .addSpanEventBoList (spanEventList );
92+ mapperData .addSpanBo (spanBo );
93+ final AgentInfoBo agentInfoBo = agentInfoMapper .map (spanBo , attributesList );
94+ mapperData .addAgentInfoBo (agentInfoBo );
95+ } catch (Exception e ) {
96+ errorCount ++;
97+ logger .warn ("Failed to map span" , e );
98+ }
99+ }
100+
101+ // Build trees from remaining child spans (orphan sub-traces)
102+ if (!childSpanList .isEmpty ()) {
69103 try {
70- switch (span .getKind ().getNumber ()) {
71- case Span .SpanKind .SPAN_KIND_SERVER_VALUE ,
72- Span .SpanKind .SPAN_KIND_CONSUMER_VALUE -> {
73- final SpanBo spanBo = spanMapper .map (idAndName , span );
74- mapperData .addSpanBo (spanBo );
75- final AgentInfoBo agentInfoBo = agentInfoMapper .map (spanBo , attributesList );
76- mapperData .addAgentInfoBo (agentInfoBo );
77- }
78- case Span .SpanKind .SPAN_KIND_CLIENT_VALUE -> {
79- final SpanChunkBo spanChunkBo = spanChunkMapper .map (idAndName , span );
80- mapperData .addSpanChunkBo (spanChunkBo );
81- }
82- case Span .SpanKind .SPAN_KIND_PRODUCER_VALUE -> {
83- final SpanChunkBo spanChunkBo = spanChunkMapper .map (idAndName , span );
84- mapperData .addSpanChunkBo (spanChunkBo );
85- }
86- default -> {
87- final SpanChunkBo spanChunkBo = spanChunkMapper .map (idAndName , span );
88- mapperData .addSpanChunkBo (spanChunkBo );
89- }
104+ List <SpanChunkBo > spanChunkBoList = findLinkSpanChunk (idAndName , childSpanList );
105+ for (SpanChunkBo spanChunkBo : spanChunkBoList ) {
106+ mapperData .addSpanChunkBo (spanChunkBo );
90107 }
91108 } catch (Exception e ) {
92109 errorCount ++;
93- logger .warn ("Failed to map" , e );
110+ logger .warn ("Failed to map spanChunk " , e );
94111 }
95112 }
96- if (errorCount > 0 ) {
97- OtlpTraceCollectorRejectedSpan rejectedSpan = mapperData .getRejectedSpan ();
98- rejectedSpan .putMessage ("mapping error (" + errorCount + ")" );
99- rejectedSpan .addCount (errorCount );
113+
114+ if (!childSpanList .isEmpty ()) {
115+ logger .warn ("Unknown spans={}" , childSpanList );
100116 }
101117 }
102118 }
103119
120+
121+ if (errorCount > 0 ) {
122+ OtlpTraceCollectorRejectedSpan rejectedSpan = mapperData .getRejectedSpan ();
123+ rejectedSpan .putMessage ("mapping error (" + errorCount + ")" );
124+ rejectedSpan .addCount (errorCount );
125+ }
104126 return mapperData ;
105127 }
106128
@@ -116,4 +138,91 @@ IdAndName getId(OtlpTraceMapperData mapperData, ResourceSpans resourceSpan) {
116138 return null ;
117139 }
118140 }
141+
142+ Map <ByteString , List <Span >> getSpanMap (List <ScopeSpans > scopeSpanList ) {
143+ Map <ByteString , List <Span >> spanMap = new HashMap <>();
144+ for (ScopeSpans scopeSpan : scopeSpanList ) {
145+ List <Span > spansList = scopeSpan .getSpansList ();
146+ for (Span span : spansList ) {
147+ spanMap .computeIfAbsent (span .getTraceId (), k -> new ArrayList <>()).add (span );
148+ }
149+ }
150+ return spanMap ;
151+ }
152+
153+ void initRootAndChild (List <Span > spanList , List <Span > rootSpanList , List <Span > childSpanList ) {
154+ for (Span span : spanList ) {
155+ if (span .getKind ().getNumber () == Span .SpanKind .SPAN_KIND_SERVER_VALUE ) {
156+ rootSpanList .add (span );
157+ } else if (span .getKind ().getNumber () == Span .SpanKind .SPAN_KIND_CONSUMER_VALUE ) {
158+ rootSpanList .add (span );
159+ } else {
160+ // client, producer, internal
161+ if (span .getParentSpanId ().isEmpty ()) {
162+ // even the client type can be root if there is no parentSpanId value.
163+ rootSpanList .add (span );
164+ } else {
165+ childSpanList .add (span );
166+ }
167+ }
168+ }
169+ }
170+
171+ List <SpanEventBo > findLinkSpan (long startTime , List <Span > childSpanList , ByteString parentSpanId , int depth ) {
172+ List <SpanEventBo > spanEventList = new ArrayList <>();
173+ if (depth > 99 ) {
174+ // defensive check
175+ return spanEventList ;
176+ }
177+
178+ List <Span > linkSpanList = new ArrayList <>();
179+ for (Span span : childSpanList ) {
180+ if (parentSpanId .equals (span .getParentSpanId ())) {
181+ linkSpanList .add (span );
182+ }
183+ }
184+ childSpanList .removeAll (linkSpanList );
185+
186+ for (Span span : linkSpanList ) {
187+ final SpanEventBo spanEventBo = spanEventMapper .map (startTime , span , depth );
188+ spanEventList .add (spanEventBo );
189+ List <SpanEventBo > list = findLinkSpan (startTime , childSpanList , span .getSpanId (), depth + 1 );
190+ spanEventList .addAll (list );
191+ }
192+
193+ return spanEventList ;
194+ }
195+
196+ List <SpanChunkBo > findLinkSpanChunk (IdAndName idAndName , List <Span > childSpanList ) {
197+ // Collect all spanIds for quick lookup
198+ List <SpanChunkBo > spanChunkList = new ArrayList <>();
199+ Set <ByteString > spanIdSet = new HashSet <>();
200+ for (Span s : childSpanList ) {
201+ spanIdSet .add (s .getSpanId ());
202+ }
203+ // Identify local roots: parentSpanId is empty or not present in current set
204+ List <Span > localRootSpanList = new ArrayList <>();
205+ for (Span s : childSpanList ) {
206+ if (!spanIdSet .contains (s .getParentSpanId ())) {
207+ localRootSpanList .add (s );
208+ }
209+ }
210+ // If no local root (possible cycle), pick one arbitrarily
211+ if (localRootSpanList .isEmpty ()) {
212+ localRootSpanList .addAll (childSpanList );
213+ }
214+ for (Span localRootSpan : localRootSpanList ) {
215+ // Remove root from remaining list so it's not processed again
216+ childSpanList .remove (localRootSpan );
217+ // Map root as SpanChunk (attached to its parentSpanId if present)
218+ SpanChunkBo spanChunkBo = spanChunkMapper .map (idAndName , localRootSpan );
219+ long rootStartTime = TimeUnit .NANOSECONDS .toMillis (localRootSpan .getStartTimeUnixNano ());
220+ // Recursively attach children as events
221+ List <SpanEventBo > childrenEvents = findLinkSpan (rootStartTime , childSpanList , localRootSpan .getSpanId (), 2 );
222+ spanChunkBo .addSpanEventBoList (childrenEvents );
223+ spanChunkList .add (spanChunkBo );
224+ }
225+
226+ return spanChunkList ;
227+ }
119228}
0 commit comments