mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-05-02 22:13:12 +00:00
feat: update trace profiling protocol (#250)
This commit is contained in:
parent
68eea52e88
commit
55e4828adc
15
src/assets/icons/cross.svg
Normal file
15
src/assets/icons/cross.svg
Normal 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 |
15
src/assets/icons/entry.svg
Normal file
15
src/assets/icons/entry.svg
Normal 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
15
src/assets/icons/exit.svg
Normal 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 |
BIN
src/assets/img/tools/ENTRY.png
Normal file
BIN
src/assets/img/tools/ENTRY.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 262 B |
BIN
src/assets/img/tools/EXIT.png
Normal file
BIN
src/assets/img/tools/EXIT.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 269 B |
BIN
src/assets/img/tools/STREAM.png
Normal file
BIN
src/assets/img/tools/STREAM.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 373 B |
@ -15,37 +15,6 @@
|
|||||||
* limitations under the License.
|
* 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 = {
|
export const CreateProfileTask = {
|
||||||
variable: "$creationRequest: ProfileTaskCreationRequest",
|
variable: "$creationRequest: ProfileTaskCreationRequest",
|
||||||
query: `
|
query: `
|
||||||
@ -79,23 +48,55 @@ export const GetProfileTaskList = {
|
|||||||
`,
|
`,
|
||||||
};
|
};
|
||||||
export const GetProfileTaskSegmentList = {
|
export const GetProfileTaskSegmentList = {
|
||||||
variable: "$taskID: String",
|
variable: "$taskID: ID!",
|
||||||
query: `
|
query: `
|
||||||
segmentList: getProfileTaskSegmentList(taskID: $taskID) {
|
segmentList: getProfileTaskSegments(taskID: $taskID) {
|
||||||
segmentId
|
traceId
|
||||||
|
instanceId
|
||||||
|
instanceName
|
||||||
endpointNames
|
endpointNames
|
||||||
start
|
|
||||||
duration
|
duration
|
||||||
traceIds
|
start
|
||||||
isError
|
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 = {
|
export const GetProfileAnalyze = {
|
||||||
variable: "$segmentId: String!, $timeRanges: [ProfileAnalyzeTimeRange!]!",
|
variable: "$queries: [SegmentProfileAnalyzeQuery!]!",
|
||||||
query: `
|
query: `
|
||||||
analyze: getProfileAnalyze(segmentId: $segmentId, timeRanges: $timeRanges) {
|
analyze: getSegmentsProfileAnalyze(queries: $queries) {
|
||||||
tip
|
tip
|
||||||
trees {
|
trees {
|
||||||
elements {
|
elements {
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ProfileSegment,
|
|
||||||
CreateProfileTask,
|
CreateProfileTask,
|
||||||
GetProfileTaskList,
|
GetProfileTaskList,
|
||||||
GetProfileTaskSegmentList,
|
GetProfileTaskSegmentList,
|
||||||
@ -24,8 +23,6 @@ import {
|
|||||||
GetProfileTaskLogs,
|
GetProfileTaskLogs,
|
||||||
} from "../fragments/profile";
|
} from "../fragments/profile";
|
||||||
|
|
||||||
export const queryProfileSegment = `query queryProfileSegment(${ProfileSegment.variable}) {${ProfileSegment.query}}`;
|
|
||||||
|
|
||||||
export const saveProfileTask = `mutation createProfileTask(${CreateProfileTask.variable}) {${CreateProfileTask.query}}`;
|
export const saveProfileTask = `mutation createProfileTask(${CreateProfileTask.variable}) {${CreateProfileTask.query}}`;
|
||||||
|
|
||||||
export const getProfileTaskList = `query getProfileTaskList(${GetProfileTaskList.variable}) {
|
export const getProfileTaskList = `query getProfileTaskList(${GetProfileTaskList.variable}) {
|
||||||
|
@ -34,6 +34,7 @@ interface ProfileState {
|
|||||||
taskEndpoints: Endpoint[];
|
taskEndpoints: Endpoint[];
|
||||||
condition: { serviceId: string; endpointName: string };
|
condition: { serviceId: string; endpointName: string };
|
||||||
taskList: TaskListItem[];
|
taskList: TaskListItem[];
|
||||||
|
currentTask: Recordable<TaskListItem>;
|
||||||
segmentList: Trace[];
|
segmentList: Trace[];
|
||||||
currentSegment: Recordable<Trace>;
|
currentSegment: Recordable<Trace>;
|
||||||
segmentSpans: Array<Recordable<SegmentSpan>>;
|
segmentSpans: Array<Recordable<SegmentSpan>>;
|
||||||
@ -51,6 +52,7 @@ export const profileStore = defineStore({
|
|||||||
condition: { serviceId: "", endpointName: "" },
|
condition: { serviceId: "", endpointName: "" },
|
||||||
taskList: [],
|
taskList: [],
|
||||||
segmentList: [],
|
segmentList: [],
|
||||||
|
currentTask: {},
|
||||||
currentSegment: {},
|
currentSegment: {},
|
||||||
segmentSpans: [],
|
segmentSpans: [],
|
||||||
currentSpan: {},
|
currentSpan: {},
|
||||||
@ -65,11 +67,27 @@ export const profileStore = defineStore({
|
|||||||
...data,
|
...data,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
setCurrentTask(task: TaskListItem) {
|
||||||
|
this.currentTask = task || {};
|
||||||
|
this.analyzeTrees = [];
|
||||||
|
},
|
||||||
|
setSegmentSpans(spans: Recordable<SegmentSpan>[]) {
|
||||||
|
this.currentSpan = spans[0] || {};
|
||||||
|
this.segmentSpans = spans;
|
||||||
|
},
|
||||||
setCurrentSpan(span: Recordable<SegmentSpan>) {
|
setCurrentSpan(span: Recordable<SegmentSpan>) {
|
||||||
this.currentSpan = span;
|
this.currentSpan = span;
|
||||||
|
this.analyzeTrees = [];
|
||||||
},
|
},
|
||||||
setCurrentSegment(s: Recordable<Trace>) {
|
setCurrentSegment(segment: Trace) {
|
||||||
this.currentSegment = s;
|
this.currentSegment = segment;
|
||||||
|
this.segmentSpans = segment.spans || [];
|
||||||
|
if (segment.spans) {
|
||||||
|
this.currentSpan = segment.spans[0] || {};
|
||||||
|
} else {
|
||||||
|
this.currentSpan = {};
|
||||||
|
}
|
||||||
|
this.analyzeTrees = [];
|
||||||
},
|
},
|
||||||
setHighlightTop() {
|
setHighlightTop() {
|
||||||
this.highlightTop = !this.highlightTop;
|
this.highlightTop = !this.highlightTop;
|
||||||
@ -104,8 +122,9 @@ export const profileStore = defineStore({
|
|||||||
if (res.data.errors) {
|
if (res.data.errors) {
|
||||||
return res.data;
|
return res.data;
|
||||||
}
|
}
|
||||||
const list = res.data.data.taskList;
|
const list = res.data.data.taskList || [];
|
||||||
this.taskList = list;
|
this.taskList = list;
|
||||||
|
this.currentTask = list[0] || {};
|
||||||
if (!list.length) {
|
if (!list.length) {
|
||||||
this.segmentList = [];
|
this.segmentList = [];
|
||||||
this.segmentSpans = [];
|
this.segmentSpans = [];
|
||||||
@ -128,7 +147,7 @@ export const profileStore = defineStore({
|
|||||||
}
|
}
|
||||||
const { segmentList } = res.data.data;
|
const { segmentList } = res.data.data;
|
||||||
|
|
||||||
this.segmentList = segmentList;
|
this.segmentList = segmentList || [];
|
||||||
if (!segmentList.length) {
|
if (!segmentList.length) {
|
||||||
this.segmentSpans = [];
|
this.segmentSpans = [];
|
||||||
this.analyzeTrees = [];
|
this.analyzeTrees = [];
|
||||||
@ -137,50 +156,22 @@ export const profileStore = defineStore({
|
|||||||
}
|
}
|
||||||
if (segmentList[0]) {
|
if (segmentList[0]) {
|
||||||
this.currentSegment = segmentList[0];
|
this.currentSegment = segmentList[0];
|
||||||
this.getSegmentSpans({ segmentId: segmentList[0].segmentId });
|
this.getSegmentSpans(segmentList[0].segmentId);
|
||||||
} else {
|
} else {
|
||||||
this.currentSegment = {};
|
this.currentSegment = {};
|
||||||
}
|
}
|
||||||
return res.data;
|
return res.data;
|
||||||
},
|
},
|
||||||
async getSegmentSpans(params: { segmentId: string }) {
|
async getSegmentSpans() {
|
||||||
if (!params.segmentId) {
|
this.analyzeTrees = [];
|
||||||
return new Promise((resolve) => resolve({}));
|
this.segmentSpans = this.currentSegment.spans;
|
||||||
}
|
this.currentSpan = this.currentSegment.spans[0] || {};
|
||||||
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 getProfileAnalyze(params: { segmentId: string; timeRanges: Array<{ start: number; end: number }> }) {
|
async getProfileAnalyze(params: Array<{ segmentId: string; timeRange: { start: number; end: number } }>) {
|
||||||
if (!params.segmentId) {
|
if (!params.length) {
|
||||||
return new Promise((resolve) => resolve({}));
|
return new Promise((resolve) => resolve({}));
|
||||||
}
|
}
|
||||||
if (!params.timeRanges.length) {
|
const res: AxiosResponse = await graphql.query("getProfileAnalyze").params({ queries: params });
|
||||||
return new Promise((resolve) => resolve({}));
|
|
||||||
}
|
|
||||||
const res: AxiosResponse = await graphql.query("getProfileAnalyze").params(params);
|
|
||||||
|
|
||||||
if (res.data.errors) {
|
if (res.data.errors) {
|
||||||
this.analyzeTrees = [];
|
this.analyzeTrees = [];
|
||||||
|
@ -215,3 +215,31 @@
|
|||||||
box-shadow: inset 0 0 6px #888;
|
box-shadow: inset 0 0 6px #888;
|
||||||
background-color: #999;
|
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;
|
||||||
|
}
|
||||||
|
1
src/types/trace.d.ts
vendored
1
src/types/trace.d.ts
vendored
@ -22,6 +22,7 @@ export interface Trace {
|
|||||||
start: string;
|
start: string;
|
||||||
traceIds: Array<string | any>;
|
traceIds: Array<string | any>;
|
||||||
segmentId: string;
|
segmentId: string;
|
||||||
|
spans: Span[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Span {
|
export interface Span {
|
||||||
|
@ -20,11 +20,11 @@ limitations under the License. -->
|
|||||||
{{ t("noData") }}
|
{{ t("noData") }}
|
||||||
</div>
|
</div>
|
||||||
<table class="profile-t">
|
<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
|
<td
|
||||||
class="profile-td"
|
class="profile-td"
|
||||||
:class="{
|
:class="{
|
||||||
selected: selectedKey == i.segmentId,
|
selected: key === i.spans[0].segmentId,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@ -47,25 +47,25 @@ limitations under the License. -->
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref } from "vue";
|
import { computed } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useProfileStore } from "@/store/modules/profile";
|
import { useProfileStore } from "@/store/modules/profile";
|
||||||
import type { Trace } from "@/types/trace";
|
import type { Trace } from "@/types/trace";
|
||||||
import { ElMessage } from "element-plus";
|
|
||||||
import { dateFormat } from "@/utils/dateFormat";
|
import { dateFormat } from "@/utils/dateFormat";
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const profileStore = useProfileStore();
|
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);
|
profileStore.setCurrentSegment(item);
|
||||||
selectedKey.value = item.segmentId;
|
profileStore.setSegmentSpans(item.spans);
|
||||||
const res = await profileStore.getSegmentSpans({ segmentId: item.segmentId });
|
|
||||||
|
|
||||||
if (res.errors) {
|
|
||||||
ElMessage.error(res.errors);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -15,14 +15,8 @@ limitations under the License. -->
|
|||||||
<template>
|
<template>
|
||||||
<div class="profile-trace-dashboard" v-if="profileStore.currentSegment">
|
<div class="profile-trace-dashboard" v-if="profileStore.currentSegment">
|
||||||
<div class="profile-trace-detail-wrapper">
|
<div class="profile-trace-detail-wrapper">
|
||||||
<Selector
|
<label>Trace ID</label>
|
||||||
size="small"
|
<el-input class="input mr-10 ml-5" readonly :value="profileStore.currentSegment.traceId" 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"
|
|
||||||
/>
|
|
||||||
<Selector
|
<Selector
|
||||||
size="small"
|
size="small"
|
||||||
:value="mode"
|
:value="mode"
|
||||||
@ -31,14 +25,14 @@ limitations under the License. -->
|
|||||||
@change="spanModeChange"
|
@change="spanModeChange"
|
||||||
class="mr-10"
|
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") }}
|
{{ t("analyze") }}
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="profile-table">
|
<div class="profile-table">
|
||||||
<Table
|
<Table
|
||||||
:data="profileStore.segmentSpans"
|
:data="profileStore.segmentSpans"
|
||||||
:traceId="profileStore.currentSegment.traceIds && profileStore.currentSegment.traceIds[0]"
|
:traceId="profileStore.currentSegment.traceId"
|
||||||
:showBtnDetail="true"
|
:showBtnDetail="true"
|
||||||
headerType="profile"
|
headerType="profile"
|
||||||
@select="selectSpan"
|
@select="selectSpan"
|
||||||
@ -47,7 +41,7 @@ limitations under the License. -->
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { ref, computed } from "vue";
|
import { ref } from "vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import Table from "../../trace/components/Table/Index.vue";
|
import Table from "../../trace/components/Table/Index.vue";
|
||||||
import { useProfileStore } from "@/store/modules/profile";
|
import { useProfileStore } from "@/store/modules/profile";
|
||||||
@ -64,13 +58,6 @@ limitations under the License. -->
|
|||||||
const mode = ref<string>("include");
|
const mode = ref<string>("include");
|
||||||
const message = ref<string>("");
|
const message = ref<string>("");
|
||||||
const timeRange = ref<Array<{ start: number; end: number }>>([]);
|
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) {
|
function selectSpan(span: Span) {
|
||||||
profileStore.setCurrentSpan(span);
|
profileStore.setCurrentSpan(span);
|
||||||
@ -81,17 +68,20 @@ limitations under the License. -->
|
|||||||
updateTimeRange();
|
updateTimeRange();
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeTraceId(opt: Option[]) {
|
|
||||||
traceId.value = opt[0].value;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function analyzeProfile() {
|
async function analyzeProfile() {
|
||||||
|
if (!profileStore.currentSpan.profiled) {
|
||||||
|
ElMessage.info("It's a un-profiled span");
|
||||||
|
return;
|
||||||
|
}
|
||||||
emits("loading", true);
|
emits("loading", true);
|
||||||
updateTimeRange();
|
updateTimeRange();
|
||||||
const res = await profileStore.getProfileAnalyze({
|
const params = timeRange.value.map((t: { start: number; end: number }) => {
|
||||||
segmentId: profileStore.currentSegment.segmentId,
|
return {
|
||||||
timeRanges: timeRange.value,
|
segmentId: profileStore.currentSpan.segmentId,
|
||||||
|
timeRange: t,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
const res = await profileStore.getProfileAnalyze(params);
|
||||||
emits("loading", false);
|
emits("loading", false);
|
||||||
if (res.errors) {
|
if (res.errors) {
|
||||||
ElMessage.error(res.errors);
|
ElMessage.error(res.errors);
|
||||||
@ -175,4 +165,8 @@ limitations under the License. -->
|
|||||||
.profile-trace-detail-ids {
|
.profile-trace-detail-ids {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
width: 250px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -25,7 +25,7 @@ limitations under the License. -->
|
|||||||
<td
|
<td
|
||||||
class="profile-td"
|
class="profile-td"
|
||||||
:class="{
|
:class="{
|
||||||
selected: selectedTask.id === i.id,
|
selected: profileStore.currentTask && profileStore.currentTask.id === i.id,
|
||||||
}"
|
}"
|
||||||
>
|
>
|
||||||
<div class="ell">
|
<div class="ell">
|
||||||
@ -49,7 +49,7 @@ limitations under the License. -->
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<el-dialog v-model="viewDetail" :destroy-on-close="true" fullscreen @closed="viewDetail = false">
|
<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>
|
<div>
|
||||||
<h5 class="mb-10">{{ t("task") }}.</h5>
|
<h5 class="mb-10">{{ t("task") }}.</h5>
|
||||||
<div class="mb-10 clear item">
|
<div class="mb-10 clear item">
|
||||||
@ -58,33 +58,35 @@ limitations under the License. -->
|
|||||||
</div>
|
</div>
|
||||||
<div class="mb-10 clear item">
|
<div class="mb-10 clear item">
|
||||||
<span class="g-sm-4 grey">{{ t("endpoint") }}:</span>
|
<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>
|
||||||
<div class="mb-10 clear item">
|
<div class="mb-10 clear item">
|
||||||
<span class="g-sm-4 grey">{{ t("monitorTime") }}:</span>
|
<span class="g-sm-4 grey">{{ t("monitorTime") }}:</span>
|
||||||
<span class="g-sm-8 wba">
|
<span class="g-sm-8 wba">
|
||||||
{{ dateFormat(selectedTask.startTime) }}
|
{{ dateFormat(profileStore.currentTask.startTime) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-10 clear item">
|
<div class="mb-10 clear item">
|
||||||
<span class="g-sm-4 grey">{{ t("monitorDuration") }}:</span
|
<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>
|
||||||
<div class="mb-10 clear item">
|
<div class="mb-10 clear item">
|
||||||
<span class="g-sm-4 grey">{{ t("minThreshold") }}:</span>
|
<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>
|
||||||
<div class="mb-10 clear item">
|
<div class="mb-10 clear item">
|
||||||
<span class="g-sm-4 grey">{{ t("dumpPeriod") }}:</span>
|
<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>
|
||||||
<div class="mb-10 clear item">
|
<div class="mb-10 clear item">
|
||||||
<span class="g-sm-4 grey">{{ t("maxSamplingCount") }}:</span>
|
<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>
|
</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="log-item" v-for="(i, index) in Object.keys(instanceLogs)" :key="index">
|
||||||
<div class="mb-10 sm">
|
<div class="mb-10 sm">
|
||||||
<span class="mr-10 grey">{{ t("instance") }}:</span>
|
<span class="mr-10 grey">{{ t("instance") }}:</span>
|
||||||
@ -115,12 +117,12 @@ limitations under the License. -->
|
|||||||
const selectorStore = useSelectorStore();
|
const selectorStore = useSelectorStore();
|
||||||
const viewDetail = ref<boolean>(false);
|
const viewDetail = ref<boolean>(false);
|
||||||
const service = ref<string>("");
|
const service = ref<string>("");
|
||||||
const selectedTask = ref<TaskListItem | Record<string, never>>({});
|
// const selectedTask = ref<TaskListItem | Record<string, never>>({});
|
||||||
const instanceLogs = ref<TaskLog | any>({});
|
const instanceLogs = ref<TaskLog | any>({});
|
||||||
|
|
||||||
async function changeTask(item: TaskListItem) {
|
async function changeTask(item: TaskListItem) {
|
||||||
profileStore.setCurrentSegment({});
|
profileStore.setCurrentSegment({});
|
||||||
selectedTask.value = item;
|
profileStore.setCurrentTask(item);
|
||||||
const res = await profileStore.getSegmentList({ taskID: item.id });
|
const res = await profileStore.getSegmentList({ taskID: item.id });
|
||||||
if (res.errors) {
|
if (res.errors) {
|
||||||
ElMessage.error(res.errors);
|
ElMessage.error(res.errors);
|
||||||
@ -130,7 +132,7 @@ limitations under the License. -->
|
|||||||
async function viewTask(e: Event, item: TaskListItem) {
|
async function viewTask(e: Event, item: TaskListItem) {
|
||||||
window.event ? (window.event.cancelBubble = true) : e.stopPropagation();
|
window.event ? (window.event.cancelBubble = true) : e.stopPropagation();
|
||||||
viewDetail.value = true;
|
viewDetail.value = true;
|
||||||
selectedTask.value = item;
|
profileStore.setCurrentTask(item);
|
||||||
service.value = (selectorStore.services.filter((s: any) => s.id === item.serviceId)[0] || {}).label;
|
service.value = (selectorStore.services.filter((s: any) => s.id === item.serviceId)[0] || {}).label;
|
||||||
const res = await profileStore.getTaskLogs({ taskID: item.id });
|
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 }];
|
instanceLogs.value[d.instanceName] = [{ operationType: d.operationType, operationTime: d.operationTime }];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
selectedTask.value = item;
|
profileStore.setCurrentTask(item);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -48,7 +48,16 @@ limitations under the License. -->
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-else>
|
<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
|
<div
|
||||||
:class="['method', 'level' + (data.level - 1)]"
|
:class="['method', 'level' + (data.level - 1)]"
|
||||||
:style="{
|
:style="{
|
||||||
@ -62,11 +71,27 @@ limitations under the License. -->
|
|||||||
v-if="data.children && data.children.length"
|
v-if="data.children && data.children.length"
|
||||||
iconName="arrow-down"
|
iconName="arrow-down"
|
||||||
size="sm"
|
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">
|
<el-tooltip :content="data.endpointName" placement="bottom">
|
||||||
<span>
|
<span>
|
||||||
{{ data.endpointName }}
|
{{ data.endpointName }}
|
||||||
</span>
|
</span>
|
||||||
|
<span v-if="data.profiled === false"></span>
|
||||||
</el-tooltip>
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<div class="start-time">
|
<div class="start-time">
|
||||||
@ -161,6 +186,10 @@ limitations under the License. -->
|
|||||||
const resultStr = result.toFixed(4) + "%";
|
const resultStr = result.toFixed(4) + "%";
|
||||||
return resultStr === "0.0000%" ? "0.9%" : resultStr;
|
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() {
|
function toggle() {
|
||||||
displayChildren.value = !displayChildren.value;
|
displayChildren.value = !displayChildren.value;
|
||||||
@ -174,6 +203,10 @@ limitations under the License. -->
|
|||||||
item.style.background = "#fff";
|
item.style.background = "#fff";
|
||||||
}
|
}
|
||||||
dom.style.background = "rgba(0, 0, 0, 0.1)";
|
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) {
|
function selectSpan(event: any) {
|
||||||
const dom = event.composedPath().find((d: any) => d.className.includes("trace-item"));
|
const dom = event.composedPath().find((d: any) => d.className.includes("trace-item"));
|
||||||
@ -202,6 +235,7 @@ limitations under the License. -->
|
|||||||
displayChildren,
|
displayChildren,
|
||||||
outterPercent,
|
outterPercent,
|
||||||
innerPercent,
|
innerPercent,
|
||||||
|
isCrossThread,
|
||||||
viewSpanDetail,
|
viewSpanDetail,
|
||||||
toggle,
|
toggle,
|
||||||
dateFormat,
|
dateFormat,
|
||||||
@ -233,17 +267,44 @@ limitations under the License. -->
|
|||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(0, 0, 0, 0.04);
|
background: rgba(0, 0, 0, 0.04);
|
||||||
color: #448dfe;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&::before {
|
.profiled {
|
||||||
position: absolute;
|
background-color: #eee;
|
||||||
content: "";
|
position: relative;
|
||||||
width: 5px;
|
}
|
||||||
height: 100%;
|
|
||||||
background: #448dfe;
|
.profiled:before {
|
||||||
left: 0;
|
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 {
|
.trace-item-error {
|
||||||
|
@ -19,6 +19,7 @@ import * as d3 from "d3";
|
|||||||
import d3tip from "d3-tip";
|
import d3tip from "d3-tip";
|
||||||
import type { Trace } from "@/types/trace";
|
import type { Trace } from "@/types/trace";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
|
import icons from "@/assets/img/icons";
|
||||||
|
|
||||||
export default class ListGraph {
|
export default class ListGraph {
|
||||||
private barHeight = 48;
|
private barHeight = 48;
|
||||||
@ -29,6 +30,7 @@ export default class ListGraph {
|
|||||||
private height = 0;
|
private height = 0;
|
||||||
private svg: any = null;
|
private svg: any = null;
|
||||||
private tip: any = null;
|
private tip: any = null;
|
||||||
|
private prompt: any = null;
|
||||||
private row: any[] = [];
|
private row: any[] = [];
|
||||||
private data: any = [];
|
private data: any = [];
|
||||||
private min = 0;
|
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.tip);
|
||||||
|
this.svg.call(this.prompt);
|
||||||
}
|
}
|
||||||
diagonal(d: any) {
|
diagonal(d: any) {
|
||||||
return `M ${d.source.y} ${d.source.x + 5}
|
return `M ${d.source.y} ${d.source.x + 5}
|
||||||
@ -152,6 +161,55 @@ export default class ListGraph {
|
|||||||
.attr("x", 20)
|
.attr("x", 20)
|
||||||
.attr("width", "100%")
|
.attr("width", "100%")
|
||||||
.attr("fill", "rgba(0,0,0,0)");
|
.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
|
nodeEnter
|
||||||
.append("text")
|
.append("text")
|
||||||
.attr("x", 13)
|
.attr("x", 13)
|
||||||
|
Loading…
Reference in New Issue
Block a user