feat: add task and segment list

This commit is contained in:
Qiuxia Fan 2022-02-28 20:07:13 +08:00
parent 5cc9884ee2
commit fa389c7971
13 changed files with 454 additions and 12 deletions

View File

@ -22,6 +22,7 @@ import * as dashboard from "./query/dashboard";
import * as topology from "./query/topology";
import * as trace from "./query/trace";
import * as log from "./query/log";
import * as profile from "./query/profile";
const query: { [key: string]: string } = {
...app,
@ -30,6 +31,7 @@ const query: { [key: string]: string } = {
...topology,
...trace,
...log,
...profile,
};
class Graphql {
private queryData = "";

View File

@ -93,6 +93,8 @@ const msg = {
default: "Default",
topSlow: "Top 5 of slow",
topChildren: "Top 5 of children",
taskList: "Task List",
sampledTraces: "Sampled Traces",
hourTip: "Select Hour",
minuteTip: "Select Minute",
secondTip: "Select Second",

View File

@ -93,6 +93,8 @@ const msg = {
topSlow: "迟缓的前5名",
topChildren: "小孩数量的前5名",
showDepth: "展示深度选择器",
taskList: "任务列表",
sampledTraces: "采样的追踪",
hourTip: "选择小时",
minuteTip: "选择分钟",
secondTip: "选择秒数",

View File

@ -35,9 +35,9 @@ interface ProfileState {
condition: { serviceId: string; endpointName: string };
taskList: TaskListItem[];
segmentList: Trace[];
currentSegment: Nullable<Trace>;
currentSegment: Trace | Record<string, never>;
segmentSpans: SegmentSpan[];
currentSpan: Nullable<SegmentSpan>;
currentSpan: SegmentSpan | Record<string, never>;
analyzeTrees: ProfileAnalyzationTrees;
taskLogs: TaskLog[];
}
@ -50,9 +50,9 @@ export const traceStore = defineStore({
condition: { serviceId: "", endpointName: "" },
taskList: [],
segmentList: [],
currentSegment: null,
currentSegment: {},
segmentSpans: [],
currentSpan: null,
currentSpan: {},
analyzeTrees: [],
taskLogs: [],
}),
@ -63,6 +63,9 @@ export const traceStore = defineStore({
...data,
};
},
setCurrentSegment(s: Trace) {
this.currentSegment = s;
},
async getServices(layer: string) {
const res: AxiosResponse = await graphql.query("queryServices").params({
layer,
@ -89,6 +92,8 @@ export const traceStore = defineStore({
if (!list.length) {
return res.data;
}
this.getSegmentList({ taskID: list[0].id });
return res.data;
},
async getSegmentList(params: { taskID: string }) {
const res: AxiosResponse = await graphql
@ -105,6 +110,7 @@ export const traceStore = defineStore({
if (segmentList[0]) {
this.currentSegment = segmentList[0];
this.getSegmentSpans({ segmentId: segmentList[0].segmentId });
} else {
this.currentSegment = null;
}

View File

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

View File

@ -24,13 +24,16 @@ limitations under the License. -->
<span @click="removeWidget">{{ t("delete") }}</span>
</div>
</el-popover>
<div class="header">header</div>
<Header />
<Content />
</div>
</template>
<script lang="ts" setup>
import type { PropType } from "vue";
import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/profile/Header.vue";
import Content from "../related/profile/Content.vue";
/*global defineProps */
const props = defineProps({

View File

@ -0,0 +1,32 @@
<!-- 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. -->
<template>
<div class="flex-h">
<div class="flex-v">
<TaskList />
<SegmentList />
</div>
<div class="flex-v">
<SpanTree />
<ThreadStack />
</div>
</div>
</template>
<script lang="ts" setup>
import TaskList from "./components/TaskList.vue";
import SegmentList from "./components/SegmentList.vue";
import SpanTree from "./components/SpanTree.vue";
import ThreadStack from "./components/ThreadStack.vue";
</script>

View File

@ -13,8 +13,8 @@ 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="mr-5">
<div class="flex-h header">
<div class="mr-10">
<span class="grey mr-5">{{ t("service") }}:</span>
<Selector
size="small"
@ -24,9 +24,9 @@ limitations under the License. -->
@change="changeService"
/>
</div>
<div class="mr-5">
<span class="sm b grey mr-5">{{ t("endpointName") }}:</span>
<el-input class="inputs mr-5" v-model="endpointName" />
<div class="mr-10">
<span class="grey mr-5">{{ t("endpointName") }}:</span>
<el-input v-model="endpointName" class="name" />
</div>
<el-button
class="search-btn"
@ -45,19 +45,51 @@ limitations under the License. -->
import { ref } from "vue";
import { useI18n } from "vue-i18n";
import { useProfileStore } from "@/store/modules/profile";
import { useDashboardStore } from "@/store/modules/dashboard";
import { ElMessage } from "element-plus";
const profileStore = useProfileStore();
const dashboardStore = useDashboardStore();
const { t } = useI18n();
const service = ref<any>({});
const endpointName = ref<string>("");
getServices();
async function getServices() {
const res = await profileStore.getServices(dashboardStore.layerId);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
service.value = profileStore.services[0];
searchTasks();
}
function changeService(opt: any[]) {
service.value = opt[0];
}
function searchTasks() {
async function searchTasks() {
profileStore.setConditions({
serviceId: service.value.id,
endpointName: endpointName.value,
});
const res = await profileStore.getTaskList();
if (res.errors) {
ElMessage.error(res.errors);
}
}
</script>
<style lang="scss" scoped>
.header {
padding: 10px;
font-size: 12px;
border-bottom: 1px solid #dcdfe6;
}
.name {
width: 270px;
}
</style>

View File

@ -0,0 +1,157 @@
<!-- 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. -->
<template>
<div class="profile-trace-wrapper profile-segment flex-v">
<div class="profile-t-tool flex-h">{{ t("sampledTraces") }}</div>
<div class="profile-t-wrapper">
<div class="no-data" v-show="!profileStore.segmentList.length">
{{ t("noData") }}
</div>
<table class="profile-t">
<tr
class="profile-tr cp"
v-for="(i, index) in profileStore.segmentList"
@click="selectTrace(i)"
:key="index"
>
<td
class="profile-td"
:class="{
selected: selectedKey == i.segmentId,
}"
>
<div
class="ell mb-5"
:class="{
blue: !i.isError,
red: i.isError,
}"
>
<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)) }}
</div>
</td>
</tr>
</table>
</div>
</div>
</template>
<script lang="ts" setup>
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";
const { t } = useI18n();
const profileStore = useProfileStore();
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(date).format(pattern);
const selectedKey = ref<string>("");
async function selectTrace(item: Trace) {
profileStore.setCurrentSegment(item);
selectedKey.value = item.segmentId;
const res = await profileStore.getSegmentSpans({ segmentId: item.segmentId });
if (res.errors) {
ElMessage.error(res.errors);
}
}
</script>
<style lang="scss" scoped>
.profile-trace-wrapper {
width: 280px;
height: calc((100% - 95px) / 2);
overflow: auto;
.no-data {
text-align: center;
margin-top: 10px;
}
.profile-t-wrapper {
overflow: auto;
flex-grow: 1;
border-right: 1px solid rgba(0, 0, 0, 0.1);
}
.profile-t-loading {
text-align: center;
position: absolute;
width: 100%;
height: 70px;
margin-top: 40px;
line-height: 88px;
overflow: hidden;
.icon {
width: 30px;
height: 30px;
}
}
.profile-t {
width: 100%;
border-spacing: 0;
table-layout: fixed;
flex-grow: 1;
position: relative;
}
.profile-tr {
&:hover {
background-color: rgba(0, 0, 0, 0.04);
}
}
.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-segment {
border-top: 1px solid rgba(0, 0, 0, 0.07);
}
</style>

View File

@ -0,0 +1,18 @@
<!-- 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. -->
<template>
<div>tree</div>
</template>
<script lang="ts" setup></script>

View File

@ -0,0 +1,169 @@
<!-- 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. -->
<template>
<div class="profile-task-list flex-v">
<div class="profile-task-wrapper flex-v">
<div class="profile-t-tool flex-h">{{ t("taskList") }}</div>
<div class="profile-t-wrapper">
<div class="no-data" v-show="!profileStore.taskList.length">
{{ t("noData") }}
</div>
<table class="profile-t">
<tr
class="profile-tr cp"
v-for="(i, index) in profileStore.taskList"
@click="changeTask(i)"
:key="index"
>
<td
class="profile-td"
:class="{
selected: selectedTask.id === i.id,
}"
>
<div class="ell">
<span>{{ i.endpointName }}</span>
<a class="profile-btn r" @click="viewTask($event, i)">
<Icon iconName="library_books" />
</a>
</div>
<div class="grey ell sm">
<span class="mr-10 sm">{{ dateFormat(i.startTime) }}</span>
<span class="mr-10 sm">
{{ dateFormat(i.startTime + i.duration * 60 * 1000) }}
</span>
</div>
</td>
</tr>
</table>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from "vue";
import dayjs from "dayjs";
import { useI18n } from "vue-i18n";
import { useProfileStore } from "@/store/modules/profile";
import { TaskLog, TaskListItem } from "@/types/profile";
import { ElMessage } from "element-plus";
const { t } = useI18n();
const profileStore = useProfileStore();
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(date).format(pattern);
const viewDetail = ref<boolean>(false);
const selectedTask = ref<TaskListItem | Record<string, never>>({});
const instanceLogs = ref<TaskLog | Record<string, never>>({});
async function changeTask(item: TaskListItem) {
selectedTask.value = item;
const res = await profileStore.getSegmentList({ taskID: item.id });
if (res.errors) {
ElMessage.error(res.errors);
}
}
async function viewTask(e: Event, item: TaskListItem) {
window.event ? (window.event.cancelBubble = true) : e.stopPropagation();
viewDetail.value = true;
const res = await profileStore.getTaskLogs({ taskID: item.id });
if (res.errors) {
ElMessage.error(res.errors);
return;
}
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({
operationType: d.operationType,
operationTime: d.operationTime,
});
} else {
name = [
{ operationType: d.operationType, operationTime: d.operationTime },
];
}
}
selectedTask.value = item;
}
</script>
<style lang="scss" scoped>
.profile-task-list {
width: 280px;
height: calc((100% - 95px) / 2);
overflow: auto;
.no-data {
text-align: center;
margin-top: 10px;
}
.profile-t-wrapper {
overflow: auto;
flex-grow: 1;
border-right: 1px solid rgba(0, 0, 0, 0.1);
}
.profile-t {
width: 100%;
border-spacing: 0;
table-layout: fixed;
flex-grow: 1;
position: relative;
}
.profile-tr {
&:hover {
background-color: rgba(0, 0, 0, 0.04);
}
}
.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-segment {
border-top: 1px solid rgba(0, 0, 0, 0.07);
}
</style>

View File

@ -0,0 +1,18 @@
<!-- 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. -->
<template>
<div>stack</div>
</template>
<script lang="ts" setup></script>

View File

@ -129,7 +129,7 @@ async function queryTraces() {
}
}
</script>
<style lang="scss">
<style lang="scss" scoped>
.trace-t-tool {
background-color: rgba(196, 200, 225, 0.2);
justify-content: space-between;