feat: update trace profiling protocol (#250)

This commit is contained in:
Fine0830 2023-03-30 09:50:14 +08:00 committed by GitHub
parent 68eea52e88
commit 55e4828adc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 320 additions and 142 deletions

View File

@ -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-101.1-0.5-62.8-53.2-113.4-116-111.2C288.1 2.1 240 51.4 240 112c0 44 25.4 82.1 62.3 100.4 10.9 5.4 17.7 16.5 17.7 28.6v541.7c0 12.2-6.8 23.5-17.7 28.9-37.1 18.4-62.6 56.8-62.3 101.1 0.4 62.8 53.1 113.3 115.9 111.2C416 1021.9 464 972.5 464 912c0-44-25.4-82.1-62.3-100.4-10.9-5.4-17.7-16.5-17.7-28.6v-19.2c0-42 19.9-81.8 54.3-105.9 3.1-2.2 6.4-4.3 9.7-6.2 19.3-11.1 33.5-12.5 57-14.8 20.2-2 45.3-4.5 79.7-14.1 50.5-14.2 119.6-33.5 161.4-92.3 24-33.7 35.4-75 34.1-123-0.2-6.9-0.7-13.8-1.4-20.9-1.1-10.7 3.5-21 11.8-27.8 25.3-20.4 41.4-51.7 41.4-86.8zM304 112c0-26.5 21.5-48 48-48s48 21.5 48 48-21.5 48-48 48-48-21.5-48-48z m96 800c0 26.5-21.5 48-48 48s-48-21.5-48-48 21.5-48 48-48 48 21.5 48 48z m320-592c-26.5 0-48-21.5-48-48s21.5-48 48-48 48 21.5 48 48-21.5 48-48 48z" p-id="15650"></path></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -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.666667 42.666667 0 0 0 0-60.586667L486.826667 554.666667H810.666667a42.666667 42.666667 0 0 0 42.666666-42.666667zM725.333333 85.333333H298.666667a128 128 0 0 0-128 128v597.333334a128 128 0 0 0 128 128h426.666666a128 128 0 0 0 128-128v-128a42.666667 42.666667 0 0 0-85.333333 0v128a42.666667 42.666667 0 0 1-42.666667 42.666666H298.666667a42.666667 42.666667 0 0 1-42.666667-42.666666V213.333333a42.666667 42.666667 0 0 1 42.666667-42.666666h426.666666a42.666667 42.666667 0 0 1 42.666667 42.666666v128a42.666667 42.666667 0 0 0 85.333333 0V213.333333a128 128 0 0 0-128-128z" p-id="1097"></path></svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

15
src/assets/icons/exit.svg Normal file
View File

@ -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-32 32L224 864c-19.2 0-32-12.8-32-32L192 192c0-19.2 12.8-32 32-32l544 0c19.2 0 32 12.8 32 32l0 64c0 19.2 12.8 32 32 32s32-12.8 32-32L864 192c0-54.4-41.6-96-96-96L224 96C169.6 96 128 137.6 128 192l0 640c0 54.4 41.6 96 96 96l544 0c54.4 0 96-41.6 96-96l0-64C864 748.8 851.2 736 832 736z" p-id="7992"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 B

View File

@ -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 {

View File

@ -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}) {

View File

@ -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) {
async getProfileAnalyze(params: Array<{ segmentId: string; timeRange: { start: number; end: number } }>) {
if (!params.length) {
return new Promise((resolve) => resolve({}));
}
if (!params.timeRanges.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 = [];

View File

@ -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;
}

View File

@ -22,6 +22,7 @@ export interface Trace {
start: string;
traceIds: Array<string | any>;
segmentId: string;
spans: Span[];
}
export interface Span {

View File

@ -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>("");
const key = computed(
() =>
(profileStore.currentSegment &&
profileStore.currentSegment.spans.length &&
profileStore.currentSegment.spans[0].segmentId) ||
"",
);
async function selectTrace(item: Trace) {
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>

View File

@ -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>

View File

@ -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>

View File

@ -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 {

View File

@ -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)