mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-10-14 11:21:29 +00:00
feat: Implement the eBPF profile widget on dashboard (#72)
This commit is contained in:
@@ -44,7 +44,7 @@ limitations under the License. -->
|
||||
:style="{ fontSize: '13px', width: '100%' }"
|
||||
v-loading="loading"
|
||||
ref="multipleTableRef"
|
||||
:default-sort="{ prop: 'name' }"
|
||||
:default-sort="{ prop: 'name', order: 'ascending' }"
|
||||
@selection-change="handleSelectionChange"
|
||||
height="637px"
|
||||
size="small"
|
||||
|
95
src/views/dashboard/controls/Ebpf.vue
Normal file
95
src/views/dashboard/controls/Ebpf.vue
Normal file
@@ -0,0 +1,95 @@
|
||||
<!-- 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-wrapper flex-v">
|
||||
<el-popover
|
||||
placement="bottom"
|
||||
trigger="click"
|
||||
:width="100"
|
||||
v-if="dashboardStore.editMode"
|
||||
>
|
||||
<template #reference>
|
||||
<span class="delete cp">
|
||||
<Icon iconName="ellipsis_v" size="middle" class="operation" />
|
||||
</span>
|
||||
</template>
|
||||
<div class="tools" @click="removeWidget">
|
||||
<span>{{ t("delete") }}</span>
|
||||
</div>
|
||||
</el-popover>
|
||||
<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/ebpf/Header.vue";
|
||||
import Content from "../related/ebpf/Content.vue";
|
||||
|
||||
/*global defineProps */
|
||||
const props = defineProps({
|
||||
data: {
|
||||
type: Object as PropType<any>,
|
||||
default: () => ({ graph: {} }),
|
||||
},
|
||||
activeIndex: { type: String, default: "" },
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const dashboardStore = useDashboardStore();
|
||||
|
||||
function removeWidget() {
|
||||
dashboardStore.removeControls(props.data);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.profile-wrapper {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
font-size: 12px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.delete {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 3px;
|
||||
}
|
||||
|
||||
.header {
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
border-bottom: 1px solid #dcdfe6;
|
||||
}
|
||||
|
||||
.tools {
|
||||
padding: 5px 0;
|
||||
color: #999;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
color: #409eff;
|
||||
background-color: #eee;
|
||||
}
|
||||
}
|
||||
|
||||
.trace {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
@@ -97,7 +97,7 @@ limitations under the License. -->
|
||||
:key="item.i"
|
||||
@click="clickTabGrid($event, item)"
|
||||
:class="{ active: activeTabWidget === item.i }"
|
||||
drag-ignore-from="svg.d3-trace-tree, .dragger, .micro-topo-chart"
|
||||
:drag-ignore-from="dragIgnoreFrom"
|
||||
>
|
||||
<component
|
||||
:is="item.type"
|
||||
@@ -122,6 +122,8 @@ import Trace from "./Trace.vue";
|
||||
import Profile from "./Profile.vue";
|
||||
import Log from "./Log.vue";
|
||||
import Text from "./Text.vue";
|
||||
import Ebpf from "./Ebpf.vue";
|
||||
import { dragIgnoreFrom } from "../data";
|
||||
|
||||
const props = {
|
||||
data: {
|
||||
@@ -132,7 +134,7 @@ const props = {
|
||||
};
|
||||
export default defineComponent({
|
||||
name: "Tab",
|
||||
components: { Topology, Widget, Trace, Profile, Log, Text },
|
||||
components: { Topology, Widget, Trace, Profile, Log, Text, Ebpf },
|
||||
props,
|
||||
setup(props) {
|
||||
const { t } = useI18n();
|
||||
@@ -248,6 +250,7 @@ export default defineComponent({
|
||||
canEditTabName,
|
||||
showTools,
|
||||
t,
|
||||
dragIgnoreFrom,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@@ -21,5 +21,6 @@ import Trace from "./Trace.vue";
|
||||
import Profile from "./Profile.vue";
|
||||
import Log from "./Log.vue";
|
||||
import Text from "./Text.vue";
|
||||
import Ebpf from "./Ebpf.vue";
|
||||
|
||||
export default { Tab, Widget, Trace, Topology, Profile, Log, Text };
|
||||
export default { Tab, Widget, Trace, Topology, Profile, Log, Text, Ebpf };
|
||||
|
@@ -14,6 +14,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
export const dragIgnoreFrom =
|
||||
"svg.d3-trace-tree, .dragger, .micro-topo-chart, .schedules";
|
||||
|
||||
export const PodsChartTypes = ["EndpointList", "InstanceList"];
|
||||
|
||||
@@ -181,7 +183,8 @@ export const ServiceTools = [
|
||||
{ name: "library_books", content: "Text", id: "addText" },
|
||||
{ name: "device_hub", content: "Topology", id: "addTopology" },
|
||||
{ name: "merge", content: "Trace", id: "addTrace" },
|
||||
{ name: "timeline", content: "Profile", id: "addProfile" },
|
||||
{ name: "timeline", content: "Trace Profiling", id: "addProfile" },
|
||||
{ name: "insert_chart", content: "eBPF Profiling", id: "addEbpf" },
|
||||
{ name: "assignment", content: "Log", id: "addLog" },
|
||||
];
|
||||
export const InstanceTools = [
|
||||
|
@@ -31,7 +31,7 @@ limitations under the License. -->
|
||||
:key="item.i"
|
||||
@click="clickGrid(item)"
|
||||
:class="{ active: dashboardStore.activedGridItem === item.i }"
|
||||
drag-ignore-from="svg.d3-trace-tree, .dragger, .micro-topo-chart"
|
||||
:drag-ignore-from="dragIgnoreFrom"
|
||||
>
|
||||
<component :is="item.type" :data="item" />
|
||||
</grid-item>
|
||||
@@ -45,6 +45,7 @@ import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import { LayoutConfig } from "@/types/dashboard";
|
||||
import controls from "../controls/index";
|
||||
import { dragIgnoreFrom } from "../data";
|
||||
|
||||
export default defineComponent({
|
||||
name: "Layout",
|
||||
@@ -72,6 +73,7 @@ export default defineComponent({
|
||||
dashboardStore,
|
||||
clickGrid,
|
||||
t,
|
||||
dragIgnoreFrom,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
@@ -258,7 +258,7 @@ async function setSourceSelector() {
|
||||
let currentPod;
|
||||
if (states.currentPod) {
|
||||
currentPod = selectorStore.pods.find(
|
||||
(d: { id: string }) => d.label === states.currentPod
|
||||
(d: { label: string }) => d.label === states.currentPod
|
||||
);
|
||||
} else {
|
||||
currentPod = selectorStore.pods.find((d: { id: string }) => d.id === pod);
|
||||
@@ -283,10 +283,10 @@ async function setDestSelector() {
|
||||
return;
|
||||
}
|
||||
const destPod = params.destPodId || selectorStore.destPods[0].id;
|
||||
let currentDestPod = "";
|
||||
let currentDestPod = { label: "" };
|
||||
if (states.currentDestPod) {
|
||||
currentDestPod = selectorStore.pods.find(
|
||||
(d: { id: string }) => d.label === states.currentDestPod
|
||||
(d: { label: string }) => d.label === states.currentDestPod
|
||||
);
|
||||
} else {
|
||||
currentDestPod = selectorStore.destPods.find(
|
||||
@@ -317,19 +317,23 @@ async function getServices() {
|
||||
let s;
|
||||
if (states.currentService) {
|
||||
s = (selectorStore.services || []).find(
|
||||
(d) => d.label === states.currentService
|
||||
(d: { label: string }) => d.label === states.currentService
|
||||
);
|
||||
} else {
|
||||
s = (selectorStore.services || []).find((d, index) => index === 0);
|
||||
s = (selectorStore.services || []).find(
|
||||
(d: unknown, index: number) => index === 0
|
||||
);
|
||||
}
|
||||
selectorStore.setCurrentService(s || null);
|
||||
let d;
|
||||
if (states.currentService) {
|
||||
d = (selectorStore.services || []).find(
|
||||
(d) => d.label === states.currentDestService
|
||||
(d: { label: string }) => d.label === states.currentDestService
|
||||
);
|
||||
} else {
|
||||
d = (selectorStore.services || []).find((d, index) => index === 1);
|
||||
d = (selectorStore.services || []).find(
|
||||
(d: unknown, index: number) => index === 1
|
||||
);
|
||||
}
|
||||
selectorStore.setCurrentDestService(d || null);
|
||||
if (!selectorStore.currentService) {
|
||||
@@ -431,6 +435,9 @@ function setTabControls(id: string) {
|
||||
case "addProfile":
|
||||
dashboardStore.addTabControls("Profile");
|
||||
break;
|
||||
case "addEbpf":
|
||||
dashboardStore.addTabControls("Ebpf");
|
||||
break;
|
||||
case "addTopology":
|
||||
dashboardStore.addTabControls("Topology");
|
||||
break;
|
||||
@@ -457,6 +464,9 @@ function setControls(id: string) {
|
||||
case "addProfile":
|
||||
dashboardStore.addControl("Profile");
|
||||
break;
|
||||
case "addEbpf":
|
||||
dashboardStore.addControl("Ebpf");
|
||||
break;
|
||||
case "addLog":
|
||||
dashboardStore.addControl("Log");
|
||||
break;
|
||||
@@ -484,9 +494,13 @@ async function fetchPods(
|
||||
if (setPod) {
|
||||
let p;
|
||||
if (states.currentPod) {
|
||||
p = selectorStore.pods.find((d) => d.label === states.currentPod);
|
||||
p = selectorStore.pods.find(
|
||||
(d: { label: unknown }) => d.label === states.currentPod
|
||||
);
|
||||
} else {
|
||||
p = selectorStore.pods.find((d, index) => index === 0);
|
||||
p = selectorStore.pods.find(
|
||||
(d: unknown, index: number) => index === 0
|
||||
);
|
||||
}
|
||||
selectorStore.setCurrentPod(p || null);
|
||||
states.currentPod = selectorStore.currentPod.label;
|
||||
@@ -497,9 +511,13 @@ async function fetchPods(
|
||||
if (setPod) {
|
||||
let p;
|
||||
if (states.currentPod) {
|
||||
p = selectorStore.pods.find((d) => d.label === states.currentPod);
|
||||
p = selectorStore.pods.find(
|
||||
(d: { label: string }) => d.label === states.currentPod
|
||||
);
|
||||
} else {
|
||||
p = selectorStore.pods.find((d, index) => index === 0);
|
||||
p = selectorStore.pods.find(
|
||||
(d: { label: string }, index: number) => index === 0
|
||||
);
|
||||
}
|
||||
selectorStore.setCurrentPod(p || null);
|
||||
states.currentPod = selectorStore.currentPod.label;
|
||||
@@ -514,9 +532,13 @@ async function fetchPods(
|
||||
if (setPod) {
|
||||
let p;
|
||||
if (states.currentDestPod) {
|
||||
p = selectorStore.pods.find((d) => d.label === states.currentDestPod);
|
||||
p = selectorStore.pods.find(
|
||||
(d: { label: string }) => d.label === states.currentDestPod
|
||||
);
|
||||
} else {
|
||||
p = selectorStore.pods.find((d, index) => index === 0);
|
||||
p = selectorStore.pods.find(
|
||||
(d: { label: string }, index: number) => index === 0
|
||||
);
|
||||
}
|
||||
selectorStore.setCurrentDestPod(p || null);
|
||||
states.currentDestPod = selectorStore.currentDestPod.label;
|
||||
@@ -530,9 +552,13 @@ async function fetchPods(
|
||||
if (setPod) {
|
||||
let p;
|
||||
if (states.currentDestPod) {
|
||||
p = selectorStore.pods.find((d) => d.label === states.currentDestPod);
|
||||
p = selectorStore.pods.find(
|
||||
(d: { label: string }) => d.label === states.currentDestPod
|
||||
);
|
||||
} else {
|
||||
p = selectorStore.pods.find((d, index) => index === 0);
|
||||
p = selectorStore.pods.find(
|
||||
(d: { label: string }, index: number) => index === 0
|
||||
);
|
||||
}
|
||||
selectorStore.setCurrentDestPod(p || null);
|
||||
states.currentDestPod = selectorStore.currentDestPod.label;
|
||||
|
50
src/views/dashboard/related/ebpf/Content.vue
Normal file
50
src/views/dashboard/related/ebpf/Content.vue
Normal file
@@ -0,0 +1,50 @@
|
||||
<!-- 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 content">
|
||||
<TaskList />
|
||||
<div class="vis-graph ml-5">
|
||||
<div class="item">
|
||||
<EBPFSchedules />
|
||||
</div>
|
||||
<div class="item">
|
||||
<EBPFStack />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import TaskList from "./components/TaskList.vue";
|
||||
import EBPFSchedules from "./components/EBPFSchedules.vue";
|
||||
import EBPFStack from "./components/EBPFStack.vue";
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.content {
|
||||
height: calc(100% - 30px);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.vis-graph {
|
||||
height: 100%;
|
||||
width: calc(100% - 300px);
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 100%;
|
||||
overflow: auto;
|
||||
height: calc(50% - 10px);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
</style>
|
104
src/views/dashboard/related/ebpf/Header.vue
Normal file
104
src/views/dashboard/related/ebpf/Header.vue
Normal file
@@ -0,0 +1,104 @@
|
||||
<!-- 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 header">
|
||||
<div class="title">eBPF Profiling</div>
|
||||
<el-button type="primary" size="small" @click="createTask">
|
||||
{{ t("newTask") }}
|
||||
</el-button>
|
||||
</div>
|
||||
<el-dialog
|
||||
v-model="newTask"
|
||||
:destroy-on-close="true"
|
||||
fullscreen
|
||||
@closed="newTask = false"
|
||||
>
|
||||
<NewTask @close="newTask = false" />
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useEbpfStore } from "@/store/modules/ebpf";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import { ElMessage } from "element-plus";
|
||||
import NewTask from "./components/NewTask.vue";
|
||||
import { useDashboardStore } from "@/store/modules/dashboard";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import { EntityType } from "../../data";
|
||||
|
||||
const ebpfStore = useEbpfStore();
|
||||
const appStore = useAppStoreWithOut();
|
||||
const selectorStore = useSelectorStore();
|
||||
const dashboardStore = useDashboardStore();
|
||||
const { t } = useI18n();
|
||||
const newTask = ref<boolean>(false);
|
||||
|
||||
searchTasks();
|
||||
|
||||
async function searchTasks() {
|
||||
const serviceId =
|
||||
(selectorStore.currentService && selectorStore.currentService.id) || "";
|
||||
const res = await ebpfStore.getTaskList(serviceId);
|
||||
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
}
|
||||
}
|
||||
|
||||
async function createTask() {
|
||||
if (!selectorStore.currentService) {
|
||||
return;
|
||||
}
|
||||
newTask.value = true;
|
||||
ebpfStore.getCreateTaskData(selectorStore.currentService.id);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => selectorStore.currentService,
|
||||
() => {
|
||||
searchTasks();
|
||||
}
|
||||
);
|
||||
watch(
|
||||
() => appStore.durationTime,
|
||||
() => {
|
||||
if (dashboardStore.entity === EntityType[1].value) {
|
||||
searchTasks();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.header {
|
||||
padding: 5px 20px 5px 10px;
|
||||
font-size: 12px;
|
||||
border-bottom: 1px solid #dcdfe6;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.name {
|
||||
width: 270px;
|
||||
}
|
||||
|
||||
.new-btn {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-weight: bold;
|
||||
line-height: 24px;
|
||||
}
|
||||
</style>
|
244
src/views/dashboard/related/ebpf/components/EBPFSchedules.vue
Normal file
244
src/views/dashboard/related/ebpf/components/EBPFSchedules.vue
Normal file
@@ -0,0 +1,244 @@
|
||||
<!-- 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="filters">
|
||||
<Selector
|
||||
:value="selectedLabels"
|
||||
:options="labels"
|
||||
size="small"
|
||||
placeholder="Please select labels"
|
||||
@change="changeLabels"
|
||||
class="inputs mr-10"
|
||||
:multiple="true"
|
||||
/>
|
||||
<el-popover placement="bottom" :width="680" trigger="click">
|
||||
<template #reference>
|
||||
<el-button type="primary" size="small">
|
||||
{{ t("processSelect") }}
|
||||
</el-button>
|
||||
</template>
|
||||
<el-input
|
||||
v-model="searchText"
|
||||
placeholder="Please input name"
|
||||
class="input-with-search"
|
||||
size="small"
|
||||
@change="searchProcesses"
|
||||
>
|
||||
<template #append>
|
||||
<el-button size="small">
|
||||
<Icon size="sm" iconName="search" />
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-table
|
||||
:data="currentProcesses"
|
||||
ref="multipleTableRef"
|
||||
@selection-change="handleSelectionChange"
|
||||
>
|
||||
<el-table-column type="selection" width="55" />
|
||||
<el-table-column
|
||||
v-for="(h, index) of TableHeader"
|
||||
:property="h.property"
|
||||
:label="h.label"
|
||||
:key="index"
|
||||
width="150"
|
||||
/>
|
||||
<el-table-column width="300" label="Attributes">
|
||||
<template #default="scope">
|
||||
{{ scope.row.attributes.map((d: {name: string, value: string}) => `${d.name}=${d.value}`).join("; ") }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class="pagination"
|
||||
background
|
||||
small
|
||||
layout="prev, pager, next"
|
||||
:page-size="pageSize"
|
||||
:total="processes.length"
|
||||
@current-change="changePage"
|
||||
@prev-click="changePage"
|
||||
@next-click="changePage"
|
||||
/>
|
||||
</el-popover>
|
||||
<el-button type="primary" size="small" @click="analyzeEBPF">
|
||||
{{ t("analyze") }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div ref="timeline" class="schedules"></div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from "vue";
|
||||
import dayjs from "dayjs";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { Option } from "@/types/app";
|
||||
import { TableHeader } from "./data";
|
||||
import { useEbpfStore } from "@/store/modules/ebpf";
|
||||
import { EBPFProfilingSchedule, Process } from "@/types/ebpf";
|
||||
import { DataSet, Timeline } from "vis-timeline/standalone";
|
||||
import "vis-timeline/styles/vis-timeline-graph2d.css";
|
||||
import { ElMessage, ElTable } from "element-plus";
|
||||
|
||||
const { t } = useI18n();
|
||||
const ebpfStore = useEbpfStore();
|
||||
const pageSize = 5;
|
||||
/*global Nullable */
|
||||
const multipleTableRef = ref<InstanceType<typeof ElTable>>();
|
||||
const selectedProcesses = ref<string[]>([]);
|
||||
const timeline = ref<Nullable<HTMLDivElement>>(null);
|
||||
const visGraph = ref<Nullable<any>>(null);
|
||||
const labels = ref<Option[]>([{ label: "All", value: "0" }]);
|
||||
const processes = ref<Process[]>([]);
|
||||
const currentProcesses = ref<Process[]>([]);
|
||||
const selectedLabels = ref<string[]>(["0"]);
|
||||
const searchText = ref<string>("");
|
||||
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
|
||||
new Date(dayjs(date).format(pattern));
|
||||
|
||||
function changeLabels(opt: any[]) {
|
||||
const arr = opt.map((d) => d.value);
|
||||
selectedLabels.value = arr;
|
||||
}
|
||||
|
||||
const handleSelectionChange = (arr: Process[]) => {
|
||||
selectedProcesses.value = arr.map((d: Process) => d.id);
|
||||
};
|
||||
|
||||
async function analyzeEBPF() {
|
||||
let arr: string[] = selectedLabels.value;
|
||||
if (selectedLabels.value.includes("0")) {
|
||||
arr = labels.value.map((d: Option) => d.value);
|
||||
}
|
||||
const ranges: { start: number; end: number }[] = [];
|
||||
const scheduleIdList = ebpfStore.eBPFSchedules.flatMap(
|
||||
(d: EBPFProfilingSchedule) => {
|
||||
const l = d.process.labels.find((d: string) => arr.includes(d));
|
||||
const i = selectedProcesses.value.includes(d.process.id);
|
||||
if (l || i) {
|
||||
ranges.push({
|
||||
start: d.startTime,
|
||||
end: d.endTime,
|
||||
});
|
||||
return d.scheduleId;
|
||||
}
|
||||
}
|
||||
);
|
||||
let timeRanges: { start: number; end: number }[] = [];
|
||||
for (const r of ranges) {
|
||||
if (timeRanges.length) {
|
||||
for (const t of timeRanges) {
|
||||
if (r.start > t.start && r.start < t.end) {
|
||||
if (r.end > t.end) {
|
||||
t.end = r.end;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
timeRanges.push(r);
|
||||
}
|
||||
}
|
||||
const res = await ebpfStore.getEBPFAnalyze({
|
||||
scheduleIdList,
|
||||
timeRanges,
|
||||
});
|
||||
if (res.data.errors) {
|
||||
ElMessage.error(res.data.errors);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
function visTimeline() {
|
||||
if (visGraph.value) {
|
||||
visGraph.value.destroy();
|
||||
}
|
||||
labels.value = [{ label: "All", value: "0" }];
|
||||
selectedLabels.value = ["0"];
|
||||
processes.value = [];
|
||||
const schedules = ebpfStore.eBPFSchedules.map(
|
||||
(d: EBPFProfilingSchedule, index: number) => {
|
||||
for (const l of d.process.labels) {
|
||||
labels.value.push({ label: l, value: l });
|
||||
}
|
||||
processes.value.push(d.process);
|
||||
return {
|
||||
id: index + 1,
|
||||
content: d.process.name,
|
||||
start: dateFormat(d.startTime),
|
||||
end: dateFormat(d.endTime),
|
||||
};
|
||||
}
|
||||
);
|
||||
searchProcesses();
|
||||
if (!timeline.value) {
|
||||
return;
|
||||
}
|
||||
const h = timeline.value.getBoundingClientRect().height;
|
||||
const items: any = new DataSet(schedules);
|
||||
const options = {
|
||||
height: h,
|
||||
width: "100%",
|
||||
locale: "en",
|
||||
};
|
||||
visGraph.value = new Timeline(timeline.value, items, options);
|
||||
}
|
||||
|
||||
function changePage(pageIndex: number) {
|
||||
searchProcesses(pageIndex);
|
||||
}
|
||||
|
||||
function searchProcesses(pageIndex?: any) {
|
||||
const arr = processes.value.filter(
|
||||
(d: { name: string; instanceName: string }) =>
|
||||
d.name.includes(searchText.value) ||
|
||||
d.instanceName.includes(searchText.value)
|
||||
);
|
||||
currentProcesses.value = arr.splice(
|
||||
(pageIndex - 1 || 0) * pageSize,
|
||||
pageSize * (pageIndex || 1)
|
||||
);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => ebpfStore.eBPFSchedules,
|
||||
() => {
|
||||
visTimeline();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.filters {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.schedules {
|
||||
width: calc(100% - 5px);
|
||||
margin: 0 5px 5px 0;
|
||||
height: calc(100% - 60px);
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
.inputs {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.input-with-search {
|
||||
width: 650px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.pagination {
|
||||
margin-top: 10px;
|
||||
}
|
||||
</style>
|
148
src/views/dashboard/related/ebpf/components/EBPFStack.vue
Normal file
148
src/views/dashboard/related/ebpf/components/EBPFStack.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<!-- 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 id="graph-stack" ref="graph">
|
||||
<span class="tip" v-show="ebpfStore.tip">{{ ebpfStore.tip }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from "vue";
|
||||
import * as d3 from "d3";
|
||||
import d3tip from "d3-tip";
|
||||
import { flamegraph } from "d3-flame-graph";
|
||||
import { useEbpfStore } from "@/store/modules/ebpf";
|
||||
import { StackElement } from "@/types/ebpf";
|
||||
import "d3-flame-graph/dist/d3-flamegraph.css";
|
||||
|
||||
/*global Nullable*/
|
||||
const ebpfStore = useEbpfStore();
|
||||
const stackTree = ref<Nullable<StackElement>>(null);
|
||||
const graph = ref<Nullable<HTMLDivElement>>(null);
|
||||
const flameChart = ref<any>(null);
|
||||
|
||||
function drawGraph() {
|
||||
if (flameChart.value) {
|
||||
flameChart.value.destroy();
|
||||
}
|
||||
if (!ebpfStore.analyzeTrees.length) {
|
||||
return (stackTree.value = null);
|
||||
}
|
||||
stackTree.value = processTree(ebpfStore.analyzeTrees);
|
||||
|
||||
const w = (graph.value && graph.value.getBoundingClientRect().width) || 10;
|
||||
flameChart.value = flamegraph()
|
||||
.width(w - 15)
|
||||
.cellHeight(18)
|
||||
.transitionDuration(750)
|
||||
.minFrameSize(5)
|
||||
.transitionEase(d3.easeCubic as any)
|
||||
.sort(true)
|
||||
.title("")
|
||||
.selfValue(false)
|
||||
.setColorMapper((d, originalColor) =>
|
||||
d.highlight ? "#6aff8f" : originalColor
|
||||
);
|
||||
const tip = (d3tip as any)()
|
||||
.attr("class", "d3-tip")
|
||||
.direction("w")
|
||||
.html(
|
||||
(d: { data: StackElement }) =>
|
||||
`<div class="mb-5">Symbol: ${d.data.name}</div><div class="mb-5">Dump Count: ${d.data.dumpCount}</div>`
|
||||
);
|
||||
flameChart.value.tooltip(tip);
|
||||
d3.select("#graph-stack").datum(stackTree.value).call(flameChart.value);
|
||||
}
|
||||
|
||||
function processTree(arr: StackElement[]) {
|
||||
const copyArr = JSON.parse(JSON.stringify(arr));
|
||||
const obj: any = {};
|
||||
let res = null;
|
||||
let min = 1;
|
||||
let max = 1;
|
||||
for (const item of copyArr) {
|
||||
item.originId = item.id;
|
||||
item.name = item.symbol;
|
||||
delete item.id;
|
||||
obj[item.originId] = item;
|
||||
if (item.dumpCount > max) {
|
||||
max = item.dumpCount;
|
||||
}
|
||||
if (item.dumpCount < min) {
|
||||
min = item.dumpCount;
|
||||
}
|
||||
}
|
||||
const scale = d3.scaleLinear().domain([min, max]).range([1, 200]);
|
||||
for (const item of copyArr) {
|
||||
if (item.parentId === "0") {
|
||||
const val = Number(scale(item.dumpCount).toFixed(4));
|
||||
res = item;
|
||||
res.value = val;
|
||||
}
|
||||
for (const key in obj) {
|
||||
if (item.originId === obj[key].parentId) {
|
||||
const val = Number(scale(obj[key].dumpCount).toFixed(4));
|
||||
|
||||
obj[key].value = val;
|
||||
if (item.children) {
|
||||
item.children.push(obj[key]);
|
||||
} else {
|
||||
item.children = [obj[key]];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
treeForeach([res], (node: StackElement) => {
|
||||
if (node.children) {
|
||||
let val = 0;
|
||||
for (const child of node.children) {
|
||||
val = child.value + val;
|
||||
}
|
||||
node.value = node.value < val ? val : node.value;
|
||||
}
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
function treeForeach(tree: StackElement[], func: (node: StackElement) => void) {
|
||||
for (const data of tree) {
|
||||
data.children && treeForeach(data.children, func);
|
||||
func(data);
|
||||
}
|
||||
return tree;
|
||||
}
|
||||
|
||||
watch(
|
||||
() => ebpfStore.analyzeTrees,
|
||||
() => {
|
||||
drawGraph();
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style>
|
||||
#graph-stack {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tip {
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: red;
|
||||
margin-top: 20px;
|
||||
}
|
||||
</style>
|
165
src/views/dashboard/related/ebpf/components/NewTask.vue
Normal file
165
src/views/dashboard/related/ebpf/components/NewTask.vue
Normal file
@@ -0,0 +1,165 @@
|
||||
<!-- 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="ebpf-task" v-if="eBPFStore.couldProfiling">
|
||||
<div>
|
||||
<div class="label">{{ t("labels") }}</div>
|
||||
<Selector
|
||||
class="profile-input"
|
||||
size="small"
|
||||
:value="labels"
|
||||
:options="eBPFStore.labels"
|
||||
placeholder="Select labels"
|
||||
:multiple="true"
|
||||
@change="changeLabel"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">{{ t("targetType") }}</div>
|
||||
<Selector
|
||||
class="profile-input"
|
||||
size="small"
|
||||
:value="type"
|
||||
:options="TargetTypes"
|
||||
placeholder="Select a type"
|
||||
:isRemote="true"
|
||||
@change="changeType"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">{{ t("monitorTime") }}</div>
|
||||
<div>
|
||||
<Radio
|
||||
:value="monitorTime"
|
||||
:options="InitTaskField.monitorTimeEn"
|
||||
@change="changeMonitorTime"
|
||||
/>
|
||||
<span class="date">
|
||||
<TimePicker
|
||||
:value="time"
|
||||
position="bottom"
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
@input="changeTimeRange"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">{{ t("monitorDuration") }}</div>
|
||||
<el-input
|
||||
class="profile-input"
|
||||
v-model="monitorDuration"
|
||||
size="small"
|
||||
placeholder="none"
|
||||
type="number"
|
||||
:min="1"
|
||||
:max="60"
|
||||
/>
|
||||
Min
|
||||
</div>
|
||||
<div>
|
||||
<el-button @click="createTask" type="primary" class="create-task-btn">
|
||||
{{ t("createTask") }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>{{ t("ebpfTip") }}</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from "vue";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useEbpfStore } from "@/store/modules/ebpf";
|
||||
import { useSelectorStore } from "@/store/modules/selectors";
|
||||
import { useAppStoreWithOut } from "@/store/modules/app";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { InitTaskField, TargetTypes } from "./data";
|
||||
/* global defineEmits */
|
||||
const emits = defineEmits(["close"]);
|
||||
const eBPFStore = useEbpfStore();
|
||||
const selectorStore = useSelectorStore();
|
||||
const appStore = useAppStoreWithOut();
|
||||
const { t } = useI18n();
|
||||
const labels = ref<string[]>([]);
|
||||
const type = ref<string>(TargetTypes[0].value);
|
||||
const monitorTime = ref<string>(InitTaskField.monitorTimeEn[0].value);
|
||||
const monitorDuration = ref<number>(10);
|
||||
const time = ref<Date>(appStore.durationRow.start);
|
||||
|
||||
function changeMonitorTime(opt: string) {
|
||||
monitorTime.value = opt;
|
||||
}
|
||||
|
||||
function changeLabel(opt: any[]) {
|
||||
labels.value = opt.map((d) => d.value);
|
||||
}
|
||||
|
||||
function changeType(opt: any[]) {
|
||||
type.value = opt[0].value;
|
||||
}
|
||||
|
||||
async function createTask() {
|
||||
if (!labels.value.length) {
|
||||
ElMessage.warning("no labels");
|
||||
return;
|
||||
}
|
||||
const date = monitorTime.value === "0" ? new Date() : time.value;
|
||||
const params = {
|
||||
serviceId: selectorStore.currentService.id,
|
||||
processLabels: labels.value,
|
||||
startTime: date.getTime(),
|
||||
duration: monitorDuration.value * 60,
|
||||
targetType: "ON_CPU",
|
||||
};
|
||||
const res = await eBPFStore.createTask(params);
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
return;
|
||||
}
|
||||
if (!res.data.createTaskData.status) {
|
||||
ElMessage.error(res.data.createTaskData.errorReason);
|
||||
return;
|
||||
}
|
||||
ElMessage.success("Task created successfully");
|
||||
emits("close");
|
||||
}
|
||||
function changeTimeRange(val: Date) {
|
||||
time.value = val;
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.ebpf-task {
|
||||
margin: 0 auto;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.date {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.label {
|
||||
margin-top: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.profile-input {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.create-task-btn {
|
||||
width: 300px;
|
||||
margin-top: 50px;
|
||||
}
|
||||
</style>
|
195
src/views/dashboard/related/ebpf/components/TaskList.vue
Normal file
195
src/views/dashboard/related/ebpf/components/TaskList.vue
Normal file
@@ -0,0 +1,195 @@
|
||||
<!-- 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="!ebpfStore.taskList.length">
|
||||
{{ t("noData") }}
|
||||
</div>
|
||||
<table class="profile-t">
|
||||
<tr
|
||||
class="profile-tr cp"
|
||||
v-for="(i, index) in ebpfStore.taskList"
|
||||
@click="changeTask(i)"
|
||||
:key="index"
|
||||
>
|
||||
<td
|
||||
class="profile-td"
|
||||
:class="{
|
||||
selected: selectedTask.taskId === i.taskId,
|
||||
}"
|
||||
>
|
||||
<div class="ell">
|
||||
<span>{{ i.processLabels.join(" ") }}</span>
|
||||
<a class="profile-btn r" @click="viewDetail = true">
|
||||
<Icon iconName="view" size="middle" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="grey ell sm">
|
||||
<span class="mr-10 sm">{{ dateFormat(i.taskStartTime) }}</span>
|
||||
<span class="mr-10 sm">
|
||||
{{
|
||||
dateFormat(i.taskStartTime + i.fixedTriggerDuration * 1000)
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</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("taskId") }}:</span>
|
||||
<span class="g-sm-8 wba">
|
||||
{{ selectedTask.taskId }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mb-10 clear item">
|
||||
<span class="g-sm-4 grey">{{ t("service") }}:</span>
|
||||
<span class="g-sm-8 wba">{{ selectedTask.serviceName }}</span>
|
||||
</div>
|
||||
<div class="mb-10 clear item">
|
||||
<span class="g-sm-4 grey">{{ t("labels") }}:</span>
|
||||
<span class="g-sm-8 wba">{{ selectedTask.processLabels }}</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.taskStartTime) }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="mb-10 clear item">
|
||||
<span class="g-sm-4 grey">{{ t("monitorDuration") }}:</span>
|
||||
<span class="g-sm-8 wba">
|
||||
{{ selectedTask.fixedTriggerDuration / 60 }} min
|
||||
</span>
|
||||
</div>
|
||||
<div class="mb-10 clear item">
|
||||
<span class="g-sm-4 grey">{{ t("triggerType") }}:</span>
|
||||
<span class="g-sm-8 wba">{{ selectedTask.triggerType }}</span>
|
||||
</div>
|
||||
<div class="mb-10 clear item">
|
||||
<span class="g-sm-4 grey">{{ t("targetType") }}:</span>
|
||||
<span class="g-sm-8 wba">{{ selectedTask.targetType }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from "vue";
|
||||
import dayjs from "dayjs";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useEbpfStore } from "@/store/modules/ebpf";
|
||||
import { EBPFTaskList } from "@/types/ebpf";
|
||||
import { ElMessage } from "element-plus";
|
||||
|
||||
const { t } = useI18n();
|
||||
const ebpfStore = useEbpfStore();
|
||||
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
|
||||
dayjs(date).format(pattern);
|
||||
const selectedTask = ref<EBPFTaskList | Record<string, never>>({});
|
||||
const viewDetail = ref<boolean>(false);
|
||||
|
||||
async function changeTask(item: EBPFTaskList) {
|
||||
selectedTask.value = item;
|
||||
const res = await ebpfStore.getEBPFSchedules({
|
||||
taskId: item.taskId,
|
||||
});
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
}
|
||||
}
|
||||
watch(
|
||||
() => ebpfStore.taskList,
|
||||
() => {
|
||||
selectedTask.value = ebpfStore.taskList[0] || {};
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.profile-task-list {
|
||||
width: 300px;
|
||||
height: calc(100% - 10px);
|
||||
overflow: auto;
|
||||
border-right: 1px solid rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.item span {
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
.profile-td {
|
||||
padding: 5px 10px;
|
||||
border-bottom: 1px solid rgba(0, 0, 0, 0.07);
|
||||
|
||||
&.selected {
|
||||
background-color: #ededed;
|
||||
}
|
||||
}
|
||||
|
||||
.no-data {
|
||||
text-align: center;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.profile-t-wrapper {
|
||||
overflow: auto;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.profile-t {
|
||||
width: 100%;
|
||||
border-spacing: 0;
|
||||
table-layout: fixed;
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.profile-tr {
|
||||
&:hover {
|
||||
background-color: rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.profile-btn {
|
||||
color: #3d444f;
|
||||
padding: 1px 3px;
|
||||
border-radius: 2px;
|
||||
font-size: 12px;
|
||||
float: right;
|
||||
}
|
||||
</style>
|
51
src/views/dashboard/related/ebpf/components/data.ts
Normal file
51
src/views/dashboard/related/ebpf/components/data.ts
Normal file
@@ -0,0 +1,51 @@
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
export const ProfileMode: any[] = [
|
||||
{ label: "Include Children", value: "include" },
|
||||
{ label: "Exclude Children", value: "exclude" },
|
||||
];
|
||||
export const NewTaskField = {
|
||||
service: { key: "", label: "None" },
|
||||
monitorTime: { key: "0", label: "monitor now" },
|
||||
monitorDuration: { key: 5, label: "5 min" },
|
||||
minThreshold: 0,
|
||||
dumpPeriod: { key: 10, label: "10ms" },
|
||||
endpointName: "",
|
||||
maxSamplingCount: { key: 5, label: "5" },
|
||||
};
|
||||
|
||||
export const TargetTypes = [{ label: "ON_CPU", value: "ON_CPU" }];
|
||||
|
||||
export const InitTaskField = {
|
||||
monitorTimeEn: [
|
||||
{ value: "0", label: "monitor now" },
|
||||
{ value: "1", label: "set start time" },
|
||||
],
|
||||
monitorTimeCn: [
|
||||
{ value: "0", label: "此刻" },
|
||||
{ value: "1", label: "设置时间" },
|
||||
],
|
||||
monitorDuration: [
|
||||
{ value: "5", label: "5 min" },
|
||||
{ value: "10", label: "10 min" },
|
||||
{ value: "15", label: "15 min" },
|
||||
],
|
||||
};
|
||||
export const TableHeader = [
|
||||
{ property: "name", label: "Name" },
|
||||
{ property: "instanceName", label: "Instance Name" },
|
||||
];
|
@@ -14,19 +14,18 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License. -->
|
||||
<template>
|
||||
<div class="flex-h header">
|
||||
<!-- <div class="mr-10" v-if="dashboardStore.entity==='All'">
|
||||
<span class="grey mr-5">{{ t("service") }}:</span>
|
||||
<Selector
|
||||
size="small"
|
||||
:value="service.value"
|
||||
:options="profileStore.services"
|
||||
placeholder="Select a service"
|
||||
@change="changeService"
|
||||
/>
|
||||
</div> -->
|
||||
<div class="mr-10">
|
||||
<span class="grey mr-5">{{ t("endpointName") }}:</span>
|
||||
<el-input v-model="endpointName" class="name" size="small" />
|
||||
<Selector
|
||||
class="name"
|
||||
size="small"
|
||||
:value="endpointName"
|
||||
:options="profileStore.endpoints"
|
||||
placeholder="Select a endpoint"
|
||||
:isRemote="true"
|
||||
@change="changeEndpoint"
|
||||
@query="searchEndpoints"
|
||||
/>
|
||||
</div>
|
||||
<el-button
|
||||
class="search-btn"
|
||||
@@ -65,27 +64,30 @@ const appStore = useAppStoreWithOut();
|
||||
const selectorStore = useSelectorStore();
|
||||
const dashboardStore = useDashboardStore();
|
||||
const { t } = useI18n();
|
||||
// const service = ref<any>({});
|
||||
const endpointName = ref<string>("");
|
||||
const newTask = ref<boolean>(false);
|
||||
|
||||
searchTasks();
|
||||
// getServices();
|
||||
searchEndpoints("");
|
||||
|
||||
// async function getServices() {
|
||||
// const res = await profileStore.getServices(dashboardStore.layerId);
|
||||
async function searchEndpoints(keyword: string) {
|
||||
if (!selectorStore.currentService) {
|
||||
return;
|
||||
}
|
||||
const service = selectorStore.currentService.id;
|
||||
const res = await profileStore.getEndpoints(service, keyword);
|
||||
|
||||
// if (res.errors) {
|
||||
// ElMessage.error(res.errors);
|
||||
// return;
|
||||
// }
|
||||
// service.value = profileStore.services[0];
|
||||
// searchTasks();
|
||||
// }
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
return;
|
||||
}
|
||||
endpointName.value = profileStore.endpoints[0].value;
|
||||
}
|
||||
|
||||
function changeEndpoint(opt: any[]) {
|
||||
endpointName.value = opt[0].value;
|
||||
}
|
||||
|
||||
// function changeService(opt: any[]) {
|
||||
// service.value = opt[0];
|
||||
// }
|
||||
async function searchTasks() {
|
||||
profileStore.setConditions({
|
||||
serviceId:
|
||||
|
@@ -17,7 +17,16 @@ limitations under the License. -->
|
||||
<div class="profile-task">
|
||||
<div>
|
||||
<div class="label">{{ t("endpointName") }}</div>
|
||||
<el-input v-model="endpointName" class="profile-input" size="small" />
|
||||
<Selector
|
||||
class="profile-input"
|
||||
size="small"
|
||||
:value="endpointName"
|
||||
:options="profileStore.endpoints"
|
||||
placeholder="Select a endpoint"
|
||||
:isRemote="true"
|
||||
@change="changeEndpoint"
|
||||
@query="searchEndpoints"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<div class="label">{{ t("monitorTime") }}</div>
|
||||
@@ -105,6 +114,20 @@ const minThreshold = ref<number>(0);
|
||||
const dumpPeriod = ref<string>(InitTaskField.dumpPeriod[0].value);
|
||||
const maxSamplingCount = ref<string>(InitTaskField.maxSamplingCount[0].value);
|
||||
|
||||
async function searchEndpoints(keyword: string) {
|
||||
if (!selectorStore.currentService) {
|
||||
return;
|
||||
}
|
||||
const service = selectorStore.currentService.id;
|
||||
const res = await profileStore.getEndpoints(service, keyword);
|
||||
|
||||
if (res.errors) {
|
||||
ElMessage.error(res.errors);
|
||||
return;
|
||||
}
|
||||
endpointName.value = profileStore.taskEndpoints[0].value;
|
||||
}
|
||||
|
||||
function changeMonitorTime(opt: string) {
|
||||
monitorTime.value = opt;
|
||||
}
|
||||
@@ -121,10 +144,13 @@ function changeMaxSamplingCount(opt: any[]) {
|
||||
maxSamplingCount.value = opt[0].value;
|
||||
}
|
||||
|
||||
function changeEndpoint(opt: any[]) {
|
||||
endpointName.value = opt[0].value;
|
||||
}
|
||||
|
||||
async function createTask() {
|
||||
emits("close");
|
||||
const date =
|
||||
monitorTime.value === "0" ? appStore.durationRow.start : time.value;
|
||||
const date = monitorTime.value === "0" ? new Date() : time.value;
|
||||
const params = {
|
||||
serviceId: selectorStore.currentService.id,
|
||||
endpointName: endpointName.value,
|
||||
@@ -153,7 +179,7 @@ function changeTimeRange(val: Date) {
|
||||
<style lang="scss" scoped>
|
||||
.profile-task {
|
||||
margin: 0 auto;
|
||||
width: 350px;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.date {
|
||||
|
@@ -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" size="middle" />
|
||||
<Icon iconName="view" size="middle" />
|
||||
</a>
|
||||
</div>
|
||||
<div class="grey ell sm">
|
||||
|
@@ -88,7 +88,7 @@ limitations under the License. -->
|
||||
</div>
|
||||
<div>
|
||||
<el-button
|
||||
class="grey"
|
||||
class="grey small"
|
||||
:class="{ ghost: displayMode !== 'List' }"
|
||||
@click="displayMode = 'List'"
|
||||
>
|
||||
@@ -96,7 +96,7 @@ limitations under the License. -->
|
||||
{{ t("list") }}
|
||||
</el-button>
|
||||
<el-button
|
||||
class="grey"
|
||||
class="grey small"
|
||||
:class="{ ghost: displayMode !== 'Tree' }"
|
||||
@click="displayMode = 'Tree'"
|
||||
>
|
||||
@@ -104,7 +104,7 @@ limitations under the License. -->
|
||||
{{ t("tree") }}
|
||||
</el-button>
|
||||
<el-button
|
||||
class="grey"
|
||||
class="grey small"
|
||||
:class="{ ghost: displayMode !== 'Table' }"
|
||||
@click="displayMode = 'Table'"
|
||||
>
|
||||
@@ -112,7 +112,7 @@ limitations under the License. -->
|
||||
{{ t("table") }}
|
||||
</el-button>
|
||||
<el-button
|
||||
class="grey"
|
||||
class="grey small"
|
||||
:class="{ ghost: displayMode !== 'Statistics' }"
|
||||
@click="displayMode = 'Statistics'"
|
||||
>
|
||||
|
Reference in New Issue
Block a user