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 @@
+
+
+
+
+
+
+
+ {{ $t("noData") }}
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+ {{ data.groupRef.endpointName }}
+
+
+
+
+
+
+ {{ data.groupRef.type }}
+
+
+
+
+ {{ data.maxTime }}
+
+
+ {{ data.minTime }}
+
+
+ {{ data.sumTime }}
+
+
+ {{ parseInt(data.avgTime) }}
+
+
+ {{ data.count }}
+
+
+
+
+
+
+
+
+
+ {{ data.endpointName }}
+
+
+
+
+ {{ dateFormat(data.startTime) }}
+
+
+ {{
+ data.endTime - data.startTime ? data.endTime - data.startTime : "0"
+ }}
+
+
+
+ {{ data.dur ? data.dur + "" : "0" }}
+
+
+
+ {{ data.component || "-" }}
+
+
+
+
+ {{ data.serviceCode }}
+
+
+
+ {{ $t("view") }}
+
+
+
+
+
+
+
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);
+ }
+ });
+ }
+}