feat: enhance the trace table graph for multiple refs (#464)

This commit is contained in:
Fine0830 2025-04-14 21:12:48 +08:00 committed by GitHub
parent 0775bf0034
commit 687ae07bb0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 207 additions and 248 deletions

View File

@ -34,6 +34,7 @@ interface TraceState {
conditions: Recordable;
traceSpanLogs: Recordable[];
selectorStore: Recordable;
selectedSpan: Recordable<Span>;
}
export const traceStore = defineStore({
@ -45,6 +46,7 @@ export const traceStore = defineStore({
traceList: [],
traceSpans: [],
currentTrace: {},
selectedSpan: {},
conditions: {
queryDuration: useAppStoreWithOut().durationTime,
traceState: "ALL",
@ -64,6 +66,9 @@ export const traceStore = defineStore({
setTraceSpans(spans: Span[]) {
this.traceSpans = spans;
},
setSelectedSpan(span: Span) {
this.selectedSpan = span;
},
resetState() {
this.traceSpans = [];
this.traceList = [];

View File

@ -48,6 +48,7 @@ export interface Span {
logs?: log[];
parentSegmentId?: string;
refs?: Ref[];
key?: string;
}
export type Ref = {
type?: string;

View File

@ -43,8 +43,7 @@ limitations under the License. -->
<Table
:data="profileStore.segmentSpans"
:traceId="profileStore.currentSegment.traceId"
:showBtnDetail="true"
headerType="profile"
:headerType="WidgetType.Profile"
@select="selectSpan"
/>
</div>
@ -53,13 +52,14 @@ limitations under the License. -->
<script lang="ts" setup>
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import Table from "../../trace/components/Table/Index.vue";
import Table from "../../trace/components/Table.vue";
import { useProfileStore } from "@/store/modules/profile";
import Selector from "@/components/Selector.vue";
import type { Span } from "@/types/trace";
import type { Option } from "@/types/app";
import { ElMessage } from "element-plus";
import { ProfileDataMode, ProfileDisplayMode } from "./data";
import { WidgetType } from "@/views/dashboard/data";
/* global defineEmits*/
const emits = defineEmits(["loading", "displayMode"]);

View File

@ -84,7 +84,7 @@ limitations under the License. -->
:data="traceStore.traceSpans"
:traceId="traceStore.currentTrace.traceIds[0].value"
:showBtnDetail="false"
HeaderType="trace"
:headerType="WidgetType.Trace"
/>
</div>
</div>
@ -160,6 +160,7 @@ limitations under the License. -->
appStore,
loading,
traceId,
WidgetType,
};
},
});

View File

@ -14,9 +14,19 @@ limitations under the License. -->
<div class="trace-t-loading" v-show="loading">
<Icon iconName="spinner" size="sm" />
</div>
<div ref="traceGraph" class="d3-graph"></div>
<TableContainer
v-if="type === TraceGraphType.TABLE"
:tableData="segmentId"
:type="type"
:headerType="headerType"
:traceId="traceId"
@select="handleSelectSpan"
>
<div class="trace-tips" v-if="!segmentId.length">{{ $t("noData") }}</div>
</TableContainer>
<div v-else ref="traceGraph" class="d3-graph"></div>
<div id="trace-action-box">
<div @click="showDetail = true">Span details</div>
<div @click="viewSpanDetails">Span details</div>
<div v-for="span in parentSpans" :key="span.segmentId" @click="viewParentSpan(span)">
{{ `Parent span: ${span.endpointName} -> Start time: ${visDate(span.startTime)}` }}
</div>
@ -45,15 +55,19 @@ limitations under the License. -->
import TreeGraph from "../../utils/d3-trace-tree";
import type { Span, Ref } from "@/types/trace";
import SpanDetail from "./SpanDetail.vue";
import TableContainer from "../Table/TableContainer.vue";
import { useAppStoreWithOut } from "@/store/modules/app";
import { debounce } from "@/utils/debounce";
import { mutationObserver } from "@/utils/mutation";
import { TraceGraphType } from "../constant";
import { Themes } from "@/constants/data";
/* global defineProps, Nullable, defineExpose, Recordable */
/* global Recordable, Nullable */
const props = defineProps({
data: { type: Array as PropType<Span[]>, default: () => [] },
traceId: { type: String, default: "" },
type: { type: String, default: "List" },
type: { type: String, default: TraceGraphType.LIST },
headerType: { type: String, default: "" },
});
const appStore = useAppStoreWithOut();
const loading = ref<boolean>(false);
@ -69,34 +83,29 @@ limitations under the License. -->
const debounceFunc = debounce(draw, 500);
const visDate = (date: number, pattern = "YYYY-MM-DD HH:mm:ss:SSS") => dayjs(date).format(pattern);
defineExpose({
tree,
});
onMounted(() => {
loading.value = true;
changeTree();
if (!traceGraph.value) {
loading.value = false;
return;
}
draw();
loading.value = false;
// monitor segment list width changes.
mutationObserver.create("trigger-resize", () => {
d3.selectAll(".d3-tip").remove();
debounceFunc();
});
window.addEventListener("resize", debounceFunc);
});
function draw() {
if (props.type === TraceGraphType.TABLE) {
segmentId.value = setLevel(segmentId.value);
return;
}
if (!traceGraph.value) {
return;
}
d3.selectAll(".d3-tip").remove();
if (props.type === "List") {
if (props.type === TraceGraphType.LIST) {
tree.value = new ListGraph(traceGraph.value, handleSelectSpan);
tree.value.init(
{ label: "TRACE_ROOT", children: segmentId.value },
@ -104,7 +113,9 @@ limitations under the License. -->
fixSpansSize.value,
);
tree.value.draw();
} else {
return;
}
if (props.type === TraceGraphType.TREE) {
tree.value = new TreeGraph(traceGraph.value, handleSelectSpan);
tree.value.init(
{ label: `${props.traceId}`, children: segmentId.value },
@ -112,12 +123,16 @@ limitations under the License. -->
);
}
}
function handleSelectSpan(i: Recordable) {
function handleSelectSpan(i: any) {
const spans = [];
const refSpans = [];
parentSpans.value = [];
refParentSpans.value = [];
currentSpan.value = i.data;
if (props.type === TraceGraphType.TABLE) {
currentSpan.value = i;
} else {
currentSpan.value = i.data;
}
if (!currentSpan.value) {
return;
}
@ -145,8 +160,29 @@ limitations under the License. -->
}
}
function viewParentSpan(span: Recordable) {
if (props.type === TraceGraphType.TABLE) {
setTableSpanStyle(span);
return;
}
tree.value.highlightParents(span);
}
function viewSpanDetails() {
showDetail.value = true;
hideActionBox();
}
function setTableSpanStyle(span: Recordable) {
const itemDom: any = document.querySelector(`.trace-item-${span.key}`);
const items: any = document.querySelectorAll(".trace-item");
for (const item of items) {
item.style.background = appStore.theme === Themes.Dark ? "#212224" : "#fff";
}
itemDom.style.background = appStore.theme === Themes.Dark ? "rgba(255, 255, 255, 0.1)" : "rgba(0, 0, 0, 0.1)";
hideActionBox();
}
function hideActionBox() {
const box: any = document.querySelector("#trace-action-box");
box.style.display = "none";
}
function traverseTree(node: Recordable, spanId: string, segmentId: string, data: Recordable) {
if (!node || node.isBroken) {
return;
@ -288,6 +324,7 @@ limitations under the License. -->
}
for (const i of [...fixSpans, ...props.data]) {
i.label = i.endpointName || "no operation name";
i.key = Math.random().toString(36).substring(2, 36);
i.children = [];
if (segmentGroup[i.segmentId]) {
segmentGroup[i.segmentId].push(i);
@ -360,6 +397,17 @@ limitations under the License. -->
}
}
}
function setLevel(arr: Recordable[], level = 1, totalExec?: number) {
for (const item of arr) {
item.level = level;
totalExec = totalExec || item.endTime - item.startTime;
item.totalExec = totalExec;
if (item.children && item.children.length > 0) {
setLevel(item.children, level + 1, totalExec);
}
}
return arr;
}
function getRefsAllNodes(tree: Recordable) {
let nodes = [];
let stack = [tree];

View File

@ -22,7 +22,7 @@ limitations under the License. -->
</el-button>
</div>
<div class="list">
<Graph :data="data" :traceId="traceId" type="List" />
<Graph :data="data" :traceId="traceId" :type="TraceGraphType.LIST" />
</div>
</div>
</template>
@ -35,6 +35,7 @@ limitations under the License. -->
import type { Span } from "@/types/trace";
import Graph from "./D3Graph/Index.vue";
import { Themes } from "@/constants/data";
import { TraceGraphType } from "./constant";
/* global defineProps, Recordable*/
const props = defineProps({

View File

@ -17,7 +17,7 @@ limitations under the License. -->
<div class="trace-t-loading" v-show="loading">
<Icon iconName="spinner" size="sm" />
</div>
<TableContainer :tableData="tableData" type="statistics" :HeaderType="HeaderType">
<TableContainer :tableData="tableData" :type="TraceGraphType.STATISTICS" :headerType="headerType">
<div class="trace-tips" v-if="!tableData.length">{{ $t("noData") }}</div>
</TableContainer>
</div>
@ -28,13 +28,14 @@ limitations under the License. -->
import TableContainer from "./Table/TableContainer.vue";
import traceTable from "../utils/trace-table";
import type { StatisticsSpan, Span, StatisticsGroupRef } from "@/types/trace";
import { TraceGraphType } from "./constant";
/* global defineProps, defineEmits, Recordable*/
const props = defineProps({
data: { type: Array as PropType<Span[]>, default: () => [] },
traceId: { type: String, default: "" },
showBtnDetail: { type: Boolean, default: false },
HeaderType: { type: String, default: "" },
headerType: { type: String, default: "" },
});
const emit = defineEmits(["load"]);
const loading = ref<boolean>(true);

View File

@ -0,0 +1,38 @@
<!-- 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. -->
<template>
<div class="trace-table-charts">
<Graph :data="data" :traceId="traceId" :type="TraceGraphType.TABLE" :headerType="headerType" />
</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import type { Span } from "@/types/trace";
import Graph from "./D3Graph/Index.vue";
import { TraceGraphType } from "./constant";
defineProps({
data: { type: Array as PropType<Span[]>, default: () => [] },
traceId: { type: String, default: "" },
headerType: { type: String, default: "" },
});
</script>
<style lang="scss" scoped>
.trace-table-charts {
overflow: auto;
padding: 10px;
height: 100%;
width: 100%;
position: relative;
}
</style>

View File

@ -1,102 +0,0 @@
<!-- 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. -->
<template>
<div class="trace-table">
<div class="trace-t-loading" v-show="loading">
<Icon iconName="spinner" size="sm" />
</div>
<TableContainer
:tableData="tableData"
type="table"
:headerType="headerType"
:traceId="traceId"
@select="handleSelectSpan"
>
<div class="trace-tips" v-if="!tableData.length">{{ $t("noData") }}</div>
</TableContainer>
</div>
</template>
<script lang="ts" setup>
import { ref, watch, onMounted } from "vue";
import type { PropType } from "vue";
import TableContainer from "./TableContainer.vue";
import traceTable from "../../utils/trace-table";
import type { Span } from "@/types/trace";
/* global defineProps, defineEmits, Recordable */
const props = defineProps({
data: { type: Array as PropType<Span[]>, default: () => [] },
traceId: { type: String, default: "" },
showBtnDetail: { type: Boolean, default: false },
headerType: { type: String, default: "" },
});
const emit = defineEmits(["select", "view", "load"]);
const loading = ref<boolean>(true);
const tableData = ref<Recordable[]>([]);
const showDetail = ref<boolean>(false);
const currentSpan = ref<Span | Recordable>({});
onMounted(() => {
tableData.value = formatData(traceTable.changeTree(props.data, props.traceId));
loading.value = false;
emit("load", () => {
loading.value = true;
});
});
function formatData(arr: Recordable[], level = 1, totalExec?: number) {
for (const item of arr) {
item.level = level;
totalExec = totalExec || item.endTime - item.startTime;
item.totalExec = totalExec;
if (item.children && item.children.length > 0) {
formatData(item.children, level + 1, totalExec);
}
}
return arr;
}
function handleSelectSpan(data: Span) {
currentSpan.value = data;
if (!props.showBtnDetail) {
showDetail.value = true;
}
emit("select", data);
}
watch(
() => props.data,
() => {
if (!props.data.length) {
tableData.value = [];
return;
}
tableData.value = formatData(traceTable.changeTree(props.data, props.traceId));
loading.value = false;
},
);
</script>
<style lang="scss" scoped>
.trace-tips {
width: 100%;
text-align: center;
margin-top: 10px;
}
.trace-table {
height: 100%;
width: 100%;
}
</style>

View File

@ -14,8 +14,8 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div class="trace">
<div class="trace-header" v-if="type === 'statistics'">
<div class="trace-table">
<div class="trace-table-header" v-if="type === TraceGraphType.STATISTICS">
<div :class="item.label" v-for="(item, index) in headerData" :key="index">
{{ item.value }}
<span
@ -28,7 +28,7 @@ limitations under the License. -->
</span>
</div>
</div>
<div class="trace-header" v-else>
<div class="trace-table-header" v-else>
<div class="method" :style="`width: ${method}px`">
<span class="cp dragger" ref="dragger">
<Icon iconName="settings_ethernet" size="sm" />
@ -44,10 +44,10 @@ limitations under the License. -->
:traceId="traceId"
v-for="(item, index) in tableData"
:data="item"
:key="'key' + index"
:key="`key${index}`"
:type="type"
:headerType="headerType"
@select="selectItem"
@click="selectItem"
/>
<slot></slot>
</div>
@ -55,9 +55,11 @@ limitations under the License. -->
<script lang="ts" setup>
import { ref, onMounted } from "vue";
import type { PropType } from "vue";
import type { Span } from "@/types/trace";
import { useTraceStore } from "@/store/modules/trace";
import TableItem from "./TableItem.vue";
import { ProfileConstant, TraceConstant, StatisticsConstant } from "./data";
import { TraceGraphType } from "../constant";
import { WidgetType } from "@/views/dashboard/data";
/* global defineProps, Nullable, defineEmits, Recordable*/
const props = defineProps({
@ -67,20 +69,21 @@ limitations under the License. -->
traceId: { type: String, default: "" },
});
const emits = defineEmits(["select"]);
const traceStore = useTraceStore();
const method = ref<number>(300);
const componentKey = ref<number>(300);
const flag = ref<boolean>(true);
const dragger = ref<Nullable<HTMLSpanElement>>(null);
let headerData: Recordable[] = TraceConstant;
if (props.headerType === "profile") {
if (props.headerType === WidgetType.Profile) {
headerData = ProfileConstant;
}
if (props.type === "statistics") {
if (props.type === TraceGraphType.STATISTICS) {
headerData = StatisticsConstant;
}
onMounted(() => {
if (props.type === "statistics") {
if (props.type === TraceGraphType.STATISTICS) {
return;
}
const drag: Nullable<HTMLSpanElement> = dragger.value;
@ -101,8 +104,24 @@ limitations under the License. -->
};
};
});
function selectItem(span: Span) {
emits("select", span);
function selectItem(event: MouseEvent) {
emits("select", traceStore.selectedSpan);
if (props.headerType === WidgetType.Profile) {
return;
}
if (props.type === TraceGraphType.STATISTICS) {
return;
}
const item: any = document.querySelector("#trace-action-box");
const tableBox = document.querySelector(".trace-table-charts")?.getBoundingClientRect();
if (!tableBox) {
return;
}
const offsetX = event.x - tableBox.x;
const offsetY = event.y - tableBox.y;
item.style.display = "block";
item.style.top = `${offsetY + 20}px`;
item.style.left = `${offsetX + 10}px`;
}
function sortStatistics(key: string) {
const element = props.tableData;
@ -152,7 +171,7 @@ limitations under the License. -->
<style lang="scss" scoped>
@import url("./table.scss");
.trace {
.trace-table {
font-size: $font-size-smaller;
height: 100%;
overflow: auto;
@ -163,7 +182,7 @@ limitations under the License. -->
float: right;
}
.trace-header {
.trace-table-header {
white-space: nowrap;
user-select: none;
border-left: 0;
@ -171,7 +190,7 @@ limitations under the License. -->
border-bottom: 1px solid var(--sw-trace-list-border);
}
.trace-header div {
.trace-table-header div {
display: inline-block;
background-color: var(--sw-table-header);
padding: 0 4px;

View File

@ -14,17 +14,17 @@ See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div v-if="type === 'statistics'">
<div v-if="type === TraceGraphType.STATISTICS">
<div class="trace-item">
<div :class="['method']">
<el-tooltip :content="data.groupRef.endpointName" placement="bottom" :show-after="300">
<el-tooltip :content="data.groupRef.endpointName" placement="top" :show-after="300">
<span>
{{ data.groupRef.endpointName }}
</span>
</el-tooltip>
</div>
<div :class="['type']">
<el-tooltip :content="data.groupRef.type" placement="bottom" :show-after="300">
<el-tooltip :content="data.groupRef.type" placement="top" :show-after="300">
<span>
{{ data.groupRef.type }}
</span>
@ -55,6 +55,7 @@ limitations under the License. -->
'level' + (data.level - 1),
{ 'trace-item-error': data.isError },
{ profiled: data.profiled === false },
`trace-item-${data.key}`,
]"
:data-text="data.profiled === false ? 'No Thread Dump' : ''"
>
@ -75,7 +76,7 @@ limitations under the License. -->
/>
<el-tooltip
:content="data.type === 'Entry' ? 'Entry' : 'Exit'"
placement="bottom"
placement="top"
:show-after="300"
v-if="['Entry', 'Exit'].includes(data.type)"
>
@ -83,12 +84,12 @@ limitations under the License. -->
<Icon :iconName="data.type === 'Entry' ? 'entry' : 'exit'" size="sm" class="mr-5" />
</span>
</el-tooltip>
<el-tooltip v-if="isCrossThread" content="CROSS_THREAD" placement="bottom" :show-after="300">
<el-tooltip v-if="isCrossThread" content="CROSS_THREAD" placement="top" :show-after="300">
<span>
<Icon iconName="cross" size="sm" class="mr-5" />
</span>
</el-tooltip>
<el-tooltip :content="data.endpointName" placement="bottom" :show-after="300">
<el-tooltip :content="data.endpointName" placement="top" :show-after="300">
<span>
{{ data.endpointName }}
</span>
@ -109,19 +110,19 @@ limitations under the License. -->
{{ data.dur ? data.dur + "" : "0" }}
</div>
<div class="api">
<el-tooltip :show-after="300" :content="data.component || '-'" placement="bottom">
<el-tooltip :show-after="300" :content="data.component || '-'" placement="top">
<span>{{ data.component || "-" }}</span>
</el-tooltip>
</div>
<div class="application">
<el-tooltip :show-after="300" :content="data.serviceCode || '-'" placement="bottom">
<el-tooltip :show-after="300" :content="data.serviceCode || '-'" placement="top">
<span>{{ data.serviceCode }}</span>
</el-tooltip>
</div>
<div class="application" v-show="headerType === 'profile'" @click="viewSpan($event)">
<div class="application" v-show="headerType === WidgetType.Profile" @click="viewSpan($event)">
<span>{{ t("view") }}</span>
</div>
<div class="application" v-show="headerType !== 'profile'">
<div class="application" v-show="headerType !== WidgetType.Profile">
<span>{{ data.attachedEvents && data.attachedEvents.length }}</span>
</div>
</div>
@ -133,7 +134,6 @@ limitations under the License. -->
:data="child"
:type="type"
:headerType="headerType"
@select="selectedItem(child)"
/>
</div>
<el-dialog v-model="showDetail" :destroy-on-close="true" fullscreen @closed="showDetail = false">
@ -148,7 +148,10 @@ limitations under the License. -->
import SpanDetail from "../D3Graph/SpanDetail.vue";
import { dateFormat } from "@/utils/dateFormat";
import { useAppStoreWithOut } from "@/store/modules/app";
import { useTraceStore } from "@/store/modules/trace";
import { Themes } from "@/constants/data";
import { TraceGraphType } from "../constant";
import { WidgetType } from "@/views/dashboard/data";
/*global Recordable*/
const props = {
@ -161,10 +164,10 @@ limitations under the License. -->
export default defineComponent({
name: "TableItem",
props,
emits: ["select"],
components: { SpanDetail },
setup(props, { emit }) {
setup(props) {
const appStore = useAppStoreWithOut();
const traceStore = useTraceStore();
const displayChildren = ref<boolean>(true);
const showDetail = ref<boolean>(false);
const { t } = useI18n();
@ -193,7 +196,6 @@ limitations under the License. -->
const key = props.data.refs.findIndex((d: { type: string }) => d.type === "CROSS_THREAD");
return key > -1 ? true : false;
});
function toggle() {
displayChildren.value = !displayChildren.value;
}
@ -213,26 +215,27 @@ limitations under the License. -->
}
function selectSpan(event: Recordable) {
const dom = event.composedPath().find((d: Recordable) => d.className.includes("trace-item"));
emit("select", props.data);
if (props.headerType === "profile") {
selectedItem(props.data);
if (props.headerType === WidgetType.Profile) {
showSelectSpan(dom);
return;
}
viewSpanDetail(dom);
}
function viewSpan(event: Recordable) {
showDetail.value = true;
const dom = event.composedPath().find((d: Recordable) => d.className.includes("trace-item"));
emit("select", props.data);
selectedItem(props.data);
viewSpanDetail(dom);
}
function selectedItem(data: HTMLSpanElement) {
emit("select", data);
function selectedItem(span: Recordable) {
traceStore.setSelectedSpan(span);
}
function viewSpanDetail(dom: HTMLSpanElement) {
showSelectSpan(dom);
showDetail.value = true;
if (props.type === TraceGraphType.STATISTICS) {
showDetail.value = true;
}
}
watch(
() => appStore.theme,
@ -262,6 +265,8 @@ limitations under the License. -->
viewSpan,
t,
appStore,
TraceGraphType,
WidgetType,
};
},
});
@ -279,8 +284,6 @@ limitations under the License. -->
}
.trace-item.level0 {
color: #448dfe;
&:hover {
background: rgb(0 0 0 / 4%);
}

View File

@ -35,7 +35,7 @@ limitations under the License. -->
</a>
</div>
<div class="trace-tree">
<Graph ref="charts" :data="data" :traceId="traceId" type="Tree" />
<Graph ref="charts" :data="data" :traceId="traceId" :type="TraceGraphType.TREE" />
</div>
</div>
</template>
@ -46,6 +46,7 @@ limitations under the License. -->
import type { Span } from "@/types/trace";
import { useI18n } from "vue-i18n";
import { ref, computed } from "vue";
import { TraceGraphType } from "./constant";
/* global defineProps */
const props = defineProps({

View File

@ -0,0 +1,23 @@
/**
* 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 enum TraceGraphType {
TREE = "Tree",
LIST = "List",
TABLE = "Table",
STATISTICS = "Statistics",
}

View File

@ -17,7 +17,7 @@
import List from "./List.vue";
import Tree from "./Tree.vue";
import Table from "./Table/Index.vue";
import Table from "./Table.vue";
import Statistics from "./Statistics.vue";
export default {

View File

@ -265,7 +265,7 @@ export default class ListGraph {
.attr("cx", (d: Recordable) => {
const events = d.data.attachedEvents;
if (events && events.length > 9) {
return 272;
return 273;
} else {
return 270;
}
@ -350,16 +350,6 @@ export default class ListGraph {
if (d.data.children.length === 0) return;
this.click(d, this);
});
nodeUpdate
.transition()
.duration(400)
.attr("transform", (d: Recordable) => `translate(${d.y + 3},${d.x})`)
.style("opacity", 1)
.select("circle")
.style("fill", (d: Recordable) =>
d._children ? `${this.sequentialScale(this.list.indexOf(d.data.serviceCode))}` : "#eee",
);
// Transition exiting nodes to the parent's new position.
node
.exit()

View File

@ -15,42 +15,13 @@
* limitations under the License.
*/
import type { Ref, Span, StatisticsSpan, StatisticsGroupRef, TraceTreeRef } from "@/types/trace";
import type { Span, 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<string, Span[]> {
const result = new Map<string, Span[]>();
const traceTreeRef = this.changeTreeCore(data);
@ -255,47 +226,6 @@ export default class TraceUtil {
}
}
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<string, Span[]>): void {
nodes.forEach((node: Span) => {
const groupRef = node.endpointName + ":" + node.type;