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)