feat: add span tree

This commit is contained in:
Qiuxia Fan 2022-03-01 14:50:10 +08:00
parent fa389c7971
commit bd43b92516
9 changed files with 342 additions and 65 deletions

View File

@ -0,0 +1,17 @@
<!-- 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 version="1.1" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path d="M18.984 6.984v-1.969h-9.984v1.969h9.984zM15 15v-2.016h-6v2.016h6zM18.984 11.016v-2.016h-9.984v2.016h9.984zM20.016 2.016q0.797 0 1.383 0.586t0.586 1.383v12q0 0.797-0.586 1.406t-1.383 0.609h-12q-0.797 0-1.406-0.609t-0.609-1.406v-12q0-0.797 0.609-1.383t1.406-0.586h12zM3.984 6v14.016h14.016v1.969h-14.016q-0.797 0-1.383-0.586t-0.586-1.383v-14.016h1.969z"></path>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -96,7 +96,7 @@ export const dashboardStore = defineStore({
};
}
if (type === "Trace" || type === "Profile") {
newItem.h = 24;
newItem.h = 36;
}
this.layout = this.layout.map((d: LayoutConfig) => {
d.y = d.y + newItem.h;

View File

@ -23,7 +23,7 @@ import {
ProfileAnalyzationTrees,
TaskLog,
} from "@/types/profile";
import { Trace } from "@/types/trace";
import { Trace, Span } from "@/types/trace";
import { store } from "@/store";
import graphql from "@/graphql";
import { AxiosResponse } from "axios";
@ -63,6 +63,9 @@ export const traceStore = defineStore({
...data,
};
},
setCurrentSpan(span: Span) {
this.currentSpan = span;
},
setCurrentSegment(s: Trace) {
this.currentSegment = s;
},

View File

@ -30,6 +30,10 @@
overflow: hidden;
}
.wba {
word-break: break-all;
}
.cp {
cursor: pointer;
}

View File

@ -214,3 +214,7 @@ export const QueryOrders = [
{ label: "duration", value: "BY_DURATION" },
];
export const TraceEntitys = ["All", "Service", "ServiceInstance", "Endpoint"];
export const ProfileMode: any[] = [
{ label: "Include Children", value: "include" },
{ label: "Exclude Children", value: "exclude" },
];

View File

@ -13,12 +13,12 @@ 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. -->
<template>
<div class="flex-h">
<div class="flex-v">
<div class="flex-h content">
<div class="list">
<TaskList />
<SegmentList />
</div>
<div class="flex-v">
<div class="item">
<SpanTree />
<ThreadStack />
</div>
@ -30,3 +30,19 @@ import SegmentList from "./components/SegmentList.vue";
import SpanTree from "./components/SpanTree.vue";
import ThreadStack from "./components/ThreadStack.vue";
</script>
<style lang="scss" scoped>
.content {
height: 100%;
width: 100%;
}
.item {
height: 100%;
width: calc(100% - 290px);
}
.list {
width: 300px;
height: 100%;
}
</style>

View File

@ -42,8 +42,8 @@ limitations under the License. -->
<span class="b">{{ i.endpointNames[0] }}</span>
</div>
<div class="grey ell sm">
<span class="tag mr-10 sm">{{ i.duration }} ms</span
>{{ dateFormat(parseInt(i.start)) }}
<span class="tag mr-10 sm"> {{ i.duration }} ms </span>
{{ dateFormat(parseInt(i.start)) }}
</div>
</td>
</tr>
@ -56,7 +56,6 @@ import { ref } from "vue";
import dayjs from "dayjs";
import { useI18n } from "vue-i18n";
import { useProfileStore } from "@/store/modules/profile";
import { TaskLog, SegmentSpan } from "@/types/profile";
import { Trace } from "@/types/trace";
import { ElMessage } from "element-plus";
@ -78,8 +77,8 @@ async function selectTrace(item: Trace) {
</script>
<style lang="scss" scoped>
.profile-trace-wrapper {
width: 280px;
height: calc((100% - 95px) / 2);
width: 300px;
height: calc((100% - 60px) / 2);
overflow: auto;
.no-data {

View File

@ -13,6 +13,163 @@ 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. -->
<template>
<div>tree</div>
<div
class="profile-trace-dashboard flex-v"
v-if="profileStore.currentSegment"
>
<div class="profile-trace-detail-wrapper">
<Selector
size="small"
:value="traceId"
:options="traceIds"
placeholder="Select a trace id"
@change="changeTraceId"
class="profile-trace-detail-ids mr-10"
/>
<Selector
size="small"
:value="mode"
:options="ProfileMode"
placeholder="Select a mode"
@change="spanModeChange"
class="mr-10"
/>
<el-button type="primary" size="small" @click="analyzeProfile()">
{{ t("analyze") }}
</el-button>
</div>
<Table
:data="profileStore.segmentSpans"
:traceId="
profileStore.currentSegment.traceIds &&
profileStore.currentSegment.traceIds[0]
"
:showBtnDetail="true"
:HeaderType="'profile'"
@selectSpan="selectSpan"
/>
</div>
</template>
<script lang="ts" setup></script>
<script lang="ts" setup>
import { ref, computed } from "vue";
import { useI18n } from "vue-i18n";
import Table from "../../trace/components/Table/Index.vue";
import { useProfileStore } from "@/store/modules/profile";
import Selector from "@/components/Selector.vue";
import { Span } from "@/types/trace";
import { Option } from "@/types/app";
import { ElMessage } from "element-plus";
import { ProfileMode } from "../../../data";
const { t } = useI18n();
const profileStore = useProfileStore();
const mode = ref<string>("include");
const message = ref<string>("");
const loading = ref<boolean>(false);
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);
}
function spanModeChange(item: Option[]) {
mode.value = item[0].value;
updateTimeRange();
}
function changeTraceId(opt: Option[]) {
traceId.value = opt[0].value;
}
async function analyzeProfile() {
loading.value = true;
updateTimeRange();
const res = await profileStore.getProfileAnalyze({
segmentId: profileStore.currentSegment.segmentId,
timeRanges: timeRange.value,
});
loading.value = false;
if (res.errors) {
ElMessage.error(res.errors);
}
if (res.tip) {
message.value = res.tip;
}
}
function updateTimeRange() {
if (mode.value === "include") {
timeRange.value = [
{
start: profileStore.currentSpan.startTime,
end: profileStore.currentSpan.endTime,
},
];
} else {
const { children, startTime, endTime } = profileStore.currentSpan;
let dateRange = [];
if (!children || !children.length) {
timeRange.value = [
{
start: this.currentSpan.startTime,
end: this.currentSpan.endTime,
},
];
return;
}
for (const item of children) {
dateRange.push(
{
start: startTime,
end: item.startTime,
},
{
start: item.endTime,
end: endTime,
}
);
}
dateRange = dateRange.reduce((prev: any[], cur) => {
let isUpdate = false;
for (const item of prev) {
if (cur.start <= item.end && item.start <= cur.start) {
isUpdate = true;
item.start = item.start < cur.start ? cur.start : item.start;
item.end = item.end < cur.end ? item.end : cur.end;
}
}
if (!isUpdate) {
prev.push(cur);
}
return prev;
}, []);
timeRange.value = dateRange.filter((item: any) => item.start !== item.end);
}
}
</script>
<style lang="scss" scoped>
.profile-trace-dashboard {
padding: 5px;
flex-shrink: 0;
height: calc(50% + 95);
overflow: auto;
width: 100%;
}
.profile-trace-detail-wrapper {
padding: 5px 0;
border-bottom: 1px solid rgba(0, 0, 0, 0.1);
}
.profile-trace-detail-ids {
width: 300px;
}
</style>

View File

@ -36,7 +36,7 @@ limitations under the License. -->
<div class="ell">
<span>{{ i.endpointName }}</span>
<a class="profile-btn r" @click="viewTask($event, i)">
<Icon iconName="library_books" />
<Icon iconName="library_books" size="middle" />
</a>
</div>
<div class="grey ell sm">
@ -51,6 +51,74 @@ limitations under the License. -->
</div>
</div>
</div>
<el-dialog
v-model="viewDetail"
:destroy-on-close="true"
fullscreen
@closed="viewDetail = false"
>
<div class="profile-detail flex-v">
<div>
<h5 class="mb-10">{{ t("task") }}.</h5>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("service") }}:</span>
<span class="g-sm-8 wba">{{ service }}</span>
</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>
</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) }}
</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>
</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>
</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>
</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>
</div>
</div>
<div>
<h5
class="mb-10 mt-10"
v-show="selectedTask.logs && selectedTask.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>
<span>{{ i }}</span>
</div>
<div v-for="(d, index) in instanceLogs[i]" :key="index">
<span class="mr-10 grey">{{ t("operationType") }}:</span>
<span class="mr-20">{{ d.operationType }}</span>
<span class="mr-10 grey">{{ t("time") }}:</span>
<span>{{ dateFormat(d.operationTime) }}</span>
</div>
</div>
</div>
</div>
</el-dialog>
</template>
<script lang="ts" setup>
import { ref } from "vue";
@ -65,8 +133,9 @@ const profileStore = useProfileStore();
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(date).format(pattern);
const viewDetail = ref<boolean>(false);
const service = ref<string>("");
const selectedTask = ref<TaskListItem | Record<string, never>>({});
const instanceLogs = ref<TaskLog | Record<string, never>>({});
const instanceLogs = ref<TaskLog | any>({});
async function changeTask(item: TaskListItem) {
selectedTask.value = item;
@ -79,6 +148,10 @@ async function changeTask(item: TaskListItem) {
async function viewTask(e: Event, item: TaskListItem) {
window.event ? (window.event.cancelBubble = true) : e.stopPropagation();
viewDetail.value = true;
selectedTask.value = item;
service.value = (
profileStore.services.filter((s: any) => s.id === item.serviceId)[0] || {}
).label;
const res = await profileStore.getTaskLogs({ taskID: item.id });
if (res.errors) {
@ -88,14 +161,13 @@ async function viewTask(e: Event, item: TaskListItem) {
item.logs = profileStore.taskLogs;
instanceLogs.value = {};
for (const d of item.logs) {
let name: any = instanceLogs.value[d.instanceName];
if (instanceLogs.value[d.instanceName]) {
name.push({
instanceLogs.value[d.instanceName].push({
operationType: d.operationType,
operationTime: d.operationTime,
});
} else {
name = [
instanceLogs.value[d.instanceName] = [
{ operationType: d.operationType, operationTime: d.operationTime },
];
}
@ -105,65 +177,70 @@ async function viewTask(e: Event, item: TaskListItem) {
</script>
<style lang="scss" scoped>
.profile-task-list {
width: 280px;
height: calc((100% - 95px) / 2);
width: 300px;
height: calc((100% - 60px) / 2);
overflow: auto;
}
.no-data {
text-align: center;
margin-top: 10px;
.item span {
height: 21px;
}
.profile-td {
padding: 5px 10px;
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
&.selected {
background-color: #ededed;
}
}
.profile-t-wrapper {
overflow: auto;
flex-grow: 1;
border-right: 1px solid rgba(0, 0, 0, 0.1);
}
.no-data {
text-align: center;
margin-top: 10px;
}
.profile-t {
width: 100%;
border-spacing: 0;
table-layout: fixed;
flex-grow: 1;
position: relative;
}
.profile-t-wrapper {
overflow: auto;
flex-grow: 1;
border-right: 1px solid rgba(0, 0, 0, 0.1);
}
.profile-tr {
&:hover {
background-color: rgba(0, 0, 0, 0.04);
}
}
.profile-t {
width: 100%;
border-spacing: 0;
table-layout: fixed;
flex-grow: 1;
position: relative;
}
.profile-td {
padding: 5px 10px;
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
&.selected {
background-color: #ededed;
}
}
.profile-t-tool {
padding: 5px 10px;
font-weight: bold;
border-right: 1px solid rgba(0, 0, 0, 0.07);
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
background: #f3f4f9;
}
.log-item {
margin-top: 20px;
}
.profile-btn {
color: #3d444f;
padding: 1px 3px;
border-radius: 2px;
font-size: 12px;
.profile-tr {
&:hover {
background-color: rgba(0, 0, 0, 0.04);
}
}
.profile-segment {
border-top: 1px solid rgba(0, 0, 0, 0.07);
}
.profile-t-tool {
padding: 5px 10px;
font-weight: bold;
border-right: 1px solid rgba(0, 0, 0, 0.07);
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
background: #f3f4f9;
}
.log-item {
margin-top: 20px;
}
.profile-btn {
color: #3d444f;
padding: 1px 3px;
border-radius: 2px;
font-size: 12px;
float: right;
}
</style>