You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2023/03/30 01:50:20 UTC

[skywalking-booster-ui] branch main updated: feat: update trace profiling protocol (#250)

This is an automated email from the ASF dual-hosted git repository.

wusheng pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/skywalking-booster-ui.git


The following commit(s) were added to refs/heads/main by this push:
     new 55e4828  feat: update trace profiling protocol (#250)
55e4828 is described below

commit 55e4828adcfbf51352c571900cc468b5563ad212
Author: Fine0830 <fa...@gmail.com>
AuthorDate: Thu Mar 30 09:50:14 2023 +0800

    feat: update trace profiling protocol (#250)
---
 src/assets/icons/cross.svg                         |  15 ++++
 src/assets/icons/entry.svg                         |  15 ++++
 src/assets/icons/exit.svg                          |  15 ++++
 src/assets/img/tools/ENTRY.png                     | Bin 0 -> 262 bytes
 src/assets/img/tools/EXIT.png                      | Bin 0 -> 269 bytes
 src/assets/img/tools/STREAM.png                    | Bin 0 -> 373 bytes
 src/graphql/fragments/profile.ts                   |  79 ++++++++++----------
 src/graphql/query/profile.ts                       |   3 -
 src/store/modules/profile.ts                       |  71 ++++++++----------
 src/styles/lib.scss                                |  28 +++++++
 src/types/trace.d.ts                               |   1 +
 .../related/profile/components/SegmentList.vue     |  26 +++----
 .../related/profile/components/SpanTree.vue        |  44 +++++------
 .../related/profile/components/TaskList.vue        |  28 +++----
 .../related/trace/components/Table/TableItem.vue   |  81 ++++++++++++++++++---
 .../dashboard/related/trace/utils/d3-trace-list.ts |  58 +++++++++++++++
 16 files changed, 321 insertions(+), 143 deletions(-)

diff --git a/src/assets/icons/cross.svg b/src/assets/icons/cross.svg
new file mode 100644
index 0000000..3f531cd
--- /dev/null
+++ b/src/assets/icons/cross.svg
@@ -0,0 +1,15 @@
+<!-- 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. -->
+<svg t="1680101648371" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15649" width="48" height="48"><path d="M832 272c0-62.4-51-112.9-113.6-112-60.7 0.9-110 50.6-110.4 111.3-0.3 52.6 35.6 96.8 84.2 109.2 14 3.6 23.8 16 24.1 30.4 0.5 27.3-4.4 57.4-22.3 82.5-28.7 40.3-80.7 54.9-126.6 67.8-29 8.1-50.1 10.2-68.7 12-26.4 2.6-51.4 5.1-82.6 23-6.6 3.8-13.1 8-19.2 12.6-5.3 4-12.8 0.2-12.8-6.4V241.3c0-12.2 6.8-23.5 17.7-28.9 37.1-18.4 62.6-56.8 62.3-10 [...]
\ No newline at end of file
diff --git a/src/assets/icons/entry.svg b/src/assets/icons/entry.svg
new file mode 100644
index 0000000..4a85fcf
--- /dev/null
+++ b/src/assets/icons/entry.svg
@@ -0,0 +1,15 @@
+<!-- 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. -->
+<svg t="1680083488716" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1096" width="48" height="48"><path d="M853.333333 512a42.666667 42.666667 0 0 0-42.666666-42.666667h-323.84l98.133333-97.706666a42.666667 42.666667 0 1 0-60.586667-60.586667l-170.666666 170.666667a42.666667 42.666667 0 0 0-8.96 14.08 42.666667 42.666667 0 0 0 0 32.426666 42.666667 42.666667 0 0 0 8.96 14.08l170.666666 170.666667a42.666667 42.666667 0 0 0 60.586667 0 42.66666 [...]
\ No newline at end of file
diff --git a/src/assets/icons/exit.svg b/src/assets/icons/exit.svg
new file mode 100644
index 0000000..5eb99ad
--- /dev/null
+++ b/src/assets/icons/exit.svg
@@ -0,0 +1,15 @@
+<!-- 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. -->
+<svg t="1680104481890" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7991" width="16" height="16"><path d="M918.4 489.6l-160-160c-12.8-12.8-32-12.8-44.8 0-12.8 12.8-12.8 32 0 44.8l105.6 105.6L512 480c-19.2 0-32 12.8-32 32s12.8 32 32 32l307.2 0-105.6 105.6c-12.8 12.8-12.8 32 0 44.8 6.4 6.4 12.8 9.6 22.4 9.6 9.6 0 16-3.2 22.4-9.6l160-163.2c0 0 0-3.2 3.2-3.2C931.2 518.4 931.2 499.2 918.4 489.6zM832 736c-19.2 0-32 12.8-32 32l0 64c0 19.2-12.8 32-3 [...]
\ No newline at end of file
diff --git a/src/assets/img/tools/ENTRY.png b/src/assets/img/tools/ENTRY.png
new file mode 100644
index 0000000..52a33a0
Binary files /dev/null and b/src/assets/img/tools/ENTRY.png differ
diff --git a/src/assets/img/tools/EXIT.png b/src/assets/img/tools/EXIT.png
new file mode 100644
index 0000000..88aca49
Binary files /dev/null and b/src/assets/img/tools/EXIT.png differ
diff --git a/src/assets/img/tools/STREAM.png b/src/assets/img/tools/STREAM.png
new file mode 100644
index 0000000..8e1c57f
Binary files /dev/null and b/src/assets/img/tools/STREAM.png differ
diff --git a/src/graphql/fragments/profile.ts b/src/graphql/fragments/profile.ts
index c105308..01744ee 100644
--- a/src/graphql/fragments/profile.ts
+++ b/src/graphql/fragments/profile.ts
@@ -15,37 +15,6 @@
  * limitations under the License.
  */
 
-export const ProfileSegment = {
-  variable: "$segmentId: String",
-  query: `
-  segment: getProfiledSegment(segmentId: $segmentId) {
-    spans {
-      spanId
-      parentSpanId
-      serviceCode
-      startTime
-      endTime
-      endpointName
-      type
-      peer
-      component
-      isError
-      layer
-      tags {
-        key value
-      }
-      logs {
-        time
-        data {
-          key
-          value
-        }
-      }
-    }
-  }
-  `,
-};
-
 export const CreateProfileTask = {
   variable: "$creationRequest: ProfileTaskCreationRequest",
   query: `
@@ -79,23 +48,55 @@ export const GetProfileTaskList = {
   `,
 };
 export const GetProfileTaskSegmentList = {
-  variable: "$taskID: String",
+  variable: "$taskID: ID!",
   query: `
-  segmentList: getProfileTaskSegmentList(taskID: $taskID) {
-    segmentId
+  segmentList: getProfileTaskSegments(taskID: $taskID) {
+    traceId
+    instanceId
+    instanceName
     endpointNames
-    start
     duration
-    traceIds
-    isError
+    start
+    spans {
+      spanId
+      parentSpanId
+      segmentId
+      refs {
+        traceId
+        parentSegmentId
+        parentSpanId
+        type
+      }
+      serviceCode
+      serviceInstanceName
+      startTime
+      endTime
+      endpointName
+      type
+      peer
+      component
+      isError
+      layer
+      tags {
+        key value
+      }
+      logs {
+        time
+        data {
+          key
+          value
+        }
+      }
+      profiled
+    }
   }
   `,
 };
 
 export const GetProfileAnalyze = {
-  variable: "$segmentId: String!, $timeRanges: [ProfileAnalyzeTimeRange!]!",
+  variable: "$queries: [SegmentProfileAnalyzeQuery!]!",
   query: `
-  analyze: getProfileAnalyze(segmentId: $segmentId, timeRanges: $timeRanges) {
+  analyze: getSegmentsProfileAnalyze(queries: $queries) {
     tip
     trees {
       elements {
diff --git a/src/graphql/query/profile.ts b/src/graphql/query/profile.ts
index 3534140..6b762f0 100644
--- a/src/graphql/query/profile.ts
+++ b/src/graphql/query/profile.ts
@@ -16,7 +16,6 @@
  */
 
 import {
-  ProfileSegment,
   CreateProfileTask,
   GetProfileTaskList,
   GetProfileTaskSegmentList,
@@ -24,8 +23,6 @@ import {
   GetProfileTaskLogs,
 } from "../fragments/profile";
 
-export const queryProfileSegment = `query queryProfileSegment(${ProfileSegment.variable}) {${ProfileSegment.query}}`;
-
 export const saveProfileTask = `mutation createProfileTask(${CreateProfileTask.variable}) {${CreateProfileTask.query}}`;
 
 export const getProfileTaskList = `query getProfileTaskList(${GetProfileTaskList.variable}) {
diff --git a/src/store/modules/profile.ts b/src/store/modules/profile.ts
index e141469..780629b 100644
--- a/src/store/modules/profile.ts
+++ b/src/store/modules/profile.ts
@@ -34,6 +34,7 @@ interface ProfileState {
   taskEndpoints: Endpoint[];
   condition: { serviceId: string; endpointName: string };
   taskList: TaskListItem[];
+  currentTask: Recordable<TaskListItem>;
   segmentList: Trace[];
   currentSegment: Recordable<Trace>;
   segmentSpans: Array<Recordable<SegmentSpan>>;
@@ -51,6 +52,7 @@ export const profileStore = defineStore({
     condition: { serviceId: "", endpointName: "" },
     taskList: [],
     segmentList: [],
+    currentTask: {},
     currentSegment: {},
     segmentSpans: [],
     currentSpan: {},
@@ -65,11 +67,27 @@ export const profileStore = defineStore({
         ...data,
       };
     },
+    setCurrentTask(task: TaskListItem) {
+      this.currentTask = task || {};
+      this.analyzeTrees = [];
+    },
+    setSegmentSpans(spans: Recordable<SegmentSpan>[]) {
+      this.currentSpan = spans[0] || {};
+      this.segmentSpans = spans;
+    },
     setCurrentSpan(span: Recordable<SegmentSpan>) {
       this.currentSpan = span;
+      this.analyzeTrees = [];
     },
-    setCurrentSegment(s: Recordable<Trace>) {
-      this.currentSegment = s;
+    setCurrentSegment(segment: Trace) {
+      this.currentSegment = segment;
+      this.segmentSpans = segment.spans || [];
+      if (segment.spans) {
+        this.currentSpan = segment.spans[0] || {};
+      } else {
+        this.currentSpan = {};
+      }
+      this.analyzeTrees = [];
     },
     setHighlightTop() {
       this.highlightTop = !this.highlightTop;
@@ -104,8 +122,9 @@ export const profileStore = defineStore({
       if (res.data.errors) {
         return res.data;
       }
-      const list = res.data.data.taskList;
+      const list = res.data.data.taskList || [];
       this.taskList = list;
+      this.currentTask = list[0] || {};
       if (!list.length) {
         this.segmentList = [];
         this.segmentSpans = [];
@@ -128,7 +147,7 @@ export const profileStore = defineStore({
       }
       const { segmentList } = res.data.data;
 
-      this.segmentList = segmentList;
+      this.segmentList = segmentList || [];
       if (!segmentList.length) {
         this.segmentSpans = [];
         this.analyzeTrees = [];
@@ -137,50 +156,22 @@ export const profileStore = defineStore({
       }
       if (segmentList[0]) {
         this.currentSegment = segmentList[0];
-        this.getSegmentSpans({ segmentId: segmentList[0].segmentId });
+        this.getSegmentSpans(segmentList[0].segmentId);
       } else {
         this.currentSegment = {};
       }
       return res.data;
     },
-    async getSegmentSpans(params: { segmentId: string }) {
-      if (!params.segmentId) {
-        return new Promise((resolve) => resolve({}));
-      }
-      const res: AxiosResponse = await graphql.query("queryProfileSegment").params(params);
-      if (res.data.errors) {
-        this.segmentSpans = [];
-        return res.data;
-      }
-      const { segment } = res.data.data;
-      if (!segment) {
-        this.segmentSpans = [];
-        this.analyzeTrees = [];
-        return res.data;
-      }
-      this.segmentSpans = segment.spans.map((d: SegmentSpan) => {
-        return {
-          ...d,
-          segmentId: this.currentSegment?.segmentId,
-          traceId: (this.currentSegment.traceIds as any)[0],
-        };
-      });
-      if (!(segment.spans && segment.spans.length)) {
-        this.analyzeTrees = [];
-        return res.data;
-      }
-      const index = segment.spans.length - 1 || 0;
-      this.currentSpan = segment.spans[index];
-      return res.data;
+    async getSegmentSpans() {
+      this.analyzeTrees = [];
+      this.segmentSpans = this.currentSegment.spans;
+      this.currentSpan = this.currentSegment.spans[0] || {};
     },
-    async getProfileAnalyze(params: { segmentId: string; timeRanges: Array<{ start: number; end: number }> }) {
-      if (!params.segmentId) {
-        return new Promise((resolve) => resolve({}));
-      }
-      if (!params.timeRanges.length) {
+    async getProfileAnalyze(params: Array<{ segmentId: string; timeRange: { start: number; end: number } }>) {
+      if (!params.length) {
         return new Promise((resolve) => resolve({}));
       }
-      const res: AxiosResponse = await graphql.query("getProfileAnalyze").params(params);
+      const res: AxiosResponse = await graphql.query("getProfileAnalyze").params({ queries: params });
 
       if (res.data.errors) {
         this.analyzeTrees = [];
diff --git a/src/styles/lib.scss b/src/styles/lib.scss
index d0c1ecb..9ea6b3d 100644
--- a/src/styles/lib.scss
+++ b/src/styles/lib.scss
@@ -215,3 +215,31 @@
   box-shadow: inset 0 0 6px #888;
   background-color: #999;
 }
+.d3-tip {
+  line-height: 1;
+  padding: 8px;
+  color: #eee;
+  border-radius: 4px;
+  font-size: 12px;
+}
+.d3-tip {
+  background: #252a2f;
+}
+
+.d3-tip:after {
+  box-sizing: border-box;
+  display: block;
+  font-size: 10px;
+  width: 100%;
+  line-height: 0.8;
+  color: #252a2f;
+  content: "\25BC";
+  position: absolute;
+  text-align: center;
+}
+
+.d3-tip.n:after {
+  margin: -2px 0 0 0;
+  top: 100%;
+  left: 0;
+}
diff --git a/src/types/trace.d.ts b/src/types/trace.d.ts
index 9055e8a..b71be5b 100644
--- a/src/types/trace.d.ts
+++ b/src/types/trace.d.ts
@@ -22,6 +22,7 @@ export interface Trace {
   start: string;
   traceIds: Array<string | any>;
   segmentId: string;
+  spans: Span[];
 }
 
 export interface Span {
diff --git a/src/views/dashboard/related/profile/components/SegmentList.vue b/src/views/dashboard/related/profile/components/SegmentList.vue
index e19aeba..23be215 100644
--- a/src/views/dashboard/related/profile/components/SegmentList.vue
+++ b/src/views/dashboard/related/profile/components/SegmentList.vue
@@ -20,11 +20,11 @@ limitations under the License. -->
         {{ t("noData") }}
       </div>
       <table class="profile-t">
-        <tr class="profile-tr cp" v-for="(i, index) in profileStore.segmentList" @click="selectTrace(i)" :key="index">
+        <tr class="profile-tr cp" v-for="(i, index) in profileStore.segmentList" @click="selectSegment(i)" :key="index">
           <td
             class="profile-td"
             :class="{
-              selected: selectedKey == i.segmentId,
+              selected: key === i.spans[0].segmentId,
             }"
           >
             <div
@@ -47,25 +47,25 @@ limitations under the License. -->
   </div>
 </template>
 <script lang="ts" setup>
-  import { ref } from "vue";
+  import { computed } from "vue";
   import { useI18n } from "vue-i18n";
   import { useProfileStore } from "@/store/modules/profile";
   import type { Trace } from "@/types/trace";
-  import { ElMessage } from "element-plus";
   import { dateFormat } from "@/utils/dateFormat";
 
   const { t } = useI18n();
   const profileStore = useProfileStore();
-  const selectedKey = ref<string>("");
-
-  async function selectTrace(item: Trace) {
+  const key = computed(
+    () =>
+      (profileStore.currentSegment &&
+        profileStore.currentSegment.spans.length &&
+        profileStore.currentSegment.spans[0].segmentId) ||
+      "",
+  );
+
+  async function selectSegment(item: Trace) {
     profileStore.setCurrentSegment(item);
-    selectedKey.value = item.segmentId;
-    const res = await profileStore.getSegmentSpans({ segmentId: item.segmentId });
-
-    if (res.errors) {
-      ElMessage.error(res.errors);
-    }
+    profileStore.setSegmentSpans(item.spans);
   }
 </script>
 <style lang="scss" scoped>
diff --git a/src/views/dashboard/related/profile/components/SpanTree.vue b/src/views/dashboard/related/profile/components/SpanTree.vue
index ab19f41..217473e 100644
--- a/src/views/dashboard/related/profile/components/SpanTree.vue
+++ b/src/views/dashboard/related/profile/components/SpanTree.vue
@@ -15,14 +15,8 @@ limitations under the License. -->
 <template>
   <div class="profile-trace-dashboard" v-if="profileStore.currentSegment">
     <div class="profile-trace-detail-wrapper">
-      <Selector
-        size="small"
-        :value="traceId || (traceIds[0] && traceIds[0].value) || ''"
-        :options="traceIds"
-        placeholder="Select a trace id"
-        @change="changeTraceId"
-        class="profile-trace-detail-ids mr-10"
-      />
+      <label>Trace ID</label>
+      <el-input class="input mr-10 ml-5" readonly :value="profileStore.currentSegment.traceId" size="small" />
       <Selector
         size="small"
         :value="mode"
@@ -31,14 +25,14 @@ limitations under the License. -->
         @change="spanModeChange"
         class="mr-10"
       />
-      <el-button type="primary" size="small" @click="analyzeProfile()">
+      <el-button type="primary" size="small" :disabled="!profileStore.currentSpan.profiled" @click="analyzeProfile()">
         {{ t("analyze") }}
       </el-button>
     </div>
     <div class="profile-table">
       <Table
         :data="profileStore.segmentSpans"
-        :traceId="profileStore.currentSegment.traceIds && profileStore.currentSegment.traceIds[0]"
+        :traceId="profileStore.currentSegment.traceId"
         :showBtnDetail="true"
         headerType="profile"
         @select="selectSpan"
@@ -47,7 +41,7 @@ limitations under the License. -->
   </div>
 </template>
 <script lang="ts" setup>
-  import { ref, computed } from "vue";
+  import { ref } from "vue";
   import { useI18n } from "vue-i18n";
   import Table from "../../trace/components/Table/Index.vue";
   import { useProfileStore } from "@/store/modules/profile";
@@ -64,13 +58,6 @@ limitations under the License. -->
   const mode = ref<string>("include");
   const message = ref<string>("");
   const timeRange = ref<Array<{ start: number; end: number }>>([]);
-  const traceId = ref<string>("");
-  const traceIds = computed(() =>
-    (profileStore.currentSegment.traceIds || []).map((id: string) => ({
-      label: id,
-      value: id,
-    })),
-  );
 
   function selectSpan(span: Span) {
     profileStore.setCurrentSpan(span);
@@ -81,17 +68,20 @@ limitations under the License. -->
     updateTimeRange();
   }
 
-  function changeTraceId(opt: Option[]) {
-    traceId.value = opt[0].value;
-  }
-
   async function analyzeProfile() {
+    if (!profileStore.currentSpan.profiled) {
+      ElMessage.info("It's a un-profiled span");
+      return;
+    }
     emits("loading", true);
     updateTimeRange();
-    const res = await profileStore.getProfileAnalyze({
-      segmentId: profileStore.currentSegment.segmentId,
-      timeRanges: timeRange.value,
+    const params = timeRange.value.map((t: { start: number; end: number }) => {
+      return {
+        segmentId: profileStore.currentSpan.segmentId,
+        timeRange: t,
+      };
     });
+    const res = await profileStore.getProfileAnalyze(params);
     emits("loading", false);
     if (res.errors) {
       ElMessage.error(res.errors);
@@ -175,4 +165,8 @@ limitations under the License. -->
   .profile-trace-detail-ids {
     width: 300px;
   }
+
+  .input {
+    width: 250px;
+  }
 </style>
diff --git a/src/views/dashboard/related/profile/components/TaskList.vue b/src/views/dashboard/related/profile/components/TaskList.vue
index 55c7f14..a385422 100644
--- a/src/views/dashboard/related/profile/components/TaskList.vue
+++ b/src/views/dashboard/related/profile/components/TaskList.vue
@@ -25,7 +25,7 @@ limitations under the License. -->
             <td
               class="profile-td"
               :class="{
-                selected: selectedTask.id === i.id,
+                selected: profileStore.currentTask && profileStore.currentTask.id === i.id,
               }"
             >
               <div class="ell">
@@ -49,7 +49,7 @@ limitations under the License. -->
     </div>
   </div>
   <el-dialog v-model="viewDetail" :destroy-on-close="true" fullscreen @closed="viewDetail = false">
-    <div class="profile-detail flex-v">
+    <div class="profile-detail flex-v" v-if="profileStore.currentTask">
       <div>
         <h5 class="mb-10">{{ t("task") }}.</h5>
         <div class="mb-10 clear item">
@@ -58,33 +58,35 @@ limitations under the License. -->
         </div>
         <div class="mb-10 clear item">
           <span class="g-sm-4 grey">{{ t("endpoint") }}:</span>
-          <span class="g-sm-8 wba">{{ selectedTask.endpointName }}</span>
+          <span class="g-sm-8 wba">{{ profileStore.currentTask.endpointName }}</span>
         </div>
         <div class="mb-10 clear item">
           <span class="g-sm-4 grey">{{ t("monitorTime") }}:</span>
           <span class="g-sm-8 wba">
-            {{ dateFormat(selectedTask.startTime) }}
+            {{ dateFormat(profileStore.currentTask.startTime) }}
           </span>
         </div>
         <div class="mb-10 clear item">
           <span class="g-sm-4 grey">{{ t("monitorDuration") }}:</span
-          ><span class="g-sm-8 wba">{{ selectedTask.duration }} min</span>
+          ><span class="g-sm-8 wba">{{ profileStore.currentTask.duration }} min</span>
         </div>
         <div class="mb-10 clear item">
           <span class="g-sm-4 grey">{{ t("minThreshold") }}:</span>
-          <span class="g-sm-8 wba"> {{ selectedTask.minDurationThreshold }} ms </span>
+          <span class="g-sm-8 wba"> {{ profileStore.currentTask.minDurationThreshold }} ms </span>
         </div>
         <div class="mb-10 clear item">
           <span class="g-sm-4 grey">{{ t("dumpPeriod") }}:</span>
-          <span class="g-sm-8 wba">{{ selectedTask.dumpPeriod }}</span>
+          <span class="g-sm-8 wba">{{ profileStore.currentTask.dumpPeriod }}</span>
         </div>
         <div class="mb-10 clear item">
           <span class="g-sm-4 grey">{{ t("maxSamplingCount") }}:</span>
-          <span class="g-sm-8 wba">{{ selectedTask.maxSamplingCount }}</span>
+          <span class="g-sm-8 wba">{{ profileStore.currentTask.maxSamplingCount }}</span>
         </div>
       </div>
       <div>
-        <h5 class="mb-10 mt-10" v-show="selectedTask.logs && selectedTask.logs.length"> {{ t("logs") }}. </h5>
+        <h5 class="mb-10 mt-10" v-show="profileStore.currentTask.logs && profileStore.currentTask.logs.length">
+          {{ t("logs") }}.
+        </h5>
         <div class="log-item" v-for="(i, index) in Object.keys(instanceLogs)" :key="index">
           <div class="mb-10 sm">
             <span class="mr-10 grey">{{ t("instance") }}:</span>
@@ -115,12 +117,12 @@ limitations under the License. -->
   const selectorStore = useSelectorStore();
   const viewDetail = ref<boolean>(false);
   const service = ref<string>("");
-  const selectedTask = ref<TaskListItem | Record<string, never>>({});
+  // const selectedTask = ref<TaskListItem | Record<string, never>>({});
   const instanceLogs = ref<TaskLog | any>({});
 
   async function changeTask(item: TaskListItem) {
     profileStore.setCurrentSegment({});
-    selectedTask.value = item;
+    profileStore.setCurrentTask(item);
     const res = await profileStore.getSegmentList({ taskID: item.id });
     if (res.errors) {
       ElMessage.error(res.errors);
@@ -130,7 +132,7 @@ limitations under the License. -->
   async function viewTask(e: Event, item: TaskListItem) {
     window.event ? (window.event.cancelBubble = true) : e.stopPropagation();
     viewDetail.value = true;
-    selectedTask.value = item;
+    profileStore.setCurrentTask(item);
     service.value = (selectorStore.services.filter((s: any) => s.id === item.serviceId)[0] || {}).label;
     const res = await profileStore.getTaskLogs({ taskID: item.id });
 
@@ -150,7 +152,7 @@ limitations under the License. -->
         instanceLogs.value[d.instanceName] = [{ operationType: d.operationType, operationTime: d.operationTime }];
       }
     }
-    selectedTask.value = item;
+    profileStore.setCurrentTask(item);
   }
 </script>
 <style lang="scss" scoped>
diff --git a/src/views/dashboard/related/trace/components/Table/TableItem.vue b/src/views/dashboard/related/trace/components/Table/TableItem.vue
index 9f88355..a9047f8 100644
--- a/src/views/dashboard/related/trace/components/Table/TableItem.vue
+++ b/src/views/dashboard/related/trace/components/Table/TableItem.vue
@@ -48,7 +48,16 @@ limitations under the License. -->
     </div>
   </div>
   <div v-else>
-    <div @click="selectSpan" :class="['trace-item', 'level' + (data.level - 1), { 'trace-item-error': data.isError }]">
+    <div
+      @click="selectSpan"
+      :class="[
+        'trace-item',
+        'level' + (data.level - 1),
+        { 'trace-item-error': data.isError },
+        { profiled: data.profiled === false },
+      ]"
+      :data-text="data.profiled === false ? 'No Thread Dump' : ''"
+    >
       <div
         :class="['method', 'level' + (data.level - 1)]"
         :style="{
@@ -62,11 +71,27 @@ limitations under the License. -->
           v-if="data.children && data.children.length"
           iconName="arrow-down"
           size="sm"
+          class="mr-5"
         />
+        <el-tooltip
+          :content="data.type === 'Entry' ? 'Entry' : 'Exit'"
+          placement="bottom"
+          v-if="['Entry', 'Exit'].includes(data.type)"
+        >
+          <span>
+            <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">
+          <span>
+            <Icon iconName="cross" size="sm" class="mr-5" />
+          </span>
+        </el-tooltip>
         <el-tooltip :content="data.endpointName" placement="bottom">
           <span>
             {{ data.endpointName }}
           </span>
+          <span v-if="data.profiled === false"></span>
         </el-tooltip>
       </div>
       <div class="start-time">
@@ -161,6 +186,10 @@ limitations under the License. -->
         const resultStr = result.toFixed(4) + "%";
         return resultStr === "0.0000%" ? "0.9%" : resultStr;
       });
+      const isCrossThread = computed(() => {
+        const key = props.data.refs.findIndex((d: { type: string }) => d.type === "CROSS_THREAD");
+        return key > -1 ? true : false;
+      });
 
       function toggle() {
         displayChildren.value = !displayChildren.value;
@@ -174,6 +203,10 @@ limitations under the License. -->
           item.style.background = "#fff";
         }
         dom.style.background = "rgba(0, 0, 0, 0.1)";
+        const p: any = document.getElementsByClassName("profiled")[0];
+        if (p) {
+          p.style.background = "#eee";
+        }
       }
       function selectSpan(event: any) {
         const dom = event.composedPath().find((d: any) => d.className.includes("trace-item"));
@@ -202,6 +235,7 @@ limitations under the License. -->
         displayChildren,
         outterPercent,
         innerPercent,
+        isCrossThread,
         viewSpanDetail,
         toggle,
         dateFormat,
@@ -233,17 +267,44 @@ limitations under the License. -->
 
     &:hover {
       background: rgba(0, 0, 0, 0.04);
-      color: #448dfe;
     }
+  }
 
-    &::before {
-      position: absolute;
-      content: "";
-      width: 5px;
-      height: 100%;
-      background: #448dfe;
-      left: 0;
-    }
+  .profiled {
+    background-color: #eee;
+    position: relative;
+  }
+
+  .profiled:before {
+    content: attr(data-text);
+    position: absolute;
+    top: 30px;
+    left: 220px;
+    width: 100px;
+    padding: 10px;
+    border-radius: 5px;
+    border: 1px solid #ccc;
+    background-color: #333;
+    color: #fff;
+    text-align: center;
+    box-shadow: #eee 1px 2px 10px;
+    display: none;
+  }
+
+  .profiled:after {
+    content: "";
+    position: absolute;
+    left: 250px;
+    top: 20px;
+    border: 6px solid #333;
+    border-color: transparent transparent #333 transparent;
+    display: none;
+  }
+
+  .profiled:hover:before,
+  .profiled:hover:after {
+    display: block;
+    z-index: 999;
   }
 
   .trace-item-error {
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 8c14382..b5bfbd9 100644
--- a/src/views/dashboard/related/trace/utils/d3-trace-list.ts
+++ b/src/views/dashboard/related/trace/utils/d3-trace-list.ts
@@ -19,6 +19,7 @@ import * as d3 from "d3";
 import d3tip from "d3-tip";
 import type { Trace } from "@/types/trace";
 import dayjs from "dayjs";
+import icons from "@/assets/img/icons";
 
 export default class ListGraph {
   private barHeight = 48;
@@ -29,6 +30,7 @@ export default class ListGraph {
   private height = 0;
   private svg: any = null;
   private tip: any = null;
+  private prompt: any = null;
   private row: any[] = [];
   private data: any = [];
   private min = 0;
@@ -64,7 +66,14 @@ export default class ListGraph {
           }
           `;
       });
+    this.prompt = (d3tip as any)()
+      .attr("class", "d3-tip")
+      .offset([-8, 0])
+      .html((d: any) => {
+        return `<div class="mb-5">${d.data.type}</div>`;
+      });
     this.svg.call(this.tip);
+    this.svg.call(this.prompt);
   }
   diagonal(d: any) {
     return `M ${d.source.y} ${d.source.x + 5}
@@ -152,6 +161,55 @@ export default class ListGraph {
       .attr("x", 20)
       .attr("width", "100%")
       .attr("fill", "rgba(0,0,0,0)");
+    nodeEnter
+      .append("image")
+      .attr("width", 16)
+      .attr("height", 16)
+      .attr("x", 6)
+      .attr("y", -10)
+      .attr("xlink:href", (d: any) =>
+        d.data.type === "Entry" ? icons.ENTRY : d.data.type === "Exit" ? icons.EXIT : "",
+      )
+      .style("display", (d: any) => {
+        ["Entry", "Exit"].includes(d.data.type) ? "inline" : "none";
+      })
+      .on("mouseover", function (event: any, d: Trace) {
+        event.stopPropagation();
+        t.prompt.show(d, this);
+      })
+      .on("mouseout", function (event: any, d: Trace) {
+        event.stopPropagation();
+        t.prompt.hide(d, this);
+      });
+    nodeEnter
+      .append("image")
+      .attr("width", 16)
+      .attr("height", 16)
+      .attr("x", 6)
+      .attr("y", -10)
+      .attr("xlink:href", (d: any) => {
+        const key = (d.data.refs || []).findIndex((d: { type: string }) => d.type === "CROSS_THREAD");
+        return key > -1 ? icons.STREAM : "";
+      })
+      .style("display", (d: any) => {
+        const key = (d.data.refs || []).findIndex((d: { type: string }) => d.type === "CROSS_THREAD");
+        return key > -1 ? "inline" : "none";
+      })
+      .on("mouseover", function (event: any, d: any) {
+        const a = {
+          ...d,
+          data: {
+            ...d.data,
+            type: "CROSS_THREAD",
+          },
+        };
+        event.stopPropagation();
+        t.prompt.show(a, this);
+      })
+      .on("mouseout", function (event: any, d: Trace) {
+        event.stopPropagation();
+        t.prompt.hide(d, this);
+      });
     nodeEnter
       .append("text")
       .attr("x", 13)