mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-10-15 04:09:14 +00:00
feat: update trace profiling protocol (#250)
This commit is contained in:
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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>
|
||||
|
@@ -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 {
|
||||
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user