protected List<ProfiledTraceSegments> buildProfiledSegmentsList(List<SegmentRecord> segmentRecords, List<String> profiledSegmentIdList) {
        final Map<String, ProfiledTraceSegments> segments = segmentRecords.stream().map(s -> {
            try {
                return Tuple.of(s, SegmentObject.parseFrom(s.getDataBinary()));
            } catch (InvalidProtocolBufferException e) {
                log.warn("parsing segment data error", e);
                return null;
            }
        }).filter(java.util.Objects::nonNull).filter(s -> CollectionUtils.isNotEmpty(s._2.getSpansList())).collect(Collectors.toMap(
            tuple -> tuple._1.getSegmentId(),
            tuple -> {
                final IDManager.ServiceInstanceID.InstanceIDDefinition serviceInstance = IDManager.ServiceInstanceID.analysisId(tuple._1.getServiceInstanceId());
                final ProfiledTraceSegments seg = new ProfiledTraceSegments();
                final boolean profiled = profiledSegmentIdList.contains(tuple._1.getSegmentId());
                seg.setTraceId(tuple._1.getTraceId());
                seg.setInstanceId(tuple._1.getServiceInstanceId());
                seg.setInstanceName(serviceInstance.getName());
                seg.getEndpointNames().add(IDManager.EndpointID.analysisId(tuple._1.getEndpointId()).getEndpointName());
                seg.setDuration(tuple._1.getLatency());
                seg.setStart(String.valueOf(tuple._1.getStartTime()));
                seg.getSpans().addAll(buildProfiledSpanList(tuple._2, profiled));
                seg.setContainsProfiled(profiled);
                return seg;
            }
        ));

        // trying to find parent
        final ArrayList<ProfiledTraceSegments> results = new ArrayList<>();
        final Iterator<Map.Entry<String, ProfiledTraceSegments>> entryIterator = segments.entrySet().iterator();
        final Set<ProfiledSpan> mergedSpans = new HashSet<>();
        while (entryIterator.hasNext()) {
            // keep segment if no ref
            final Map.Entry<String, ProfiledTraceSegments> current = entryIterator.next();

            boolean spanBeenAdded = false;
            for (ProfiledSpan span : current.getValue().getSpans()) {
                if (mergedSpans.contains(span)) {
                    continue;
                }
                if (CollectionUtils.isEmpty(span.getRefs())) {
                    continue;
                }
                // keep segment if ref type is not same process(analyze only match with the same process)
                final Ref ref = span.getRefs().get(0);
                if (RefType.CROSS_PROCESS.equals(ref.getType())) {
                    results.add(current.getValue());
                    spanBeenAdded = true;
                    break;
                }
                // find parent segment if exist
                final ProfiledTraceSegments parentSegments = segments.get(ref.getParentSegmentId());
                if (parentSegments != null) {
                    // append merged spans
                    mergedSpans.addAll(current.getValue().getSpans());
                    // add current segments into parent
                    parentSegments.merge(current.getValue());
                    // set parent segments(combined) as current segment
                    current.setValue(parentSegments);
                    spanBeenAdded = true;
                    break;
                }
            }

            if (!spanBeenAdded) {
                results.add(current.getValue());
            }
        }

        return results.stream().filter(ProfiledTraceSegments::isContainsProfiled).peek(this::removeAllCrossProcessRef).collect(Collectors.toList());
    }
@Test
    public void testBuildProfiledSegmentsList() {
        // all segment in same process
        validate(Arrays.asList(
            buildRecord("1B", "2A", RefType.CrossThread),
            buildRecord("2A", "", null),
            buildRecord("3C", "1B", RefType.CrossThread)
        ), Arrays.asList(
            Arrays.asList("2A", "1B", "3C")
        ));

        // segment with different process
        validate(Arrays.asList(
            buildRecord("A", "", null),
            buildRecord("B", "A", RefType.CrossThread),

            buildRecord("C", "B", RefType.CrossProcess),

            buildRecord("D", "Z", RefType.CrossThread)
        ), Arrays.asList(
            Arrays.asList("A", "B"),
            Arrays.asList("C"),
            Arrays.asList("D")
        ));
    }