This commit is contained in:
Qiuxia Fan 2022-04-18 17:22:43 +08:00
parent 91aeb45604
commit 493ec4bec2
11 changed files with 694 additions and 8 deletions

View File

@ -32,7 +32,7 @@ interface Option {
const emit = defineEmits(["change"]); const emit = defineEmits(["change"]);
const props = defineProps({ const props = defineProps({
options: { options: {
type: Array as PropType<(Option & { disabled: boolean })[]>, type: Array as PropType<Option[]>,
default: () => [], default: () => [],
}, },
value: { value: {
@ -44,7 +44,7 @@ const props = defineProps({
const selected = ref<string>(props.value); const selected = ref<string>(props.value);
function checked(opt: string) { function checked(opt: unknown) {
emit("change", opt); emit("change", opt);
} }
</script> </script>

View File

@ -0,0 +1,227 @@
/**
* 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.
*/
import { defineStore } from "pinia";
import { Duration, Option } from "@/types/app";
import { Endpoint } from "@/types/selector";
import {
TaskListItem,
SegmentSpan,
ProfileAnalyzationTrees,
TaskLog,
ProfileTaskCreationRequest,
} from "@/types/profile";
import { Trace, Span } from "@/types/trace";
import { store } from "@/store";
import graphql from "@/graphql";
import { AxiosResponse } from "axios";
import { useAppStoreWithOut } from "@/store/modules/app";
interface EbpfStore {
endpoints: Endpoint[];
taskEndpoints: Endpoint[];
durationTime: Duration;
condition: { serviceId: string; endpointName: string };
taskList: TaskListItem[];
segmentList: Trace[];
currentSegment: Trace | Record<string, never>;
segmentSpans: SegmentSpan[];
currentSpan: SegmentSpan | Record<string, never>;
analyzeTrees: ProfileAnalyzationTrees;
taskLogs: TaskLog[];
highlightTop: boolean;
labels: Option[];
}
export const ebpfStore = defineStore({
id: "profile",
state: (): EbpfStore => ({
endpoints: [{ value: "", label: "All" }],
taskEndpoints: [{ value: "", label: "All" }],
durationTime: useAppStoreWithOut().durationTime,
condition: { serviceId: "", endpointName: "" },
taskList: [],
segmentList: [],
currentSegment: {},
segmentSpans: [],
currentSpan: {},
analyzeTrees: [],
taskLogs: [],
highlightTop: true,
labels: [{ value: "", label: "" }],
}),
actions: {
setConditions(data: { serviceId?: string; endpointName?: string }) {
this.condition = {
...this.condition,
...data,
};
},
setCurrentSpan(span: Span) {
this.currentSpan = span;
},
setCurrentSegment(s: Trace) {
this.currentSegment = s;
},
setHighlightTop() {
this.highlightTop = !this.highlightTop;
},
async getEndpoints(serviceId: string, keyword?: string) {
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
serviceId,
duration: this.durationTime,
keyword: keyword || "",
});
if (res.data.errors) {
return res.data;
}
this.endpoints = [{ value: "", label: "All" }, ...res.data.data.pods];
return res.data;
},
async getTaskEndpoints(serviceId: string, keyword?: string) {
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
serviceId,
duration: this.durationTime,
keyword: keyword || "",
});
if (res.data.errors) {
return res.data;
}
this.taskEndpoints = [{ value: "", label: "All" }, ...res.data.data.pods];
return res.data;
},
async getTaskList() {
const res: AxiosResponse = await graphql
.query("getProfileTaskList")
.params(this.condition);
if (res.data.errors) {
return res.data;
}
const list = res.data.data.taskList;
this.taskList = list;
if (!list.length) {
this.segmentList = [];
this.segmentSpans = [];
this.analyzeTrees = [];
return res.data;
}
this.getSegmentList({ taskID: list[0].id });
return res.data;
},
async getSegmentList(params: { taskID: string }) {
const res: AxiosResponse = await graphql
.query("getProfileTaskSegmentList")
.params(params);
if (res.data.errors) {
this.segmentList = [];
return res.data;
}
const { segmentList } = res.data.data;
this.segmentList = segmentList;
if (!segmentList.length) {
this.segmentSpans = [];
this.analyzeTrees = [];
return res.data;
}
if (segmentList[0]) {
this.currentSegment = segmentList[0];
this.getSegmentSpans({ segmentId: segmentList[0].segmentId });
} else {
this.currentSegment = null;
}
return res.data;
},
async getSegmentSpans(params: { segmentId: string }) {
const res: AxiosResponse = await graphql
.query("queryProfileSegment")
.params(params);
if (res.data.errors) {
this.segmentSpans = [];
return res.data;
}
const { segment } = res.data.data;
if (!segment) {
this.segmentSpans = [];
this.analyzeTrees = [];
return res.data;
}
this.segmentSpans = segment.spans;
if (!(segment.spans && segment.spans.length)) {
this.analyzeTrees = [];
return res.data;
}
const index = segment.spans.length - 1 || 0;
this.currentSpan = segment.spans[index];
return res.data;
},
async getProfileAnalyze(params: {
segmentId: string;
timeRanges: Array<{ start: number; end: number }>;
}) {
const res: AxiosResponse = await graphql
.query("getProfileAnalyze")
.params(params);
if (res.data.errors) {
this.analyzeTrees = [];
return res.data;
}
const { analyze, tip } = res.data.data;
if (tip) {
this.analyzeTrees = [];
return res.data;
}
if (!analyze) {
this.analyzeTrees = [];
return res.data;
}
this.analyzeTrees = analyze.trees;
return res.data;
},
async createTask(param: ProfileTaskCreationRequest) {
const res: AxiosResponse = await graphql
.query("saveProfileTask")
.params({ creationRequest: param });
if (res.data.errors) {
return res.data;
}
this.getTaskList();
return res.data;
},
async getTaskLogs(param: { taskID: string }) {
const res: AxiosResponse = await graphql
.query("getProfileTaskLogs")
.params(param);
if (res.data.errors) {
return res.data;
}
this.taskLogs = res.data.data.taskLogs;
return res.data;
},
},
});
export function useEbpfStore(): any {
return ebpfStore(store);
}

View File

@ -32,6 +32,7 @@ import { useAppStoreWithOut } from "@/store/modules/app";
interface ProfileState { interface ProfileState {
endpoints: Endpoint[]; endpoints: Endpoint[];
taskEndpoints: Endpoint[];
durationTime: Duration; durationTime: Duration;
condition: { serviceId: string; endpointName: string }; condition: { serviceId: string; endpointName: string };
taskList: TaskListItem[]; taskList: TaskListItem[];
@ -48,6 +49,7 @@ export const profileStore = defineStore({
id: "profile", id: "profile",
state: (): ProfileState => ({ state: (): ProfileState => ({
endpoints: [{ value: "", label: "All" }], endpoints: [{ value: "", label: "All" }],
taskEndpoints: [{ value: "", label: "All" }],
durationTime: useAppStoreWithOut().durationTime, durationTime: useAppStoreWithOut().durationTime,
condition: { serviceId: "", endpointName: "" }, condition: { serviceId: "", endpointName: "" },
taskList: [], taskList: [],
@ -87,6 +89,18 @@ export const profileStore = defineStore({
this.endpoints = [{ value: "", label: "All" }, ...res.data.data.pods]; this.endpoints = [{ value: "", label: "All" }, ...res.data.data.pods];
return res.data; return res.data;
}, },
async getTaskEndpoints(serviceId: string, keyword?: string) {
const res: AxiosResponse = await graphql.query("queryEndpoints").params({
serviceId,
duration: this.durationTime,
keyword: keyword || "",
});
if (res.data.errors) {
return res.data;
}
this.taskEndpoints = [{ value: "", label: "All" }, ...res.data.data.pods];
return res.data;
},
async getTaskList() { async getTaskList() {
const res: AxiosResponse = await graphql const res: AxiosResponse = await graphql
.query("getProfileTaskList") .query("getProfileTaskList")

View File

@ -37,8 +37,8 @@ limitations under the License. -->
import type { PropType } from "vue"; import type { PropType } from "vue";
import { useI18n } from "vue-i18n"; import { useI18n } from "vue-i18n";
import { useDashboardStore } from "@/store/modules/dashboard"; import { useDashboardStore } from "@/store/modules/dashboard";
import Header from "../related/profile/Header.vue"; import Header from "../related/ebpf/Header.vue";
import Content from "../related/profile/Content.vue"; import Content from "../related/ebpf/Content.vue";
/*global defineProps */ /*global defineProps */
const props = defineProps({ const props = defineProps({

View File

@ -122,6 +122,7 @@ import Trace from "./Trace.vue";
import Profile from "./Profile.vue"; import Profile from "./Profile.vue";
import Log from "./Log.vue"; import Log from "./Log.vue";
import Text from "./Text.vue"; import Text from "./Text.vue";
import Ebpf from "./Ebpf.vue";
const props = { const props = {
data: { data: {
@ -132,7 +133,7 @@ const props = {
}; };
export default defineComponent({ export default defineComponent({
name: "Tab", name: "Tab",
components: { Topology, Widget, Trace, Profile, Log, Text }, components: { Topology, Widget, Trace, Profile, Log, Text, Ebpf },
props, props,
setup(props) { setup(props) {
const { t } = useI18n(); const { t } = useI18n();

View File

@ -21,5 +21,6 @@ import Trace from "./Trace.vue";
import Profile from "./Profile.vue"; import Profile from "./Profile.vue";
import Log from "./Log.vue"; import Log from "./Log.vue";
import Text from "./Text.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 };

View File

@ -0,0 +1,17 @@
<!-- Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. -->
<template>
<div>content</div>
</template>

View File

@ -0,0 +1,133 @@
<!-- 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="mr-10">
<span class="grey mr-5">{{ t("endpointName") }}:</span>
<Selector
class="name"
size="small"
:value="endpointName"
:options="ebpfStore.endpoints"
placeholder="Select a endpoint"
:isRemote="true"
@change="changeEndpoint"
@query="searchEndpoints"
/>
</div>
<el-button
class="search-btn"
size="small"
type="primary"
@click="searchTasks"
>
{{ t("search") }}
</el-button>
<el-button class="search-btn" 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 endpointName = ref<string>("");
const newTask = ref<boolean>(false);
searchTasks();
searchEndpoints("");
async function searchEndpoints(keyword: string) {
if (!selectorStore.currentService) {
return;
}
const service = selectorStore.currentService.value;
const res = await ebpfStore.getEndpoints(service, keyword);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
endpointName.value = ebpfStore.endpoints[0].value;
}
function changeEndpoint(opt: any[]) {
endpointName.value = opt[0].value;
}
async function searchTasks() {
ebpfStore.setConditions({
serviceId:
(selectorStore.currentService && selectorStore.currentService.id) || "",
endpointName: endpointName.value,
});
const res = await ebpfStore.getTaskList();
if (res.errors) {
ElMessage.error(res.errors);
}
}
function createTask() {
newTask.value = true;
}
watch(
() => selectorStore.currentService,
() => {
searchTasks();
}
);
watch(
() => appStore.durationTime,
() => {
if (dashboardStore.entity === EntityType[1].value) {
searchTasks();
}
}
);
</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,203 @@
<!-- 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">
<div>
<div class="label">{{ t("endpointName") }}</div>
<Selector
class="profile-input"
size="small"
:value="endpointName"
:options="eBPFStore.taskEndpoints"
placeholder="Select a endpoint"
:isRemote="true"
@change="changeEndpoint"
@query="searchEndpoints"
/>
</div>
<div>
<div class="label">{{ t("monitorTime") }}</div>
<div>
<Radio
class="mb-5"
: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>
<Radio
class="mb-5"
:value="monitorDuration"
:options="InitTaskField.monitorDuration"
@change="changeMonitorDuration"
/>
</div>
<div>
<div class="label">{{ t("minThreshold") }} (ms)</div>
<el-input-number
size="small"
class="profile-input"
:min="0"
v-model="minThreshold"
/>
</div>
<div>
<div class="label">{{ t("dumpPeriod") }}</div>
<Radio
class="mb-5"
:value="dumpPeriod"
:options="InitTaskField.dumpPeriod"
@change="changeDumpPeriod"
/>
</div>
<div>
<div class="label">{{ t("maxSamplingCount") }}</div>
<Selector
size="small"
:value="maxSamplingCount"
:options="InitTaskField.maxSamplingCount"
placeholder="Select a data"
@change="changeMaxSamplingCount"
class="profile-input"
/>
</div>
<div>
<el-button @click="createTask" type="primary" class="create-task-btn">
{{ t("createTask") }}
</el-button>
</div>
</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 } from "./data";
/* global defineEmits */
const emits = defineEmits(["close"]);
const eBPFStore = useEbpfStore();
const selectorStore = useSelectorStore();
const appStore = useAppStoreWithOut();
const { t } = useI18n();
const endpointName = ref<string>("");
const monitorTime = ref<string>(InitTaskField.monitorTimeEn[0].value);
const monitorDuration = ref<string>(InitTaskField.monitorDuration[0].value);
const time = ref<Date>(appStore.durationRow.start);
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.value;
const res = await eBPFStore.getEndpoints(service, keyword);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
endpointName.value = eBPFStore.taskEndpoints[0].value;
}
function changeMonitorTime(opt: string) {
monitorTime.value = opt;
}
function changeMonitorDuration(val: string) {
monitorDuration.value = val;
}
function changeDumpPeriod(val: string) {
dumpPeriod.value = val;
}
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 params = {
serviceId: selectorStore.currentService.id,
endpointName: endpointName.value,
startTime: date.getTime(),
duration: Number(monitorDuration.value),
minDurationThreshold: Number(minThreshold.value),
dumpPeriod: Number(dumpPeriod.value),
maxSamplingCount: Number(maxSamplingCount.value),
};
const res = await eBPFStore.createTask(params);
if (res.errors) {
ElMessage.error(res.errors);
return;
}
const { tip } = res.data;
if (tip) {
ElMessage.error(tip);
return;
}
ElMessage.success("Task created successfully");
}
function changeTimeRange(val: Date) {
time.value = val;
}
</script>
<style lang="scss" scoped>
.profile-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>

View File

@ -0,0 +1,63 @@
/**
* 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 InitTaskField = {
serviceSource: [{ key: "", label: "None" }],
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" },
],
dumpPeriod: [
{ value: "10", label: "10 ms" },
{ value: "20", label: "20 ms" },
{ value: "50", label: "50 ms" },
{ value: "100", label: "100 ms" },
],
maxSamplingCount: [
{ value: "1", label: "1" },
{ value: "2", label: "2" },
{ value: "3", label: "3" },
{ value: "4", label: "4" },
{ value: "5", label: "5" },
{ value: "6", label: "6" },
{ value: "7", label: "7" },
{ value: "8", label: "8" },
{ value: "9", label: "9" },
],
};

View File

@ -17,7 +17,16 @@ limitations under the License. -->
<div class="profile-task"> <div class="profile-task">
<div> <div>
<div class="label">{{ t("endpointName") }}</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.taskEndpoints"
placeholder="Select a endpoint"
:isRemote="true"
@change="changeEndpoint"
@query="searchEndpoints"
/>
</div> </div>
<div> <div>
<div class="label">{{ t("monitorTime") }}</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 dumpPeriod = ref<string>(InitTaskField.dumpPeriod[0].value);
const maxSamplingCount = ref<string>(InitTaskField.maxSamplingCount[0].value); const maxSamplingCount = ref<string>(InitTaskField.maxSamplingCount[0].value);
async function searchEndpoints(keyword: string) {
if (!selectorStore.currentService) {
return;
}
const service = selectorStore.currentService.value;
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) { function changeMonitorTime(opt: string) {
monitorTime.value = opt; monitorTime.value = opt;
} }
@ -121,6 +144,10 @@ function changeMaxSamplingCount(opt: any[]) {
maxSamplingCount.value = opt[0].value; maxSamplingCount.value = opt[0].value;
} }
function changeEndpoint(opt: any[]) {
endpointName.value = opt[0].value;
}
async function createTask() { async function createTask() {
emits("close"); emits("close");
const date = const date =
@ -153,7 +180,7 @@ function changeTimeRange(val: Date) {
<style lang="scss" scoped> <style lang="scss" scoped>
.profile-task { .profile-task {
margin: 0 auto; margin: 0 auto;
width: 350px; width: 400px;
} }
.date { .date {