From 39b46263170ea24f5f394f32dd313b907d067b71 Mon Sep 17 00:00:00 2001 From: Fine0830 Date: Fri, 28 Mar 2025 10:34:01 +0800 Subject: [PATCH] feat: enhance trace list graph (#459) --- src/types/trace.d.ts | 9 +- .../dashboard/related/log/LogTable/Index.vue | 1 + .../trace/components/D3Graph/Index.vue | 158 +++++++++++++----- .../trace/components/D3Graph/SpanDetail.vue | 24 ++- .../related/trace/components/List.vue | 12 +- .../related/trace/components/Table/Index.vue | 6 - .../related/trace/utils/d3-trace-list.ts | 74 +++++++- .../related/trace/utils/d3-trace-tree.ts | 2 +- 8 files changed, 213 insertions(+), 73 deletions(-) diff --git a/src/types/trace.d.ts b/src/types/trace.d.ts index b71be5bb..822f3965 100644 --- a/src/types/trace.d.ts +++ b/src/types/trace.d.ts @@ -50,7 +50,7 @@ export interface Span { refs?: Ref[]; } export type Ref = { - type: string; + type?: string; parentSegmentId: string; parentSpanId: number; traceId: string; @@ -60,13 +60,6 @@ export interface log { data: Map; } -export interface Ref { - traceId: string; - parentSegmentId: string; - parentSpanId: number; - type: string; -} - export interface StatisticsSpan { groupRef: StatisticsGroupRef; maxTime: number; diff --git a/src/views/dashboard/related/log/LogTable/Index.vue b/src/views/dashboard/related/log/LogTable/Index.vue index 8b60f33c..fec460ff 100644 --- a/src/views/dashboard/related/log/LogTable/Index.vue +++ b/src/views/dashboard/related/log/LogTable/Index.vue @@ -77,6 +77,7 @@ limitations under the License. --> border-bottom: 1px solid $border-color-primary; width: 100%; overflow: auto; + min-height: 350px; } .log-header { diff --git a/src/views/dashboard/related/trace/components/D3Graph/Index.vue b/src/views/dashboard/related/trace/components/D3Graph/Index.vue index fe342b89..a04e2280 100644 --- a/src/views/dashboard/related/trace/components/D3Graph/Index.vue +++ b/src/views/dashboard/related/trace/components/D3Graph/Index.vue @@ -15,7 +15,24 @@ limitations under the License. -->
- +
+
Span details
+
+ {{ `Parent span: ${span.endpointName} -> Start time: ${visDate(span.startTime)}` }} +
+
+ {{ `Ref to span: ${span.endpointName} -> Start time: ${visDate(span.startTime)}` }} +
+
+ @@ -23,6 +40,7 @@ limitations under the License. --> import { ref, watch, onBeforeUnmount, onMounted } from "vue"; import type { PropType } from "vue"; import * as d3 from "d3"; + import dayjs from "dayjs"; import ListGraph from "../../utils/d3-trace-list"; import TreeGraph from "../../utils/d3-trace-tree"; import type { Span, Ref } from "@/types/trace"; @@ -42,11 +60,14 @@ limitations under the License. --> const showDetail = ref(false); const fixSpansSize = ref(0); const segmentId = ref([]); - const currentSpan = ref>([]); + const currentSpan = ref>(null); const refSpans = ref>([]); const tree = ref>(null); const traceGraph = ref>(null); + const parentSpans = ref>([]); + const refParentSpans = ref>([]); const debounceFunc = debounce(draw, 500); + const visDate = (date: number, pattern = "YYYY-MM-DD HH:mm:ss:SSS") => dayjs(date).format(pattern); defineExpose({ tree, @@ -77,16 +98,54 @@ limitations under the License. --> d3.selectAll(".d3-tip").remove(); if (props.type === "List") { tree.value = new ListGraph(traceGraph.value, handleSelectSpan); - tree.value.init({ label: "TRACE_ROOT", children: segmentId.value }, props.data, fixSpansSize.value); + tree.value.init( + { label: "TRACE_ROOT", children: segmentId.value }, + getRefsAllNodes({ label: "TRACE_ROOT", children: segmentId.value }), + fixSpansSize.value, + ); tree.value.draw(); } else { tree.value = new TreeGraph(traceGraph.value, handleSelectSpan); - tree.value.init({ label: `${props.traceId}`, children: segmentId.value }, props.data); + tree.value.init( + { label: `${props.traceId}`, children: segmentId.value }, + getRefsAllNodes({ label: "TRACE_ROOT", children: segmentId.value }), + ); } } function handleSelectSpan(i: Recordable) { + const spans = []; + const refSpans = []; + parentSpans.value = []; + refParentSpans.value = []; currentSpan.value = i.data; - showDetail.value = true; + if (!currentSpan.value) { + return; + } + for (const ref of currentSpan.value.refs || []) { + refSpans.push(ref); + } + if (currentSpan.value.parentSpanId > -1) { + spans.push({ + parentSegmentId: currentSpan.value.segmentId, + parentSpanId: currentSpan.value.parentSpanId, + traceId: currentSpan.value.traceId, + }); + } + for (const span of refSpans) { + const item = props.data.find( + (d) => d.segmentId === span.parentSegmentId && d.spanId === span.parentSpanId && d.traceId === span.traceId, + ); + item && refParentSpans.value.push(item); + } + for (const span of spans) { + const item = props.data.find( + (d) => d.segmentId === span.parentSegmentId && d.spanId === span.parentSpanId && d.traceId === span.traceId, + ); + item && parentSpans.value.push(item); + } + } + function viewParentSpan(span: Recordable) { + tree.value.highlightParents(span); } function traverseTree(node: Recordable, spanId: string, segmentId: string, data: Recordable) { if (!node || node.isBroken) { @@ -272,21 +331,12 @@ limitations under the License. --> } } for (const i in segmentGroup) { - if (segmentGroup[i].refs.length) { - let exit = null; - for (const ref of segmentGroup[i].refs) { - const e = props.data.find( - (i: Recordable) => - ref.traceId === i.traceId && ref.parentSegmentId === i.segmentId && ref.parentSpanId === i.spanId, - ); - if (e) { - exit = e; - } - } - if (exit) { + for (const ref of segmentGroup[i].refs) { + if (!segmentGroup[ref.parentSegmentId]) { segmentId.value.push(segmentGroup[i]); } - } else { + } + if (!segmentGroup[i].refs.length && segmentGroup[i].parentSpanId === -1) { segmentId.value.push(segmentGroup[i]); } } @@ -310,6 +360,23 @@ limitations under the License. --> } } } + function getRefsAllNodes(tree: Recordable) { + let nodes = []; + let stack = [tree]; + + while (stack.length > 0) { + const node = stack.pop(); + nodes.push(node); + + if (node?.children && node.children.length > 0) { + for (let i = node.children.length - 1; i >= 0; i--) { + stack.push(node.children[i]); + } + } + } + + return nodes; + } function compare(p: string) { return (m: Recordable, n: Recordable) => { const a = m[p]; @@ -346,7 +413,7 @@ limitations under the License. --> }, ); - diff --git a/src/views/dashboard/related/trace/components/D3Graph/SpanDetail.vue b/src/views/dashboard/related/trace/components/D3Graph/SpanDetail.vue index 727416f9..df310606 100644 --- a/src/views/dashboard/related/trace/components/D3Graph/SpanDetail.vue +++ b/src/views/dashboard/related/trace/components/D3Graph/SpanDetail.vue @@ -87,7 +87,14 @@ limitations under the License. --> {{ t("relatedTraceLogs") }} - +
Name: @@ -115,7 +122,14 @@ limitations under the License. -->
- + .link { text-decoration: underline; } + + .log-tips { + width: 100%; + text-align: center; + margin: 50px 0; + } diff --git a/src/views/dashboard/related/trace/components/List.vue b/src/views/dashboard/related/trace/components/List.vue index 20f072e2..a444074b 100644 --- a/src/views/dashboard/related/trace/components/List.vue +++ b/src/views/dashboard/related/trace/components/List.vue @@ -31,8 +31,10 @@ limitations under the License. --> import type { PropType } from "vue"; import { useI18n } from "vue-i18n"; import * as d3 from "d3"; + import { useAppStoreWithOut } from "@/store/modules/app"; import type { Span } from "@/types/trace"; import Graph from "./D3Graph/Index.vue"; + import { Themes } from "@/constants/data"; /* global defineProps, Recordable*/ const props = defineProps({ @@ -40,6 +42,7 @@ limitations under the License. --> traceId: { type: String, default: "" }, }); const { t } = useI18n(); + const appStore = useAppStoreWithOut(); const list = computed(() => Array.from(new Set(props.data.map((i: Span) => i.serviceCode)))); function computedScale(i: number) { @@ -52,13 +55,13 @@ limitations under the License. --> function downloadTrace() { const serializer = new XMLSerializer(); - const svgNode: any = d3.select(".trace-list-dowanload").node(); + const svgNode: any = d3.select(".trace-list").node(); const source = `\r\n${serializer.serializeToString(svgNode)}`; const canvas = document.createElement("canvas"); const context: any = canvas.getContext("2d"); - canvas.width = (d3.select(".trace-list-dowanload") as Recordable)._groups[0][0].clientWidth; - canvas.height = (d3.select(".trace-list-dowanload") as Recordable)._groups[0][0].clientHeight; - context.fillStyle = "#fff"; + canvas.width = (d3.select(".trace-list") as Recordable)._groups[0][0].clientWidth; + canvas.height = (d3.select(".trace-list") as Recordable)._groups[0][0].clientHeight; + context.fillStyle = appStore.theme === Themes.Dark ? "#212224" : `#fff`; context.fillRect(0, 0, canvas.width, canvas.height); const image = new Image(); image.src = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(source)}`; @@ -93,6 +96,7 @@ limitations under the License. --> .list { height: calc(100% - 150px); + position: relative; } .event-tag { diff --git a/src/views/dashboard/related/trace/components/Table/Index.vue b/src/views/dashboard/related/trace/components/Table/Index.vue index 0d210b1d..659328e8 100644 --- a/src/views/dashboard/related/trace/components/Table/Index.vue +++ b/src/views/dashboard/related/trace/components/Table/Index.vue @@ -89,12 +89,6 @@ limitations under the License. --> );