From 77cfae3149feb7e95bdc339c95a8f6cd2b8657b0 Mon Sep 17 00:00:00 2001 From: Qiuxia Fan Date: Fri, 25 Feb 2022 17:24:50 +0800 Subject: [PATCH] feat: add trace table --- src/views/dashboard/panel/Layout.vue | 1 + src/views/dashboard/related/trace/Detail.vue | 2 + .../related/trace/components/Graph.vue | 6 +- .../related/trace/components/Tree.vue | 10 +- .../related/trace/components/index.ts | 2 + .../related/trace/components/table/Index.vue | 128 +++++++ .../trace/components/table/TableContainer.vue | 160 +++++++++ .../trace/components/table/TableItem.vue | 289 +++++++++++++++ .../related/trace/components/table/data.ts | 120 +++++++ .../related/trace/components/table/table.scss | 76 ++++ .../related/trace/utils/d3-trace-list.ts | 1 - .../related/trace/utils/d3-trace-tree.ts | 10 +- .../related/trace/utils/trace-table.ts | 338 ++++++++++++++++++ 13 files changed, 1129 insertions(+), 14 deletions(-) create mode 100644 src/views/dashboard/related/trace/components/table/Index.vue create mode 100644 src/views/dashboard/related/trace/components/table/TableContainer.vue create mode 100644 src/views/dashboard/related/trace/components/table/TableItem.vue create mode 100644 src/views/dashboard/related/trace/components/table/data.ts create mode 100644 src/views/dashboard/related/trace/components/table/table.scss create mode 100644 src/views/dashboard/related/trace/utils/trace-table.ts diff --git a/src/views/dashboard/panel/Layout.vue b/src/views/dashboard/panel/Layout.vue index f80c05c6..a437f6ee 100644 --- a/src/views/dashboard/panel/Layout.vue +++ b/src/views/dashboard/panel/Layout.vue @@ -31,6 +31,7 @@ limitations under the License. --> :key="item.i" @click="clickGrid(item)" :class="{ active: dashboardStore.activedGridItem === item.i }" + drag-ignore-from="svg.d3-trace-tree" > diff --git a/src/views/dashboard/related/trace/Detail.vue b/src/views/dashboard/related/trace/Detail.vue index 4b1cf054..9f7fee2f 100644 --- a/src/views/dashboard/related/trace/Detail.vue +++ b/src/views/dashboard/related/trace/Detail.vue @@ -100,6 +100,8 @@ limitations under the License. --> :is="displayMode" :data="traceStore.traceSpans" :traceId="traceStore.currentTrace.traceIds[0].value" + :showBtnDetail="false" + HeaderType="trace" /> diff --git a/src/views/dashboard/related/trace/components/Graph.vue b/src/views/dashboard/related/trace/components/Graph.vue index 4d965591..15f8466d 100644 --- a/src/views/dashboard/related/trace/components/Graph.vue +++ b/src/views/dashboard/related/trace/components/Graph.vue @@ -25,7 +25,7 @@ import ListGraph from "../utils/d3-trace-list"; import TreeGraph from "../utils/d3-trace-tree"; import { Span } from "@/types/trace"; -/* global defineProps, Nullable*/ +/* global defineProps, Nullable, defineExpose*/ const props = defineProps({ data: { type: Array as PropType, default: () => [] }, traceId: { type: String, default: "" }, @@ -38,7 +38,9 @@ const segmentId = ref([]); const currentSpan = ref>([]); const tree = ref(null); const traceGraph = ref>(null); - +defineExpose({ + tree, +}); onMounted(() => { loading.value = true; changeTree(); diff --git a/src/views/dashboard/related/trace/components/Tree.vue b/src/views/dashboard/related/trace/components/Tree.vue index 0bd2b0f5..9f681d11 100644 --- a/src/views/dashboard/related/trace/components/Tree.vue +++ b/src/views/dashboard/related/trace/components/Tree.vue @@ -24,18 +24,18 @@ limitations under the License. -->
- +
@@ -54,7 +54,7 @@ const props = defineProps({ }); const { t } = useI18n(); const list = ref([]); -const tree = ref(null); +const charts = ref(null); onMounted(() => { list.value = Array.from(new Set(props.data.map((i: Span) => i.serviceCode))); diff --git a/src/views/dashboard/related/trace/components/index.ts b/src/views/dashboard/related/trace/components/index.ts index eafd9c4a..ede288ba 100644 --- a/src/views/dashboard/related/trace/components/index.ts +++ b/src/views/dashboard/related/trace/components/index.ts @@ -17,8 +17,10 @@ import List from "./List.vue"; import Tree from "./Tree.vue"; +import Table from "./table/Index.vue"; export default { List, Tree, + Table, }; diff --git a/src/views/dashboard/related/trace/components/table/Index.vue b/src/views/dashboard/related/trace/components/table/Index.vue new file mode 100644 index 00000000..6b4a3176 --- /dev/null +++ b/src/views/dashboard/related/trace/components/table/Index.vue @@ -0,0 +1,128 @@ + + + + diff --git a/src/views/dashboard/related/trace/components/table/TableContainer.vue b/src/views/dashboard/related/trace/components/table/TableContainer.vue new file mode 100644 index 00000000..4cecf08c --- /dev/null +++ b/src/views/dashboard/related/trace/components/table/TableContainer.vue @@ -0,0 +1,160 @@ + + + + + diff --git a/src/views/dashboard/related/trace/components/table/TableItem.vue b/src/views/dashboard/related/trace/components/table/TableItem.vue new file mode 100644 index 00000000..9c76642d --- /dev/null +++ b/src/views/dashboard/related/trace/components/table/TableItem.vue @@ -0,0 +1,289 @@ + + + + + diff --git a/src/views/dashboard/related/trace/components/table/data.ts b/src/views/dashboard/related/trace/components/table/data.ts new file mode 100644 index 00000000..6c7c0411 --- /dev/null +++ b/src/views/dashboard/related/trace/components/table/data.ts @@ -0,0 +1,120 @@ +/** + * 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. + */ + +export const ProfileConstant = [ + { + label: "method", + value: "Span", + }, + { + label: "start-time", + value: "Start Time", + }, + { + label: "exec-ms", + value: "Exec(ms)", + }, + { + label: "exec-percent", + value: "Exec(%)", + }, + { + label: "self", + value: "Self(ms)", + }, + { + label: "api", + value: "API", + }, + { + label: "application", + value: "Service", + }, + { + label: "application", + value: "Operation", + }, +]; + +export const TraceConstant = [ + { + label: "method", + value: "Method", + }, + { + label: "start-time", + value: "Start Time", + }, + { + label: "exec-ms", + value: "Exec(ms)", + }, + { + label: "exec-percent", + value: "Exec(%)", + }, + { + label: "self", + value: "Self(ms)", + }, + { + label: "api", + value: "API", + }, + { + label: "application", + value: "Service", + }, +]; + +export const StatisticsConstant = [ + { + label: "method", + value: "Endpoint Name", + key: "endpointName", + }, + { + label: "type", + value: "Type", + key: "type", + }, + { + label: "max-time", + value: "Max Time(ms)", + key: "maxTime", + }, + { + label: "min-time", + value: "Min Time(ms)", + key: "minTime", + }, + { + label: "sum-time", + value: "Sum Time(ms)", + key: "sumTime", + }, + { + label: "avg-time", + value: "Avg Time(ms)", + key: "avgTime", + }, + { + label: "count", + value: "Hits", + key: "count", + }, +]; diff --git a/src/views/dashboard/related/trace/components/table/table.scss b/src/views/dashboard/related/trace/components/table/table.scss new file mode 100644 index 00000000..e48b8e29 --- /dev/null +++ b/src/views/dashboard/related/trace/components/table/table.scss @@ -0,0 +1,76 @@ +/** + * 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. + */ +.argument { + width: 150px; +} + +.start-time { + width: 150px; +} + +.exec-ms { + width: 80px; +} + +.exec-percent { + width: 100px; +} + +.self { + width: 100px; +} + +.api { + width: 120px; +} + +.agent { + width: 150px; +} + +.application { + width: 150px; + text-align: center; +} + +.max-time { + width: 150px; +} + +.method { + width: 300px; +} + +.avg-time { + width: 150px; +} + +.min-time { + width: 150px; +} + +.count { + width: 120px; +} + +.sum-time { + width: 150px; +} + +.type { + width: 60px; +} diff --git a/src/views/dashboard/related/trace/utils/d3-trace-list.ts b/src/views/dashboard/related/trace/utils/d3-trace-list.ts index c46e736d..2b577da2 100644 --- a/src/views/dashboard/related/trace/utils/d3-trace-list.ts +++ b/src/views/dashboard/related/trace/utils/d3-trace-list.ts @@ -239,7 +239,6 @@ export default class ListGraph { ) .on("click", (d: any) => { this.click(d, this); - // (d3 as any).event.stopPropagation(); }); node .transition() diff --git a/src/views/dashboard/related/trace/utils/d3-trace-tree.ts b/src/views/dashboard/related/trace/utils/d3-trace-tree.ts index 973e63c8..9c065922 100644 --- a/src/views/dashboard/related/trace/utils/d3-trace-tree.ts +++ b/src/views/dashboard/related/trace/utils/d3-trace-tree.ts @@ -18,7 +18,6 @@ import * as d3 from "d3"; import d3tip from "d3-tip"; import { Trace, Span } from "@/types/trace"; -import { style } from "d3"; export default class TraceMap { private i = 0; @@ -53,10 +52,11 @@ export default class TraceMap { this.topSlow = []; this.topChild = []; this.width = el.clientWidth - 20; - this.height = el.clientHeight; + this.height = el.clientHeight - 30; this.body = d3 .select(this.el) .append("svg") + .attr("class", "d3-trace-tree") .attr("width", this.width) .attr("height", this.height); this.tip = (d3tip as any)() @@ -186,7 +186,6 @@ export default class TraceMap { } }) .on("click", function (d: any) { - (d3 as any).event.stopPropagation(); that.handleSelectSpan(d); }); @@ -289,7 +288,6 @@ export default class TraceMap { ) .attr("cursor", "pointer") .on("click", (d: any) => { - (d3 as any).event.stopPropagation(); click(d); }); const nodeExit = node @@ -320,9 +318,9 @@ export default class TraceMap { const o = { x: source.x0, y: source.y0 }; return diagonal(o, o); }) + .attr("stroke", "rgba(0, 0, 0, 0.1)") .style("stroke-width", 1.5) - .style("fill", "none") - .attr("stroke", "rgba(0, 0, 0, 0.1)"); + .style("fill", "none"); const linkUpdate = linkEnter.merge(link); linkUpdate diff --git a/src/views/dashboard/related/trace/utils/trace-table.ts b/src/views/dashboard/related/trace/utils/trace-table.ts new file mode 100644 index 00000000..3368b561 --- /dev/null +++ b/src/views/dashboard/related/trace/utils/trace-table.ts @@ -0,0 +1,338 @@ +/** + * 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 { + Ref, + Span, + StatisticsSpan, + StatisticsGroupRef, + TraceTreeRef, +} from "@/types/trace"; +import lodash from "lodash"; + +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[], cureentTraceId: string) { + const segmentIdList: Span[] = []; + const traceTreeRef: any = this.changeTreeCore(data, cureentTraceId); + traceTreeRef.segmentIdGroup.forEach((segmentId: string) => { + if (traceTreeRef.segmentMap.get(segmentId).refs) { + traceTreeRef.segmentMap.get(segmentId).refs.forEach((ref: Ref) => { + if (ref.traceId === cureentTraceId) { + 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[], + cureentTraceId: string + ): Map { + const result = new Map(); + const traceTreeRef = this.changeTreeCore(data, cureentTraceId); + traceTreeRef.segmentMap.forEach((span, segmentId) => { + 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[], + cureentTraceId: string + ): TraceTreeRef { + // set a breakpoint at this line + if (data.length === 0) { + return { + segmentMap: new Map(), + segmentIdGroup: [], + }; + } + const segmentGroup: any = {}; + 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 fixSpanKeyContent = { + traceId: span.traceId, + segmentId: span.segmentId, + spanId: span.spanId - 1, + parentSpanId: span.spanId - 2, + }; + if (index === -1 && !lodash.find(fixSpans, fixSpanKeyContent)) { + fixSpans.push({ + ...fixSpanKeyContent, + 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: number = ref.parentSpanId; + const fixSpanKeyContent = { + traceId: ref.traceId, + segmentId: ref.parentSegmentId, + spanId: parentSpanId, + parentSpanId: parentSpanId > -1 ? 0 : -1, + }; + if (lodash.find(fixSpans, fixSpanKeyContent)) { + fixSpans.push({ + ...fixSpanKeyContent, + 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 (fixSpanKeyContent.parentSpanId > -1) { + const fixRootSpanKeyContent = { + traceId: ref.traceId, + segmentId: ref.parentSegmentId, + spanId: 0, + parentSpanId: -1, + }; + if (!lodash.find(fixSpans, fixRootSpanKeyContent)) { + fixSpans.push({ + ...fixRootSpanKeyContent, + 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 = lodash.filter(data, (span: Span) => { + return lodash.find(span.refs, { + traceId: curSegment.traceId, + parentSegmentId: curSegment.segmentId, + parentSpanId: curSegment.spanId, + }); + }) as Span[]; + 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((chlid: Span) => { + dur -= chlid.endTime - chlid.startTime; + }); + span.dur = dur < 0 ? 0 : dur; + span.children.forEach((chlid) => this.collapse(chlid)); + } + } + + 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); + } + }); + } +}