feat: visualize the OFF CPU eBPF profiling (#97)

This commit is contained in:
Fine0830 2022-05-23 16:00:30 +08:00 committed by GitHub
parent f334985cf0
commit 45f896bf36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 153 additions and 86 deletions

View File

@ -75,9 +75,9 @@ export const queryEBPFSchedules = {
export const analysisEBPFResult = {
variable:
"$scheduleIdList: [ID!]!, $timeRanges: [EBPFProfilingAnalyzeTimeRange!]!",
"$scheduleIdList: [ID!]!, $timeRanges: [EBPFProfilingAnalyzeTimeRange!]!, $aggregateType: EBPFProfilingAnalyzeAggregateType",
query: `
analysisEBPFResult: analysisEBPFProfilingResult(scheduleIdList: $scheduleIdList, timeRanges: $timeRanges) {
analysisEBPFResult: analysisEBPFProfilingResult(scheduleIdList: $scheduleIdList, timeRanges: $timeRanges, aggregateType: $aggregateType) {
tip
trees {
elements {

View File

@ -22,7 +22,6 @@ import {
EBPFTaskList,
AnalyzationTrees,
} from "@/types/ebpf";
import { Trace, Span } from "@/types/trace";
import { store } from "@/store";
import graphql from "@/graphql";
import { AxiosResponse } from "axios";
@ -35,6 +34,7 @@ interface EbpfStore {
labels: Option[];
couldProfiling: boolean;
tip: string;
selectedTask: Recordable<EBPFTaskList>;
}
export const ebpfStore = defineStore({
@ -47,14 +47,18 @@ export const ebpfStore = defineStore({
labels: [{ value: "", label: "" }],
couldProfiling: false,
tip: "",
selectedTask: {},
}),
actions: {
setCurrentSpan(span: Span) {
this.currentSpan = span;
setSelectedTask(task: EBPFTaskList) {
this.selectedTask = task;
},
setCurrentSchedule(s: Trace) {
setCurrentSchedule(s: EBPFProfilingSchedule) {
this.currentSchedule = s;
},
setAnalyzeTrees(tree: AnalyzationTrees[]) {
this.analyzeTrees = tree;
},
async getCreateTaskData(serviceId: string) {
const res: AxiosResponse = await graphql
.query("getCreateTaskData")
@ -93,7 +97,7 @@ export const ebpfStore = defineStore({
if (res.data.errors) {
return res.data;
}
this.taskList = res.data.data.queryEBPFTasks.reverse() || [];
this.taskList = res.data.data.queryEBPFTasks || [];
if (!this.taskList.length) {
return res.data;
}
@ -118,13 +122,14 @@ export const ebpfStore = defineStore({
this.eBPFSchedules = eBPFSchedules;
if (!eBPFSchedules.length) {
this.eBPFSchedules = [];
this.analyzeTrees = [];
}
this.analyzeTrees = [];
return res.data;
},
async getEBPFAnalyze(params: {
scheduleIdList: string[];
timeRanges: Array<{ start: number; end: number }>;
aggregateType: string;
}) {
if (!params.scheduleIdList.length) {
return new Promise((resolve) => resolve({}));

View File

@ -46,11 +46,13 @@ import EBPFStack from "./components/EBPFStack.vue";
.item {
width: 100%;
overflow: auto;
height: calc(100% - 70px);
height: calc(100% - 100px);
padding-bottom: 10px;
}
.schedules {
height: 60px;
height: 90px;
border-bottom: 1px solid #ccc;
padding-right: 10px;
}
</style>

View File

@ -13,74 +13,87 @@ 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 flex-h">
<Selector
:value="selectedLabels"
:options="labels"
size="small"
placeholder="Please select labels"
@change="changeLabels"
class="inputs mr-10"
:multiple="true"
/>
<el-button type="primary" size="small">
<span>{{ duration[0] }}</span>
<span> ~ </span>
<span>{{ duration[1] }}</span>
</el-button>
<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"
<div class="filters">
<div class="mb-10 flex-h">
<Selector
:value="selectedLabels"
:options="labels"
size="small"
@change="searchProcesses(0)"
>
<template #append>
<el-button size="small">
<Icon size="sm" iconName="search" />
placeholder="Please select labels"
@change="changeLabels"
class="inputs mr-10"
:multiple="true"
/>
<div class="mr-5 duration" v-if="duration.length">
<span>{{ duration[0] }}</span>
<span> ~ </span>
<span>{{ duration[1] }}</span>
</div>
</div>
<div class="flex-h">
<Selector
v-if="ebpfStore.selectedTask.targetType === 'OFF_CPU'"
:value="aggregateType"
:options="AggregateTypes"
size="small"
placeholder="Please select a type"
@change="changeAggregateType"
class="selector mr-10"
/>
<el-popover placement="bottom" :width="680" trigger="click">
<template #reference>
<el-button type="primary" size="small">
{{ t("processSelect") }}
</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("; ") }}
<el-input
v-model="searchText"
placeholder="Please input name"
class="input-with-search"
size="small"
@change="searchProcesses(0)"
>
<template #append>
<el-button size="small">
<Icon size="sm" iconName="search" />
</el-button>
</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>
</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>
</template>
<script lang="ts" setup>
@ -88,7 +101,7 @@ import { ref, watch } from "vue";
import dayjs from "dayjs";
import { useI18n } from "vue-i18n";
import { Option } from "@/types/app";
import { TableHeader } from "./data";
import { TableHeader, AggregateTypes } from "./data";
import { useEbpfStore } from "@/store/modules/ebpf";
import { EBPFProfilingSchedule, Process } from "@/types/ebpf";
import { ElMessage, ElTable } from "element-plus";
@ -103,6 +116,7 @@ const processes = ref<Process[]>([]);
const currentProcesses = ref<Process[]>([]);
const selectedLabels = ref<string[]>(["0"]);
const searchText = ref<string>("");
const aggregateType = ref<string>(AggregateTypes[0].value);
const duration = ref<string[]>([]);
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(date).format(pattern);
@ -112,6 +126,11 @@ function changeLabels(opt: any[]) {
selectedLabels.value = arr;
}
function changeAggregateType(opt: any[]) {
aggregateType.value = opt[0].value;
ebpfStore.setAnalyzeTrees([]);
}
const handleSelectionChange = (arr: Process[]) => {
selectedProcesses.value = arr.map((d: Process) => d.id);
};
@ -152,6 +171,7 @@ async function analyzeEBPF() {
const res = await ebpfStore.getEBPFAnalyze({
scheduleIdList,
timeRanges,
aggregateType: aggregateType.value,
});
if (res.data.errors) {
ElMessage.error(res.data.errors);
@ -159,7 +179,7 @@ async function analyzeEBPF() {
}
}
function visTimeline() {
function getSchedules() {
labels.value = [{ label: "All", value: "0" }];
selectedLabels.value = ["0"];
processes.value = [];
@ -170,11 +190,16 @@ function visTimeline() {
processes.value.push(d.process);
return [d.startTime / 10000, d.endTime / 10000];
});
const arr = ranges.flat(1);
const min = Math.min(...arr);
const max = Math.max(...arr);
duration.value = [dateFormat(min * 10000), dateFormat(max * 10000)];
if (ranges.length) {
const arr = ranges.flat(1);
const min = Math.min(...arr);
const max = Math.max(...arr);
duration.value = [dateFormat(min * 10000), dateFormat(max * 10000)];
} else {
duration.value = [];
}
searchProcesses(0);
analyzeEBPF();
}
function changePage(pageIndex: number) {
@ -216,7 +241,7 @@ function searchAttribute(
watch(
() => ebpfStore.eBPFSchedules,
() => {
visTimeline();
getSchedules();
}
);
</script>
@ -235,7 +260,7 @@ watch(
}
.inputs {
width: 300px;
width: 350px;
}
.input-with-search {
@ -246,4 +271,12 @@ watch(
.pagination {
margin-top: 10px;
}
.selector {
width: 120px;
}
.duration {
line-height: 30px;
}
</style>

View File

@ -86,6 +86,7 @@ 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();
@ -97,6 +98,7 @@ 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);
const disabled = ref<boolean>(false);
function changeMonitorTime(opt: string) {
monitorTime.value = opt;
@ -111,19 +113,24 @@ function changeType(opt: any[]) {
}
async function createTask() {
if (disabled.value) {
return;
}
disabled.value = true;
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",
targetType: type.value,
};
const res = await eBPFStore.createTask(params);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
disabled.value = false;
if (!res.data.createTaskData.status) {
ElMessage.error(res.data.createTaskData.errorReason);
return;

View File

@ -34,7 +34,15 @@ limitations under the License. -->
}"
>
<div class="ell">
<span>{{ i.processLabels.join(" ") }}</span>
<span>
{{
i.targetType +
": " +
(i.processLabels.length
? i.processLabels.join(" ")
: `All Processes`)
}}
</span>
<a class="profile-btn r" @click="viewDetail = true">
<Icon iconName="view" size="middle" />
</a>
@ -74,7 +82,9 @@ limitations under the License. -->
</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>
<span class="g-sm-8 wba">
{{ selectedTask.processLabels.join(";") }}
</span>
</div>
<div class="mb-10 clear item">
<span class="g-sm-4 grey">{{ t("monitorTime") }}:</span>
@ -117,6 +127,7 @@ const viewDetail = ref<boolean>(false);
async function changeTask(item: EBPFTaskList) {
selectedTask.value = item;
ebpfStore.setSelectedTask(item);
const res = await ebpfStore.getEBPFSchedules({
taskId: item.taskId,
});
@ -128,6 +139,7 @@ watch(
() => ebpfStore.taskList,
() => {
selectedTask.value = ebpfStore.taskList[0] || {};
ebpfStore.setSelectedTask(selectedTask.value);
}
);
</script>

View File

@ -28,7 +28,15 @@ export const NewTaskField = {
maxSamplingCount: { key: 5, label: "5" },
};
export const TargetTypes = [{ label: "ON_CPU", value: "ON_CPU" }];
export const TargetTypes = [
{ label: "ON_CPU", value: "ON_CPU" },
{ label: "OFF_CPU", value: "OFF_CPU" },
];
export const AggregateTypes = [
{ label: "Count", value: "COUNT" },
{ label: "Duration", value: "DURATION" },
];
export const InitTaskField = {
monitorTimeEn: [