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 = { export const analysisEBPFResult = {
variable: variable:
"$scheduleIdList: [ID!]!, $timeRanges: [EBPFProfilingAnalyzeTimeRange!]!", "$scheduleIdList: [ID!]!, $timeRanges: [EBPFProfilingAnalyzeTimeRange!]!, $aggregateType: EBPFProfilingAnalyzeAggregateType",
query: ` query: `
analysisEBPFResult: analysisEBPFProfilingResult(scheduleIdList: $scheduleIdList, timeRanges: $timeRanges) { analysisEBPFResult: analysisEBPFProfilingResult(scheduleIdList: $scheduleIdList, timeRanges: $timeRanges, aggregateType: $aggregateType) {
tip tip
trees { trees {
elements { elements {

View File

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

View File

@ -46,11 +46,13 @@ import EBPFStack from "./components/EBPFStack.vue";
.item { .item {
width: 100%; width: 100%;
overflow: auto; overflow: auto;
height: calc(100% - 70px); height: calc(100% - 100px);
padding-bottom: 10px; padding-bottom: 10px;
} }
.schedules { .schedules {
height: 60px; height: 90px;
border-bottom: 1px solid #ccc;
padding-right: 10px;
} }
</style> </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 See the License for the specific language governing permissions and
limitations under the License. --> limitations under the License. -->
<template> <template>
<div class="filters flex-h"> <div class="filters">
<Selector <div class="mb-10 flex-h">
:value="selectedLabels" <Selector
:options="labels" :value="selectedLabels"
size="small" :options="labels"
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"
size="small" size="small"
@change="searchProcesses(0)" placeholder="Please select labels"
> @change="changeLabels"
<template #append> class="inputs mr-10"
<el-button size="small"> :multiple="true"
<Icon size="sm" iconName="search" /> />
<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> </el-button>
</template> </template>
</el-input> <el-input
<el-table v-model="searchText"
:data="currentProcesses" placeholder="Please input name"
ref="multipleTableRef" class="input-with-search"
@selection-change="handleSelectionChange" size="small"
> @change="searchProcesses(0)"
<el-table-column type="selection" width="55" /> >
<el-table-column <template #append>
v-for="(h, index) of TableHeader" <el-button size="small">
:property="h.property" <Icon size="sm" iconName="search" />
:label="h.label" </el-button>
: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> </template>
</el-table-column> </el-input>
</el-table> <el-table
<el-pagination :data="currentProcesses"
class="pagination" ref="multipleTableRef"
background @selection-change="handleSelectionChange"
small >
layout="prev, pager, next" <el-table-column type="selection" width="55" />
:page-size="pageSize" <el-table-column
:total="processes.length" v-for="(h, index) of TableHeader"
@current-change="changePage" :property="h.property"
@prev-click="changePage" :label="h.label"
@next-click="changePage" :key="index"
/> width="150"
</el-popover> />
<el-button type="primary" size="small" @click="analyzeEBPF"> <el-table-column width="300" label="Attributes">
{{ t("analyze") }} <template #default="scope">
</el-button> {{ 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> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -88,7 +101,7 @@ import { ref, watch } from "vue";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { Option } from "@/types/app"; import { Option } from "@/types/app";
import { TableHeader } from "./data"; import { TableHeader, AggregateTypes } from "./data";
import { useEbpfStore } from "@/store/modules/ebpf"; import { useEbpfStore } from "@/store/modules/ebpf";
import { EBPFProfilingSchedule, Process } from "@/types/ebpf"; import { EBPFProfilingSchedule, Process } from "@/types/ebpf";
import { ElMessage, ElTable } from "element-plus"; import { ElMessage, ElTable } from "element-plus";
@ -103,6 +116,7 @@ const processes = ref<Process[]>([]);
const currentProcesses = ref<Process[]>([]); const currentProcesses = ref<Process[]>([]);
const selectedLabels = ref<string[]>(["0"]); const selectedLabels = ref<string[]>(["0"]);
const searchText = ref<string>(""); const searchText = ref<string>("");
const aggregateType = ref<string>(AggregateTypes[0].value);
const duration = ref<string[]>([]); const duration = ref<string[]>([]);
const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") => const dateFormat = (date: number, pattern = "YYYY-MM-DD HH:mm:ss") =>
dayjs(date).format(pattern); dayjs(date).format(pattern);
@ -112,6 +126,11 @@ function changeLabels(opt: any[]) {
selectedLabels.value = arr; selectedLabels.value = arr;
} }
function changeAggregateType(opt: any[]) {
aggregateType.value = opt[0].value;
ebpfStore.setAnalyzeTrees([]);
}
const handleSelectionChange = (arr: Process[]) => { const handleSelectionChange = (arr: Process[]) => {
selectedProcesses.value = arr.map((d: Process) => d.id); selectedProcesses.value = arr.map((d: Process) => d.id);
}; };
@ -152,6 +171,7 @@ async function analyzeEBPF() {
const res = await ebpfStore.getEBPFAnalyze({ const res = await ebpfStore.getEBPFAnalyze({
scheduleIdList, scheduleIdList,
timeRanges, timeRanges,
aggregateType: aggregateType.value,
}); });
if (res.data.errors) { if (res.data.errors) {
ElMessage.error(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" }]; labels.value = [{ label: "All", value: "0" }];
selectedLabels.value = ["0"]; selectedLabels.value = ["0"];
processes.value = []; processes.value = [];
@ -170,11 +190,16 @@ function visTimeline() {
processes.value.push(d.process); processes.value.push(d.process);
return [d.startTime / 10000, d.endTime / 10000]; return [d.startTime / 10000, d.endTime / 10000];
}); });
const arr = ranges.flat(1); if (ranges.length) {
const min = Math.min(...arr); const arr = ranges.flat(1);
const max = Math.max(...arr); const min = Math.min(...arr);
duration.value = [dateFormat(min * 10000), dateFormat(max * 10000)]; const max = Math.max(...arr);
duration.value = [dateFormat(min * 10000), dateFormat(max * 10000)];
} else {
duration.value = [];
}
searchProcesses(0); searchProcesses(0);
analyzeEBPF();
} }
function changePage(pageIndex: number) { function changePage(pageIndex: number) {
@ -216,7 +241,7 @@ function searchAttribute(
watch( watch(
() => ebpfStore.eBPFSchedules, () => ebpfStore.eBPFSchedules,
() => { () => {
visTimeline(); getSchedules();
} }
); );
</script> </script>
@ -235,7 +260,7 @@ watch(
} }
.inputs { .inputs {
width: 300px; width: 350px;
} }
.input-with-search { .input-with-search {
@ -246,4 +271,12 @@ watch(
.pagination { .pagination {
margin-top: 10px; margin-top: 10px;
} }
.selector {
width: 120px;
}
.duration {
line-height: 30px;
}
</style> </style>

View File

@ -86,6 +86,7 @@ import { useSelectorStore } from "@/store/modules/selectors";
import { useAppStoreWithOut } from "@/store/modules/app"; import { useAppStoreWithOut } from "@/store/modules/app";
import { ElMessage } from "element-plus"; import { ElMessage } from "element-plus";
import { InitTaskField, TargetTypes } from "./data"; import { InitTaskField, TargetTypes } from "./data";
/* global defineEmits */ /* global defineEmits */
const emits = defineEmits(["close"]); const emits = defineEmits(["close"]);
const eBPFStore = useEbpfStore(); const eBPFStore = useEbpfStore();
@ -97,6 +98,7 @@ const type = ref<string>(TargetTypes[0].value);
const monitorTime = ref<string>(InitTaskField.monitorTimeEn[0].value); const monitorTime = ref<string>(InitTaskField.monitorTimeEn[0].value);
const monitorDuration = ref<number>(10); const monitorDuration = ref<number>(10);
const time = ref<Date>(appStore.durationRow.start); const time = ref<Date>(appStore.durationRow.start);
const disabled = ref<boolean>(false);
function changeMonitorTime(opt: string) { function changeMonitorTime(opt: string) {
monitorTime.value = opt; monitorTime.value = opt;
@ -111,19 +113,24 @@ function changeType(opt: any[]) {
} }
async function createTask() { async function createTask() {
if (disabled.value) {
return;
}
disabled.value = true;
const date = monitorTime.value === "0" ? new Date() : time.value; const date = monitorTime.value === "0" ? new Date() : time.value;
const params = { const params = {
serviceId: selectorStore.currentService.id, serviceId: selectorStore.currentService.id,
processLabels: labels.value, processLabels: labels.value,
startTime: date.getTime(), startTime: date.getTime(),
duration: monitorDuration.value * 60, duration: monitorDuration.value * 60,
targetType: "ON_CPU", targetType: type.value,
}; };
const res = await eBPFStore.createTask(params); const res = await eBPFStore.createTask(params);
if (res.errors) { if (res.errors) {
ElMessage.error(res.errors); ElMessage.error(res.errors);
return; return;
} }
disabled.value = false;
if (!res.data.createTaskData.status) { if (!res.data.createTaskData.status) {
ElMessage.error(res.data.createTaskData.errorReason); ElMessage.error(res.data.createTaskData.errorReason);
return; return;

View File

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

View File

@ -28,7 +28,15 @@ export const NewTaskField = {
maxSamplingCount: { key: 5, label: "5" }, 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 = { export const InitTaskField = {
monitorTimeEn: [ monitorTimeEn: [