/** * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import type { Ref, Span, StatisticsSpan, StatisticsGroupRef, TraceTreeRef } from "@/types/trace"; export default class TraceUtil { public static buildTraceDataList(data: Span[]): string[] { return Array.from(new Set(data.map((span: Span) => span.serviceCode))); } public static changeTree(data: Span[], currentTraceId: string) { const segmentIdList: Span[] = []; const traceTreeRef: Recordable = this.changeTreeCore(data); traceTreeRef.segmentIdGroup.forEach((segmentId: string) => { if (traceTreeRef.segmentMap.get(segmentId).refs) { traceTreeRef.segmentMap.get(segmentId).refs.forEach((ref: Ref) => { if (ref.traceId === currentTraceId) { this.traverseTree( traceTreeRef.segmentMap.get(ref.parentSegmentId) as Span, ref.parentSpanId, ref.parentSegmentId, traceTreeRef.segmentMap.get(segmentId) as Span, ); } }); } }); // set a breakpoint at this line traceTreeRef.segmentMap.forEach((value: Span) => { if ((value.refs && value.refs.length === 0) || !value.refs) { segmentIdList.push(value as Span); } }); segmentIdList.forEach((segmentId: Span) => { this.collapse(segmentId); }); return segmentIdList; } public static changeStatisticsTree(data: Span[]): Map { const result = new Map(); const traceTreeRef = this.changeTreeCore(data); traceTreeRef.segmentMap.forEach((span) => { const groupRef = span.endpointName + ":" + span.type; if (span.children && span.children.length > 0) { this.calculationChildren(span.children, result); this.collapse(span); } if (result.get(groupRef) === undefined) { result.set(groupRef, []); result.get(groupRef)!.push(span); } else { result.get(groupRef)!.push(span); } }); return result; } private static changeTreeCore(data: Span[]): TraceTreeRef { // set a breakpoint at this line if (data.length === 0) { return { segmentMap: new Map(), segmentIdGroup: [], }; } const segmentGroup: Recordable = {}; const segmentMap: Map = new Map(); const segmentIdGroup: string[] = []; const fixSpans: Span[] = []; const segmentHeaders: Span[] = []; data.forEach((span) => { if (span.parentSpanId === -1) { segmentHeaders.push(span); } else { const index = data.findIndex((patchSpan: Span) => { return patchSpan.segmentId === span.segmentId && patchSpan.spanId === span.spanId - 1; }); const content = fixSpans.find( (i: Span) => i.traceId === span.traceId && i.segmentId === span.segmentId && i.spanId === span.spanId - 1 && i.parentSpanId === span.spanId - 2, ); if (index === -1 && !content) { fixSpans.push({ traceId: span.traceId, segmentId: span.segmentId, spanId: span.spanId - 1, parentSpanId: span.spanId - 2, refs: [], endpointName: `VNode: ${span.segmentId}`, serviceCode: "VirtualNode", type: `[Broken] ${span.type}`, peer: "", component: `VirtualNode: #${span.spanId - 1}`, isError: true, isBroken: true, layer: "Broken", tags: [], logs: [], startTime: 0, endTime: 0, }); } } }); segmentHeaders.forEach((span) => { if (span.refs && span.refs.length) { span.refs.forEach((ref) => { const index = data.findIndex((patchSpan: Span) => { return ref.parentSegmentId === patchSpan.segmentId && ref.parentSpanId === patchSpan.spanId; }); if (index === -1) { // create a known broken node. const parentSpanId = ref.parentSpanId > -1 ? 0 : -1; const item = fixSpans.find( (i: Span) => i.traceId === ref.traceId && i.segmentId === ref.parentSegmentId && i.spanId === ref.parentSpanId && i.parentSpanId === parentSpanId, ); if (!item) { fixSpans.push({ traceId: ref.traceId, segmentId: ref.parentSegmentId, spanId: ref.parentSpanId, parentSpanId: parentSpanId, refs: [], endpointName: `VNode: ${ref.parentSegmentId}`, serviceCode: "VirtualNode", type: `[Broken] ${ref.type}`, peer: "", component: `VirtualNode: #${parentSpanId}`, isError: true, isBroken: true, layer: "Broken", tags: [], logs: [], startTime: 0, endTime: 0, }); } // if root broken node is not exist, create a root broken node. if (parentSpanId > -1) { const item = fixSpans.find( (i: Span) => i.traceId === ref.traceId && i.segmentId === ref.parentSegmentId && i.spanId === 0 && i.parentSpanId === -1, ); if (!item) { fixSpans.push({ traceId: ref.traceId, segmentId: ref.parentSegmentId, spanId: 0, parentSpanId: -1, refs: [], endpointName: `VNode: ${ref.parentSegmentId}`, serviceCode: "VirtualNode", type: `[Broken] ${ref.type}`, peer: "", component: `VirtualNode: #0`, isError: true, isBroken: true, layer: "Broken", tags: [], logs: [], startTime: 0, endTime: 0, }); } } } }); } }); [...fixSpans, ...data].forEach((fixSpan: Span) => { fixSpan.label = fixSpan.endpointName || "no operation name"; fixSpan.children = []; const id = fixSpan.segmentId || "top"; if (segmentGroup[id] === undefined) { segmentIdGroup.push(id); segmentGroup[id] = []; segmentGroup[id].push(fixSpan); } else { segmentGroup[id].push(fixSpan); } }); segmentIdGroup.forEach((segmentId: string) => { const currentSegmentSet = segmentGroup[segmentId].sort((a: Span, b: Span) => b.parentSpanId - a.parentSpanId); currentSegmentSet.forEach((curSegment: Span) => { const index = currentSegmentSet.findIndex( (curSegment2: Span) => curSegment2.spanId === curSegment.parentSpanId, ); if (index !== -1) { if ( (currentSegmentSet[index].isBroken && currentSegmentSet[index].parentSpanId === -1) || !currentSegmentSet[index].isBroken ) { currentSegmentSet[index].children.push(curSegment); currentSegmentSet[index].children.sort((a: Span, b: Span) => a.spanId - b.spanId); } } if (curSegment.isBroken) { const children = data.filter((span: Span) => span.refs.find( (d) => d.traceId === curSegment.traceId && d.parentSegmentId === curSegment.segmentId && d.parentSpanId === curSegment.spanId, ), ); if (children.length) { curSegment.children = curSegment.children || []; curSegment.children.push(...children); } } }); segmentMap.set(segmentId, currentSegmentSet[currentSegmentSet.length - 1]); }); return { segmentMap, segmentIdGroup, }; } private static collapse(span: Span) { if (span.children) { let dur = span.endTime - span.startTime; span.children.forEach((child: Span) => { dur -= child.endTime - child.startTime; }); span.dur = dur < 0 ? 0 : dur; span.children.forEach((child) => this.collapse(child)); } } private static traverseTree(node: Span, spanId: number, segmentId: string, childNode: Span) { if (!node || node.isBroken) { return; } if (node.spanId === spanId && node.segmentId === segmentId) { node.children!.push(childNode); return; } if (node.children && node.children.length > 0) { for (const grandchild of node.children) { this.traverseTree(grandchild, spanId, segmentId, childNode); } } } private static getSpanGroupData(groupspans: Span[], groupRef: StatisticsGroupRef): StatisticsSpan { let maxTime = 0; let minTime = 0; let sumTime = 0; const count = groupspans.length; groupspans.forEach((groupspan: Span) => { const duration = groupspan.dur || 0; if (duration > maxTime) { maxTime = duration; } if (duration < minTime) { minTime = duration; } sumTime = sumTime + duration; }); const avgTime = count === 0 ? 0 : sumTime / count; return { groupRef, maxTime, minTime, sumTime, avgTime, count, }; } private static calculationChildren(nodes: Span[], result: Map): void { nodes.forEach((node: Span) => { const groupRef = node.endpointName + ":" + node.type; if (node.children && node.children.length > 0) { this.calculationChildren(node.children, result); } if (result.get(groupRef) === undefined) { result.set(groupRef, []); result.get(groupRef)!.push(node); } else { result.get(groupRef)!.push(node); } }); } }