mirror of
https://github.com/apache/skywalking-booster-ui.git
synced 2025-05-01 18:33:37 +00:00
feat: visualize the OFF CPU eBPF profiling (#97)
This commit is contained in:
parent
f334985cf0
commit
45f896bf36
@ -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 {
|
||||||
|
@ -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({}));
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
|
@ -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>
|
||||||
|
@ -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: [
|
||||||
|
Loading…
Reference in New Issue
Block a user